UX: Improve touch event accuracy in cards and mosaic view #1048
This commit is contained in:
parent
fd078fe323
commit
d00864acf8
103
frontend/src/common/input.js
Normal file
103
frontend/src/common/input.js
Normal file
|
@ -0,0 +1,103 @@
|
|||
/*
|
||||
|
||||
Copyright (c) 2018 - 2021 Michael Mayer <hello@photoprism.org>
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as published
|
||||
by the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
PhotoPrism® is a registered trademark of Michael Mayer. You may use it as required
|
||||
to describe our software, run your own server, for educational purposes, but not for
|
||||
offering commercial goods, products, or services without prior written permission.
|
||||
In other words, please ask.
|
||||
|
||||
Feel free to send an e-mail to hello@photoprism.org if you have questions,
|
||||
want to support our work, or just want to say hello.
|
||||
|
||||
Additional information can be found in our Developer Guide:
|
||||
https://docs.photoprism.org/developer-guide/
|
||||
|
||||
*/
|
||||
|
||||
export const InputInvalid = 0;
|
||||
export const ClickShort = 1;
|
||||
export const ClickLong = 2;
|
||||
|
||||
export class Input {
|
||||
constructor() {
|
||||
this.reset();
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.index = -1;
|
||||
this.scrollY = window.scrollY;
|
||||
this.touches = [];
|
||||
this.timeStamp = -1;
|
||||
}
|
||||
|
||||
touchStart(ev, index) {
|
||||
this.index = index;
|
||||
this.scrollY = window.scrollY;
|
||||
if (ev.touches) {
|
||||
this.touches = ev.touches;
|
||||
}
|
||||
this.timeStamp = ev.timeStamp;
|
||||
}
|
||||
|
||||
mouseDown(ev, index) {
|
||||
this.index = index;
|
||||
this.scrollY = window.scrollY;
|
||||
this.touches = [];
|
||||
this.timeStamp = ev.timeStamp;
|
||||
}
|
||||
|
||||
clickType(ev, index) {
|
||||
if (this.timeStamp < 0) {
|
||||
return InputInvalid;
|
||||
}
|
||||
|
||||
if (ev.changedTouches && ev.changedTouches.length === 1) {
|
||||
if (this.touches.length !== 1) {
|
||||
return InputInvalid;
|
||||
}
|
||||
|
||||
if (
|
||||
this.touches[0].screenX !== ev.changedTouches[0].screenX ||
|
||||
this.touches[0].screenY !== ev.changedTouches[0].screenY
|
||||
) {
|
||||
return InputInvalid;
|
||||
}
|
||||
}
|
||||
|
||||
if (this.index !== index || this.scrollY - window.scrollY !== 0) {
|
||||
return InputInvalid;
|
||||
}
|
||||
|
||||
const clickDuration = ev.timeStamp - this.timeStamp;
|
||||
|
||||
if (clickDuration > 0 && clickDuration < 200) {
|
||||
return ClickShort;
|
||||
} else if (clickDuration > 400) {
|
||||
return ClickLong;
|
||||
}
|
||||
|
||||
return InputInvalid;
|
||||
}
|
||||
|
||||
eval(ev, index) {
|
||||
const result = this.clickType(ev, index);
|
||||
this.reset();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export default Input;
|
|
@ -40,9 +40,9 @@
|
|||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
@touchstart="onMouseDown($event, index)"
|
||||
@touchend.stop.prevent="onClick($event, index)"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@touchstart="input.touchStart($event, index)"
|
||||
@touchend.prevent="onClick($event, index)"
|
||||
@mousedown="input.mouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
@mouseover="playLive(photo)"
|
||||
@mouseleave="pauseLive(photo)"
|
||||
|
@ -56,10 +56,10 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat absolute
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">$vuetify.icons.live_photo</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-play" :title="$gettext('Video')">play_arrow</v-icon>
|
||||
|
@ -68,19 +68,19 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat absolute :title="$gettext('View')"
|
||||
@touchstart.stop.prevent="openPhoto(index, false)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, false)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
@click.stop.prevent="onOpen($event, index, false)">
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
outline fab large absolute :title="$gettext('Play')"
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
|
@ -93,8 +93,8 @@
|
|||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@touchstart.stop.prevent="onSelect($event, index)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onSelect($event, index)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
|
@ -104,7 +104,7 @@
|
|||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-favorite"
|
||||
@touchstart.stop.prevent="onTouchStart($event, index)"
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="toggleLike($event, index)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="toggleLike($event, index)">
|
||||
|
@ -192,7 +192,8 @@
|
|||
</template>
|
||||
<script>
|
||||
import download from "common/download";
|
||||
import Notify from "../../common/notify";
|
||||
import Notify from "common/notify";
|
||||
import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
|
||||
|
||||
export default {
|
||||
name: 'PPhotoCards',
|
||||
|
@ -221,16 +222,7 @@ export default {
|
|||
video: this.$gettext("Video"),
|
||||
name: this.$gettext("Name"),
|
||||
},
|
||||
touchStart: {
|
||||
index: -1,
|
||||
scrollY: window.scrollY,
|
||||
timeStamp: -1,
|
||||
},
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
scrollY: window.scrollY,
|
||||
timeStamp: -1,
|
||||
},
|
||||
input: new Input(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -257,26 +249,10 @@ export default {
|
|||
const photo = this.photos[index];
|
||||
download(`/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`, photo.FileName);
|
||||
},
|
||||
onTouchStart(ev, index) {
|
||||
this.touchStart.index = index;
|
||||
this.touchStart.scrollY = window.scrollY;
|
||||
this.touchStart.timeStamp = ev.timeStamp;
|
||||
},
|
||||
resetTouchStart() {
|
||||
this.touchStart.index = -1;
|
||||
this.touchStart.scrollY = window.scrollY;
|
||||
this.touchStart.timeStamp = -1;
|
||||
},
|
||||
isClick(ev, index) {
|
||||
if (this.touchStart.timeStamp < 0) return true;
|
||||
|
||||
return this.touchStart.index === index
|
||||
&& (ev.timeStamp - this.touchStart.timeStamp) < 200
|
||||
&& (this.touchStart.scrollY - window.scrollY) === 0;
|
||||
},
|
||||
toggleLike(ev, index) {
|
||||
if (!this.isClick(ev, index)) {
|
||||
this.resetTouchStart();
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -289,22 +265,32 @@ export default {
|
|||
photo.toggleLike();
|
||||
},
|
||||
onSelect(ev, index) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
this.mouseDown.index = index;
|
||||
this.mouseDown.scrollY = window.scrollY;
|
||||
this.mouseDown.timeStamp = ev.timeStamp;
|
||||
onOpen(ev, index, showMerged) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openPhoto(index, showMerged);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
const longClick = (this.mouseDown.index === index && (ev.timeStamp - this.mouseDown.timeStamp) > 400);
|
||||
const scrolled = (this.mouseDown.scrollY - window.scrollY) !== 0;
|
||||
const inputType = this.input.eval(ev, index);
|
||||
const longClick = inputType === ClickLong;
|
||||
|
||||
if (scrolled) {
|
||||
if (inputType === InputInvalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -39,9 +39,9 @@
|
|||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
@touchstart="onMouseDown($event, index)"
|
||||
@touchend.stop.prevent="onClick($event, index)"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@touchstart="input.touchStart($event, index)"
|
||||
@touchend.prevent="onClick($event, index)"
|
||||
@mousedown="input.mouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
@mouseover="playLive(photo)"
|
||||
@mouseleave="pauseLive(photo)"
|
||||
|
@ -55,10 +55,10 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat small absolute
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">$vuetify.icons.live_photo</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-play" :title="$gettext('Video')">play_arrow</v-icon>
|
||||
|
@ -67,19 +67,19 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat small absolute :title="$gettext('View')"
|
||||
@touchstart.stop.prevent="openPhoto(index, false)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, false)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
@click.stop.prevent="onOpen($event, index, false)">
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
icon flat small absolute :title="$gettext('Play')"
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
|
@ -92,8 +92,8 @@
|
|||
<v-btn :ripple="false"
|
||||
icon flat small absolute
|
||||
class="input-select"
|
||||
@touchstart.stop.prevent="onSelect($event, index)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onSelect($event, index)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
|
@ -103,7 +103,7 @@
|
|||
<v-btn :ripple="false"
|
||||
icon flat small absolute
|
||||
class="input-favorite"
|
||||
@touchstart.stop.prevent="onTouchStart($event, index)"
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="toggleLike($event, index)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="toggleLike($event, index)">
|
||||
|
@ -117,6 +117,8 @@
|
|||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
|
||||
|
||||
export default {
|
||||
name: 'PPhotoMosaic',
|
||||
props: {
|
||||
|
@ -131,16 +133,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
hidePrivate: this.$config.settings().features.private,
|
||||
touchStart: {
|
||||
index: -1,
|
||||
scrollY: window.scrollY,
|
||||
timeStamp: -1,
|
||||
},
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
scrollY: window.scrollY,
|
||||
timeStamp: -1,
|
||||
},
|
||||
input: new Input(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -161,26 +154,10 @@ export default {
|
|||
// Ignore.
|
||||
}
|
||||
},
|
||||
onTouchStart(ev, index) {
|
||||
this.touchStart.index = index;
|
||||
this.touchStart.scrollY = window.scrollY;
|
||||
this.touchStart.timeStamp = ev.timeStamp;
|
||||
},
|
||||
resetTouchStart() {
|
||||
this.touchStart.index = -1;
|
||||
this.touchStart.scrollY = window.scrollY;
|
||||
this.touchStart.timeStamp = -1;
|
||||
},
|
||||
isClick(ev, index) {
|
||||
if (this.touchStart.timeStamp < 0) return true;
|
||||
|
||||
return this.touchStart.index === index
|
||||
&& (ev.timeStamp - this.touchStart.timeStamp) < 200
|
||||
&& (this.touchStart.scrollY - window.scrollY) === 0;
|
||||
},
|
||||
toggleLike(ev, index) {
|
||||
if (!this.isClick(ev, index)) {
|
||||
this.resetTouchStart();
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -193,25 +170,35 @@ export default {
|
|||
photo.toggleLike();
|
||||
},
|
||||
onSelect(ev, index) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
this.mouseDown.index = index;
|
||||
this.mouseDown.scrollY = window.scrollY;
|
||||
this.mouseDown.timeStamp = ev.timeStamp;
|
||||
},
|
||||
toggle(photo) {
|
||||
this.$clipboard.toggle(photo);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
const longClick = (this.mouseDown.index === index && (ev.timeStamp - this.mouseDown.timeStamp) > 400);
|
||||
const scrolled = (this.mouseDown.scrollY - window.scrollY) !== 0;
|
||||
onOpen(ev, index, showMerged) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (scrolled) {
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openPhoto(index, showMerged);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
const longClick = inputType === ClickLong;
|
||||
|
||||
if (inputType === InputInvalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -36,9 +36,9 @@
|
|||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
@touchstart="onMouseDown($event, index)"
|
||||
@touchend.stop.prevent="onClick($event, index)"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@touchstart="input.touchStart($event, index)"
|
||||
@touchend.prevent="onClick($event, index)"
|
||||
@mousedown="input.mouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
@mouseover="playLive(photo)"
|
||||
@mouseleave="pauseLive(photo)"
|
||||
|
@ -52,10 +52,10 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat absolute
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">$vuetify.icons.live_photo</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-play" :title="$gettext('Video')">play_arrow</v-icon>
|
||||
|
@ -64,27 +64,27 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat absolute :title="$gettext('View')"
|
||||
@touchstart.stop.prevent="openPhoto(index, false)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, false)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
outline fab large absolute :title="$gettext('Play')"
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false"
|
||||
icon flat absolute
|
||||
class="input-select"
|
||||
@touchstart.stop.prevent="onSelect($event, index)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onSelect($event, index)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
|
@ -141,7 +141,8 @@
|
|||
</template>
|
||||
<script>
|
||||
import download from "common/download";
|
||||
import Notify from "../../common/notify";
|
||||
import Notify from "common/notify";
|
||||
import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
|
||||
|
||||
export default {
|
||||
name: 'PPhotoCards',
|
||||
|
@ -170,11 +171,7 @@ export default {
|
|||
video: this.$gettext("Video"),
|
||||
name: this.$gettext("Name"),
|
||||
},
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
scrollY: window.scrollY,
|
||||
timeStamp: -1,
|
||||
},
|
||||
input: new Input(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -202,22 +199,32 @@ export default {
|
|||
download(`/api/v1/dl/${photo.Hash}?t=${this.$config.downloadToken()}`, photo.FileName);
|
||||
},
|
||||
onSelect(ev, index) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.$clipboard.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
this.mouseDown.index = index;
|
||||
this.mouseDown.scrollY = window.scrollY;
|
||||
this.mouseDown.timeStamp = ev.timeStamp;
|
||||
onOpen(ev, index, showMerged) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openPhoto(index, showMerged);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
const longClick = (this.mouseDown.index === index && (ev.timeStamp - this.mouseDown.timeStamp) > 400);
|
||||
const scrolled = (this.mouseDown.scrollY - window.scrollY) !== 0;
|
||||
const inputType = this.input.eval(ev, index);
|
||||
const longClick = inputType === ClickLong;
|
||||
|
||||
if (scrolled) {
|
||||
if (inputType === InputInvalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
|
@ -35,9 +35,9 @@
|
|||
:transition="false"
|
||||
aspect-ratio="1"
|
||||
class="accent lighten-2 clickable"
|
||||
@touchstart="onMouseDown($event, index)"
|
||||
@touchend.stop.prevent="onClick($event, index)"
|
||||
@mousedown="onMouseDown($event, index)"
|
||||
@touchstart="input.touchStart($event, index)"
|
||||
@touchend.prevent="onClick($event, index)"
|
||||
@mousedown="input.mouseDown($event, index)"
|
||||
@click.stop.prevent="onClick($event, index)"
|
||||
@mouseover="playLive(photo)"
|
||||
@mouseleave="pauseLive(photo)"
|
||||
|
@ -51,10 +51,10 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-open"
|
||||
icon flat small absolute
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="default-hidden action-raw" :title="$gettext('RAW')">photo_camera</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-live" :title="$gettext('Live')">$vuetify.icons.live_photo</v-icon>
|
||||
<v-icon color="white" class="default-hidden action-play" :title="$gettext('Video')">play_arrow</v-icon>
|
||||
|
@ -63,27 +63,27 @@
|
|||
|
||||
<v-btn :ripple="false" :depressed="false" class="input-view"
|
||||
icon flat small absolute :title="$gettext('View')"
|
||||
@touchstart.stop.prevent="openPhoto(index, false)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, false)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, false)">
|
||||
@click.stop.prevent="onOpen($event, index, false)">
|
||||
<v-icon color="white" class="action-fullscreen">zoom_in</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false" :depressed="false" color="white" class="input-play"
|
||||
icon flat small absolute :title="$gettext('Play')"
|
||||
@touchstart.stop.prevent="openPhoto(index, true)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onOpen($event, index, true)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="openPhoto(index, true)">
|
||||
@click.stop.prevent="onOpen($event, index, true)">
|
||||
<v-icon color="white" class="action-play">play_arrow</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn :ripple="false"
|
||||
icon flat small absolute
|
||||
class="input-select"
|
||||
@touchstart.stop.prevent="onSelect($event, index)"
|
||||
@touchend.stop.prevent
|
||||
@touchstart.stop.prevent="input.touchStart($event, index)"
|
||||
@touchend.stop.prevent="onSelect($event, index)"
|
||||
@touchmove.stop.prevent
|
||||
@click.stop.prevent="onSelect($event, index)">
|
||||
<v-icon color="white" class="select-on">check_circle</v-icon>
|
||||
|
@ -96,6 +96,8 @@
|
|||
</v-container>
|
||||
</template>
|
||||
<script>
|
||||
import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
|
||||
|
||||
export default {
|
||||
name: 'PPhotoMosaic',
|
||||
props: {
|
||||
|
@ -110,11 +112,7 @@ export default {
|
|||
data() {
|
||||
return {
|
||||
hidePrivate: this.$config.settings().features.private,
|
||||
mouseDown: {
|
||||
index: -1,
|
||||
scrollY: window.scrollY,
|
||||
timeStamp: -1,
|
||||
},
|
||||
input: new Input(),
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
|
@ -136,25 +134,35 @@ export default {
|
|||
}
|
||||
},
|
||||
onSelect(ev, index) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (ev.shiftKey) {
|
||||
this.selectRange(index);
|
||||
} else {
|
||||
this.toggle(this.photos[index]);
|
||||
}
|
||||
},
|
||||
onMouseDown(ev, index) {
|
||||
this.mouseDown.index = index;
|
||||
this.mouseDown.scrollY = window.scrollY;
|
||||
this.mouseDown.timeStamp = ev.timeStamp;
|
||||
},
|
||||
toggle(photo) {
|
||||
this.$clipboard.toggle(photo);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
const longClick = (this.mouseDown.index === index && (ev.timeStamp - this.mouseDown.timeStamp) > 400);
|
||||
const scrolled = (this.mouseDown.scrollY - window.scrollY) !== 0;
|
||||
onOpen(ev, index, showMerged) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
|
||||
if (scrolled) {
|
||||
if (inputType !== ClickShort) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.openPhoto(index, showMerged);
|
||||
},
|
||||
onClick(ev, index) {
|
||||
const inputType = this.input.eval(ev, index);
|
||||
const longClick = inputType === ClickLong;
|
||||
|
||||
if (inputType === InputInvalid) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue