Compare commits
1 Commits
Author | SHA1 | Date |
---|---|---|
Varun Patil | c87134b16d |
File diff suppressed because it is too large
Load Diff
29
package.json
29
package.json
|
@ -32,21 +32,22 @@
|
|||
"@nextcloud/l10n": "^1.6.0",
|
||||
"@nextcloud/paths": "^2.1.0",
|
||||
"@nextcloud/sharing": "^0.1.0",
|
||||
"@nextcloud/vue": "7.1.0",
|
||||
"@nextcloud/vue": "7.2.0",
|
||||
"camelcase": "^7.0.0",
|
||||
"filerobot-image-editor": "^4.3.7",
|
||||
"justified-layout": "^4.1.0",
|
||||
"moment": "^2.29.4",
|
||||
"node-polyfill-webpack-plugin": "^2.0.1",
|
||||
"path-posix": "^1.0.0",
|
||||
"photoswipe": "^5.3.4",
|
||||
"plyr": "^3.7.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"video.js": "^7.20.3",
|
||||
"videojs-contrib-quality-levels": "^2.2.0",
|
||||
"vue": "^2.7.10",
|
||||
"videojs-contrib-quality-levels": "^2.2.1",
|
||||
"vue": "^3.2.45",
|
||||
"vue-material-design-icons": "^5.1.2",
|
||||
"vue-router": "^3.5.4",
|
||||
"vue-virtual-scroller": "1.1.2",
|
||||
"vue-router": "^4.1.6",
|
||||
"vue-virtual-scroller": "2.0.0-beta.5",
|
||||
"webdav": "^4.11.2"
|
||||
},
|
||||
"browserslist": [
|
||||
|
@ -59,13 +60,19 @@
|
|||
"devDependencies": {
|
||||
"@nextcloud/babel-config": "^1.0.0",
|
||||
"@nextcloud/browserslist-config": "^2.3.0",
|
||||
"@nextcloud/webpack-vue-config": "^5.4.0",
|
||||
"@playwright/test": "^1.28.0",
|
||||
"@playwright/test": "^1.28.1",
|
||||
"@types/url-parse": "^1.4.8",
|
||||
"@types/video.js": "^7.3.49",
|
||||
"playwright": "^1.28.0",
|
||||
"ts-loader": "^9.4.1",
|
||||
"typescript": "^4.9.3",
|
||||
"@types/video.js": "^7.3.50",
|
||||
"babel-loader": "^9.1.0",
|
||||
"css-loader": "^6.7.2",
|
||||
"playwright": "^1.28.1",
|
||||
"sass": "^1.56.2",
|
||||
"sass-loader": "^13.2.0",
|
||||
"style-loader": "^3.3.1",
|
||||
"ts-loader": "^9.4.2",
|
||||
"typescript": "^4.9.4",
|
||||
"vue-loader": "^17.0.1",
|
||||
"webpack-cli": "^5.0.1",
|
||||
"workbox-webpack-plugin": "^6.5.4"
|
||||
}
|
||||
}
|
||||
|
|
30
src/App.vue
30
src/App.vue
|
@ -2,8 +2,8 @@
|
|||
<FirstStart v-if="isFirstStart" />
|
||||
|
||||
<NcContent
|
||||
v-else-if="false"
|
||||
app-name="memories"
|
||||
v-else
|
||||
:class="{
|
||||
'remove-gap': removeOuterGap,
|
||||
}"
|
||||
|
@ -35,10 +35,14 @@
|
|||
</div>
|
||||
</NcAppContent>
|
||||
</NcContent>
|
||||
|
||||
<div class="outer" v-else>
|
||||
<router-view />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import Vue, { defineComponent } from "vue";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
import NcContent from "@nextcloud/vue/dist/Components/NcContent";
|
||||
import NcAppContent from "@nextcloud/vue/dist/Components/NcAppContent";
|
||||
|
@ -95,16 +99,16 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
navItems: [],
|
||||
metadataComponent: null as Metadata,
|
||||
metadataComponent: null as any,
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
ncVersion() {
|
||||
ncVersion(): number {
|
||||
const version = (<any>window.OC).config.version.split(".");
|
||||
return Number(version[0]);
|
||||
},
|
||||
recognize() {
|
||||
recognize(): string | boolean {
|
||||
if (!this.config_recognizeEnabled) {
|
||||
return false;
|
||||
}
|
||||
|
@ -115,7 +119,7 @@ export default defineComponent({
|
|||
|
||||
return t("memories", "People");
|
||||
},
|
||||
facerecognition() {
|
||||
facerecognition(): string | boolean {
|
||||
if (!this.config_facerecognitionInstalled) {
|
||||
return false;
|
||||
}
|
||||
|
@ -126,16 +130,16 @@ export default defineComponent({
|
|||
|
||||
return t("memories", "People");
|
||||
},
|
||||
isFirstStart() {
|
||||
isFirstStart(): boolean {
|
||||
return this.config_timelinePath === "EMPTY";
|
||||
},
|
||||
showAlbums() {
|
||||
showAlbums(): boolean {
|
||||
return this.config_albumsEnabled;
|
||||
},
|
||||
removeOuterGap() {
|
||||
removeOuterGap(): boolean {
|
||||
return this.ncVersion >= 25;
|
||||
},
|
||||
showNavigation() {
|
||||
showNavigation(): boolean {
|
||||
return this.$route.name !== "folder-share";
|
||||
},
|
||||
},
|
||||
|
@ -174,7 +178,7 @@ export default defineComponent({
|
|||
if (this.metadataComponent) {
|
||||
this.metadataComponent.$destroy();
|
||||
}
|
||||
this.metadataComponent = new Vue(Metadata);
|
||||
this.metadataComponent = new Metadata();
|
||||
// Only mount after we have all the info we need
|
||||
await this.metadataComponent.update(fileInfo);
|
||||
this.metadataComponent.$mount(el);
|
||||
|
@ -281,9 +285,9 @@ export default defineComponent({
|
|||
if (globalThis.windowInnerWidth <= 1024) nav?.toggleNavigation(false);
|
||||
},
|
||||
|
||||
doRouteChecks() {
|
||||
doRouteChecks(): void {
|
||||
if (this.$route.name === "folder-share") {
|
||||
this.putFolderShareToken(this.$route.params.token);
|
||||
this.putFolderShareToken(<string>this.$route.params.token);
|
||||
}
|
||||
},
|
||||
|
||||
|
|
|
@ -160,6 +160,7 @@ export default defineComponent({
|
|||
|
||||
transition: opacity 1s ease;
|
||||
opacity: 0;
|
||||
|
||||
&.show {
|
||||
opacity: 1;
|
||||
}
|
||||
|
|
|
@ -27,12 +27,11 @@
|
|||
|
||||
<div class="edit" v-if="field.edit">
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Edit')"
|
||||
@click="field.edit()"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Edit')" @click="field.edit()">
|
||||
{{ t("memories", "Edit") }}
|
||||
<template #icon> <EditIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<EditIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
|
@ -67,6 +66,14 @@ import InfoIcon from "vue-material-design-icons/InformationOutline.vue";
|
|||
import LocationIcon from "vue-material-design-icons/MapMarker.vue";
|
||||
import { API } from "../services/API";
|
||||
|
||||
interface TopField {
|
||||
title: string;
|
||||
subtitle: string[];
|
||||
icon: any;
|
||||
href?: string;
|
||||
edit?: () => void;
|
||||
};
|
||||
|
||||
export default defineComponent({
|
||||
name: "Metadata",
|
||||
components: {
|
||||
|
@ -78,7 +85,7 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
fileInfo: null as IFileInfo,
|
||||
exif: {} as { [prop: string]: any },
|
||||
exif: {} as { [prop: string]: any; },
|
||||
baseInfo: {} as any,
|
||||
nominatim: null as any,
|
||||
state: 0,
|
||||
|
@ -94,14 +101,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
computed: {
|
||||
topFields() {
|
||||
let list: {
|
||||
title: string;
|
||||
subtitle: string[];
|
||||
icon: any;
|
||||
href?: string;
|
||||
edit?: () => void;
|
||||
}[] = [];
|
||||
topFields(): TopField[] {
|
||||
let list: TopField[] = [];
|
||||
|
||||
if (this.dateOriginal) {
|
||||
list.push({
|
||||
|
@ -152,7 +153,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Date taken info */
|
||||
dateOriginal() {
|
||||
dateOriginal(): moment.Moment | null {
|
||||
const dt = this.exif["DateTimeOriginal"] || this.exif["CreateDate"];
|
||||
if (!dt) return null;
|
||||
|
||||
|
@ -162,12 +163,12 @@ export default defineComponent({
|
|||
return m;
|
||||
},
|
||||
|
||||
dateOriginalStr() {
|
||||
dateOriginalStr(): string | null {
|
||||
if (!this.dateOriginal) return null;
|
||||
return utils.getLongDateStr(this.dateOriginal.toDate(), true);
|
||||
},
|
||||
|
||||
dateOriginalTime() {
|
||||
dateOriginalTime(): string[] | null {
|
||||
if (!this.dateOriginal) return null;
|
||||
|
||||
// Try to get timezone
|
||||
|
@ -182,7 +183,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Camera make and model info */
|
||||
camera() {
|
||||
camera(): string | null {
|
||||
const make = this.exif["Make"];
|
||||
const model = this.exif["Model"];
|
||||
if (!make || !model) return null;
|
||||
|
@ -190,7 +191,7 @@ export default defineComponent({
|
|||
return `${make} ${model}`;
|
||||
},
|
||||
|
||||
cameraSub() {
|
||||
cameraSub(): string[] | null {
|
||||
const f = this.exif["FNumber"] || this.exif["Aperture"];
|
||||
const s = this.shutterSpeed;
|
||||
const len = this.exif["FocalLength"];
|
||||
|
@ -205,7 +206,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Convert shutter speed decimal to 1/x format */
|
||||
shutterSpeed() {
|
||||
shutterSpeed(): string | null {
|
||||
const speed = Number(
|
||||
this.exif["ShutterSpeedValue"] ||
|
||||
this.exif["ShutterSpeed"] ||
|
||||
|
@ -221,11 +222,11 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Image info */
|
||||
imageInfo() {
|
||||
imageInfo(): string | null {
|
||||
return this.fileInfo.basename || (<any>this.fileInfo).name;
|
||||
},
|
||||
|
||||
imageInfoSub() {
|
||||
imageInfoSub(): string[] | null {
|
||||
let parts = [];
|
||||
let mp = Number(this.exif["Megapixels"]);
|
||||
|
||||
|
@ -244,7 +245,7 @@ export default defineComponent({
|
|||
return parts;
|
||||
},
|
||||
|
||||
address() {
|
||||
address(): string | null {
|
||||
if (!this.lat || !this.lon) return null;
|
||||
|
||||
if (!this.nominatim) return this.t("memories", "Loading …");
|
||||
|
@ -263,15 +264,15 @@ export default defineComponent({
|
|||
}
|
||||
},
|
||||
|
||||
lat() {
|
||||
lat(): number | null {
|
||||
return this.exif["GPSLatitude"];
|
||||
},
|
||||
|
||||
lon() {
|
||||
lon(): number | null {
|
||||
return this.exif["GPSLongitude"];
|
||||
},
|
||||
|
||||
mapUrl() {
|
||||
mapUrl(): string | null {
|
||||
const boxSize = 0.0075;
|
||||
const bbox = [
|
||||
this.lon - boxSize,
|
||||
|
@ -283,7 +284,7 @@ export default defineComponent({
|
|||
return `https://www.openstreetmap.org/export/embed.html?bbox=${bbox.join()}&marker=${m}`;
|
||||
},
|
||||
|
||||
mapFullUrl() {
|
||||
mapFullUrl(): string | null {
|
||||
return `https://www.openstreetmap.org/?mlat=${this.lat}&mlon=${this.lon}#map=18/${this.lat}/${this.lon}`;
|
||||
},
|
||||
},
|
||||
|
@ -345,9 +346,11 @@ export default defineComponent({
|
|||
color: var(--color-text-lighter);
|
||||
}
|
||||
}
|
||||
|
||||
.edit {
|
||||
transform: translateX(10px);
|
||||
}
|
||||
|
||||
.text {
|
||||
display: inline-block;
|
||||
word-break: break-word;
|
||||
|
@ -355,6 +358,7 @@ export default defineComponent({
|
|||
|
||||
.subtitle {
|
||||
font-size: 0.95em;
|
||||
|
||||
.part {
|
||||
margin-right: 5px;
|
||||
}
|
||||
|
|
|
@ -1,48 +1,25 @@
|
|||
<template>
|
||||
<div
|
||||
class="scroller"
|
||||
ref="scroller"
|
||||
v-bind:class="{
|
||||
<div class="scroller" ref="scroller" v-bind:class="{
|
||||
'scrolling-recycler-now': scrollingRecyclerNowTimer,
|
||||
'scrolling-recycler': scrollingRecyclerTimer,
|
||||
'scrolling-now': scrollingNowTimer,
|
||||
scrolling: scrollingTimer,
|
||||
}"
|
||||
@mousemove.passive="mousemove"
|
||||
@mouseleave.passive="mouseleave"
|
||||
@mousedown.passive="mousedown"
|
||||
@mouseup.passive="interactend"
|
||||
@touchmove.prevent="touchmove"
|
||||
@touchstart.passive="interactstart"
|
||||
@touchend.passive="interactend"
|
||||
@touchcancel.passive="interactend"
|
||||
>
|
||||
<span
|
||||
class="cursor st"
|
||||
ref="cursorSt"
|
||||
:style="{ transform: `translateY(${cursorY}px)` }"
|
||||
>
|
||||
}" @mousemove.passive="mousemove" @mouseleave.passive="mouseleave" @mousedown.passive="mousedown"
|
||||
@mouseup.passive="interactend" @touchmove.prevent="touchmove" @touchstart.passive="interactstart"
|
||||
@touchend.passive="interactend" @touchcancel.passive="interactend">
|
||||
<span class="cursor st" ref="cursorSt" :style="{ transform: `translateY(${cursorY}px)` }">
|
||||
</span>
|
||||
|
||||
<span
|
||||
class="cursor hv"
|
||||
:style="{ transform: `translateY(${hoverCursorY}px)` }"
|
||||
@touchmove.prevent="touchmove"
|
||||
@touchstart.passive="interactstart"
|
||||
@touchend.passive="interactend"
|
||||
@touchcancel.passive="interactend"
|
||||
>
|
||||
<span class="cursor hv" :style="{ transform: `translateY(${hoverCursorY}px)` }" @touchmove.prevent="touchmove"
|
||||
@touchstart.passive="interactstart" @touchend.passive="interactend" @touchcancel.passive="interactend">
|
||||
<div class="text">{{ hoverCursorText }}</div>
|
||||
<div class="icon"><ScrollIcon :size="22" /></div>
|
||||
<div class="icon">
|
||||
<ScrollIcon :size="22" />
|
||||
</div>
|
||||
</span>
|
||||
|
||||
<div
|
||||
v-for="tick of visibleTicks"
|
||||
:key="tick.key"
|
||||
class="tick"
|
||||
:class="{ dash: !tick.text }"
|
||||
:style="{ transform: `translateY(calc(${tick.top}px - 50%))` }"
|
||||
>
|
||||
<div v-for="tick of visibleTicks" :key="tick.key" class="tick" :class="{ dash: !tick.text }"
|
||||
:style="{ transform: `translateY(calc(${tick.top}px - 50%))` }">
|
||||
<span v-if="tick.text">{{ tick.text }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -152,7 +129,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Recycler scroll event, must be called by timeline */
|
||||
recyclerScrolled() {
|
||||
recyclerScrolled(event: Event | null) {
|
||||
// This isn't a renewing timer, it's a scheduled task
|
||||
if (this.scrollingRecyclerUpdateTimer) return;
|
||||
this.scrollingRecyclerUpdateTimer = window.setTimeout(() => {
|
||||
|
@ -510,7 +487,7 @@ export default defineComponent({
|
|||
|
||||
interactend() {
|
||||
this.interacting = false;
|
||||
this.recyclerScrolled(); // make sure final position is correct
|
||||
this.recyclerScrolled(null); // make sure final position is correct
|
||||
},
|
||||
|
||||
/** Update scroller is being used to scroll recycler */
|
||||
|
@ -566,6 +543,7 @@ export default defineComponent({
|
|||
background-color: var(--color-main-text);
|
||||
opacity: 0.15;
|
||||
display: block;
|
||||
|
||||
@include phone {
|
||||
display: none;
|
||||
}
|
||||
|
@ -609,11 +587,14 @@ export default defineComponent({
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.scrolling-recycler-now:not(.scrolling-now)>.cursor {
|
||||
transition: transform 0.1s linear;
|
||||
}
|
||||
|
||||
&:hover>.cursor {
|
||||
transition: none !important;
|
||||
|
||||
&.st {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -623,6 +604,7 @@ export default defineComponent({
|
|||
@include phone {
|
||||
// Shift pointer events to hover cursor
|
||||
pointer-events: none;
|
||||
|
||||
.cursor.hv {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
@ -630,6 +612,7 @@ export default defineComponent({
|
|||
>.tick {
|
||||
right: 40px;
|
||||
}
|
||||
|
||||
&:not(.scrolling) {
|
||||
>.tick {
|
||||
display: none;
|
||||
|
@ -643,9 +626,11 @@ export default defineComponent({
|
|||
height: 40px;
|
||||
width: 70px;
|
||||
border-radius: 20px;
|
||||
|
||||
>.text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
>.icon {
|
||||
display: block;
|
||||
}
|
||||
|
|
|
@ -2,12 +2,11 @@
|
|||
<div>
|
||||
<div v-if="show" class="top-bar">
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Cancel')"
|
||||
@click="clearSelection()"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Cancel')" @click="clearSelection()">
|
||||
{{ t("memories", "Cancel") }}
|
||||
<template #icon> <CloseIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<CloseIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
|
||||
|
@ -20,13 +19,8 @@
|
|||
</div>
|
||||
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton
|
||||
v-for="action of getActions()"
|
||||
:key="action.name"
|
||||
:aria-label="action.name"
|
||||
close-after-click
|
||||
@click="click(action)"
|
||||
>
|
||||
<NcActionButton v-for="action of getActions()" :key="action.name" :aria-label="action.name" close-after-click
|
||||
@click="click(action)">
|
||||
{{ action.name }}
|
||||
<template #icon>
|
||||
<component :is="action.icon" :size="20" />
|
||||
|
@ -38,11 +32,7 @@
|
|||
<!-- Selection Modals -->
|
||||
<EditDate ref="editDate" @refresh="refresh" />
|
||||
<EditExif ref="editExif" @refresh="refresh" />
|
||||
<FaceMoveModal
|
||||
ref="faceMoveModal"
|
||||
@moved="deletePhotos"
|
||||
:updateLoading="updateLoading"
|
||||
/>
|
||||
<FaceMoveModal ref="faceMoveModal" @moved="deletePhotos" :updateLoading="updateLoading" />
|
||||
<AddToAlbumModal ref="addToAlbumModal" @added="clearSelection" />
|
||||
</div>
|
||||
</template>
|
||||
|
@ -103,7 +93,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
props: {
|
||||
heads: Object as PropType<{ [dayid: number]: IHeadRow }>,
|
||||
heads: Object as PropType<{ [dayid: number]: IHeadRow; }>,
|
||||
/** List of rows for multi selection */
|
||||
rows: Array as PropType<IRow[]>,
|
||||
/** Rows are in ascending order (desc is normal) */
|
||||
|
@ -840,8 +830,8 @@ export default defineComponent({
|
|||
|
||||
// Run query
|
||||
for await (let delIds of dav.removeFaceImages(
|
||||
user,
|
||||
name,
|
||||
<string>user,
|
||||
<string>name,
|
||||
Array.from(selection.values())
|
||||
)) {
|
||||
const delPhotos = delIds
|
||||
|
|
|
@ -23,42 +23,20 @@
|
|||
<template>
|
||||
<div>
|
||||
<label for="timeline-path">{{ t("memories", "Timeline Path") }}</label>
|
||||
<input
|
||||
id="timeline-path"
|
||||
@click="chooseTimelinePath"
|
||||
v-model="config_timelinePath"
|
||||
type="text"
|
||||
/>
|
||||
<input id="timeline-path" @click="chooseTimelinePath" v-model="config_timelinePath" type="text" />
|
||||
|
||||
<label for="folders-path">{{ t("memories", "Folders Path") }}</label>
|
||||
<input
|
||||
id="folders-path"
|
||||
@click="chooseFoldersPath"
|
||||
v-model="config_foldersPath"
|
||||
type="text"
|
||||
/>
|
||||
<input id="folders-path" @click="chooseFoldersPath" v-model="config_foldersPath" type="text" />
|
||||
|
||||
<NcCheckboxRadioSwitch
|
||||
:checked.sync="config_showHidden"
|
||||
@update:checked="updateShowHidden"
|
||||
type="switch"
|
||||
>
|
||||
<NcCheckboxRadioSwitch :checked.sync="config_showHidden" @update:checked="updateShowHidden" type="switch">
|
||||
{{ t("memories", "Show hidden folders") }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<NcCheckboxRadioSwitch
|
||||
:checked.sync="config_squareThumbs"
|
||||
@update:checked="updateSquareThumbs"
|
||||
type="switch"
|
||||
>
|
||||
<NcCheckboxRadioSwitch :checked.sync="config_squareThumbs" @update:checked="updateSquareThumbs" type="switch">
|
||||
{{ t("memories", "Square grid mode") }}
|
||||
</NcCheckboxRadioSwitch>
|
||||
|
||||
<MultiPathSelectionModal
|
||||
ref="multiPathModal"
|
||||
:title="pathSelTitle"
|
||||
@close="saveTimelinePath"
|
||||
/>
|
||||
<MultiPathSelectionModal ref="multiPathModal" :title="pathSelTitle" @close="saveTimelinePath" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -86,7 +64,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
computed: {
|
||||
pathSelTitle() {
|
||||
pathSelTitle(): string {
|
||||
return this.t("memories", "Choose Timeline Paths");
|
||||
},
|
||||
},
|
||||
|
|
|
@ -1,14 +1,10 @@
|
|||
<template>
|
||||
<div
|
||||
class="container"
|
||||
ref="container"
|
||||
:class="{ 'icon-loading': loading > 0 }"
|
||||
>
|
||||
<div class="container" ref="container" :class="{ 'icon-loading': loading > 0 }">
|
||||
<!-- Static top matter -->
|
||||
<TopMatter ref="topmatter" />
|
||||
|
||||
<!-- No content found and nothing is loading -->
|
||||
<NcEmptyContent
|
||||
<!-- <NcEmptyContent
|
||||
title="Nothing to show here"
|
||||
:description="emptyViewDescription"
|
||||
v-if="loading === 0 && list.length === 0"
|
||||
|
@ -18,49 +14,28 @@
|
|||
<ArchiveIcon v-else-if="routeIsArchive" />
|
||||
<ImageMultipleIcon v-else />
|
||||
</template>
|
||||
</NcEmptyContent>
|
||||
</NcEmptyContent> -->
|
||||
|
||||
<!-- Main recycler view for rows -->
|
||||
<RecycleScroller
|
||||
ref="recycler"
|
||||
class="recycler"
|
||||
:class="{ empty: list.length === 0 }"
|
||||
:items="list"
|
||||
:emit-update="true"
|
||||
:buffer="800"
|
||||
:skipHover="true"
|
||||
key-field="id"
|
||||
size-field="size"
|
||||
type-field="type"
|
||||
:updateInterval="100"
|
||||
@update="scrollChange"
|
||||
@resize="handleResizeWithDelay"
|
||||
>
|
||||
<RecycleScroller ref="recycler" class="recycler" :class="{ empty: list.length === 0 }" :items="list"
|
||||
:emit-update="true" :buffer="800" :skipHover="true" key-field="id" size-field="size" type-field="type"
|
||||
:updateInterval="100" @update="scrollChange" @resize="handleResizeWithDelay">
|
||||
<template #before>
|
||||
<!-- Show dynamic top matter, name of the view -->
|
||||
<div class="recycler-before" ref="recyclerBefore">
|
||||
<div class="text" v-show="!$refs.topmatter.type && list.length > 0">
|
||||
<div class="text" v-show="!(<any>$refs.topmatter)?.type && list.length > 0">
|
||||
{{ viewName }}
|
||||
</div>
|
||||
|
||||
<OnThisDay
|
||||
v-if="routeIsBase"
|
||||
:key="config_timelinePath"
|
||||
:viewer="$refs.viewer"
|
||||
@load="scrollerManager.adjust()"
|
||||
>
|
||||
<OnThisDay v-if="routeIsBase" :key="config_timelinePath" :viewer="$refs.viewer"
|
||||
@load="scrollerManager.adjust()">
|
||||
</OnThisDay>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot="{ item, index }">
|
||||
<div
|
||||
v-if="item.type === 0"
|
||||
class="head-row"
|
||||
:class="{ selected: item.selected }"
|
||||
:style="{ height: item.size + 'px' }"
|
||||
:key="item.id"
|
||||
>
|
||||
<div v-if="item.type === 0" class="head-row" :class="{ selected: item.selected }"
|
||||
:style="{ height: item.size + 'px' }" :key="item.id">
|
||||
<div class="super" v-if="item.super !== undefined">
|
||||
{{ item.super }}
|
||||
</div>
|
||||
|
@ -71,72 +46,34 @@
|
|||
</div>
|
||||
|
||||
<template v-else>
|
||||
<div
|
||||
class="photo"
|
||||
v-for="photo of item.photos"
|
||||
:key="photo.key"
|
||||
:style="{
|
||||
<div class="photo" v-for="photo of item.photos" :key="photo.key" :style="{
|
||||
height: photo.dispH + 'px',
|
||||
width: photo.dispW + 'px',
|
||||
transform: `translate(${photo.dispX}px, ${photo.dispY}px`,
|
||||
}"
|
||||
>
|
||||
<Folder
|
||||
v-if="photo.flag & c.FLAG_IS_FOLDER"
|
||||
:data="photo"
|
||||
:key="photo.fileid"
|
||||
/>
|
||||
}">
|
||||
<Folder v-if="photo.flag & c.FLAG_IS_FOLDER" :data="photo" />
|
||||
|
||||
<Tag
|
||||
v-else-if="photo.flag & c.FLAG_IS_TAG"
|
||||
:data="photo"
|
||||
:key="photo.fileid"
|
||||
/>
|
||||
<Tag v-else-if="photo.flag & c.FLAG_IS_TAG" :data="photo" />
|
||||
|
||||
<Photo
|
||||
v-else
|
||||
:data="photo"
|
||||
:day="item.day"
|
||||
:key="photo.fileid"
|
||||
@select="selectionManager.selectPhoto"
|
||||
@pointerdown="selectionManager.clickPhoto(photo, $event, index)"
|
||||
@touchstart="
|
||||
<Photo v-else :data="photo" :day="item.day" @select="selectionManager.selectPhoto"
|
||||
@pointerdown="selectionManager.clickPhoto(photo, $event, index)" @touchstart="
|
||||
selectionManager.touchstartPhoto(photo, $event, index)
|
||||
"
|
||||
@touchend="selectionManager.touchendPhoto(photo, $event, index)"
|
||||
@touchmove="selectionManager.touchmovePhoto(photo, $event, index)"
|
||||
/>
|
||||
" @touchend="selectionManager.touchendPhoto(photo, $event, index)"
|
||||
@touchmove="selectionManager.touchmovePhoto(photo, $event, index)" />
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</RecycleScroller>
|
||||
|
||||
<!-- Managers -->
|
||||
<ScrollerManager
|
||||
ref="scrollerManager"
|
||||
:rows="list"
|
||||
:height="scrollerHeight"
|
||||
:recycler="$refs.recycler"
|
||||
:recyclerBefore="$refs.recyclerBefore"
|
||||
/>
|
||||
<ScrollerManager ref="scrollerManager" :rows="list" :height="scrollerHeight" :recycler="$refs.recycler"
|
||||
:recyclerBefore="($refs.recyclerBefore as any)" />
|
||||
|
||||
<SelectionManager
|
||||
ref="selectionManager"
|
||||
:heads="heads"
|
||||
:rows="list"
|
||||
:isreverse="isMonthView"
|
||||
:recycler="$refs.recycler"
|
||||
@refresh="softRefresh"
|
||||
@delete="deleteFromViewWithAnimation"
|
||||
@updateLoading="updateLoading"
|
||||
/>
|
||||
<SelectionManager ref="selectionManager" :heads="heads" :rows="list" :isreverse="isMonthView"
|
||||
:recycler="$refs.recycler" @refresh="softRefresh" @delete="deleteFromViewWithAnimation"
|
||||
@updateLoading="updateLoading" />
|
||||
|
||||
<Viewer
|
||||
ref="viewer"
|
||||
@deleted="deleteFromViewWithAnimation"
|
||||
@fetchDay="fetchDay"
|
||||
@updateLoading="updateLoading"
|
||||
/>
|
||||
<Viewer ref="viewer" @deleted="deleteFromViewWithAnimation" @fetchDay="fetchDay" @updateLoading="updateLoading" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
@ -201,7 +138,7 @@ export default defineComponent({
|
|||
/** Computed number of columns */
|
||||
numCols: 0,
|
||||
/** Header rows for dayId key */
|
||||
heads: {} as { [dayid: number]: IHeadRow },
|
||||
heads: {} as { [dayid: number]: IHeadRow; },
|
||||
|
||||
/** Computed row height */
|
||||
rowHeight: 100,
|
||||
|
@ -230,15 +167,15 @@ export default defineComponent({
|
|||
state: Math.random(),
|
||||
|
||||
/** Selection manager component */
|
||||
selectionManager: null as SelectionManager & any,
|
||||
selectionManager: null as InstanceType<typeof SelectionManager>,
|
||||
/** Scroller manager component */
|
||||
scrollerManager: null as ScrollerManager & any,
|
||||
scrollerManager: null as InstanceType<typeof ScrollerManager>,
|
||||
};
|
||||
},
|
||||
|
||||
mounted() {
|
||||
this.selectionManager = this.$refs.selectionManager;
|
||||
this.scrollerManager = this.$refs.scrollerManager;
|
||||
this.selectionManager = <any>this.$refs.selectionManager;
|
||||
this.scrollerManager = <any>this.$refs.scrollerManager;
|
||||
this.routeChange(this.$route);
|
||||
},
|
||||
|
||||
|
@ -265,20 +202,20 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
computed: {
|
||||
routeIsBase() {
|
||||
routeIsBase(): boolean {
|
||||
return this.$route.name === "timeline";
|
||||
},
|
||||
routeIsPeople() {
|
||||
return ["recognize", "facerecognition"].includes(this.$route.name);
|
||||
routeIsPeople(): boolean {
|
||||
return ["recognize", "facerecognition"].includes(<string>this.$route.name);
|
||||
},
|
||||
routeIsArchive() {
|
||||
routeIsArchive(): boolean {
|
||||
return this.$route.name === "archive";
|
||||
},
|
||||
isMonthView() {
|
||||
isMonthView(): boolean {
|
||||
return this.$route.name === "albums";
|
||||
},
|
||||
/** Get view name for dynamic top matter */
|
||||
viewName() {
|
||||
viewName(): string {
|
||||
switch (this.$route.name) {
|
||||
case "timeline":
|
||||
return this.t("memories", "Your Timeline");
|
||||
|
@ -301,7 +238,7 @@ export default defineComponent({
|
|||
return "";
|
||||
}
|
||||
},
|
||||
emptyViewDescription() {
|
||||
emptyViewDescription(): string {
|
||||
switch (this.$route.name) {
|
||||
case "facerecognition":
|
||||
if (this.config_facerecognitionEnabled)
|
||||
|
@ -655,7 +592,7 @@ export default defineComponent({
|
|||
this.$route.params.name
|
||||
) {
|
||||
query.set(
|
||||
this.$route.name, // "recognize" or "facerecognition"
|
||||
<string>this.$route.name, // "recognize" or "facerecognition"
|
||||
`${this.$route.params.user}/${this.$route.params.name}`
|
||||
);
|
||||
|
||||
|
@ -667,7 +604,7 @@ export default defineComponent({
|
|||
|
||||
// Tags
|
||||
if (this.$route.name === "tags" && this.$route.params.name) {
|
||||
query.set("tag", this.$route.params.name);
|
||||
query.set("tag", <string>this.$route.params.name);
|
||||
}
|
||||
|
||||
// Albums
|
||||
|
@ -718,7 +655,7 @@ export default defineComponent({
|
|||
/** Fetch timeline main call */
|
||||
async fetchDays(noCache = false) {
|
||||
const url = API.Q(API.DAYS(), this.getQuery());
|
||||
const cacheUrl = this.$route.name + url;
|
||||
const cacheUrl = <string>this.$route.name + url;
|
||||
|
||||
// Try cache first
|
||||
let cache: IDay[];
|
||||
|
@ -1351,11 +1288,13 @@ export default defineComponent({
|
|||
|
||||
>div {
|
||||
position: relative;
|
||||
|
||||
&.super {
|
||||
font-size: 1.4em;
|
||||
font-weight: bold;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
&.main {
|
||||
display: inline-block;
|
||||
font-weight: 600;
|
||||
|
@ -1373,6 +1312,7 @@ export default defineComponent({
|
|||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.name {
|
||||
display: block;
|
||||
transition: transform 0.2s ease;
|
||||
|
@ -1386,10 +1326,12 @@ export default defineComponent({
|
|||
display: flex;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
.name {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
}
|
||||
|
||||
&.selected .select {
|
||||
opacity: 1;
|
||||
color: var(--color-primary);
|
||||
|
@ -1403,16 +1345,20 @@ export default defineComponent({
|
|||
/** Static and dynamic top matter */
|
||||
.top-matter {
|
||||
padding-top: 4px;
|
||||
|
||||
@include phone {
|
||||
padding-left: 40px;
|
||||
}
|
||||
}
|
||||
|
||||
.recycler-before {
|
||||
width: 100%;
|
||||
|
||||
>.text {
|
||||
font-size: 1.2em;
|
||||
padding-top: 13px;
|
||||
padding-left: 8px;
|
||||
|
||||
@include phone {
|
||||
padding-left: 48px;
|
||||
}
|
||||
|
|
|
@ -1,14 +1,9 @@
|
|||
<template>
|
||||
<router-link
|
||||
draggable="false"
|
||||
class="folder fill-block"
|
||||
:class="{
|
||||
<router-link draggable="false" class="folder fill-block" :class="{
|
||||
hasPreview: previews.length > 0,
|
||||
onePreview: previews.length === 1,
|
||||
hasError: error,
|
||||
}"
|
||||
:to="target"
|
||||
>
|
||||
}" :to="target">
|
||||
<div class="big-icon fill-block">
|
||||
<FolderIcon class="icon" />
|
||||
<div class="name">{{ data.name }}</div>
|
||||
|
@ -17,11 +12,7 @@
|
|||
<div class="previews fill-block">
|
||||
<div class="preview-container fill-block">
|
||||
<div class="img-outer" v-for="info of previews" :key="info.fileid">
|
||||
<img
|
||||
class="fill-block"
|
||||
:src="getPreviewUrl(info, true, 256)"
|
||||
@error="$event.target.classList.add('error')"
|
||||
/>
|
||||
<img class="fill-block" :src="getPreviewUrl(info, true, 256)" @error="$event.target.classList.add('error')" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -156,6 +147,7 @@ export default defineComponent({
|
|||
opacity: 1;
|
||||
filter: invert(1) brightness(100);
|
||||
}
|
||||
|
||||
.name {
|
||||
color: white;
|
||||
}
|
||||
|
@ -165,6 +157,7 @@ export default defineComponent({
|
|||
.folder:hover>&>.folder-icon {
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.folder.hasPreview:hover>& {
|
||||
opacity: 0;
|
||||
}
|
||||
|
@ -172,9 +165,9 @@ export default defineComponent({
|
|||
// Make it red if has an error
|
||||
.folder.hasError>& {
|
||||
.folder-icon {
|
||||
filter: invert(12%) sepia(62%) saturate(5862%) hue-rotate(8deg)
|
||||
brightness(103%) contrast(128%);
|
||||
filter: invert(12%) sepia(62%) saturate(5862%) hue-rotate(8deg) brightness(103%) contrast(128%);
|
||||
}
|
||||
|
||||
.name {
|
||||
color: #bb0000;
|
||||
}
|
||||
|
@ -222,6 +215,7 @@ export default defineComponent({
|
|||
&.error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.folder:hover & {
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
|
|
@ -1,19 +1,11 @@
|
|||
<template>
|
||||
<div
|
||||
class="p-outer fill-block"
|
||||
:class="{
|
||||
<div class="p-outer fill-block" :class="{
|
||||
selected: data.flag & c.FLAG_SELECTED,
|
||||
placeholder: data.flag & c.FLAG_PLACEHOLDER,
|
||||
leaving: data.flag & c.FLAG_LEAVING,
|
||||
error: data.flag & c.FLAG_LOAD_FAIL,
|
||||
}"
|
||||
>
|
||||
<CheckCircle
|
||||
:size="18"
|
||||
class="select"
|
||||
v-if="!(data.flag & c.FLAG_PLACEHOLDER)"
|
||||
@click="toggleSelect"
|
||||
/>
|
||||
}">
|
||||
<CheckCircle :size="18" class="select" v-if="!(data.flag & c.FLAG_PLACEHOLDER)" @click="toggleSelect" />
|
||||
|
||||
<div class="video" v-if="data.flag & c.FLAG_IS_VIDEO">
|
||||
<span v-if="data.video_duration" class="time">
|
||||
|
@ -22,45 +14,20 @@
|
|||
<Video :size="22" />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="livephoto"
|
||||
@mouseenter.passive="playVideo"
|
||||
@mouseleave.passive="stopVideo"
|
||||
>
|
||||
<div class="livephoto" @mouseenter.passive="playVideo" @mouseleave.passive="stopVideo">
|
||||
<LivePhoto :size="22" v-if="data.liveid" />
|
||||
</div>
|
||||
|
||||
<Star :size="22" v-if="data.flag & c.FLAG_IS_FAVORITE" />
|
||||
|
||||
<div
|
||||
class="img-outer fill-block"
|
||||
:class="{
|
||||
<div class="img-outer fill-block" :class="{
|
||||
'memories-livephoto': data.liveid,
|
||||
}"
|
||||
@contextmenu="contextmenu"
|
||||
@pointerdown.passive="$emit('pointerdown', $event)"
|
||||
@touchstart.passive="$emit('touchstart', $event)"
|
||||
@touchmove="$emit('touchmove', $event)"
|
||||
@touchend.passive="$emit('touchend', $event)"
|
||||
@touchcancel.passive="$emit('touchend', $event)"
|
||||
>
|
||||
<img
|
||||
ref="img"
|
||||
:class="['fill-block', `memories-thumb-${data.key}`]"
|
||||
draggable="false"
|
||||
:src="src"
|
||||
:key="data.fileid"
|
||||
@load="load"
|
||||
@error="error"
|
||||
/>
|
||||
<video
|
||||
ref="video"
|
||||
v-if="videoUrl"
|
||||
:src="videoUrl"
|
||||
preload="none"
|
||||
muted
|
||||
playsinline
|
||||
/>
|
||||
}" @contextmenu="contextmenu" @pointerdown.passive="$emit('pointerdown', $event)"
|
||||
@touchstart.passive="$emit('touchstart', $event)" @touchmove="$emit('touchmove', $event)"
|
||||
@touchend.passive="$emit('touchend', $event)" @touchcancel.passive="$emit('touchend', $event)">
|
||||
<img ref="img" :class="['fill-block', `memories-thumb-${data.key}`]" draggable="false" :src="src"
|
||||
:key="data.fileid" @load="load" @error="error" />
|
||||
<video ref="video" v-if="videoUrl" :src="videoUrl" preload="none" muted playsinline />
|
||||
<div class="overlay fill-block" />
|
||||
</div>
|
||||
</div>
|
||||
|
@ -275,11 +242,13 @@ export default defineComponent({
|
|||
/* Container and selection */
|
||||
.p-outer {
|
||||
padding: 2px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
padding: 1px;
|
||||
}
|
||||
|
||||
transition: background-color 0.15s ease, opacity 0.2s ease-in,
|
||||
transition: background-color 0.15s ease,
|
||||
opacity 0.2s ease-in,
|
||||
transform 0.2s ease-in;
|
||||
|
||||
&.leaving {
|
||||
|
@ -293,6 +262,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
--icon-dist: 8px;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
--icon-dist: 4px;
|
||||
}
|
||||
|
@ -312,6 +282,7 @@ $icon-size: $icon-half-size * 2;
|
|||
cursor: pointer;
|
||||
|
||||
display: none;
|
||||
|
||||
@media (hover: hover) {
|
||||
.p-outer:hover>& {
|
||||
display: flex;
|
||||
|
@ -319,6 +290,7 @@ $icon-size: $icon-half-size * 2;
|
|||
}
|
||||
|
||||
opacity: 0.7;
|
||||
|
||||
&:hover,
|
||||
.p-outer.selected & {
|
||||
opacity: 1;
|
||||
|
@ -331,6 +303,7 @@ $icon-size: $icon-half-size * 2;
|
|||
}
|
||||
|
||||
filter: invert(1) brightness(100);
|
||||
|
||||
.p-outer.selected>& {
|
||||
display: flex;
|
||||
filter: invert(0);
|
||||
|
@ -338,6 +311,7 @@ $icon-size: $icon-half-size * 2;
|
|||
color: var(--color-primary);
|
||||
}
|
||||
}
|
||||
|
||||
.video,
|
||||
.star-icon,
|
||||
.livephoto {
|
||||
|
@ -347,11 +321,13 @@ $icon-size: $icon-half-size * 2;
|
|||
transition: transform 0.15s ease;
|
||||
filter: invert(1) brightness(100);
|
||||
}
|
||||
|
||||
.video,
|
||||
.livephoto {
|
||||
position: absolute;
|
||||
top: var(--icon-dist);
|
||||
right: var(--icon-dist);
|
||||
|
||||
.p-outer.selected>& {
|
||||
transform: translate(-$icon-size, $icon-size);
|
||||
}
|
||||
|
@ -366,12 +342,15 @@ $icon-size: $icon-half-size * 2;
|
|||
margin-right: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.livephoto {
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.star-icon {
|
||||
bottom: var(--icon-dist);
|
||||
left: var(--icon-dist);
|
||||
|
||||
.p-outer.selected>& {
|
||||
transform: translate($icon-size, -$icon-size);
|
||||
}
|
||||
|
@ -384,6 +363,7 @@ div.img-outer {
|
|||
padding: 0;
|
||||
|
||||
transition: padding 0.15s ease;
|
||||
|
||||
.p-outer.selected>& {
|
||||
padding: calc(var(--icon-dist) + $icon-half-size);
|
||||
}
|
||||
|
@ -408,6 +388,7 @@ div.img-outer {
|
|||
.p-outer.placeholder>& {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.p-outer.error & {
|
||||
object-fit: contain;
|
||||
}
|
||||
|
@ -427,6 +408,7 @@ div.img-outer {
|
|||
|
||||
display: none;
|
||||
transition: border-radius 0.1s ease-in;
|
||||
|
||||
@media (hover: hover) {
|
||||
.p-outer:not(.selected):hover>& {
|
||||
display: block;
|
||||
|
|
|
@ -1,11 +1,6 @@
|
|||
<template>
|
||||
<router-link
|
||||
draggable="false"
|
||||
class="tag fill-block"
|
||||
:class="{ face, error }"
|
||||
:to="target"
|
||||
@click.native="openTag(data)"
|
||||
>
|
||||
<router-link draggable="false" class="tag fill-block" :class="{ face, error }" :to="target"
|
||||
@click.native="openTag(data)">
|
||||
<div class="bbl">
|
||||
<NcCounterBubble> {{ data.count }} </NcCounterBubble>
|
||||
</div>
|
||||
|
@ -16,13 +11,8 @@
|
|||
|
||||
<div class="previews fill-block" ref="previews">
|
||||
<div class="img-outer">
|
||||
<img
|
||||
draggable="false"
|
||||
class="fill-block"
|
||||
:class="{ error }"
|
||||
:src="previewUrl"
|
||||
@error="data.flag |= c.FLAG_LOAD_FAIL"
|
||||
/>
|
||||
<img draggable="false" class="fill-block" :class="{ error }" :src="previewUrl"
|
||||
@error="data.flag |= c.FLAG_LOAD_FAIL" />
|
||||
</div>
|
||||
</div>
|
||||
</router-link>
|
||||
|
@ -219,6 +209,7 @@ img {
|
|||
&.error {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.tag:hover & {
|
||||
filter: brightness(100%);
|
||||
}
|
||||
|
|
|
@ -7,36 +7,22 @@
|
|||
<form class="manage-collaborators__form" @submit.prevent>
|
||||
<NcPopover ref="popover" :auto-size="true" :distance="0">
|
||||
<label slot="trigger" class="manage-collaborators__form__input">
|
||||
<NcTextField
|
||||
:value.sync="searchText"
|
||||
autocomplete="off"
|
||||
type="search"
|
||||
name="search"
|
||||
:aria-label="t('photos', 'Search for collaborators')"
|
||||
aria-autocomplete="list"
|
||||
<NcTextField :value.sync="searchText" autocomplete="off" type="search" name="search"
|
||||
:aria-label="t('photos', 'Search for collaborators')" aria-autocomplete="list"
|
||||
:aria-controls="`manage-collaborators__form__selection-${randomId} manage-collaborators__form__list-${randomId}`"
|
||||
:placeholder="t('photos', 'Search people or groups')"
|
||||
@input="searchCollaborators"
|
||||
>
|
||||
:placeholder="t('photos', 'Search people or groups')" @input="searchCollaborators">
|
||||
<Magnify :size="16" />
|
||||
</NcTextField>
|
||||
<NcLoadingIcon v-if="loadingCollaborators" />
|
||||
</label>
|
||||
|
||||
<ul
|
||||
v-if="searchResults.length !== 0"
|
||||
:id="`manage-collaborators__form__list-${randomId}`"
|
||||
class="manage-collaborators__form__list"
|
||||
>
|
||||
<ul v-if="searchResults.length !== 0" :id="`manage-collaborators__form__list-${randomId}`"
|
||||
class="manage-collaborators__form__list">
|
||||
<li v-for="collaboratorKey of searchResults" :key="collaboratorKey">
|
||||
<NcListItemIcon
|
||||
:id="availableCollaborators[collaboratorKey].id"
|
||||
class="manage-collaborators__form__list__result"
|
||||
:title="availableCollaborators[collaboratorKey].id"
|
||||
:search="searchText"
|
||||
:user="availableCollaborators[collaboratorKey].id"
|
||||
:display-name="availableCollaborators[collaboratorKey].label"
|
||||
:aria-label="
|
||||
<NcListItemIcon :id="availableCollaborators[collaboratorKey].id"
|
||||
class="manage-collaborators__form__list__result" :title="availableCollaborators[collaboratorKey].id"
|
||||
:search="searchText" :user="availableCollaborators[collaboratorKey].id"
|
||||
:display-name="availableCollaborators[collaboratorKey].label" :aria-label="
|
||||
t(
|
||||
'photos',
|
||||
'Add {collaboratorLabel} to the collaborators list',
|
||||
|
@ -45,37 +31,23 @@
|
|||
availableCollaborators[collaboratorKey].label,
|
||||
}
|
||||
)
|
||||
"
|
||||
@click="selectEntity(collaboratorKey)"
|
||||
/>
|
||||
" @click="selectEntity(collaboratorKey)" />
|
||||
</li>
|
||||
</ul>
|
||||
<NcEmptyContent
|
||||
v-else
|
||||
key="emptycontent"
|
||||
class="manage-collaborators__form__list--empty"
|
||||
:title="t('photos', 'No collaborators available')"
|
||||
>
|
||||
<NcEmptyContent v-else key="emptycontent" class="manage-collaborators__form__list--empty"
|
||||
:title="t('photos', 'No collaborators available')">
|
||||
<AccountGroup slot="icon" />
|
||||
</NcEmptyContent>
|
||||
</NcPopover>
|
||||
</form>
|
||||
|
||||
<ul class="manage-collaborators__selection">
|
||||
<li
|
||||
v-for="collaboratorKey of listableSelectedCollaboratorsKeys"
|
||||
:key="collaboratorKey"
|
||||
class="manage-collaborators__selection__item"
|
||||
>
|
||||
<NcListItemIcon
|
||||
:id="availableCollaborators[collaboratorKey].id"
|
||||
<li v-for="collaboratorKey of listableSelectedCollaboratorsKeys" :key="collaboratorKey"
|
||||
class="manage-collaborators__selection__item">
|
||||
<NcListItemIcon :id="availableCollaborators[collaboratorKey].id"
|
||||
:display-name="availableCollaborators[collaboratorKey].label"
|
||||
:title="availableCollaborators[collaboratorKey].id"
|
||||
:user="availableCollaborators[collaboratorKey].id"
|
||||
>
|
||||
<NcButton
|
||||
type="tertiary"
|
||||
:aria-label="
|
||||
:title="availableCollaborators[collaboratorKey].id" :user="availableCollaborators[collaboratorKey].id">
|
||||
<NcButton type="tertiary" :aria-label="
|
||||
t(
|
||||
'photos',
|
||||
'Remove {collaboratorLabel} from the collaborators list',
|
||||
|
@ -84,9 +56,7 @@
|
|||
availableCollaborators[collaboratorKey].label,
|
||||
}
|
||||
)
|
||||
"
|
||||
@click="unselectEntity(collaboratorKey)"
|
||||
>
|
||||
" @click="unselectEntity(collaboratorKey)">
|
||||
<Close slot="icon" :size="20" />
|
||||
</NcButton>
|
||||
</NcListItemIcon>
|
||||
|
@ -96,12 +66,8 @@
|
|||
<div class="actions">
|
||||
<div v-if="allowPublicLink" class="actions__public-link">
|
||||
<template v-if="isPublicLinkSelected">
|
||||
<NcButton
|
||||
class="manage-collaborators__public-link-button"
|
||||
:aria-label="t('photos', 'Copy the public link')"
|
||||
:disabled="publicLink.id === ''"
|
||||
@click="copyPublicLink"
|
||||
>
|
||||
<NcButton class="manage-collaborators__public-link-button" :aria-label="t('photos', 'Copy the public link')"
|
||||
:disabled="publicLink.id === ''" @click="copyPublicLink">
|
||||
<template v-if="publicLinkCopied">
|
||||
{{ t("photos", "Public link copied!") }}
|
||||
</template>
|
||||
|
@ -113,21 +79,13 @@
|
|||
<ContentCopy v-else />
|
||||
</template>
|
||||
</NcButton>
|
||||
<NcButton
|
||||
type="tertiary"
|
||||
:aria-label="t('photos', 'Delete the public link')"
|
||||
:disabled="publicLink.id === ''"
|
||||
@click="deletePublicLink"
|
||||
>
|
||||
<NcButton type="tertiary" :aria-label="t('photos', 'Delete the public link')" :disabled="publicLink.id === ''"
|
||||
@click="deletePublicLink">
|
||||
<NcLoadingIcon v-if="publicLink.id === ''" slot="icon" />
|
||||
<Close v-else slot="icon" />
|
||||
</NcButton>
|
||||
</template>
|
||||
<NcButton
|
||||
v-else
|
||||
class="manage-collaborators__public-link-button"
|
||||
@click="createPublicLinkForAlbum"
|
||||
>
|
||||
<NcButton v-else class="manage-collaborators__public-link-button" @click="createPublicLinkForAlbum">
|
||||
<Earth slot="icon" />
|
||||
{{ t("photos", "Share via public link") }}
|
||||
</NcButton>
|
||||
|
@ -207,7 +165,7 @@ export default defineComponent({
|
|||
data() {
|
||||
return {
|
||||
searchText: "",
|
||||
availableCollaborators: {} as { [key: string]: Collaborator },
|
||||
availableCollaborators: {} as { [key: string]: Collaborator; },
|
||||
selectedCollaboratorsKeys: [] as string[],
|
||||
currentSearchResults: [] as Collaborator[],
|
||||
loadingAlbum: false,
|
||||
|
@ -353,13 +311,12 @@ export default defineComponent({
|
|||
* @param {Collaborator} collaborator - A collaborator
|
||||
*/
|
||||
indexCollaborators(
|
||||
collaborators: { [s: string]: Collaborator },
|
||||
collaborators: { [s: string]: Collaborator; },
|
||||
collaborator: Collaborator
|
||||
) {
|
||||
return {
|
||||
...collaborators,
|
||||
[`${collaborator.type}${
|
||||
collaborator.type === Type.SHARE_TYPE_LINK ? "" : ":"
|
||||
[`${collaborator.type}${collaborator.type === Type.SHARE_TYPE_LINK ? "" : ":"
|
||||
}${collaborator.type === Type.SHARE_TYPE_LINK ? "" : collaborator.id}`]:
|
||||
collaborator,
|
||||
};
|
||||
|
|
|
@ -10,12 +10,7 @@
|
|||
</template>
|
||||
|
||||
<div class="outer">
|
||||
<AlbumForm
|
||||
:album="album"
|
||||
:display-back-button="false"
|
||||
:title="t('photos', 'New album')"
|
||||
@done="done"
|
||||
/>
|
||||
<AlbumForm :album="album" :display-back-button="false" :title="t('photos', 'New album')" @done="done" />
|
||||
</div>
|
||||
</Modal>
|
||||
</template>
|
||||
|
@ -52,8 +47,8 @@ export default defineComponent({
|
|||
if (edit) {
|
||||
try {
|
||||
this.album = await dav.getAlbum(
|
||||
this.$route.params.user,
|
||||
this.$route.params.name
|
||||
<string>this.$route.params.user,
|
||||
<string>this.$route.params.name
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
|
|
|
@ -78,8 +78,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
refreshParams() {
|
||||
this.user = this.$route.params.user || "";
|
||||
this.name = this.$route.params.name || "";
|
||||
this.user = <string>this.$route.params.user || "";
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
},
|
||||
|
||||
async save() {
|
||||
|
|
|
@ -1,58 +1,29 @@
|
|||
<template>
|
||||
<form
|
||||
v-if="!showCollaboratorView"
|
||||
class="album-form"
|
||||
@submit.prevent="submit"
|
||||
>
|
||||
<form v-if="!showCollaboratorView" class="album-form" @submit.prevent="submit">
|
||||
<div class="form-inputs">
|
||||
<NcTextField
|
||||
ref="nameInput"
|
||||
:value.sync="albumName"
|
||||
type="text"
|
||||
name="name"
|
||||
:required="true"
|
||||
autofocus="true"
|
||||
:placeholder="t('photos', 'Name of the album')"
|
||||
/>
|
||||
<NcTextField ref="nameInput" :value.sync="albumName" type="text" name="name" :required="true" autofocus="true"
|
||||
:placeholder="t('photos', 'Name of the album')" />
|
||||
<label>
|
||||
<NcTextField
|
||||
:value.sync="albumLocation"
|
||||
name="location"
|
||||
type="text"
|
||||
:placeholder="t('photos', 'Location of the album')"
|
||||
/>
|
||||
<NcTextField :value.sync="albumLocation" name="location" type="text"
|
||||
:placeholder="t('photos', 'Location of the album')" />
|
||||
</label>
|
||||
</div>
|
||||
<div class="form-buttons">
|
||||
<span class="left-buttons">
|
||||
<NcButton
|
||||
v-if="displayBackButton"
|
||||
:aria-label="t('photos', 'Go back to the previous view.')"
|
||||
type="tertiary"
|
||||
@click="back"
|
||||
>
|
||||
<NcButton v-if="displayBackButton" :aria-label="t('photos', 'Go back to the previous view.')" type="tertiary"
|
||||
@click="back">
|
||||
{{ t("photos", "Back") }}
|
||||
</NcButton>
|
||||
</span>
|
||||
<span class="right-buttons">
|
||||
<NcButton
|
||||
v-if="sharingEnabled && !editMode"
|
||||
:aria-label="t('photos', 'Go to the add collaborators view.')"
|
||||
type="secondary"
|
||||
:disabled="albumName.trim() === '' || loading"
|
||||
@click="showCollaboratorView = true"
|
||||
>
|
||||
<NcButton v-if="sharingEnabled && !editMode" :aria-label="t('photos', 'Go to the add collaborators view.')"
|
||||
type="secondary" :disabled="albumName.trim() === '' || loading" @click="showCollaboratorView = true">
|
||||
<template #icon>
|
||||
<AccountMultiplePlus />
|
||||
</template>
|
||||
{{ t("photos", "Add collaborators") }}
|
||||
</NcButton>
|
||||
<NcButton
|
||||
:aria-label="saveText"
|
||||
type="primary"
|
||||
:disabled="albumName === '' || loading"
|
||||
@click="submit()"
|
||||
>
|
||||
<NcButton :aria-label="saveText" type="primary" :disabled="albumName === '' || loading" @click="submit()">
|
||||
<template #icon>
|
||||
<NcLoadingIcon v-if="loading" />
|
||||
<Send v-else />
|
||||
|
@ -62,29 +33,17 @@
|
|||
</span>
|
||||
</div>
|
||||
</form>
|
||||
<AlbumCollaborators
|
||||
v-else
|
||||
:album-name="albumName"
|
||||
:allow-public-link="false"
|
||||
:collaborators="[]"
|
||||
>
|
||||
<AlbumCollaborators v-else :album-name="albumName" :allow-public-link="false" :collaborators="[]">
|
||||
<template slot-scope="{ collaborators }">
|
||||
<span class="left-buttons">
|
||||
<NcButton
|
||||
:aria-label="t('photos', 'Back to the new album form.')"
|
||||
type="tertiary"
|
||||
@click="showCollaboratorView = false"
|
||||
>
|
||||
<NcButton :aria-label="t('photos', 'Back to the new album form.')" type="tertiary"
|
||||
@click="showCollaboratorView = false">
|
||||
{{ t("photos", "Back") }}
|
||||
</NcButton>
|
||||
</span>
|
||||
<span class="right-buttons">
|
||||
<NcButton
|
||||
:aria-label="saveText"
|
||||
type="primary"
|
||||
:disabled="albumName.trim() === '' || loading"
|
||||
@click="submit(collaborators)"
|
||||
>
|
||||
<NcButton :aria-label="saveText" type="primary" :disabled="albumName.trim() === '' || loading"
|
||||
@click="submit(collaborators)">
|
||||
<template #icon>
|
||||
<NcLoadingIcon v-if="loading" />
|
||||
<Send v-else />
|
||||
|
@ -136,6 +95,7 @@ export default defineComponent({
|
|||
|
||||
data() {
|
||||
return {
|
||||
collaborators: [],
|
||||
showCollaboratorView: false,
|
||||
albumName: "",
|
||||
albumLocation: "",
|
||||
|
@ -176,7 +136,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
submit(collaborators = []) {
|
||||
submit(collaborators: any = []) {
|
||||
if (this.albumName === "" || this.loading) {
|
||||
return;
|
||||
}
|
||||
|
@ -251,41 +211,52 @@ export default defineComponent({
|
|||
flex-direction: column;
|
||||
height: 350px;
|
||||
padding: 16px;
|
||||
|
||||
.form-title {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.form-subtitle {
|
||||
color: var(--color-text-lighter);
|
||||
}
|
||||
|
||||
.form-inputs {
|
||||
flex-grow: 1;
|
||||
justify-items: flex-end;
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
label {
|
||||
display: flex;
|
||||
margin-top: 16px;
|
||||
|
||||
:deep svg {
|
||||
margin-right: 12px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.form-buttons {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
|
||||
.left-buttons,
|
||||
.right-buttons {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.right-buttons {
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
button {
|
||||
margin-right: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.left-buttons {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
|
|
@ -3,24 +3,13 @@
|
|||
<NcLoadingIcon v-if="loadingAlbums" class="loading-icon" />
|
||||
|
||||
<ul class="albums-container">
|
||||
<NcListItem
|
||||
v-for="album in albums"
|
||||
:key="album.album_id"
|
||||
class="album"
|
||||
:title="getAlbumName(album)"
|
||||
:aria-label="
|
||||
<NcListItem v-for="album in albums" :key="album.album_id" class="album" :title="getAlbumName(album)" :aria-label="
|
||||
t('photos', 'Add selection to album {albumName}', {
|
||||
albumName: getAlbumName(album),
|
||||
})
|
||||
"
|
||||
@click="pickAlbum(album)"
|
||||
>
|
||||
" @click="pickAlbum(album)">
|
||||
<template slot="icon">
|
||||
<img
|
||||
v-if="album.last_added_photo !== -1"
|
||||
class="album__image"
|
||||
:src="album.last_added_photo | toCoverUrl"
|
||||
/>
|
||||
<img v-if="album.last_added_photo !== -1" class="album__image" :src="toCoverUrl(album.last_added_photo)" />
|
||||
<div v-else class="album__image album__image--placeholder">
|
||||
<ImageMultiple :size="32" />
|
||||
</div>
|
||||
|
@ -34,12 +23,8 @@
|
|||
</NcListItem>
|
||||
</ul>
|
||||
|
||||
<NcButton
|
||||
:aria-label="t('photos', 'Create a new album.')"
|
||||
class="new-album-button"
|
||||
type="tertiary"
|
||||
@click="showAlbumCreationForm = true"
|
||||
>
|
||||
<NcButton :aria-label="t('photos', 'Create a new album.')" class="new-album-button" type="tertiary"
|
||||
@click="showAlbumCreationForm = true">
|
||||
<template #icon>
|
||||
<Plus />
|
||||
</template>
|
||||
|
@ -47,13 +32,8 @@
|
|||
</NcButton>
|
||||
</div>
|
||||
|
||||
<AlbumForm
|
||||
v-else
|
||||
:display-back-button="true"
|
||||
:title="t('photos', 'New album')"
|
||||
@back="showAlbumCreationForm = false"
|
||||
@done="albumCreatedHandler"
|
||||
/>
|
||||
<AlbumForm v-else :display-back-button="true" :title="t('photos', 'New album')" @back="showAlbumCreationForm = false"
|
||||
@done="albumCreatedHandler" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -85,18 +65,6 @@ export default defineComponent({
|
|||
NcLoadingIcon,
|
||||
},
|
||||
|
||||
filters: {
|
||||
toCoverUrl(fileId: string) {
|
||||
return getPreviewUrl(
|
||||
{
|
||||
fileid: Number(fileId),
|
||||
} as IPhoto,
|
||||
true,
|
||||
256
|
||||
);
|
||||
},
|
||||
},
|
||||
|
||||
data() {
|
||||
return {
|
||||
showAlbumCreationForm: false,
|
||||
|
@ -110,6 +78,16 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
toCoverUrl(fileId: number | string) {
|
||||
return getPreviewUrl(
|
||||
{
|
||||
fileid: Number(fileId),
|
||||
} as IPhoto,
|
||||
true,
|
||||
256
|
||||
);
|
||||
},
|
||||
|
||||
albumCreatedHandler() {
|
||||
this.showAlbumCreationForm = false;
|
||||
this.loadAlbums();
|
||||
|
|
|
@ -4,19 +4,11 @@
|
|||
{{ t("memories", "Share Album") }}
|
||||
</template>
|
||||
|
||||
<AlbumCollaborators
|
||||
v-if="album"
|
||||
:album-name="album.basename"
|
||||
:collaborators="album.collaborators"
|
||||
:public-link="album.publicLink"
|
||||
>
|
||||
<AlbumCollaborators v-if="album" :album-name="album.basename" :collaborators="album.collaborators"
|
||||
:public-link="album.publicLink">
|
||||
<template slot-scope="{ collaborators }">
|
||||
<NcButton
|
||||
:aria-label="t('photos', 'Save collaborators for this album.')"
|
||||
type="primary"
|
||||
:disabled="loadingAddCollaborators"
|
||||
@click="handleSetCollaborators(collaborators)"
|
||||
>
|
||||
<NcButton :aria-label="t('photos', 'Save collaborators for this album.')" type="primary"
|
||||
:disabled="loadingAddCollaborators" @click="handleSetCollaborators(collaborators)">
|
||||
<template #icon>
|
||||
<NcLoadingIcon v-if="loadingAddCollaborators" />
|
||||
</template>
|
||||
|
@ -52,6 +44,7 @@ export default defineComponent({
|
|||
album: null as any,
|
||||
show: false,
|
||||
loadingAddCollaborators: false,
|
||||
collaborators: [] as any[],
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -65,8 +58,8 @@ export default defineComponent({
|
|||
async open() {
|
||||
this.show = true;
|
||||
this.loadingAddCollaborators = true;
|
||||
const user = this.$route.params.user || "";
|
||||
const name = this.$route.params.name || "";
|
||||
const user = <string>this.$route.params.user || "";
|
||||
const name = <string>this.$route.params.name || "";
|
||||
this.album = await dav.getAlbum(user, name);
|
||||
this.loadingAddCollaborators = false;
|
||||
},
|
||||
|
|
|
@ -15,45 +15,16 @@
|
|||
{{ longDateStr }}
|
||||
|
||||
<div class="fields">
|
||||
<NcTextField
|
||||
:value.sync="year"
|
||||
class="field"
|
||||
@input="newestChange()"
|
||||
:label="t('memories', 'Year')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Year')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="month"
|
||||
class="field"
|
||||
@input="newestChange()"
|
||||
:label="t('memories', 'Month')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Month')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="day"
|
||||
class="field"
|
||||
@input="newestChange()"
|
||||
:label="t('memories', 'Day')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Day')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="hour"
|
||||
class="field"
|
||||
@input="newestChange(true)"
|
||||
:label="t('memories', 'Time')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Hour')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="minute"
|
||||
class="field"
|
||||
@input="newestChange(true)"
|
||||
:label="t('memories', 'Minute')"
|
||||
:placeholder="t('memories', 'Minute')"
|
||||
/>
|
||||
<NcTextField :value.sync="year" class="field" @input="newestChange()" :label="t('memories', 'Year')"
|
||||
:label-visible="true" :placeholder="t('memories', 'Year')" />
|
||||
<NcTextField :value.sync="month" class="field" @input="newestChange()" :label="t('memories', 'Month')"
|
||||
:label-visible="true" :placeholder="t('memories', 'Month')" />
|
||||
<NcTextField :value.sync="day" class="field" @input="newestChange()" :label="t('memories', 'Day')"
|
||||
:label-visible="true" :placeholder="t('memories', 'Day')" />
|
||||
<NcTextField :value.sync="hour" class="field" @input="newestChange(true)" :label="t('memories', 'Time')"
|
||||
:label-visible="true" :placeholder="t('memories', 'Hour')" />
|
||||
<NcTextField :value.sync="minute" class="field" @input="newestChange(true)" :label="t('memories', 'Minute')"
|
||||
:placeholder="t('memories', 'Minute')" />
|
||||
</div>
|
||||
|
||||
<div v-if="photos.length > 1" class="oldest">
|
||||
|
@ -61,40 +32,16 @@
|
|||
{{ longDateStrLast }}
|
||||
|
||||
<div class="fields">
|
||||
<NcTextField
|
||||
:value.sync="yearLast"
|
||||
class="field"
|
||||
:label="t('memories', 'Year')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Year')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="monthLast"
|
||||
class="field"
|
||||
:label="t('memories', 'Month')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Month')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="dayLast"
|
||||
class="field"
|
||||
:label="t('memories', 'Day')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Day')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="hourLast"
|
||||
class="field"
|
||||
:label="t('memories', 'Time')"
|
||||
:label-visible="true"
|
||||
:placeholder="t('memories', 'Hour')"
|
||||
/>
|
||||
<NcTextField
|
||||
:value.sync="minuteLast"
|
||||
class="field"
|
||||
:label="t('memories', 'Minute')"
|
||||
:placeholder="t('memories', 'Minute')"
|
||||
/>
|
||||
<NcTextField :value.sync="yearLast" class="field" :label="t('memories', 'Year')" :label-visible="true"
|
||||
:placeholder="t('memories', 'Year')" />
|
||||
<NcTextField :value.sync="monthLast" class="field" :label="t('memories', 'Month')" :label-visible="true"
|
||||
:placeholder="t('memories', 'Month')" />
|
||||
<NcTextField :value.sync="dayLast" class="field" :label="t('memories', 'Day')" :label-visible="true"
|
||||
:placeholder="t('memories', 'Day')" />
|
||||
<NcTextField :value.sync="hourLast" class="field" :label="t('memories', 'Time')" :label-visible="true"
|
||||
:placeholder="t('memories', 'Hour')" />
|
||||
<NcTextField :value.sync="minuteLast" class="field" :label="t('memories', 'Minute')"
|
||||
:placeholder="t('memories', 'Minute')" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
|
@ -5,28 +5,15 @@
|
|||
</template>
|
||||
|
||||
<template #buttons>
|
||||
<NcButton
|
||||
@click="save"
|
||||
class="button"
|
||||
type="error"
|
||||
v-if="exif"
|
||||
:disabled="processing"
|
||||
>
|
||||
<NcButton @click="save" class="button" type="error" v-if="exif" :disabled="processing">
|
||||
{{ t("memories", "Update Exif") }}
|
||||
</NcButton>
|
||||
</template>
|
||||
|
||||
<div v-if="exif">
|
||||
<div class="fields">
|
||||
<NcTextField
|
||||
v-for="field of fields"
|
||||
:key="field.field"
|
||||
:value.sync="exif[field.field]"
|
||||
class="field"
|
||||
:label="field.label"
|
||||
:label-visible="true"
|
||||
:placeholder="field.label"
|
||||
/>
|
||||
<NcTextField v-for="field of fields" :key="field.field" :value.sync="exif[field.field]" class="field"
|
||||
:label="field.label" :label-visible="true" :placeholder="field.label" />
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
|
@ -168,6 +155,7 @@ export default defineComponent({
|
|||
.field {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
:deep label {
|
||||
font-size: 0.8em;
|
||||
padding: 0 !important;
|
||||
|
|
|
@ -74,8 +74,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
refreshParams() {
|
||||
this.user = this.$route.params.user || "";
|
||||
this.name = this.$route.params.name || "";
|
||||
this.user = <string>this.$route.params.user || "";
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
},
|
||||
|
||||
async save() {
|
||||
|
|
|
@ -5,14 +5,8 @@
|
|||
</template>
|
||||
|
||||
<div class="fields">
|
||||
<NcTextField
|
||||
:value.sync="name"
|
||||
class="field"
|
||||
:label="t('memories', 'Name')"
|
||||
:label-visible="false"
|
||||
:placeholder="t('memories', 'Name')"
|
||||
@keypress.enter="save()"
|
||||
/>
|
||||
<NcTextField :value.sync="name" class="field" :label="t('memories', 'Name')" :label-visible="false"
|
||||
:placeholder="t('memories', 'Name')" @keypress.enter="save()" />
|
||||
</div>
|
||||
|
||||
<template #buttons>
|
||||
|
@ -82,9 +76,9 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
refreshParams() {
|
||||
this.user = this.$route.params.user || "";
|
||||
this.name = this.$route.params.name || "";
|
||||
this.oldName = this.$route.params.name || "";
|
||||
this.user = <string>this.$route.params.user || "";
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
this.oldName = <string>this.$route.params.name || "";
|
||||
},
|
||||
|
||||
async save() {
|
||||
|
|
|
@ -26,7 +26,7 @@ export default defineComponent({
|
|||
return {
|
||||
user: "",
|
||||
name: "",
|
||||
detail: null as IPhoto[] | null,
|
||||
detail: null as ITag[] | null,
|
||||
};
|
||||
},
|
||||
|
||||
|
@ -46,8 +46,8 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
async refreshParams() {
|
||||
this.user = this.$route.params.user || "";
|
||||
this.name = this.$route.params.name || "";
|
||||
this.user = <string>this.$route.params.user || "";
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
this.detail = null;
|
||||
|
||||
let data = [];
|
||||
|
@ -85,6 +85,7 @@ export default defineComponent({
|
|||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.photo {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
|
|
|
@ -157,6 +157,7 @@ export default defineComponent({
|
|||
.outer {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.info-pad {
|
||||
margin-top: 6px;
|
||||
margin-bottom: 2px;
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<Modal
|
||||
@close="close"
|
||||
size="normal"
|
||||
v-if="show"
|
||||
:sidebar="!isRoot ? this.folderPath : null"
|
||||
>
|
||||
<Modal @close="close" size="normal" v-if="show" :sidebar="!isRoot ? folderPath : null">
|
||||
<template #title>
|
||||
{{ t("memories", "Share Folder") }}
|
||||
</template>
|
||||
|
@ -23,13 +18,7 @@
|
|||
</div>
|
||||
|
||||
<div class="links">
|
||||
<a
|
||||
v-for="link of links"
|
||||
:key="link.url"
|
||||
:href="link.url"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<a v-for="link of links" :key="link.url" :href="link.url" target="_blank" rel="noopener noreferrer">
|
||||
{{ link.url }}
|
||||
</a>
|
||||
</div>
|
||||
|
@ -64,12 +53,12 @@ export default defineComponent({
|
|||
return {
|
||||
show: false,
|
||||
folderPath: "",
|
||||
links: [] as { url: string }[],
|
||||
links: [] as { url: string; }[],
|
||||
};
|
||||
},
|
||||
|
||||
computed: {
|
||||
isRoot() {
|
||||
isRoot(): boolean {
|
||||
return this.folderPath === "/" || this.folderPath === "";
|
||||
},
|
||||
},
|
||||
|
@ -111,6 +100,7 @@ export default defineComponent({
|
|||
<style lang="scss" scoped>
|
||||
.links {
|
||||
margin-top: 1em;
|
||||
|
||||
a {
|
||||
display: block;
|
||||
margin-bottom: 0.2em;
|
||||
|
|
|
@ -1,14 +1,11 @@
|
|||
<template>
|
||||
<NcModal
|
||||
:size="size"
|
||||
@close="close"
|
||||
:outTransition="true"
|
||||
:style="{ width: isSidebarShown ? `calc(100% - ${sidebarWidth}px)` : null }"
|
||||
:additionalTrapElements="trapElements"
|
||||
>
|
||||
<NcModal :size="size" @close="close" :outTransition="true"
|
||||
:style="{ width: isSidebarShown ? `calc(100% - ${sidebarWidth}px)` : null }" :additionalTrapElements="trapElements">
|
||||
<div class="container">
|
||||
<div class="head">
|
||||
<span> <slot name="title"></slot> </span>
|
||||
<span>
|
||||
<slot name="title"></slot>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<slot></slot>
|
||||
|
|
|
@ -9,12 +9,11 @@
|
|||
{{ path }}
|
||||
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Remove')"
|
||||
@click="remove(index)"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Remove')" @click="remove(index)">
|
||||
{{ t("memories", "Remove") }}
|
||||
<template #icon> <CloseIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<CloseIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</li>
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<NcActions v-if="!isAlbumList">
|
||||
<NcActionButton :aria-label="t('memories', 'Back')" @click="back()">
|
||||
{{ t("memories", "Back") }}
|
||||
<template #icon> <BackIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<BackIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
|
||||
|
@ -11,50 +13,40 @@
|
|||
|
||||
<div class="right-actions">
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Create new album')"
|
||||
@click="$refs.createModal.open(false)"
|
||||
close-after-click
|
||||
v-if="isAlbumList"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Create new album')" @click="$refs.createModal.open(false)"
|
||||
close-after-click v-if="isAlbumList">
|
||||
{{ t("memories", "Create new album") }}
|
||||
<template #icon> <PlusIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<PlusIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Share album')"
|
||||
@click="$refs.shareModal.open(false)"
|
||||
close-after-click
|
||||
v-if="canEditAlbum"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Share album')" @click="$refs.shareModal.open(false)"
|
||||
close-after-click v-if="canEditAlbum">
|
||||
{{ t("memories", "Share album") }}
|
||||
<template #icon> <ShareIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<ShareIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Download album')"
|
||||
@click="downloadAlbum()"
|
||||
close-after-click
|
||||
v-if="!isAlbumList"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Download album')" @click="downloadAlbum()" close-after-click
|
||||
v-if="!isAlbumList">
|
||||
{{ t("memories", "Download album") }}
|
||||
<template #icon> <DownloadIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<DownloadIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Edit album details')"
|
||||
@click="$refs.createModal.open(true)"
|
||||
close-after-click
|
||||
v-if="canEditAlbum"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Edit album details')" @click="$refs.createModal.open(true)"
|
||||
close-after-click v-if="canEditAlbum">
|
||||
{{ t("memories", "Edit album details") }}
|
||||
<template #icon> <EditIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<EditIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Delete album')"
|
||||
@click="$refs.deleteModal.open()"
|
||||
close-after-click
|
||||
v-if="canEditAlbum"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Delete album')" @click="$refs.deleteModal.open()" close-after-click
|
||||
v-if="canEditAlbum">
|
||||
{{ t("memories", "Delete album") }}
|
||||
<template #icon> <DeleteIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<DeleteIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
|
@ -114,11 +106,11 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
computed: {
|
||||
isAlbumList() {
|
||||
isAlbumList(): boolean {
|
||||
return !Boolean(this.$route.params.name);
|
||||
},
|
||||
|
||||
canEditAlbum() {
|
||||
canEditAlbum(): boolean {
|
||||
return (
|
||||
!this.isAlbumList && this.$route.params.user === getCurrentUser()?.uid
|
||||
);
|
||||
|
@ -137,7 +129,7 @@ export default defineComponent({
|
|||
|
||||
methods: {
|
||||
createMatter() {
|
||||
this.name = this.$route.params.name || this.t("memories", "Albums");
|
||||
this.name = <string>this.$route.params.name || this.t("memories", "Albums");
|
||||
},
|
||||
|
||||
back() {
|
||||
|
@ -146,7 +138,7 @@ export default defineComponent({
|
|||
|
||||
async downloadAlbum() {
|
||||
const res = await axios.post(
|
||||
API.ALBUM_DOWNLOAD(this.$route.params.user, this.$route.params.name)
|
||||
API.ALBUM_DOWNLOAD(<string>this.$route.params.user, <string>this.$route.params.name)
|
||||
);
|
||||
if (res.status === 200 && res.data.handle) {
|
||||
downloadWithHandle(res.data.handle);
|
||||
|
@ -172,6 +164,7 @@ export default defineComponent({
|
|||
.right-actions {
|
||||
margin-right: 40px;
|
||||
z-index: 50;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<NcActions>
|
||||
<NcActionButton :aria-label="t('memories', 'Back')" @click="back()">
|
||||
{{ t("memories", "Back") }}
|
||||
<template #icon> <BackIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<BackIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
|
||||
|
@ -11,36 +13,29 @@
|
|||
|
||||
<div class="right-actions">
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Rename person')"
|
||||
@click="$refs.editModal.open()"
|
||||
close-after-click
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Rename person')" @click="$refs.editModal.open()" close-after-click>
|
||||
{{ t("memories", "Rename person") }}
|
||||
<template #icon> <EditIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<EditIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Merge with different person')"
|
||||
@click="$refs.mergeModal.open()"
|
||||
close-after-click
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Merge with different person')" @click="$refs.mergeModal.open()"
|
||||
close-after-click>
|
||||
{{ t("memories", "Merge with different person") }}
|
||||
<template #icon> <MergeIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<MergeIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionCheckbox
|
||||
:aria-label="t('memories', 'Mark person in preview')"
|
||||
:checked.sync="config_showFaceRect"
|
||||
@change="changeShowFaceRect"
|
||||
>
|
||||
<NcActionCheckbox :aria-label="t('memories', 'Mark person in preview')" :checked.sync="config_showFaceRect"
|
||||
@change="changeShowFaceRect">
|
||||
{{ t("memories", "Mark person in preview") }}
|
||||
</NcActionCheckbox>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Remove person')"
|
||||
@click="$refs.deleteModal.open()"
|
||||
close-after-click
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Remove person')" @click="$refs.deleteModal.open()"
|
||||
close-after-click>
|
||||
{{ t("memories", "Remove person") }}
|
||||
<template #icon> <DeleteIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<DeleteIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
|
@ -99,7 +94,7 @@ export default defineComponent({
|
|||
|
||||
methods: {
|
||||
createMatter() {
|
||||
this.name = this.$route.params.name || "";
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
},
|
||||
|
||||
back() {
|
||||
|
@ -135,6 +130,7 @@ export default defineComponent({
|
|||
.right-actions {
|
||||
margin-right: 40px;
|
||||
z-index: 50;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
|
@ -6,23 +6,18 @@
|
|||
<HomeIcon :size="20" />
|
||||
</template>
|
||||
</NcBreadcrumb>
|
||||
<NcBreadcrumb
|
||||
v-for="folder in topMatter.list"
|
||||
:key="folder.path"
|
||||
:title="folder.text"
|
||||
:to="{ name: 'folders', params: { path: folder.path } }"
|
||||
/>
|
||||
<NcBreadcrumb v-for="folder in topMatter.list" :key="folder.path" :title="folder.text"
|
||||
:to="{ name: 'folders', params: { path: folder.path } }" />
|
||||
</NcBreadcrumbs>
|
||||
|
||||
<div class="right-actions">
|
||||
<NcActions :inline="1">
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Share folder')"
|
||||
@click="$refs.shareModal.open(false)"
|
||||
close-after-click
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Share folder')" @click="$refs.shareModal.open(false)"
|
||||
close-after-click>
|
||||
{{ t("memories", "Share folder") }}
|
||||
<template #icon> <ShareIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<ShareIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
|
@ -110,6 +105,7 @@ export default defineComponent({
|
|||
.right-actions {
|
||||
margin-right: 40px;
|
||||
z-index: 50;
|
||||
|
||||
@media (max-width: 768px) {
|
||||
margin-right: 10px;
|
||||
}
|
||||
|
|
|
@ -1,12 +1,7 @@
|
|||
<template>
|
||||
<div class="outer" v-show="years.length > 0">
|
||||
<div class="inner" ref="inner">
|
||||
<div
|
||||
v-for="year of years"
|
||||
class="group"
|
||||
:key="year.year"
|
||||
@click="click(year)"
|
||||
>
|
||||
<div v-for="year of years" class="group" :key="year.year" @click="click(year)">
|
||||
<img class="fill-block" :src="year.url" />
|
||||
|
||||
<div class="overlay">
|
||||
|
@ -17,23 +12,21 @@
|
|||
|
||||
<div class="left-btn dir-btn" v-if="hasLeft">
|
||||
<NcActions>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Move left')"
|
||||
@click="moveLeft"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Move left')" @click="moveLeft">
|
||||
{{ t("memories", "Move left") }}
|
||||
<template #icon> <LeftMoveIcon :size="28" /> </template>
|
||||
<template #icon>
|
||||
<LeftMoveIcon :size="28" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
<div class="right-btn dir-btn" v-if="hasRight">
|
||||
<NcActions>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Move right')"
|
||||
@click="moveRight"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Move right')" @click="moveRight">
|
||||
{{ t("memories", "Move right") }}
|
||||
<template #icon> <RightMoveIcon :size="28" /> </template>
|
||||
<template #icon>
|
||||
<RightMoveIcon :size="28" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
</div>
|
||||
|
@ -255,13 +248,16 @@ $mobHeight: 150px;
|
|||
@media (max-width: 768px) {
|
||||
width: 98%;
|
||||
padding: 0;
|
||||
|
||||
.inner {
|
||||
padding: 0 8px;
|
||||
}
|
||||
|
||||
.dir-btn {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 600px) {
|
||||
height: $mobHeight;
|
||||
}
|
||||
|
@ -312,6 +308,7 @@ $mobHeight: 150px;
|
|||
@media (max-width: 600px) {
|
||||
aspect-ratio: 3/4;
|
||||
height: $mobHeight;
|
||||
|
||||
.overlay {
|
||||
font-size: 1.1em;
|
||||
}
|
||||
|
|
|
@ -3,7 +3,9 @@
|
|||
<NcActions>
|
||||
<NcActionButton :aria-label="t('memories', 'Back')" @click="back()">
|
||||
{{ t("memories", "Back") }}
|
||||
<template #icon> <BackIcon :size="20" /> </template>
|
||||
<template #icon>
|
||||
<BackIcon :size="20" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
</NcActions>
|
||||
<span class="name">{{ name }}</span>
|
||||
|
@ -44,7 +46,7 @@ export default defineComponent({
|
|||
|
||||
methods: {
|
||||
createMatter() {
|
||||
this.name = this.$route.params.name || "";
|
||||
this.name = <string>this.$route.params.name || "";
|
||||
},
|
||||
|
||||
back() {
|
||||
|
|
|
@ -1,10 +1,5 @@
|
|||
<template>
|
||||
<div
|
||||
ref="editor"
|
||||
class="viewer__image-editor"
|
||||
:class="{ loading: !imageEditor }"
|
||||
v-bind="themeDataAttr"
|
||||
/>
|
||||
<div ref="editor" class="viewer__image-editor" :class="{ loading: !imageEditor }" v-bind="themeDataAttr" />
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
|
@ -58,7 +53,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
computed: {
|
||||
config(): FilerobotImageEditorConfig & { theme: any } {
|
||||
config(): FilerobotImageEditorConfig & { theme: any; } {
|
||||
let src: string;
|
||||
if (["image/png", "image/jpeg", "image/webp"].includes(this.mime)) {
|
||||
src = this.src;
|
||||
|
@ -361,6 +356,7 @@ export default defineComponent({
|
|||
label,
|
||||
button {
|
||||
color: var(--color-main-text);
|
||||
|
||||
>span {
|
||||
font-size: var(--default-font-size) !important;
|
||||
}
|
||||
|
@ -381,6 +377,7 @@ export default defineComponent({
|
|||
.SfxInput-root {
|
||||
height: auto !important;
|
||||
padding: 0 !important;
|
||||
|
||||
.SfxInput-Base {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
@ -396,18 +393,22 @@ export default defineComponent({
|
|||
min-height: 44px !important;
|
||||
margin: 0 !important;
|
||||
border: transparent !important;
|
||||
|
||||
&[color="error"] {
|
||||
color: white !important;
|
||||
background-color: var(--color-error) !important;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
border-color: white !important;
|
||||
background-color: var(--color-error-hover) !important;
|
||||
}
|
||||
}
|
||||
|
||||
&[color="primary"] {
|
||||
color: var(--color-primary-text) !important;
|
||||
background-color: var(--color-primary-element) !important;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--color-primary-element-hover) !important;
|
||||
|
@ -419,6 +420,7 @@ export default defineComponent({
|
|||
.SfxMenuItem-root {
|
||||
height: 44px;
|
||||
padding-left: 8px !important;
|
||||
|
||||
// Center the menu entry icon and fix width
|
||||
>div {
|
||||
margin-right: 0;
|
||||
|
@ -446,9 +448,11 @@ export default defineComponent({
|
|||
justify-content: center;
|
||||
color: var(--color-main-text);
|
||||
}
|
||||
|
||||
.SfxModalTitle-Icon {
|
||||
margin-bottom: 22px !important;
|
||||
background: none !important;
|
||||
|
||||
// Fit EmptyContent styling
|
||||
svg {
|
||||
width: 64px;
|
||||
|
@ -460,10 +464,12 @@ export default defineComponent({
|
|||
--color-error: var(--color-main-text);
|
||||
}
|
||||
}
|
||||
|
||||
// Hide close icon (use cancel button)
|
||||
.SfxModalTitle-Close {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
// Modal actions buttons display
|
||||
.SfxModalActions-root {
|
||||
justify-content: space-evenly !important;
|
||||
|
@ -488,10 +494,12 @@ export default defineComponent({
|
|||
height: 80px !important;
|
||||
padding: 8px;
|
||||
border-radius: var(--border-radius-large) !important;
|
||||
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
&-label {
|
||||
margin-top: 8px !important;
|
||||
overflow: hidden;
|
||||
|
@ -567,6 +575,7 @@ export default defineComponent({
|
|||
content: attr(title);
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
svg {
|
||||
display: none;
|
||||
}
|
||||
|
@ -578,6 +587,7 @@ export default defineComponent({
|
|||
color: var(--color-primary-text) !important;
|
||||
border: none !important;
|
||||
background-color: var(--color-primary-element) !important;
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: var(--color-primary-element-hover) !important;
|
||||
|
@ -586,6 +596,7 @@ export default defineComponent({
|
|||
|
||||
// Save Modal fixes
|
||||
.FIE_resize-tool-options {
|
||||
|
||||
.FIE_resize-width-option,
|
||||
.FIE_resize-height-option {
|
||||
flex: 1 1;
|
||||
|
@ -596,10 +607,12 @@ export default defineComponent({
|
|||
// Resize lock
|
||||
.FIE_resize-ratio-locker {
|
||||
margin-right: 8px !important;
|
||||
|
||||
// Icon is very thin
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
|
||||
path {
|
||||
stroke-width: 1;
|
||||
stroke: var(--color-main-text);
|
||||
|
|
|
@ -1,141 +1,85 @@
|
|||
<template>
|
||||
<div
|
||||
class="memories_viewer outer"
|
||||
v-if="show"
|
||||
:class="{ fullyOpened, slideshowTimer }"
|
||||
:style="{ width: outerWidth }"
|
||||
@fullscreenchange="fullscreenChange"
|
||||
>
|
||||
<ImageEditor
|
||||
v-if="editorOpen"
|
||||
:mime="currentPhoto.mimetype"
|
||||
:src="currentDownloadLink"
|
||||
:fileid="currentPhoto.fileid"
|
||||
@close="editorOpen = false"
|
||||
/>
|
||||
<div class="memories_viewer outer" v-if="show" :class="{ fullyOpened, slideshowTimer }" :style="{ width: outerWidth }"
|
||||
@fullscreenchange="fullscreenChange">
|
||||
<ImageEditor v-if="editorOpen" :mime="currentPhoto.mimetype" :src="currentDownloadLink"
|
||||
:fileid="currentPhoto.fileid" @close="editorOpen = false" />
|
||||
|
||||
<div
|
||||
class="inner"
|
||||
ref="inner"
|
||||
v-show="!editorOpen"
|
||||
@pointermove.passive="setUiVisible"
|
||||
@pointerdown.passive="setUiVisible"
|
||||
>
|
||||
<div class="inner" ref="inner" v-show="!editorOpen" @pointermove.passive="setUiVisible"
|
||||
@pointerdown.passive="setUiVisible">
|
||||
<div class="top-bar" v-if="photoswipe" :class="{ showControls }">
|
||||
<NcActions
|
||||
:inline="numInlineActions"
|
||||
container=".memories_viewer .pswp"
|
||||
>
|
||||
<NcActionButton
|
||||
v-if="canShare"
|
||||
:aria-label="t('memories', 'Share')"
|
||||
@click="shareCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActions :inline="numInlineActions" container=".memories_viewer .pswp">
|
||||
<NcActionButton v-if="canShare" :aria-label="t('memories', 'Share')" @click="shareCurrent"
|
||||
:close-after-click="true">
|
||||
{{ t("memories", "Share") }}
|
||||
<template #icon> <ShareIcon :size="24" /> </template>
|
||||
<template #icon>
|
||||
<ShareIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-if="!routeIsPublic && !routeIsAlbum"
|
||||
:aria-label="t('memories', 'Delete')"
|
||||
@click="deleteCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton v-if="!routeIsPublic && !routeIsAlbum" :aria-label="t('memories', 'Delete')"
|
||||
@click="deleteCurrent" :close-after-click="true">
|
||||
{{ t("memories", "Delete") }}
|
||||
<template #icon> <DeleteIcon :size="24" /> </template>
|
||||
<template #icon>
|
||||
<DeleteIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-if="!routeIsPublic && routeIsAlbum"
|
||||
:aria-label="t('memories', 'Remove from album')"
|
||||
@click="deleteCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton v-if="!routeIsPublic && routeIsAlbum" :aria-label="t('memories', 'Remove from album')"
|
||||
@click="deleteCurrent" :close-after-click="true">
|
||||
{{ t("memories", "Remove from album") }}
|
||||
<template #icon> <AlbumRemoveIcon :size="24" /> </template>
|
||||
<template #icon>
|
||||
<AlbumRemoveIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-if="!routeIsPublic"
|
||||
:aria-label="t('memories', 'Favorite')"
|
||||
@click="favoriteCurrent"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton v-if="!routeIsPublic" :aria-label="t('memories', 'Favorite')" @click="favoriteCurrent"
|
||||
:close-after-click="true">
|
||||
{{ t("memories", "Favorite") }}
|
||||
<template #icon>
|
||||
<StarIcon v-if="isFavorite()" :size="24" />
|
||||
<StarOutlineIcon v-else :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-if="!routeIsPublic"
|
||||
:aria-label="t('memories', 'Sidebar')"
|
||||
@click="toggleSidebar"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton v-if="!routeIsPublic" :aria-label="t('memories', 'Sidebar')" @click="toggleSidebar"
|
||||
:close-after-click="true">
|
||||
{{ t("memories", "Sidebar") }}
|
||||
<template #icon>
|
||||
<InfoIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-if="canEdit && !routeIsPublic"
|
||||
:aria-label="t('memories', 'Edit')"
|
||||
@click="openEditor"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton v-if="canEdit && !routeIsPublic" :aria-label="t('memories', 'Edit')" @click="openEditor"
|
||||
:close-after-click="true">
|
||||
{{ t("memories", "Edit") }}
|
||||
<template #icon>
|
||||
<TuneIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Download')"
|
||||
@click="downloadCurrent"
|
||||
:close-after-click="true"
|
||||
v-if="!this.state_noDownload"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Download')" @click="downloadCurrent" :close-after-click="true"
|
||||
v-if="!state_noDownload">
|
||||
{{ t("memories", "Download") }}
|
||||
<template #icon>
|
||||
<DownloadIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-if="!this.state_noDownload && currentPhoto?.liveid"
|
||||
:aria-label="t('memories', 'Download Video')"
|
||||
@click="downloadCurrentLiveVideo"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton v-if="!state_noDownload && currentPhoto?.liveid" :aria-label="t('memories', 'Download Video')"
|
||||
@click="downloadCurrentLiveVideo" :close-after-click="true">
|
||||
{{ t("memories", "Download Video") }}
|
||||
<template #icon>
|
||||
<DownloadIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
v-if="!routeIsPublic && !routeIsAlbum"
|
||||
:aria-label="t('memories', 'View in folder')"
|
||||
@click="viewInFolder"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton v-if="!routeIsPublic && !routeIsAlbum" :aria-label="t('memories', 'View in folder')"
|
||||
@click="viewInFolder" :close-after-click="true">
|
||||
{{ t("memories", "View in folder") }}
|
||||
<template #icon>
|
||||
<OpenInNewIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Slideshow')"
|
||||
@click="startSlideshow"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Slideshow')" @click="startSlideshow" :close-after-click="true">
|
||||
{{ t("memories", "Slideshow") }}
|
||||
<template #icon>
|
||||
<SlideshowIcon :size="24" />
|
||||
</template>
|
||||
</NcActionButton>
|
||||
<NcActionButton
|
||||
:aria-label="t('memories', 'Edit EXIF Data')"
|
||||
v-if="!routeIsPublic"
|
||||
@click="editExif"
|
||||
:close-after-click="true"
|
||||
>
|
||||
<NcActionButton :aria-label="t('memories', 'Edit EXIF Data')" v-if="!routeIsPublic" @click="editExif"
|
||||
:close-after-click="true">
|
||||
{{ t("memories", "Edit EXIF Data") }}
|
||||
<template #icon>
|
||||
<EditFileIcon :size="24" />
|
||||
|
@ -144,18 +88,11 @@
|
|||
</NcActions>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="bottom-bar"
|
||||
v-if="photoswipe"
|
||||
:class="{ showControls, showBottomBar }"
|
||||
>
|
||||
<div class="bottom-bar" v-if="photoswipe" :class="{ showControls, showBottomBar }">
|
||||
<div class="exif title" v-if="currentPhoto?.imageInfo?.exif?.Title">
|
||||
{{ currentPhoto.imageInfo.exif.Title }}
|
||||
</div>
|
||||
<div
|
||||
class="exif description"
|
||||
v-if="currentPhoto?.imageInfo?.exif?.Description"
|
||||
>
|
||||
<div class="exif description" v-if="currentPhoto?.imageInfo?.exif?.Description">
|
||||
{{ currentPhoto.imageInfo.exif.Description }}
|
||||
</div>
|
||||
<div class="exif date" v-if="currentDateTaken">
|
||||
|
@ -270,7 +207,7 @@ export default defineComponent({
|
|||
|
||||
computed: {
|
||||
/** Number of buttons to show inline */
|
||||
numInlineActions() {
|
||||
numInlineActions(): number {
|
||||
let base = 3;
|
||||
if (this.canShare) base++;
|
||||
if (this.canEdit) base++;
|
||||
|
@ -283,17 +220,17 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Route is public */
|
||||
routeIsPublic() {
|
||||
routeIsPublic(): boolean {
|
||||
return this.$route.name === "folder-share";
|
||||
},
|
||||
|
||||
/** Route is album */
|
||||
routeIsAlbum() {
|
||||
routeIsAlbum(): boolean {
|
||||
return this.$route.name === "albums";
|
||||
},
|
||||
|
||||
/** Get the currently open photo */
|
||||
currentPhoto() {
|
||||
currentPhoto(): IPhoto | null {
|
||||
if (!this.list.length || !this.photoswipe) {
|
||||
return null;
|
||||
}
|
||||
|
@ -305,31 +242,31 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Is the current slide a video */
|
||||
isVideo() {
|
||||
return this.currentPhoto?.flag & this.c.FLAG_IS_VIDEO;
|
||||
isVideo(): boolean {
|
||||
return Boolean(this.currentPhoto?.flag & this.c.FLAG_IS_VIDEO);
|
||||
},
|
||||
|
||||
/** Show bottom bar info such as date taken */
|
||||
showBottomBar() {
|
||||
return !this.isVideo && this.fullyOpened && this.currentPhoto?.imageInfo;
|
||||
showBottomBar(): boolean {
|
||||
return !this.isVideo && this.fullyOpened && Boolean(this.currentPhoto?.imageInfo);
|
||||
},
|
||||
|
||||
/** Get date taken string */
|
||||
currentDateTaken() {
|
||||
currentDateTaken(): string | null {
|
||||
const date = this.currentPhoto?.imageInfo?.datetaken;
|
||||
if (!date) return null;
|
||||
return utils.getLongDateStr(new Date(date * 1000), false, true);
|
||||
},
|
||||
|
||||
/** Get download link for current photo */
|
||||
currentDownloadLink() {
|
||||
currentDownloadLink(): string | null {
|
||||
return this.currentPhoto
|
||||
? window.location.origin + getDownloadLink(this.currentPhoto)
|
||||
: null;
|
||||
},
|
||||
|
||||
/** Allow opening editor */
|
||||
canEdit() {
|
||||
canEdit(): boolean {
|
||||
return (
|
||||
this.currentPhoto?.mimetype?.startsWith("image/") &&
|
||||
!this.currentPhoto.liveid
|
||||
|
@ -337,7 +274,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Does the browser support native share API */
|
||||
canShare() {
|
||||
canShare(): boolean {
|
||||
return "share" in navigator && this.currentPhoto && !this.isVideo;
|
||||
},
|
||||
},
|
||||
|
@ -369,7 +306,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
/** Event on file changed */
|
||||
handleFileUpdated({ fileid }: { fileid: number }) {
|
||||
handleFileUpdated({ fileid }: { fileid: number; }) {
|
||||
if (this.currentPhoto && this.currentPhoto.fileid === fileid) {
|
||||
this.currentPhoto.etag += "_";
|
||||
this.currentPhoto.imageInfo = null;
|
||||
|
@ -547,14 +484,14 @@ export default defineComponent({
|
|||
});
|
||||
|
||||
// Video support
|
||||
new PsVideo(this.photoswipe, {
|
||||
new PsVideo(<PhotoSwipe>this.photoswipe, {
|
||||
videoAttributes: { controls: "", playsinline: "", preload: "none" },
|
||||
autoplay: true,
|
||||
preventDragOffset: 40,
|
||||
});
|
||||
|
||||
// Live photo support
|
||||
new PsLivePhoto(this.photoswipe, {});
|
||||
new PsLivePhoto(<PhotoSwipe>this.photoswipe, {});
|
||||
|
||||
// Patch the close button to stop the slideshow
|
||||
const _close = this.photoswipe.close.bind(this.photoswipe);
|
||||
|
@ -1170,6 +1107,7 @@ export default defineComponent({
|
|||
transition: opacity 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
|
||||
&.showControls {
|
||||
opacity: 1;
|
||||
pointer-events: auto;
|
||||
|
@ -1188,6 +1126,7 @@ export default defineComponent({
|
|||
|
||||
transition: opacity 0.2s ease-in-out;
|
||||
opacity: 0;
|
||||
|
||||
&.showControls.showBottomBar {
|
||||
opacity: 1;
|
||||
}
|
||||
|
@ -1197,6 +1136,7 @@ export default defineComponent({
|
|||
font-weight: bold;
|
||||
font-size: 0.9em;
|
||||
}
|
||||
|
||||
&.description {
|
||||
margin-top: -2px;
|
||||
margin-bottom: 2px;
|
||||
|
@ -1228,6 +1168,7 @@ export default defineComponent({
|
|||
}
|
||||
|
||||
:deep .plyr__volume {
|
||||
|
||||
// Cannot be vertical yet :(
|
||||
@media (max-width: 768px) {
|
||||
display: none;
|
||||
|
@ -1255,6 +1196,7 @@ export default defineComponent({
|
|||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
.pswp__icn-shadow {
|
||||
display: none;
|
||||
}
|
||||
|
|
20
src/main.ts
20
src/main.ts
|
@ -1,7 +1,7 @@
|
|||
/// <reference types="@nextcloud/typings" />
|
||||
|
||||
import "reflect-metadata";
|
||||
import Vue from "vue";
|
||||
import { createApp } from "vue";
|
||||
import VueVirtualScroller from "vue-virtual-scroller";
|
||||
import "vue-virtual-scroller/dist/vue-virtual-scroller.css";
|
||||
import GlobalMixin from "./mixins/GlobalMixin";
|
||||
|
@ -66,10 +66,6 @@ if (!globalThis.videoClientIdPersistent) {
|
|||
);
|
||||
}
|
||||
|
||||
Vue.mixin(GlobalMixin);
|
||||
Vue.mixin(UserConfig);
|
||||
Vue.use(VueVirtualScroller);
|
||||
|
||||
// https://github.com/nextcloud/photos/blob/156f280c0476c483cb9ce81769ccb0c1c6500a4e/src/main.js
|
||||
// TODO: remove when we have a proper fileinfo standalone library
|
||||
// original scripts are loaded from
|
||||
|
@ -90,8 +86,12 @@ window.addEventListener("DOMContentLoaded", () => {
|
|||
);
|
||||
});
|
||||
|
||||
export default new Vue({
|
||||
el: "#content",
|
||||
router,
|
||||
render: (h) => h(App),
|
||||
});
|
||||
const Vue = createApp(App);
|
||||
Vue.use(router);
|
||||
Vue.use(VueVirtualScroller);
|
||||
|
||||
Vue.mixin(GlobalMixin);
|
||||
Vue.mixin(UserConfig);
|
||||
|
||||
Vue.mount("#content");
|
||||
export default Vue;
|
||||
|
|
|
@ -2,11 +2,12 @@ import { emit, subscribe, unsubscribe } from "@nextcloud/event-bus";
|
|||
import { loadState } from "@nextcloud/initial-state";
|
||||
import axios from "@nextcloud/axios";
|
||||
import { API } from "../services/API";
|
||||
import { defineComponent } from "vue";
|
||||
|
||||
const eventName = "memories:user-config-changed";
|
||||
const localSettings = ["squareThumbs", "showFaceRect"];
|
||||
|
||||
export default {
|
||||
export default defineComponent({
|
||||
name: "UserConfig",
|
||||
|
||||
data() {
|
||||
|
@ -83,4 +84,4 @@ export default {
|
|||
emit(eventName, { setting, value });
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
|
|
|
@ -1,16 +1,13 @@
|
|||
import { generateUrl } from "@nextcloud/router";
|
||||
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
||||
import Router from "vue-router";
|
||||
import Vue from "vue";
|
||||
import { createRouter, createWebHistory } from "vue-router";
|
||||
import Timeline from "./components/Timeline.vue";
|
||||
|
||||
Vue.use(Router);
|
||||
|
||||
export default new Router({
|
||||
mode: "history",
|
||||
export default createRouter({
|
||||
// if index.php is in the url AND we got this far, then it's working:
|
||||
// let's keep using index.php in the url
|
||||
base: generateUrl("/apps/memories"),
|
||||
history: createWebHistory(generateUrl("/apps/memories")),
|
||||
|
||||
linkActiveClass: "active",
|
||||
routes: [
|
||||
{
|
||||
|
@ -23,7 +20,7 @@ export default new Router({
|
|||
},
|
||||
|
||||
{
|
||||
path: "/folders/:path*",
|
||||
path: "/folders/:path*", // REMOVED IN VUE 3
|
||||
component: Timeline,
|
||||
name: "folders",
|
||||
props: (route) => ({
|
||||
|
@ -95,7 +92,7 @@ export default new Router({
|
|||
},
|
||||
|
||||
{
|
||||
path: "/tags/:name*",
|
||||
path: "/tags/:name*", // REMOVED IN VUE 3
|
||||
component: Timeline,
|
||||
name: "tags",
|
||||
props: (route) => ({
|
||||
|
@ -106,9 +103,8 @@ export default new Router({
|
|||
{
|
||||
path: "/maps",
|
||||
name: "maps",
|
||||
// router-link doesn't support external url, let's force the redirect
|
||||
beforeEnter() {
|
||||
window.open(generateUrl("/apps/maps"), "_blank");
|
||||
redirect: () => {
|
||||
return generateUrl("/apps/maps");
|
||||
},
|
||||
},
|
||||
|
||||
|
|
|
@ -6,8 +6,11 @@ const gen = generateUrl;
|
|||
|
||||
/** Add auth token to this URL */
|
||||
function tok(url: string) {
|
||||
if (vuerouter.currentRoute.name === "folder-share") {
|
||||
url = API.Q(url, `folder_share=${vuerouter.currentRoute.params.token}`);
|
||||
if (vuerouter.currentRoute.value.name === "folder-share") {
|
||||
url = API.Q(
|
||||
url,
|
||||
`folder_share=${vuerouter.currentRoute.value.params.token}`
|
||||
);
|
||||
}
|
||||
return url;
|
||||
}
|
||||
|
|
|
@ -27,15 +27,11 @@ import { generateRemoteUrl } from "@nextcloud/router";
|
|||
|
||||
// Monkey business
|
||||
import * as rq from "webdav/dist/node/request";
|
||||
(<any>rq).prepareRequestOptionsOld = rq.prepareRequestOptions.bind(rq);
|
||||
(<any>rq).prepareRequestOptions = function (
|
||||
requestOptions,
|
||||
context,
|
||||
userOptions
|
||||
) {
|
||||
const prepareRequestOptionsOld = rq.prepareRequestOptions.bind(rq);
|
||||
(<any>rq).prepareRequestOptions = (requestOptions, context, userOptions) => {
|
||||
requestOptions.method = userOptions.method || requestOptions.method;
|
||||
return this.prepareRequestOptionsOld(requestOptions, context, userOptions);
|
||||
}.bind(rq);
|
||||
return prepareRequestOptionsOld(requestOptions, context, userOptions);
|
||||
};
|
||||
|
||||
// force our axios
|
||||
const patcher = webdav.getPatcher();
|
||||
|
|
|
@ -235,7 +235,7 @@ export function convertFlags(photo: IPhoto) {
|
|||
* This function does not check if this is the folder route
|
||||
*/
|
||||
export function getFolderRoutePath(basePath: string) {
|
||||
let path: any = vuerouter.currentRoute.params.path || "/";
|
||||
let path: any = vuerouter.currentRoute.value.params.path || "/";
|
||||
path = typeof path === "string" ? path : path.join("/");
|
||||
path = basePath + "/" + path;
|
||||
path = path.replace(/\/\/+/, "/"); // Remove double slashes
|
||||
|
|
|
@ -44,9 +44,13 @@ const GET_FILE_CHUNK_SIZE = 50;
|
|||
*/
|
||||
export async function getFiles(photos: IPhoto[]): Promise<IFileInfo[]> {
|
||||
// Check if albums
|
||||
const route = vuerouter.currentRoute;
|
||||
const route = vuerouter.currentRoute.value;
|
||||
if (route.name === "albums") {
|
||||
return getAlbumFileInfos(photos, route.params.user, route.params.name);
|
||||
return getAlbumFileInfos(
|
||||
photos,
|
||||
<string>route.params.user,
|
||||
<string>route.params.name
|
||||
);
|
||||
}
|
||||
|
||||
// Get file infos
|
||||
|
|
|
@ -48,8 +48,8 @@ export async function downloadFilesByPhotos(photos: IPhoto[]) {
|
|||
/** Get URL to download one file (e.g. for video streaming) */
|
||||
export function getDownloadLink(photo: IPhoto) {
|
||||
// Check if public
|
||||
if (vuerouter.currentRoute.name === "folder-share") {
|
||||
const token = window.vuerouter.currentRoute.params.token;
|
||||
if (vuerouter.currentRoute.value.name === "folder-share") {
|
||||
const token = window.vuerouter.currentRoute.value.params.token;
|
||||
// TODO: allow proper dav access without the need of basic auth
|
||||
// https://github.com/nextcloud/server/issues/19700
|
||||
return generateUrl(`/s/${token}/download?path={dirname}&files={basename}`, {
|
||||
|
@ -59,12 +59,12 @@ export function getDownloadLink(photo: IPhoto) {
|
|||
}
|
||||
|
||||
// Check if albums
|
||||
const route = vuerouter.currentRoute;
|
||||
const route = vuerouter.currentRoute.value;
|
||||
if (route.name === "albums") {
|
||||
const fInfos = getAlbumFileInfos(
|
||||
[photo],
|
||||
route.params.user,
|
||||
route.params.name
|
||||
route.params.user as string,
|
||||
route.params.name as string
|
||||
);
|
||||
if (fInfos.length) {
|
||||
return generateUrl(`/remote.php/dav${fInfos[0].originalFilename}`);
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
import { VueConstructor } from "vue";
|
||||
|
||||
export type IFileInfo = {
|
||||
/** Same as fileid */
|
||||
id: number;
|
||||
|
@ -229,7 +227,7 @@ export type ISelectionAction = {
|
|||
/** Display text */
|
||||
name: string;
|
||||
/** Icon component */
|
||||
icon: VueConstructor;
|
||||
icon: any;
|
||||
/** Action to perform */
|
||||
callback: (selection: Map<number, IPhoto>) => Promise<void>;
|
||||
/** Condition to check for including */
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { constants } from "./services/Utils";
|
||||
import { translate as t, translatePlural as n } from "@nextcloud/l10n";
|
||||
|
||||
declare module "vue" {
|
||||
interface ComponentCustomProperties {
|
||||
// GlobalMixin.ts
|
||||
t: typeof t;
|
||||
n: typeof n;
|
||||
|
||||
c: typeof constants.c;
|
||||
TagDayID: typeof constants.TagDayID;
|
||||
TagDayIDValueSet: typeof constants.TagDayIDValueSet;
|
||||
|
||||
state_noDownload: boolean;
|
||||
|
||||
// UserConfig.ts
|
||||
config_timelinePath: string;
|
||||
config_foldersPath: string;
|
||||
config_showHidden: boolean;
|
||||
config_tagsEnabled: boolean;
|
||||
config_recognizeEnabled: boolean;
|
||||
config_facerecognitionInstalled: boolean;
|
||||
config_facerecognitionEnabled: boolean;
|
||||
config_mapsEnabled: boolean;
|
||||
config_albumsEnabled: boolean;
|
||||
config_squareThumbs: boolean;
|
||||
config_showFaceRect: boolean;
|
||||
config_eventName: string;
|
||||
|
||||
updateSetting(setting: string): Promise<void>;
|
||||
updateLocalSetting({
|
||||
setting,
|
||||
value,
|
||||
}: {
|
||||
setting: string;
|
||||
value: any;
|
||||
}): void;
|
||||
}
|
||||
}
|
||||
|
||||
export {};
|
|
@ -1,10 +1,10 @@
|
|||
declare module "*.vue" {
|
||||
import Vue from "vue";
|
||||
export default Vue;
|
||||
import { defineComponent } from "vue";
|
||||
const Component: ReturnType<typeof defineComponent>;
|
||||
export default Component;
|
||||
}
|
||||
|
||||
declare module "*.svg" {
|
||||
import Vue, { VueConstructor } from "vue";
|
||||
const content: VueConstructor<Vue>;
|
||||
const content: any;
|
||||
export default content;
|
||||
}
|
||||
|
|
|
@ -1,13 +1,15 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"useDefineForClassFields": true,
|
||||
"jsx": "preserve",
|
||||
"lib": ["dom", "es2017"],
|
||||
"target": "ES2017",
|
||||
"module": "es2020",
|
||||
"moduleResolution": "node",
|
||||
"sourceMap": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"experimentalDecorators": true,
|
||||
"emitDecoratorMetadata": true,
|
||||
"jsx": "preserve"
|
||||
"noImplicitThis": true,
|
||||
"esModuleInterop": true
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,144 @@
|
|||
/**
|
||||
* @copyright Copyright (c) 2020 John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @author John Molakvoæ <skjnldsv@protonmail.com>
|
||||
*
|
||||
* @license AGPL-3.0-or-later
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
const path = require("path");
|
||||
const webpack = require("webpack");
|
||||
|
||||
const { VueLoaderPlugin } = require("vue-loader");
|
||||
const NodePolyfillPlugin = require('node-polyfill-webpack-plugin')
|
||||
const TerserPlugin = require("terser-webpack-plugin");
|
||||
|
||||
const appName = process.env.npm_package_name;
|
||||
const appVersion = process.env.npm_package_version;
|
||||
const buildMode = process.env.NODE_ENV;
|
||||
const isDev = buildMode === "development";
|
||||
console.info("Building", appName, appVersion, "\n");
|
||||
|
||||
const rules = {
|
||||
RULE_CSS: {
|
||||
test: /\.css$/,
|
||||
use: ["style-loader", "css-loader"],
|
||||
},
|
||||
RULE_SCSS: {
|
||||
test: /\.scss$/,
|
||||
use: ["style-loader", "css-loader", "sass-loader"],
|
||||
},
|
||||
RULE_VUE: {
|
||||
test: /\.vue$/,
|
||||
loader: "vue-loader",
|
||||
},
|
||||
RULE_JS: {
|
||||
test: /\.js$/,
|
||||
loader: "babel-loader",
|
||||
exclude: /node_modules/,
|
||||
},
|
||||
RULE_ASSETS: {
|
||||
test: /\.(png|jpe?g|gif|svg|woff2?|eot|ttf)$/,
|
||||
type: "asset/inline",
|
||||
},
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
target: "web",
|
||||
mode: buildMode,
|
||||
devtool: isDev ? "cheap-source-map" : "source-map",
|
||||
|
||||
entry: {
|
||||
main: path.resolve(path.join("src", "main.js")),
|
||||
},
|
||||
output: {
|
||||
path: path.resolve("./js"),
|
||||
publicPath: path.join("/apps/", appName, "/js/"),
|
||||
|
||||
// Output file names
|
||||
filename: `${appName}-[name].js?v=[contenthash]`,
|
||||
chunkFilename: `${appName}-[name].js?v=[contenthash]`,
|
||||
|
||||
// Clean output before each build
|
||||
clean: true,
|
||||
|
||||
// Make sure sourcemaps have a proper path and do not
|
||||
// leak local paths https://github.com/webpack/webpack/issues/3603
|
||||
devtoolNamespace: appName,
|
||||
devtoolModuleFilenameTemplate(info) {
|
||||
const rootDir = process.cwd();
|
||||
const rel = path.relative(rootDir, info.absoluteResourcePath);
|
||||
return `webpack:///${appName}/${rel}`;
|
||||
},
|
||||
},
|
||||
|
||||
devServer: {
|
||||
hot: true,
|
||||
host: "127.0.0.1",
|
||||
port: 3000,
|
||||
client: {
|
||||
overlay: false,
|
||||
},
|
||||
devMiddleware: {
|
||||
writeToDisk: true,
|
||||
},
|
||||
headers: {
|
||||
"Access-Control-Allow-Origin": "*",
|
||||
},
|
||||
},
|
||||
|
||||
cache: !isDev,
|
||||
|
||||
optimization: {
|
||||
chunkIds: "named",
|
||||
splitChunks: {
|
||||
automaticNameDelimiter: "-",
|
||||
},
|
||||
minimize: !isDev,
|
||||
minimizer: [
|
||||
new TerserPlugin({
|
||||
terserOptions: {
|
||||
output: {
|
||||
comments: false,
|
||||
},
|
||||
},
|
||||
extractComments: true,
|
||||
}),
|
||||
],
|
||||
},
|
||||
|
||||
module: {
|
||||
rules: Object.values(rules),
|
||||
},
|
||||
|
||||
plugins: [
|
||||
new VueLoaderPlugin(),
|
||||
|
||||
// Make sure we auto-inject node polyfills on demand
|
||||
// https://webpack.js.org/blog/2020-10-10-webpack-5-release/#automatic-nodejs-polyfills-removed
|
||||
new NodePolyfillPlugin(),
|
||||
|
||||
// Make appName & appVersion available as a constant
|
||||
new webpack.DefinePlugin({ appName: JSON.stringify(appName) }),
|
||||
new webpack.DefinePlugin({ appVersion: JSON.stringify(appVersion) }),
|
||||
],
|
||||
|
||||
resolve: {
|
||||
extensions: ["*", ".js", ".vue"],
|
||||
symlinks: false,
|
||||
},
|
||||
};
|
|
@ -1,4 +1,4 @@
|
|||
const webpackConfig = require('@nextcloud/webpack-vue-config')
|
||||
const webpackConfig = require('./webpack-base')
|
||||
const WorkboxPlugin = require('workbox-webpack-plugin')
|
||||
const path = require('path')
|
||||
|
||||
|
@ -14,9 +14,6 @@ webpackConfig.module.rules.push({
|
|||
},
|
||||
});
|
||||
webpackConfig.resolve.extensions.push('.ts');
|
||||
webpackConfig.resolve.alias = {
|
||||
'vue$': 'vue/dist/vue.esm.js',
|
||||
}
|
||||
webpackConfig.entry.main = path.resolve(path.join('src', 'main'));
|
||||
|
||||
webpackConfig.watchOptions = {
|
||||
|
@ -24,11 +21,13 @@ webpackConfig.watchOptions = {
|
|||
aggregateTimeout: 300,
|
||||
};
|
||||
|
||||
if (!isDev) {
|
||||
webpackConfig.plugins.push(
|
||||
new WorkboxPlugin.InjectManifest({
|
||||
swSrc: path.resolve(path.join('src', 'service-worker.js')),
|
||||
swDest: 'memories-service-worker.js',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
module.exports = webpackConfig
|
||||
|
|
Loading…
Reference in New Issue