2022-08-20 07:11:51 +00:00
|
|
|
<template>
|
2022-10-15 18:48:17 +00:00
|
|
|
<div class="p-outer fill-block"
|
2022-09-08 17:41:16 +00:00
|
|
|
:class="{
|
|
|
|
'selected': (data.flag & c.FLAG_SELECTED),
|
2022-10-16 19:01:49 +00:00
|
|
|
'placeholder': (data.flag & c.FLAG_PLACEHOLDER),
|
2022-09-08 17:41:16 +00:00
|
|
|
'leaving': (data.flag & c.FLAG_LEAVING),
|
2022-10-16 02:55:53 +00:00
|
|
|
'error': (data.flag & c.FLAG_LOAD_FAIL),
|
2022-09-08 17:41:16 +00:00
|
|
|
}">
|
2022-09-08 17:12:16 +00:00
|
|
|
|
2022-09-16 03:17:40 +00:00
|
|
|
<Check :size="15" class="select"
|
2022-09-08 17:12:16 +00:00
|
|
|
v-if="!(data.flag & c.FLAG_PLACEHOLDER)"
|
2022-09-16 03:17:40 +00:00
|
|
|
@click="toggleSelect" />
|
2022-09-07 22:14:12 +00:00
|
|
|
|
2022-09-16 03:17:40 +00:00
|
|
|
<Video :size="20" v-if="data.flag & c.FLAG_IS_VIDEO" />
|
|
|
|
<Star :size="20" v-if="data.flag & c.FLAG_IS_FAVORITE" />
|
2022-09-12 03:46:31 +00:00
|
|
|
|
2022-10-15 18:48:17 +00:00
|
|
|
<div class="img-outer fill-block"
|
2022-10-17 03:53:38 +00:00
|
|
|
@click="emitClick"
|
2022-09-15 03:52:58 +00:00
|
|
|
@contextmenu="contextmenu"
|
|
|
|
@touchstart="touchstart"
|
|
|
|
@touchmove="touchend"
|
|
|
|
@touchend="touchend"
|
|
|
|
@touchcancel="touchend" >
|
2022-09-07 23:12:06 +00:00
|
|
|
<img
|
2022-10-18 21:08:27 +00:00
|
|
|
ref="img"
|
2022-10-15 18:48:17 +00:00
|
|
|
class="fill-block"
|
2022-10-18 21:08:27 +00:00
|
|
|
:src="src"
|
2022-09-10 00:54:53 +00:00
|
|
|
:key="data.fileid"
|
2022-10-18 21:08:27 +00:00
|
|
|
@load="load"
|
2022-10-16 19:01:49 +00:00
|
|
|
@error="error" />
|
2022-09-07 23:12:06 +00:00
|
|
|
</div>
|
2022-08-20 07:11:51 +00:00
|
|
|
</div>
|
|
|
|
</template>
|
|
|
|
|
2022-09-13 01:33:24 +00:00
|
|
|
<script lang="ts">
|
2022-10-18 21:08:27 +00:00
|
|
|
import { Component, Prop, Emit, Mixins, Watch } from 'vue-property-decorator';
|
2022-10-14 21:45:23 +00:00
|
|
|
import { IDay, IPhoto } from "../../types";
|
2022-09-13 01:33:24 +00:00
|
|
|
|
2022-10-14 21:45:23 +00:00
|
|
|
import { getPreviewUrl } from "../../services/FileUtils";
|
|
|
|
import errorsvg from "../../assets/error.svg";
|
|
|
|
import GlobalMixin from '../../mixins/GlobalMixin';
|
2022-08-20 07:11:51 +00:00
|
|
|
|
2022-09-16 03:17:40 +00:00
|
|
|
import Check from 'vue-material-design-icons/Check.vue';
|
|
|
|
import Video from 'vue-material-design-icons/Video.vue';
|
|
|
|
import Star from 'vue-material-design-icons/Star.vue';
|
|
|
|
|
|
|
|
@Component({
|
|
|
|
components: {
|
|
|
|
Check,
|
|
|
|
Video,
|
|
|
|
Star,
|
|
|
|
},
|
|
|
|
})
|
2022-09-13 02:36:27 +00:00
|
|
|
export default class Photo extends Mixins(GlobalMixin) {
|
2022-09-13 01:33:24 +00:00
|
|
|
private touchTimer = 0;
|
2022-10-18 21:08:27 +00:00
|
|
|
private src = null;
|
|
|
|
private hasFaceRect = false;
|
2022-09-13 01:33:24 +00:00
|
|
|
|
|
|
|
@Prop() data: IPhoto;
|
|
|
|
@Prop() day: IDay;
|
|
|
|
|
2022-09-13 01:57:51 +00:00
|
|
|
@Emit('select') emitSelect(data: IPhoto) {}
|
2022-10-17 03:53:38 +00:00
|
|
|
@Emit('click') emitClick() {}
|
2022-09-13 01:57:51 +00:00
|
|
|
|
2022-10-18 21:08:27 +00:00
|
|
|
@Watch('data')
|
|
|
|
onDataChange(newData: IPhoto, oldData: IPhoto) {
|
|
|
|
// Copy flags relevant to this component
|
|
|
|
if (oldData && newData) {
|
|
|
|
newData.flag |= oldData.flag & (this.c.FLAG_SELECTED | this.c.FLAG_LOAD_FAIL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
mounted() {
|
|
|
|
this.hasFaceRect = false;
|
|
|
|
this.refresh();
|
|
|
|
}
|
|
|
|
|
|
|
|
async refresh() {
|
|
|
|
this.src = await this.getSrc();
|
|
|
|
}
|
|
|
|
|
2022-09-13 05:33:20 +00:00
|
|
|
/** Get src for image to show */
|
2022-10-18 21:08:27 +00:00
|
|
|
async getSrc() {
|
2022-09-13 02:36:27 +00:00
|
|
|
if (this.data.flag & this.c.FLAG_PLACEHOLDER) {
|
2022-10-18 21:08:27 +00:00
|
|
|
return null;
|
2022-09-13 02:36:27 +00:00
|
|
|
} else if (this.data.flag & this.c.FLAG_LOAD_FAIL) {
|
2022-09-13 01:33:24 +00:00
|
|
|
return errorsvg;
|
|
|
|
} else {
|
2022-09-15 03:52:58 +00:00
|
|
|
return this.url;
|
2022-09-13 05:33:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Get url of the photo */
|
2022-09-15 03:52:58 +00:00
|
|
|
get url() {
|
2022-10-16 04:26:01 +00:00
|
|
|
let size = 256;
|
|
|
|
if (this.data.w && this.data.h) {
|
|
|
|
size = Math.floor(size * Math.max(this.data.w, this.data.h) / Math.min(this.data.w, this.data.h));
|
|
|
|
}
|
|
|
|
return getPreviewUrl(this.data.fileid, this.data.etag, false, size)
|
2022-09-13 05:33:20 +00:00
|
|
|
}
|
|
|
|
|
2022-10-18 21:08:27 +00:00
|
|
|
/** Set src with overlay face rect */
|
|
|
|
async addFaceRect() {
|
|
|
|
if (!this.data.facerect || this.hasFaceRect) return;
|
|
|
|
this.hasFaceRect = true;
|
|
|
|
|
|
|
|
const canvas = document.createElement('canvas');
|
|
|
|
const context = canvas.getContext('2d');
|
|
|
|
const img = this.$refs.img as HTMLImageElement;
|
|
|
|
|
|
|
|
canvas.width = img.naturalWidth;
|
|
|
|
canvas.height = img.naturalHeight;
|
|
|
|
context.drawImage(img, 0, 0);
|
|
|
|
context.strokeStyle = '#00ff00';
|
|
|
|
context.lineWidth = 2;
|
|
|
|
context.strokeRect(
|
|
|
|
this.data.facerect.x * img.naturalWidth,
|
|
|
|
this.data.facerect.y * img.naturalHeight,
|
|
|
|
this.data.facerect.w * img.naturalWidth,
|
|
|
|
this.data.facerect.h * img.naturalHeight,
|
|
|
|
);
|
|
|
|
|
|
|
|
canvas.toBlob((blob) => {
|
|
|
|
this.src = URL.createObjectURL(blob);
|
|
|
|
}, 'image/jpeg', 0.95)
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Post load tasks */
|
|
|
|
load() {
|
|
|
|
this.addFaceRect();
|
|
|
|
}
|
|
|
|
|
2022-09-13 01:33:24 +00:00
|
|
|
/** Error in loading image */
|
|
|
|
error(e: any) {
|
2022-10-16 19:01:49 +00:00
|
|
|
this.data.flag |= this.c.FLAG_LOAD_FAIL;
|
2022-10-18 21:08:27 +00:00
|
|
|
this.refresh();
|
2022-09-13 01:33:24 +00:00
|
|
|
}
|
|
|
|
|
2022-09-13 05:25:30 +00:00
|
|
|
/** Clear timers */
|
|
|
|
beforeUnmount() {
|
|
|
|
clearTimeout(this.touchTimer);
|
|
|
|
}
|
|
|
|
|
2022-09-13 01:33:24 +00:00
|
|
|
toggleSelect() {
|
2022-10-17 03:53:38 +00:00
|
|
|
if (this.data.flag & this.c.FLAG_PLACEHOLDER) return;
|
2022-09-13 01:57:51 +00:00
|
|
|
this.emitSelect(this.data);
|
2022-09-13 01:33:24 +00:00
|
|
|
}
|
2022-09-07 20:04:03 +00:00
|
|
|
|
2022-09-13 01:33:24 +00:00
|
|
|
touchstart() {
|
|
|
|
this.touchTimer = window.setTimeout(() => {
|
|
|
|
this.toggleSelect();
|
|
|
|
this.touchTimer = 0;
|
|
|
|
}, 600);
|
|
|
|
}
|
2022-09-07 22:14:12 +00:00
|
|
|
|
2022-09-13 01:33:24 +00:00
|
|
|
contextmenu(e: Event) {
|
|
|
|
e.preventDefault();
|
|
|
|
e.stopPropagation();
|
|
|
|
}
|
|
|
|
|
|
|
|
touchend() {
|
|
|
|
if (this.touchTimer) {
|
|
|
|
clearTimeout(this.touchTimer);
|
|
|
|
this.touchTimer = 0;
|
|
|
|
}
|
2022-08-20 07:11:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
|
2022-09-10 02:10:51 +00:00
|
|
|
<style lang="scss" scoped>
|
2022-09-08 17:41:16 +00:00
|
|
|
/* Container and selection */
|
2022-09-10 00:54:53 +00:00
|
|
|
.p-outer {
|
2022-09-10 02:10:51 +00:00
|
|
|
&.leaving {
|
|
|
|
transition: all 0.2s ease-in;
|
|
|
|
transform: scale(0.9);
|
|
|
|
opacity: 0;
|
|
|
|
}
|
2022-09-08 17:41:16 +00:00
|
|
|
}
|
2022-09-10 02:10:51 +00:00
|
|
|
|
2022-10-16 04:06:26 +00:00
|
|
|
// Distance of icon from border
|
|
|
|
$icon-dist: min(10px, 6%);
|
|
|
|
|
2022-09-12 03:57:47 +00:00
|
|
|
/* Extra icons */
|
2022-09-16 03:17:40 +00:00
|
|
|
.check-icon.select {
|
2022-09-07 22:14:12 +00:00
|
|
|
position: absolute;
|
2022-10-16 04:06:26 +00:00
|
|
|
top: $icon-dist; left: $icon-dist;
|
2022-09-12 03:46:31 +00:00
|
|
|
z-index: 100;
|
2022-09-07 22:14:12 +00:00
|
|
|
background-color: var(--color-main-background);
|
|
|
|
border-radius: 50%;
|
|
|
|
cursor: pointer;
|
2022-10-10 23:13:52 +00:00
|
|
|
display: none;
|
2022-09-10 02:10:51 +00:00
|
|
|
|
2022-10-10 23:13:52 +00:00
|
|
|
.p-outer:hover > & { display: flex; }
|
|
|
|
.selected > & { display: flex; filter: invert(1); }
|
2022-09-07 22:14:12 +00:00
|
|
|
}
|
2022-09-16 03:17:40 +00:00
|
|
|
.video-icon, .star-icon {
|
2022-08-20 07:11:51 +00:00
|
|
|
position: absolute;
|
2022-09-12 03:46:31 +00:00
|
|
|
z-index: 100;
|
|
|
|
pointer-events: none;
|
2022-09-16 03:17:40 +00:00
|
|
|
filter: invert(1) brightness(100);
|
|
|
|
}
|
|
|
|
.video-icon {
|
2022-10-16 04:06:26 +00:00
|
|
|
top: $icon-dist; right: $icon-dist;
|
2022-09-16 03:17:40 +00:00
|
|
|
}
|
|
|
|
.star-icon {
|
2022-10-16 04:06:26 +00:00
|
|
|
bottom: $icon-dist; left: $icon-dist;
|
2022-08-20 07:11:51 +00:00
|
|
|
}
|
2022-09-08 17:41:16 +00:00
|
|
|
|
|
|
|
/* Actual image */
|
2022-09-10 02:10:51 +00:00
|
|
|
div.img-outer {
|
2022-09-07 23:12:06 +00:00
|
|
|
padding: 2px;
|
2022-10-12 19:30:54 +00:00
|
|
|
@media (max-width: 768px) { padding: 1px; }
|
|
|
|
|
2022-10-16 04:06:26 +00:00
|
|
|
transition: padding 0.1s ease;
|
2022-09-10 00:54:53 +00:00
|
|
|
background-clip: content-box, padding-box;
|
2022-09-26 06:27:41 +00:00
|
|
|
background-color: var(--color-background-dark);
|
2022-09-10 02:10:51 +00:00
|
|
|
|
2022-10-16 04:06:26 +00:00
|
|
|
.selected > & { padding: calc($icon-dist - 2px); }
|
2022-09-08 01:27:51 +00:00
|
|
|
|
2022-09-15 17:20:02 +00:00
|
|
|
> img {
|
|
|
|
background-clip: content-box;
|
|
|
|
object-fit: cover;
|
|
|
|
cursor: pointer;
|
2022-09-10 02:10:51 +00:00
|
|
|
|
2022-09-15 17:20:02 +00:00
|
|
|
-webkit-tap-highlight-color: transparent;
|
|
|
|
-webkit-touch-callout: none;
|
|
|
|
user-select: none;
|
2022-10-16 04:06:26 +00:00
|
|
|
transition: box-shadow 0.1s ease;
|
2022-09-15 17:20:02 +00:00
|
|
|
|
2022-10-16 04:06:26 +00:00
|
|
|
.selected > & { box-shadow: 0 0 4px 2px var(--color-primary); }
|
2022-10-16 19:01:49 +00:00
|
|
|
.p-outer.placeholder > & { display: none; }
|
2022-10-16 02:55:53 +00:00
|
|
|
.p-outer.error & { object-fit: contain; }
|
2022-09-15 17:20:02 +00:00
|
|
|
}
|
2022-09-10 00:54:53 +00:00
|
|
|
}
|
2022-08-20 07:11:51 +00:00
|
|
|
</style>
|