puzzle/src/frontend/components/NewImageDialog.vue

256 lines
6.1 KiB
Vue
Raw Normal View History

2021-05-21 00:43:02 +02:00
"Upload image" clicked: what it looks like when the image was uploaded.
Probably needs a (x) in the upper right corner of the image to allow the
user to remove the image if a wrong one was selected. The image should
be uploaded to the actual gallery only when the user presses "post to
gallery", if possible!
<template>
<div class="overlay new-image-dialog" @click="$emit('bgclick')">
<div class="overlay-content" @click.stop="">
2021-06-05 09:54:01 +02:00
<div
class="area-image"
:class="{'has-image': !!previewUrl, 'no-image': !previewUrl, droppable: droppable}"
@drop="onDrop"
@dragover="onDragover"
@dragleave="onDragleave">
2021-05-21 00:43:02 +02:00
<!-- TODO: ... -->
2021-05-22 01:51:44 +02:00
<div v-if="previewUrl" class="has-image">
<span class="remove btn" @click="previewUrl=''">X</span>
<responsive-image :src="previewUrl" />
2021-05-21 00:43:02 +02:00
</div>
<div v-else>
2021-05-22 01:51:44 +02:00
<label class="upload">
2021-06-05 09:54:01 +02:00
<input type="file" style="display: none" @change="onFileSelect" accept="image/*" />
2021-05-22 15:48:13 +02:00
<span class="btn">Upload File</span>
2021-05-22 01:51:44 +02:00
</label>
2021-05-21 00:43:02 +02:00
</div>
</div>
<div class="area-settings">
<table>
<tr>
<td><label>Title</label></td>
2021-05-22 01:51:44 +02:00
<td><input type="text" v-model="title" placeholder="Flower by @artist" /></td>
2021-05-21 00:43:02 +02:00
</tr>
<tr>
<td colspan="2">
<div class="hint">Feel free to leave a credit to the artist/photographer in the title :)</div>
</td>
</tr>
<tr>
2021-05-22 15:48:13 +02:00
<!-- TODO: autocomplete tags -->
<td><label>Tags</label></td>
<td>
2021-06-03 23:30:08 +02:00
<tags-input v-model="tags" :autocompleteTags="autocompleteTags" />
2021-05-22 15:48:13 +02:00
</td>
2021-05-21 00:43:02 +02:00
</tr>
</table>
</div>
<div class="area-buttons">
2021-05-22 01:51:44 +02:00
<button class="btn" :disabled="!canPostToGallery" @click="postToGallery">🖼 Post to gallery</button>
2021-05-21 00:43:02 +02:00
<button class="btn" :disabled="!canSetupGameClick" @click="setupGameClick">🧩 Post to gallery <br /> + set up game</button>
</div>
</div>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
2021-06-05 09:54:01 +02:00
import { logger } from '../../common/Util'
2021-05-21 00:43:02 +02:00
import ResponsiveImage from './ResponsiveImage.vue'
2021-05-22 15:48:13 +02:00
import TagsInput from './TagsInput.vue'
2021-05-21 00:43:02 +02:00
2021-06-05 09:54:01 +02:00
const log = logger('NewImageDialog.vue')
2021-05-21 00:43:02 +02:00
export default defineComponent({
name: 'new-image-dialog',
components: {
ResponsiveImage,
2021-05-22 15:48:13 +02:00
TagsInput,
2021-05-21 00:43:02 +02:00
},
2021-06-03 23:30:08 +02:00
props: {
autocompleteTags: {
type: Function,
},
},
2021-05-21 00:43:02 +02:00
emits: {
bgclick: null,
setupGameClick: null,
2021-05-22 01:51:44 +02:00
postToGalleryClick: null,
2021-05-21 00:43:02 +02:00
},
data () {
return {
2021-05-22 01:51:44 +02:00
previewUrl: '',
file: null as File|null,
title: '',
2021-05-22 15:48:13 +02:00
tags: [] as string[],
2021-06-05 09:54:01 +02:00
droppable: false,
2021-05-21 00:43:02 +02:00
}
},
computed: {
2021-05-22 01:51:44 +02:00
canPostToGallery (): boolean {
return !!(this.previewUrl && this.file)
2021-05-21 00:43:02 +02:00
},
2021-05-22 01:51:44 +02:00
canSetupGameClick (): boolean {
return !!(this.previewUrl && this.file)
2021-05-21 00:43:02 +02:00
},
},
methods: {
2021-06-05 09:54:01 +02:00
imageFromDragEvt (evt: DragEvent): DataTransferItem|null {
const items = evt.dataTransfer?.items
if (!items || items.length === 0) {
return null
}
const item = items[0]
if (!item.type.startsWith('image/')) {
return null
}
return item
},
onFileSelect (evt: Event) {
2021-05-22 01:51:44 +02:00
const target = (evt.target as HTMLInputElement)
if (!target.files) return;
const file = target.files[0]
if (!file) return;
2021-06-05 09:54:01 +02:00
this.preview(file)
},
preview (file: File) {
2021-05-22 01:51:44 +02:00
const r = new FileReader()
r.readAsDataURL(file)
r.onload = (ev: any) => {
this.previewUrl = ev.target.result
this.file = file
}
2021-05-21 00:43:02 +02:00
},
postToGallery () {
2021-05-22 01:51:44 +02:00
this.$emit('postToGalleryClick', {
file: this.file,
title: this.title,
2021-05-22 15:48:13 +02:00
tags: this.tags,
2021-05-22 01:51:44 +02:00
})
2021-05-21 00:43:02 +02:00
},
setupGameClick () {
2021-05-22 01:51:44 +02:00
this.$emit('setupGameClick', {
file: this.file,
title: this.title,
2021-05-22 15:48:13 +02:00
tags: this.tags,
2021-05-22 01:51:44 +02:00
})
2021-05-21 00:43:02 +02:00
},
2021-06-05 09:54:01 +02:00
onDrop (evt: DragEvent): boolean {
this.droppable = false
const img = this.imageFromDragEvt(evt)
if (!img) {
return false
}
const f = img.getAsFile()
if (!f) {
return false
}
this.file = f
this.preview(f)
evt.preventDefault()
return false
},
onDragover (evt: DragEvent): boolean {
const img = this.imageFromDragEvt(evt)
if (!img) {
return false
}
this.droppable = true
evt.preventDefault()
return false
},
onDragleave () {
log.info('onDragleave')
this.droppable = false
},
2021-05-21 00:43:02 +02:00
},
})
</script>
// TODO: scoped vs .new-image-dialog
<style>
.new-image-dialog .overlay-content {
display: grid;
grid-template-columns: auto 450px;
grid-template-rows: auto;
grid-template-areas:
"image settings"
"image buttons";
height: 90%;
width: 80%;
}
2021-06-05 09:54:01 +02:00
@media (max-width: 1400px) {
.new-image-dialog .overlay-content {
grid-template-columns: auto;
grid-template-rows: 1fr min-content min-content;
grid-template-areas:
"image"
"settings"
"buttons";
}
}
2021-05-21 00:43:02 +02:00
.new-image-dialog .area-image {
grid-area: image;
2021-05-22 01:51:44 +02:00
margin: 20px;
2021-05-21 00:43:02 +02:00
}
.new-image-dialog .area-image.no-image {
align-content: center;
display: grid;
text-align: center;
2021-06-05 09:54:01 +02:00
border: solid 6px;
2021-05-21 00:43:02 +02:00
position: relative;
}
2021-06-05 09:54:01 +02:00
.new-image-dialog .area-image.droppable {
border: dashed 6px;
}
.area-image * {
pointer-events: none;
}
2021-05-21 00:43:02 +02:00
.new-image-dialog .area-image .has-image {
position: relative;
width: 100%;
height: 100%;
}
.new-image-dialog .area-image .has-image .remove {
position: absolute;
top: .5em;
left: .5em;
}
.new-image-dialog .area-settings {
grid-area: settings;
}
.new-image-dialog .area-settings table input[type="text"] {
width: 100%;
box-sizing: border-box;
}
.new-image-dialog .area-buttons {
align-self: end;
grid-area: buttons;
}
.new-image-dialog .area-buttons button {
width: 100%;
2021-05-22 01:51:44 +02:00
margin-top: .5em;
2021-05-21 00:43:02 +02:00
}
.new-image-dialog .upload {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
cursor: pointer;
}
.new-image-dialog .upload .btn {
position: absolute;
top: 50%;
transform: translate(-50%,-50%);
}
</style>