puzzle/src/frontend/views/NewGame.vue

221 lines
6.4 KiB
Vue
Raw Normal View History

2021-05-21 00:43:02 +02:00
"New Game" page: Upload button big, centered, at the top of the page, as
visible as possible. Upload button has a warning that the image will
be added to public gallery, just so noone uploads anything naughty on
accident. The page can show all the images by default, or one of the categories
of images. Instead of categories, you can make the system tag-based, like
in jigsawpuzzles.io
2021-05-17 00:27:47 +02:00
<template>
<div>
2021-05-21 00:43:02 +02:00
<div class="upload-image-teaser">
<div class="btn btn-big" @click="dialog='new-image'">Upload your image</div>
<div class="hint">(The image you upload will be added to the public gallery.)</div>
</div>
<div>
2021-05-22 15:48:13 +02:00
<label v-if="tags.length > 0">
Tags:
2021-06-03 23:46:09 +02:00
<span
class="bit"
v-for="(t,idx) in relevantTags"
:key="idx"
@click="toggleTag(t)"
:class="{on: filters.tags.includes(t.slug)}">{{t.title}} ({{t.total}})</span>
2021-05-22 15:48:13 +02:00
<!-- <select v-model="filters.tags" @change="filtersChanged">
2021-05-21 00:43:02 +02:00
<option value="">All</option>
2021-05-22 15:48:13 +02:00
<option v-for="(c, idx) in tags" :key="idx" :value="c.slug">{{c.title}}</option>
</select> -->
2021-05-21 00:43:02 +02:00
</label>
<label>
Sort by:
<select v-model="filters.sort" @change="filtersChanged">
<option value="date_desc">Newest first</option>
<option value="date_asc">Oldest first</option>
<option value="alpha_asc">A-Z</option>
<option value="alpha_desc">Z-A</option>
</select>
</label>
</div>
2021-06-03 23:30:08 +02:00
<image-library
:images="images"
@imageClicked="onImageClicked"
@imageEditClicked="onImageEditClicked" />
<new-image-dialog
v-if="dialog==='new-image'"
:autocompleteTags="autocompleteTags"
@bgclick="dialog=''"
:uploadProgress="uploadProgress"
:uploading="uploading"
2021-06-03 23:30:08 +02:00
@postToGalleryClick="postToGalleryClick"
@setupGameClick="setupGameClick"
/>
2021-06-03 23:30:08 +02:00
<edit-image-dialog
v-if="dialog==='edit-image'"
:autocompleteTags="autocompleteTags"
@bgclick="dialog=''"
@saveClick="onSaveImageClick"
:image="image" />
<new-game-dialog
v-if="image && dialog==='new-game'"
@bgclick="dialog=''"
@newGame="onNewGame"
:image="image" />
2021-05-17 00:27:47 +02:00
</div>
</template>
2021-05-13 16:58:21 +02:00
2021-05-17 00:27:47 +02:00
<script lang="ts">
import { defineComponent } from 'vue'
2021-05-13 16:58:21 +02:00
2021-05-21 00:43:02 +02:00
import ImageLibrary from './../components/ImageLibrary.vue'
import NewImageDialog from './../components/NewImageDialog.vue'
2021-05-22 14:26:04 +02:00
import EditImageDialog from './../components/EditImageDialog.vue'
2021-05-17 00:27:47 +02:00
import NewGameDialog from './../components/NewGameDialog.vue'
2021-05-29 18:56:43 +02:00
import { GameSettings, Image, Tag } from '../../common/Types'
2021-05-21 00:43:02 +02:00
import Util from '../../common/Util'
import xhr from '../xhr'
2021-05-17 00:27:47 +02:00
export default defineComponent({
2021-05-13 16:58:21 +02:00
components: {
2021-05-21 00:43:02 +02:00
ImageLibrary,
NewImageDialog,
2021-05-22 14:26:04 +02:00
EditImageDialog,
2021-05-13 16:58:21 +02:00
NewGameDialog,
},
data() {
return {
2021-05-21 00:43:02 +02:00
filters: {
sort: 'date_desc',
2021-05-22 15:48:13 +02:00
tags: [] as string[],
2021-05-21 00:43:02 +02:00
},
2021-05-13 16:58:21 +02:00
images: [],
2021-06-03 23:30:08 +02:00
tags: [] as Tag[],
2021-05-21 00:43:02 +02:00
image: {
2021-05-22 01:51:44 +02:00
id: 0,
filename: '',
2021-05-21 00:43:02 +02:00
file: '',
2021-05-22 01:51:44 +02:00
url: '',
2021-05-21 00:43:02 +02:00
title: '',
2021-05-22 15:48:13 +02:00
tags: [],
2021-05-22 01:51:44 +02:00
created: 0,
2021-05-21 00:43:02 +02:00
} as Image,
dialog: '',
uploading: '',
uploadProgress: 0,
2021-05-13 16:58:21 +02:00
}
},
async created() {
2021-05-21 00:43:02 +02:00
await this.loadImages()
2021-05-13 16:58:21 +02:00
},
2021-06-03 23:46:09 +02:00
computed: {
relevantTags (): Tag[] {
return this.tags.filter((tag: Tag) => tag.total > 0)
},
},
2021-05-13 16:58:21 +02:00
methods: {
2021-06-03 23:30:08 +02:00
autocompleteTags (input: string, exclude: string[]): string[] {
return this.tags
.filter((tag: Tag) => {
return !exclude.includes(tag.title)
&& tag.title.toLowerCase().startsWith(input.toLowerCase())
})
.slice(0, 10)
.map((tag: Tag) => tag.title)
},
2021-05-22 15:48:13 +02:00
toggleTag (t: Tag) {
if (this.filters.tags.includes(t.slug)) {
this.filters.tags = this.filters.tags.filter(slug => slug !== t.slug)
} else {
this.filters.tags.push(t.slug)
}
this.filtersChanged()
},
2021-05-21 00:43:02 +02:00
async loadImages () {
const res = await xhr.get(`/api/newgame-data${Util.asQueryArgs(this.filters)}`, {})
2021-05-21 00:43:02 +02:00
const json = await res.json()
this.images = json.images
2021-05-22 15:48:13 +02:00
this.tags = json.tags
2021-05-21 00:43:02 +02:00
},
async filtersChanged () {
await this.loadImages()
},
2021-05-22 14:26:04 +02:00
onImageClicked (image: Image) {
2021-05-21 00:43:02 +02:00
this.image = image
this.dialog = 'new-game'
},
2021-05-22 14:26:04 +02:00
onImageEditClicked (image: Image) {
this.image = image
this.dialog = 'edit-image'
},
2021-05-22 01:51:44 +02:00
async uploadImage (data: any) {
this.uploadProgress = 0
2021-05-22 01:51:44 +02:00
const formData = new FormData();
formData.append('file', data.file, data.file.name);
formData.append('title', data.title)
2021-05-22 15:48:13 +02:00
formData.append('tags', data.tags)
const res = await xhr.post('/api/upload', {
2021-05-22 01:51:44 +02:00
body: formData,
onUploadProgress: (evt: ProgressEvent<XMLHttpRequestEventTarget>): void => {
this.uploadProgress = evt.loaded / evt.total
},
2021-05-22 01:51:44 +02:00
})
this.uploadProgress = 1
2021-05-22 01:51:44 +02:00
return await res.json()
},
2021-05-22 14:26:04 +02:00
async saveImage (data: any) {
const res = await xhr.post('/api/save-image', {
2021-05-22 14:26:04 +02:00
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: data.id,
title: data.title,
2021-05-22 15:48:13 +02:00
tags: data.tags,
2021-05-22 14:26:04 +02:00
}),
})
return await res.json()
},
async onSaveImageClick(data: any) {
2021-07-11 18:44:59 +02:00
const res = await this.saveImage(data)
if (res.ok) {
this.dialog = ''
await this.loadImages()
} else {
alert(res.error)
}
2021-05-22 14:26:04 +02:00
},
2021-05-22 01:51:44 +02:00
async postToGalleryClick(data: any) {
this.uploading = 'postToGallery'
2021-05-22 01:51:44 +02:00
await this.uploadImage(data)
this.uploading = ''
2021-05-22 01:51:44 +02:00
this.dialog = ''
await this.loadImages()
},
async setupGameClick (data: any) {
this.uploading = 'setupGame'
2021-05-22 01:51:44 +02:00
const image = await this.uploadImage(data)
this.uploading = ''
2021-05-22 01:51:44 +02:00
this.loadImages() // load images in background
2021-05-21 00:43:02 +02:00
this.image = image
this.dialog = 'new-game'
},
async onNewGame(gameSettings: GameSettings) {
const res = await xhr.post('/api/newgame', {
2021-05-13 16:58:21 +02:00
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(gameSettings),
})
if (res.status === 200) {
const game = await res.json()
2021-05-15 11:06:37 +02:00
this.$router.push({ name: 'game', params: { id: game.id } })
2021-05-13 16:58:21 +02:00
}
2021-05-21 00:43:02 +02:00
},
},
2021-05-17 00:27:47 +02:00
})
</script>