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=''"
|
2021-06-20 13:40:28 +02:00
|
|
|
:uploadProgress="uploadProgress"
|
|
|
|
|
:uploading="uploading"
|
2021-06-03 23:30:08 +02:00
|
|
|
@postToGalleryClick="postToGalleryClick"
|
2021-06-20 13:40:28 +02:00
|
|
|
@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">
|
2021-06-20 13:40:28 +02:00
|
|
|
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'
|
2021-06-20 13:40:28 +02:00
|
|
|
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: '',
|
2021-06-20 13:40:28 +02:00
|
|
|
|
|
|
|
|
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 () {
|
2021-07-11 17:48:49 +02:00
|
|
|
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) {
|
2021-06-20 13:40:28 +02:00
|
|
|
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)
|
2021-06-20 13:40:28 +02:00
|
|
|
const res = await xhr.post('/api/upload', {
|
2021-05-22 01:51:44 +02:00
|
|
|
body: formData,
|
2021-06-20 13:40:28 +02:00
|
|
|
onUploadProgress: (evt: ProgressEvent<XMLHttpRequestEventTarget>): void => {
|
|
|
|
|
this.uploadProgress = evt.loaded / evt.total
|
|
|
|
|
},
|
2021-05-22 01:51:44 +02:00
|
|
|
})
|
2021-06-20 13:40:28 +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) {
|
2021-07-11 17:48:49 +02:00
|
|
|
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) {
|
2021-06-20 13:40:28 +02:00
|
|
|
this.uploading = 'postToGallery'
|
2021-05-22 01:51:44 +02:00
|
|
|
await this.uploadImage(data)
|
2021-06-20 13:40:28 +02:00
|
|
|
this.uploading = ''
|
2021-05-22 01:51:44 +02:00
|
|
|
this.dialog = ''
|
|
|
|
|
await this.loadImages()
|
|
|
|
|
},
|
|
|
|
|
async setupGameClick (data: any) {
|
2021-06-20 13:40:28 +02:00
|
|
|
this.uploading = 'setupGame'
|
2021-05-22 01:51:44 +02:00
|
|
|
const image = await this.uploadImage(data)
|
2021-06-20 13:40:28 +02:00
|
|
|
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) {
|
2021-07-11 17:48:49 +02:00
|
|
|
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>
|