Compare commits
10 commits
develop
...
feature/24
Author | SHA1 | Date | |
---|---|---|---|
![]() |
6f4e26f547 | ||
![]() |
f68746768d | ||
![]() |
186b2da58a | ||
![]() |
a6e06844f3 | ||
![]() |
344c88b84c | ||
![]() |
3e4d19c8c7 | ||
![]() |
bbf6f206f4 | ||
![]() |
e0aff43d16 | ||
![]() |
d2bf020e23 | ||
![]() |
c39ec9695f |
50 changed files with 549 additions and 370 deletions
|
@ -6,12 +6,12 @@ import PPhotoViewer from "./p-photo-viewer.vue";
|
|||
import PPhotoCards from "./p-photo-cards.vue";
|
||||
import PPhotoMosaic from "./p-photo-mosaic.vue";
|
||||
import PPhotoList from "./p-photo-list.vue";
|
||||
import PPhotoSearch from "./p-photo-search.vue";
|
||||
import PPhotoClipboard from "./p-photo-clipboard.vue";
|
||||
import PLabelClipboard from "./p-label-clipboard.vue";
|
||||
import PFileClipboard from "./p-file-clipboard.vue";
|
||||
import PAlbumClipboard from "./p-album-clipboard.vue";
|
||||
import PAlbumToolbar from "./p-album-toolbar.vue";
|
||||
import PPhotoToolbar from "./p-photo-toolbar.vue";
|
||||
import PScrollTop from "./p-scroll-top.vue";
|
||||
|
||||
const components = {};
|
||||
|
@ -25,12 +25,12 @@ components.install = (Vue) => {
|
|||
Vue.component("p-photo-cards", PPhotoCards);
|
||||
Vue.component("p-photo-mosaic", PPhotoMosaic);
|
||||
Vue.component("p-photo-list", PPhotoList);
|
||||
Vue.component("p-photo-search", PPhotoSearch);
|
||||
Vue.component("p-photo-clipboard", PPhotoClipboard);
|
||||
Vue.component("p-label-clipboard", PLabelClipboard);
|
||||
Vue.component("p-file-clipboard", PFileClipboard);
|
||||
Vue.component("p-album-clipboard", PAlbumClipboard);
|
||||
Vue.component("p-album-toolbar", PAlbumToolbar);
|
||||
Vue.component("p-photo-toolbar", PPhotoToolbar);
|
||||
Vue.component("p-scroll-top", PScrollTop);
|
||||
};
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-form lazy-validation dense
|
||||
ref="form" autocomplete="off" class="p-photo-search p-album-toolbar" accept-charset="UTF-8"
|
||||
ref="form" autocomplete="off" class="p-photo-toolbar p-album-toolbar" accept-charset="UTF-8"
|
||||
@submit.prevent="filterChange">
|
||||
<v-toolbar flat color="secondary">
|
||||
<v-edit-dialog
|
||||
|
@ -39,6 +39,11 @@
|
|||
<v-icon>view_column</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click.stop="showUpload()" v-if="!$config.values.readonly && $config.feature('upload')"
|
||||
class="hidden-sm-and-down">
|
||||
<v-icon>cloud_upload</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click.stop="expand" class="p-expand-search">
|
||||
<v-icon>{{ searchExpanded ? 'keyboard_arrow_up' : 'keyboard_arrow_down' }}</v-icon>
|
||||
</v-btn>
|
||||
|
@ -108,7 +113,7 @@
|
|||
</v-form>
|
||||
</template>
|
||||
<script>
|
||||
import Album from "../model/album";
|
||||
import Event from "pubsub-js";
|
||||
|
||||
export default {
|
||||
name: 'p-album-toolbar',
|
||||
|
@ -164,12 +169,14 @@
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
showUpload() {
|
||||
Event.publish("dialog.upload");
|
||||
},
|
||||
expand() {
|
||||
this.searchExpanded = !this.searchExpanded;
|
||||
this.growDesc = !this.growDesc;
|
||||
},
|
||||
updateAlbum() {
|
||||
console.log("UPDATE ALBUM", this.album.getValues(true));
|
||||
if(this.album.wasChanged()) {
|
||||
this.album.update();
|
||||
}
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
<template>
|
||||
<v-form lazy-validation dense
|
||||
ref="form" autocomplete="off" class="p-photo-search" accept-charset="UTF-8"
|
||||
ref="form" autocomplete="off" class="p-photo-toolbar" accept-charset="UTF-8"
|
||||
@submit.prevent="filterChange">
|
||||
<v-toolbar flat color="secondary">
|
||||
<v-text-field class="pt-3 pr-3 p-search-field"
|
||||
|
@ -153,7 +153,7 @@
|
|||
import { Info } from "luxon";
|
||||
|
||||
export default {
|
||||
name: 'p-photo-search',
|
||||
name: 'p-photo-toolbar',
|
||||
props: {
|
||||
dirty: Boolean,
|
||||
filter: Object,
|
|
@ -6,7 +6,9 @@
|
|||
<v-btn icon dark @click.stop="cancel">
|
||||
<v-icon>close</v-icon>
|
||||
</v-btn>
|
||||
<v-toolbar-title><translate>Upload</translate></v-toolbar-title>
|
||||
<v-toolbar-title>
|
||||
<translate>Upload</translate>
|
||||
</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-container grid-list-xs text-xs-left fluid>
|
||||
<v-form ref="form" class="p-photo-upload" lazy-validation @submit.prevent="submit" dense>
|
||||
|
@ -14,7 +16,7 @@
|
|||
|
||||
<v-container fluid>
|
||||
<p class="subheading">
|
||||
<span v-if="total === 0">Select photos to start upload...</span>
|
||||
<span v-if="total === 0">Select files to start upload...</span>
|
||||
<span v-else-if="failed">Upload failed</span>
|
||||
<span v-else-if="total > 0 && completed < 100">
|
||||
Uploading {{current}} of {{total}}...
|
||||
|
@ -23,6 +25,39 @@
|
|||
<span v-else-if="completed === 100">Done.</span>
|
||||
</p>
|
||||
|
||||
<v-combobox flat solo hide-details chips deletable-chips
|
||||
multiple color="secondary-dark" class="my-3"
|
||||
v-model="selectedAlbums"
|
||||
:items="albums"
|
||||
item-text="Title"
|
||||
item-value="UID"
|
||||
:allow-overflow="false"
|
||||
label="Add to existing albums or create a new one."
|
||||
return-object
|
||||
>
|
||||
<template v-slot:no-data>
|
||||
<v-list-tile>
|
||||
<v-list-tile-content>
|
||||
<v-list-tile-title>
|
||||
Press <kbd>enter</kbd> to create a new album.
|
||||
</v-list-tile-title>
|
||||
</v-list-tile-content>
|
||||
</v-list-tile>
|
||||
</template>
|
||||
<template v-slot:selection="data">
|
||||
<v-chip
|
||||
:key="JSON.stringify(data.item)"
|
||||
:selected="data.selected"
|
||||
:disabled="data.disabled"
|
||||
class="v-chip--select-multi"
|
||||
@input="data.parent.selectItem(data.item)"
|
||||
>
|
||||
<v-icon class="pr-1">folder</v-icon>
|
||||
{{ data.item.Title ? data.item.Title : data.item | truncate(40) }}
|
||||
</v-chip>
|
||||
</template>
|
||||
</v-combobox>
|
||||
|
||||
<v-progress-linear color="secondary-dark" v-model="completed"
|
||||
:indeterminate="indexing"></v-progress-linear>
|
||||
|
||||
|
@ -56,6 +91,7 @@
|
|||
<script>
|
||||
import Api from "common/api";
|
||||
import Notify from "common/notify";
|
||||
import Album from "../model/album";
|
||||
|
||||
export default {
|
||||
name: 'p-tab-upload',
|
||||
|
@ -64,7 +100,10 @@
|
|||
},
|
||||
data() {
|
||||
return {
|
||||
albums: [],
|
||||
selectedAlbums: [],
|
||||
selected: [],
|
||||
loading: false,
|
||||
uploads: [],
|
||||
busy: false,
|
||||
indexing: false,
|
||||
|
@ -78,8 +117,27 @@
|
|||
}
|
||||
},
|
||||
methods: {
|
||||
findAlbums(q) {
|
||||
if (this.loading) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.loading = true;
|
||||
|
||||
const params = {
|
||||
q: q,
|
||||
count: 1000,
|
||||
offset: 0,
|
||||
type: "album"
|
||||
};
|
||||
|
||||
Album.search(params).then(response => {
|
||||
this.loading = false;
|
||||
this.albums = response.models;
|
||||
}).catch(() => this.loading = false);
|
||||
},
|
||||
cancel() {
|
||||
if(this.busy) {
|
||||
if (this.busy) {
|
||||
Notify.info(this.$gettext("Uploading photos..."));
|
||||
return;
|
||||
}
|
||||
|
@ -87,7 +145,7 @@
|
|||
this.$emit('cancel');
|
||||
},
|
||||
confirm() {
|
||||
if(this.busy) {
|
||||
if (this.busy) {
|
||||
Notify.info(this.$gettext("Uploading photos..."));
|
||||
return;
|
||||
}
|
||||
|
@ -117,6 +175,18 @@
|
|||
|
||||
Notify.info(this.$gettext("Uploading photos..."));
|
||||
|
||||
let addToAlbums = [];
|
||||
|
||||
if (this.selectedAlbums && this.selectedAlbums.length > 0) {
|
||||
this.selectedAlbums.forEach((a) => {
|
||||
if (typeof a === "string") {
|
||||
addToAlbums.push(a)
|
||||
} else if (a instanceof Album && a.UID) {
|
||||
addToAlbums.push(a.UID)
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async function performUpload(ctx) {
|
||||
for (let i = 0; i < ctx.selected.length; i++) {
|
||||
let file = ctx.selected[i];
|
||||
|
@ -152,6 +222,7 @@
|
|||
|
||||
Api.post('import/upload/' + this.started, {
|
||||
move: true,
|
||||
albums: addToAlbums,
|
||||
}).then(() => {
|
||||
Notify.success(ctx.$gettext("Upload complete"));
|
||||
ctx.busy = false;
|
||||
|
@ -178,6 +249,7 @@
|
|||
this.started = 0;
|
||||
this.review = this.$config.feature("review");
|
||||
this.safe = !this.$config.get("uploadNSFW");
|
||||
this.findAlbums("");
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
@ -31,6 +31,11 @@
|
|||
<v-icon>refresh</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click.stop="showUpload()" v-if="!$config.values.readonly && $config.feature('upload')"
|
||||
class="hidden-sm-and-down">
|
||||
<v-icon>cloud_upload</v-icon>
|
||||
</v-btn>
|
||||
|
||||
<v-btn icon @click.prevent="create">
|
||||
<v-icon>add</v-icon>
|
||||
</v-btn>
|
||||
|
@ -222,6 +227,9 @@
|
|||
};
|
||||
},
|
||||
methods: {
|
||||
showUpload() {
|
||||
Event.publish("dialog.upload");
|
||||
},
|
||||
selectRange(rangeEnd, models) {
|
||||
if (!models || !models[rangeEnd] || !(models[rangeEnd] instanceof RestModel)) {
|
||||
console.warn("selectRange() - invalid arguments:", rangeEnd, models);
|
||||
|
|
|
@ -2,8 +2,8 @@
|
|||
<div class="p-page p-page-photos" v-infinite-scroll="loadMore" :infinite-scroll-disabled="scrollDisabled"
|
||||
:infinite-scroll-distance="10" :infinite-scroll-listen-for-event="'scrollRefresh'">
|
||||
|
||||
<p-photo-search :settings="settings" :filter="filter" :filter-change="updateQuery" :dirty="dirty"
|
||||
:refresh="refresh"></p-photo-search>
|
||||
<p-photo-toolbar :settings="settings" :filter="filter" :filter-change="updateQuery" :dirty="dirty"
|
||||
:refresh="refresh"></p-photo-toolbar>
|
||||
|
||||
<v-container fluid class="pa-4" v-if="loading">
|
||||
<v-progress-linear color="secondary-dark" :indeterminate="true"></v-progress-linear>
|
||||
|
|
|
@ -89,27 +89,27 @@ msgstr "Alben gelöscht"
|
|||
msgid "All "
|
||||
msgstr "Alle"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:136 src/component/p-photo-search.vue:157
|
||||
#: src/component/p-album-toolbar.vue:136 src/component/p-photo-toolbar.vue:157
|
||||
msgid "All Cameras"
|
||||
msgstr "Alle Kameras"
|
||||
|
||||
#: src/component/p-photo-search.vue:160
|
||||
#: src/component/p-photo-toolbar.vue:160
|
||||
msgid "All Categories"
|
||||
msgstr "Alle Kategorien"
|
||||
|
||||
#: src/component/p-photo-search.vue:159
|
||||
#: src/component/p-photo-toolbar.vue:159
|
||||
msgid "All Colors"
|
||||
msgstr "Alle Farben"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:140 src/component/p-photo-search.vue:156
|
||||
#: src/component/p-album-toolbar.vue:140 src/component/p-photo-toolbar.vue:156
|
||||
msgid "All Countries"
|
||||
msgstr "Alle Länder"
|
||||
|
||||
#: src/component/p-photo-search.vue:158
|
||||
#: src/component/p-photo-toolbar.vue:158
|
||||
msgid "All Lenses"
|
||||
msgstr "Alle Linsen"
|
||||
|
||||
#: src/component/p-photo-search.vue:208
|
||||
#: src/component/p-photo-toolbar.vue:208
|
||||
msgid "All Years"
|
||||
msgstr "Alle Jahre"
|
||||
|
||||
|
@ -155,7 +155,7 @@ msgid "Browse and edit image classification labels."
|
|||
msgstr "Labels können angesehen und bearbeitet werden."
|
||||
|
||||
#: src/component/p-album-toolbar.vue:166 src/component/p-photo-list.vue:92
|
||||
#: src/component/p-photo-search.vue:180 src/dialog/photo/details.vue:409
|
||||
#: src/component/p-photo-toolbar.vue:180 src/dialog/photo/details.vue:409
|
||||
msgid "Camera"
|
||||
msgstr "Kamera"
|
||||
|
||||
|
@ -171,11 +171,11 @@ msgstr "Kamera"
|
|||
msgid "Cancel"
|
||||
msgstr "Abbrechen"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:148 src/component/p-photo-search.vue:165
|
||||
#: src/component/p-album-toolbar.vue:148 src/component/p-photo-toolbar.vue:165
|
||||
msgid "Cards"
|
||||
msgstr "Karten"
|
||||
|
||||
#: src/component/p-photo-search.vue:184 src/dialog/photo/details.vue:413
|
||||
#: src/component/p-photo-toolbar.vue:184 src/dialog/photo/details.vue:413
|
||||
msgid "Category"
|
||||
msgstr "Kategorie"
|
||||
|
||||
|
@ -191,7 +191,7 @@ msgstr "Private Flag ändern"
|
|||
msgid "Close"
|
||||
msgstr "Schließen"
|
||||
|
||||
#: src/component/p-photo-search.vue:183 src/dialog/photo/details.vue:412
|
||||
#: src/component/p-photo-toolbar.vue:183 src/dialog/photo/details.vue:412
|
||||
msgid "Color"
|
||||
msgstr "Farbe"
|
||||
|
||||
|
@ -215,7 +215,7 @@ msgstr "Verbinden"
|
|||
msgid "Convert RAW files"
|
||||
msgstr "RAW Dateien konvertieren"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:165 src/component/p-photo-search.vue:179
|
||||
#: src/component/p-album-toolbar.vue:165 src/component/p-photo-toolbar.vue:179
|
||||
#: src/dialog/photo/details.vue:408
|
||||
msgid "Country"
|
||||
msgstr "Land"
|
||||
|
@ -352,7 +352,7 @@ msgstr ""
|
|||
msgid "General"
|
||||
msgstr "Allgemein"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:157 src/component/p-photo-search.vue:172
|
||||
#: src/component/p-album-toolbar.vue:157 src/component/p-photo-toolbar.vue:172
|
||||
msgid "Group by similarity"
|
||||
msgstr "Ähnlichkeit"
|
||||
|
||||
|
@ -438,7 +438,7 @@ msgstr "Labels gelöscht"
|
|||
msgid "Language"
|
||||
msgstr "Sprache"
|
||||
|
||||
#: src/component/p-photo-search.vue:181 src/dialog/photo/details.vue:410
|
||||
#: src/component/p-photo-toolbar.vue:181 src/dialog/photo/details.vue:410
|
||||
msgid "Lens"
|
||||
msgstr "Linse"
|
||||
|
||||
|
@ -446,7 +446,7 @@ msgstr "Linse"
|
|||
msgid "Library"
|
||||
msgstr "Bibliothek"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:149 src/component/p-photo-search.vue:166
|
||||
#: src/component/p-album-toolbar.vue:149 src/component/p-photo-toolbar.vue:166
|
||||
msgid "List"
|
||||
msgstr "Liste"
|
||||
|
||||
|
@ -499,11 +499,11 @@ msgstr "Über 20 Labels gefunden"
|
|||
msgid "More than 50 photos found"
|
||||
msgstr "Über 50 Fotos gefunden"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:147 src/component/p-photo-search.vue:164
|
||||
#: src/component/p-album-toolbar.vue:147 src/component/p-photo-toolbar.vue:164
|
||||
msgid "Mosaic"
|
||||
msgstr "Mosaik"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:158 src/component/p-photo-search.vue:173
|
||||
#: src/component/p-album-toolbar.vue:158 src/component/p-photo-toolbar.vue:173
|
||||
msgid "Most relevant"
|
||||
msgstr "Relevanz"
|
||||
|
||||
|
@ -529,7 +529,7 @@ msgstr "Name zu lang"
|
|||
msgid "Never"
|
||||
msgstr "Nie"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:155 src/component/p-photo-search.vue:170
|
||||
#: src/component/p-album-toolbar.vue:155 src/component/p-photo-toolbar.vue:170
|
||||
msgid "Newest first"
|
||||
msgstr "Neueste zuerst"
|
||||
|
||||
|
@ -584,7 +584,7 @@ msgstr ""
|
|||
msgid "Nothing to see here yet. Be patient."
|
||||
msgstr "Noch nichts zu sehen. Gedulde dich noch ein bisschen."
|
||||
|
||||
#: src/component/p-album-toolbar.vue:156 src/component/p-photo-search.vue:171
|
||||
#: src/component/p-album-toolbar.vue:156 src/component/p-photo-toolbar.vue:171
|
||||
msgid "Oldest first"
|
||||
msgstr "Älteste zuerst"
|
||||
|
||||
|
@ -687,7 +687,7 @@ msgid "Re-index all originals, including already indexed and unchanged files."
|
|||
msgstr ""
|
||||
"Indexiere alle Dateien, auch bereits indexierte und unveränderte Dateien."
|
||||
|
||||
#: src/component/p-album-toolbar.vue:154 src/component/p-photo-search.vue:169
|
||||
#: src/component/p-album-toolbar.vue:154 src/component/p-photo-toolbar.vue:169
|
||||
msgid "Recently imported"
|
||||
msgstr "Zuletzt importiert"
|
||||
|
||||
|
@ -729,7 +729,7 @@ msgstr "Review"
|
|||
msgid "Save"
|
||||
msgstr "Speichern"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:163 src/component/p-photo-search.vue:177
|
||||
#: src/component/p-album-toolbar.vue:163 src/component/p-photo-toolbar.vue:177
|
||||
#: src/dialog/photo/details.vue:406 src/dialog/photo/labels.vue:111
|
||||
#: src/pages/albums.vue:183 src/pages/labels.vue:184 src/pages/places.vue:66
|
||||
msgid "Search"
|
||||
|
@ -781,7 +781,7 @@ msgstr "Ähnlich"
|
|||
msgid "Size"
|
||||
msgstr "Größe"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:167 src/component/p-photo-search.vue:185
|
||||
#: src/component/p-album-toolbar.vue:167 src/component/p-photo-toolbar.vue:185
|
||||
#: src/dialog/photo/details.vue:414
|
||||
msgid "Sort By"
|
||||
msgstr "Sortiere nach"
|
||||
|
@ -822,11 +822,11 @@ msgstr "Synchronisiert"
|
|||
msgid "Taken"
|
||||
msgstr "Erstellt am"
|
||||
|
||||
#: src/component/p-photo-search.vue:187 src/dialog/photo/details.vue:416
|
||||
#: src/component/p-photo-toolbar.vue:187 src/dialog/photo/details.vue:416
|
||||
msgid "Taken after"
|
||||
msgstr "Erstell nach"
|
||||
|
||||
#: src/component/p-photo-search.vue:186 src/dialog/photo/details.vue:415
|
||||
#: src/component/p-photo-toolbar.vue:186 src/dialog/photo/details.vue:415
|
||||
msgid "Taken before"
|
||||
msgstr "Erstellt vor"
|
||||
|
||||
|
@ -910,7 +910,7 @@ msgstr "Nutzername"
|
|||
msgid "Video"
|
||||
msgstr "Video"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:164 src/component/p-photo-search.vue:178
|
||||
#: src/component/p-album-toolbar.vue:164 src/component/p-photo-toolbar.vue:178
|
||||
#: src/dialog/photo/details.vue:407
|
||||
msgid "View"
|
||||
msgstr "Ansicht"
|
||||
|
@ -919,7 +919,7 @@ msgstr "Ansicht"
|
|||
msgid "Week"
|
||||
msgstr "Woche"
|
||||
|
||||
#: src/component/p-photo-search.vue:182 src/dialog/photo/details.vue:411
|
||||
#: src/component/p-photo-toolbar.vue:182 src/dialog/photo/details.vue:411
|
||||
msgid "Year"
|
||||
msgstr "Jahr"
|
||||
|
||||
|
|
|
@ -64,11 +64,11 @@ msgstr "Albums deleted"
|
|||
msgid "All "
|
||||
msgstr "All "
|
||||
|
||||
#: src/component/p-album-search.vue:107 src/component/p-photo-search.vue:157
|
||||
#: src/component/p-album-search.vue:107 src/component/p-photo-toolbar.vue:157
|
||||
msgid "All Cameras"
|
||||
msgstr "All Cameras"
|
||||
|
||||
#: src/component/p-album-search.vue:110 src/component/p-photo-search.vue:160
|
||||
#: src/component/p-album-search.vue:110 src/component/p-photo-toolbar.vue:160
|
||||
msgid "All Countries"
|
||||
msgstr "All Countries"
|
||||
|
||||
|
@ -81,7 +81,7 @@ msgid "Are you sure you want to delete these photos?"
|
|||
msgstr "Are you sure you want to delete these photos?"
|
||||
|
||||
#: src/component/p-album-search.vue:134 src/component/p-photo-list.vue:53
|
||||
#: src/component/p-photo-search.vue:186
|
||||
#: src/component/p-photo-toolbar.vue:186
|
||||
msgid "Camera"
|
||||
msgstr "Camera"
|
||||
|
||||
|
@ -93,7 +93,7 @@ msgid "Cancel"
|
|||
msgstr "Cancel"
|
||||
|
||||
#: src/component/p-album-search.vue:133 src/component/p-photo-list.vue:52
|
||||
#: src/component/p-photo-search.vue:185
|
||||
#: src/component/p-photo-toolbar.vue:185
|
||||
msgid "Country"
|
||||
msgstr "Country"
|
||||
|
||||
|
@ -121,7 +121,7 @@ msgstr "Day"
|
|||
msgid "Delete"
|
||||
msgstr "Delete"
|
||||
|
||||
#: src/component/p-album-search.vue:119 src/component/p-photo-search.vue:169
|
||||
#: src/component/p-album-search.vue:119 src/component/p-photo-toolbar.vue:169
|
||||
msgid "Details"
|
||||
msgstr "Details"
|
||||
|
||||
|
@ -204,7 +204,7 @@ msgstr "Language"
|
|||
msgid "Library"
|
||||
msgstr "Library"
|
||||
|
||||
#: src/component/p-album-search.vue:120 src/component/p-photo-search.vue:170
|
||||
#: src/component/p-album-search.vue:120 src/component/p-photo-toolbar.vue:170
|
||||
msgid "List"
|
||||
msgstr "List"
|
||||
|
||||
|
@ -248,7 +248,7 @@ msgstr "More than 20 labels found"
|
|||
msgid "More than 50 photos found"
|
||||
msgstr "More than 50 photos found"
|
||||
|
||||
#: src/component/p-album-search.vue:118 src/component/p-photo-search.vue:168
|
||||
#: src/component/p-album-search.vue:118 src/component/p-photo-toolbar.vue:168
|
||||
msgid "Mosaic"
|
||||
msgstr "Mosaic"
|
||||
|
||||
|
@ -256,7 +256,7 @@ msgstr "Mosaic"
|
|||
msgid "New Album"
|
||||
msgstr "New Album"
|
||||
|
||||
#: src/component/p-album-search.vue:125 src/component/p-photo-search.vue:175
|
||||
#: src/component/p-album-search.vue:125 src/component/p-photo-toolbar.vue:175
|
||||
msgid "Newest first"
|
||||
msgstr "Newest first"
|
||||
|
||||
|
@ -303,7 +303,7 @@ msgstr "Nothing to see here yet. Be patient."
|
|||
msgid "of"
|
||||
msgstr "of"
|
||||
|
||||
#: src/component/p-album-search.vue:126 src/component/p-photo-search.vue:176
|
||||
#: src/component/p-album-search.vue:126 src/component/p-photo-toolbar.vue:176
|
||||
msgid "Oldest first"
|
||||
msgstr "Oldest first"
|
||||
|
||||
|
@ -351,7 +351,7 @@ msgstr "Prev"
|
|||
msgid "Private"
|
||||
msgstr "Private"
|
||||
|
||||
#: src/component/p-album-search.vue:127 src/component/p-photo-search.vue:177
|
||||
#: src/component/p-album-search.vue:127 src/component/p-photo-toolbar.vue:177
|
||||
msgid "Recently imported"
|
||||
msgstr "Recently imported"
|
||||
|
||||
|
@ -367,7 +367,7 @@ msgstr "Remove from album"
|
|||
msgid "Save"
|
||||
msgstr "Save"
|
||||
|
||||
#: src/component/p-album-search.vue:131 src/component/p-photo-search.vue:183
|
||||
#: src/component/p-album-search.vue:131 src/component/p-photo-toolbar.vue:183
|
||||
#: src/pages/albums.vue:162 src/pages/labels.vue:116 src/pages/places.vue:73
|
||||
msgid "Search"
|
||||
msgstr "Search"
|
||||
|
@ -396,7 +396,7 @@ msgstr "Sign in"
|
|||
msgid "Skip existing photos and sidecar files"
|
||||
msgstr "Skip existing photos and sidecar files"
|
||||
|
||||
#: src/component/p-album-search.vue:135 src/component/p-photo-search.vue:187
|
||||
#: src/component/p-album-search.vue:135 src/component/p-photo-toolbar.vue:187
|
||||
msgid "Sort By"
|
||||
msgstr "Sort By"
|
||||
|
||||
|
@ -408,7 +408,7 @@ msgstr "Start"
|
|||
msgid "Story"
|
||||
msgstr "Story"
|
||||
|
||||
#: src/component/p-photo-search.vue:189
|
||||
#: src/component/p-photo-toolbar.vue:189
|
||||
msgid "Taken after"
|
||||
msgstr "Taken after"
|
||||
|
||||
|
@ -416,7 +416,7 @@ msgstr "Taken after"
|
|||
msgid "Taken At"
|
||||
msgstr "Taken At"
|
||||
|
||||
#: src/component/p-photo-search.vue:188
|
||||
#: src/component/p-photo-toolbar.vue:188
|
||||
msgid "Taken before"
|
||||
msgstr "Taken before"
|
||||
|
||||
|
@ -424,7 +424,7 @@ msgstr "Taken before"
|
|||
msgid "Theme"
|
||||
msgstr "Theme"
|
||||
|
||||
#: src/component/p-album-search.vue:117 src/component/p-photo-search.vue:167
|
||||
#: src/component/p-album-search.vue:117 src/component/p-photo-toolbar.vue:167
|
||||
msgid "Tiles"
|
||||
msgstr "Tiles"
|
||||
|
||||
|
@ -493,7 +493,7 @@ msgstr "Uploading photos…"
|
|||
msgid "Vibrant"
|
||||
msgstr "Vibrant"
|
||||
|
||||
#: src/component/p-album-search.vue:132 src/component/p-photo-search.vue:184
|
||||
#: src/component/p-album-search.vue:132 src/component/p-photo-toolbar.vue:184
|
||||
msgid "View"
|
||||
msgstr "View"
|
||||
|
||||
|
|
|
@ -64,11 +64,11 @@ msgstr "Albums verwijderd"
|
|||
msgid "All "
|
||||
msgstr "Alle "
|
||||
|
||||
#: src/component/p-album-search.vue:107 src/component/p-photo-search.vue:157
|
||||
#: src/component/p-album-search.vue:107 src/component/p-photo-toolbar.vue:157
|
||||
msgid "All Cameras"
|
||||
msgstr "Alle camera’s"
|
||||
|
||||
#: src/component/p-album-search.vue:110 src/component/p-photo-search.vue:160
|
||||
#: src/component/p-album-search.vue:110 src/component/p-photo-toolbar.vue:160
|
||||
msgid "All Countries"
|
||||
msgstr "Alle landen"
|
||||
|
||||
|
@ -81,7 +81,7 @@ msgid "Are you sure you want to delete these photos?"
|
|||
msgstr "Weet je zeker dat je deze foto’s wil verwijderen?"
|
||||
|
||||
#: src/component/p-album-search.vue:134 src/component/p-photo-list.vue:53
|
||||
#: src/component/p-photo-search.vue:186
|
||||
#: src/component/p-photo-toolbar.vue:186
|
||||
msgid "Camera"
|
||||
msgstr "Camera"
|
||||
|
||||
|
@ -93,7 +93,7 @@ msgid "Cancel"
|
|||
msgstr "Annuleren"
|
||||
|
||||
#: src/component/p-album-search.vue:133 src/component/p-photo-list.vue:52
|
||||
#: src/component/p-photo-search.vue:185
|
||||
#: src/component/p-photo-toolbar.vue:185
|
||||
msgid "Country"
|
||||
msgstr "Land"
|
||||
|
||||
|
@ -121,7 +121,7 @@ msgstr "Dag"
|
|||
msgid "Delete"
|
||||
msgstr "Verwijder"
|
||||
|
||||
#: src/component/p-album-search.vue:119 src/component/p-photo-search.vue:169
|
||||
#: src/component/p-album-search.vue:119 src/component/p-photo-toolbar.vue:169
|
||||
msgid "Details"
|
||||
msgstr "Details"
|
||||
|
||||
|
@ -204,7 +204,7 @@ msgstr "Taal"
|
|||
msgid "Library"
|
||||
msgstr "Bibliotheek"
|
||||
|
||||
#: src/component/p-album-search.vue:120 src/component/p-photo-search.vue:170
|
||||
#: src/component/p-album-search.vue:120 src/component/p-photo-toolbar.vue:170
|
||||
msgid "List"
|
||||
msgstr "Lijst"
|
||||
|
||||
|
@ -248,7 +248,7 @@ msgstr "Meer dan 20 labels gevonden"
|
|||
msgid "More than 50 photos found"
|
||||
msgstr "Meer dan 50 foto’s gevonden"
|
||||
|
||||
#: src/component/p-album-search.vue:118 src/component/p-photo-search.vue:168
|
||||
#: src/component/p-album-search.vue:118 src/component/p-photo-toolbar.vue:168
|
||||
msgid "Mosaic"
|
||||
msgstr "Mozaiek"
|
||||
|
||||
|
@ -256,7 +256,7 @@ msgstr "Mozaiek"
|
|||
msgid "New Album"
|
||||
msgstr "Nieuw album"
|
||||
|
||||
#: src/component/p-album-search.vue:125 src/component/p-photo-search.vue:175
|
||||
#: src/component/p-album-search.vue:125 src/component/p-photo-toolbar.vue:175
|
||||
msgid "Newest first"
|
||||
msgstr "Nieuwste eerst"
|
||||
|
||||
|
@ -303,7 +303,7 @@ msgstr "Niets te zien hier. Weer geduldig."
|
|||
msgid "of"
|
||||
msgstr "van"
|
||||
|
||||
#: src/component/p-album-search.vue:126 src/component/p-photo-search.vue:176
|
||||
#: src/component/p-album-search.vue:126 src/component/p-photo-toolbar.vue:176
|
||||
msgid "Oldest first"
|
||||
msgstr "Oudste eerst"
|
||||
|
||||
|
@ -351,7 +351,7 @@ msgstr "Vorige"
|
|||
msgid "Private"
|
||||
msgstr "Privé"
|
||||
|
||||
#: src/component/p-album-search.vue:127 src/component/p-photo-search.vue:177
|
||||
#: src/component/p-album-search.vue:127 src/component/p-photo-toolbar.vue:177
|
||||
msgid "Recently imported"
|
||||
msgstr "Recent geïmporteerd"
|
||||
|
||||
|
@ -367,7 +367,7 @@ msgstr "Verwijder uit album"
|
|||
msgid "Save"
|
||||
msgstr "Opslaan"
|
||||
|
||||
#: src/component/p-album-search.vue:131 src/component/p-photo-search.vue:183
|
||||
#: src/component/p-album-search.vue:131 src/component/p-photo-toolbar.vue:183
|
||||
#: src/pages/albums.vue:162 src/pages/labels.vue:116 src/pages/places.vue:73
|
||||
msgid "Search"
|
||||
msgstr "Zoeken"
|
||||
|
@ -396,7 +396,7 @@ msgstr "Inloggen"
|
|||
msgid "Skip existing photos and sidecar files"
|
||||
msgstr "Sla bestaande foto’s en sidecar-bestanden over"
|
||||
|
||||
#: src/component/p-album-search.vue:135 src/component/p-photo-search.vue:187
|
||||
#: src/component/p-album-search.vue:135 src/component/p-photo-toolbar.vue:187
|
||||
msgid "Sort By"
|
||||
msgstr "Sorteer op"
|
||||
|
||||
|
@ -408,7 +408,7 @@ msgstr "Start"
|
|||
msgid "Story"
|
||||
msgstr "Verhaal"
|
||||
|
||||
#: src/component/p-photo-search.vue:189
|
||||
#: src/component/p-photo-toolbar.vue:189
|
||||
msgid "Taken after"
|
||||
msgstr "Genomen na"
|
||||
|
||||
|
@ -416,7 +416,7 @@ msgstr "Genomen na"
|
|||
msgid "Taken At"
|
||||
msgstr "Genomen op"
|
||||
|
||||
#: src/component/p-photo-search.vue:188
|
||||
#: src/component/p-photo-toolbar.vue:188
|
||||
msgid "Taken before"
|
||||
msgstr "Genomen voor"
|
||||
|
||||
|
@ -424,7 +424,7 @@ msgstr "Genomen voor"
|
|||
msgid "Theme"
|
||||
msgstr "Thema"
|
||||
|
||||
#: src/component/p-album-search.vue:117 src/component/p-photo-search.vue:167
|
||||
#: src/component/p-album-search.vue:117 src/component/p-photo-toolbar.vue:167
|
||||
msgid "Tiles"
|
||||
msgstr "Tegels"
|
||||
|
||||
|
@ -493,7 +493,7 @@ msgstr "Foto’s uploaden…"
|
|||
msgid "Vibrant"
|
||||
msgstr "Levendig"
|
||||
|
||||
#: src/component/p-album-search.vue:132 src/component/p-photo-search.vue:184
|
||||
#: src/component/p-album-search.vue:132 src/component/p-photo-toolbar.vue:184
|
||||
msgid "View"
|
||||
msgstr "Weergave"
|
||||
|
||||
|
|
|
@ -68,27 +68,27 @@ msgstr "Альбом удален"
|
|||
msgid "All "
|
||||
msgstr "Все "
|
||||
|
||||
#: src/component/p-album-toolbar.vue:124 src/component/p-photo-search.vue:150
|
||||
#: src/component/p-album-toolbar.vue:124 src/component/p-photo-toolbar.vue:150
|
||||
msgid "All Cameras"
|
||||
msgstr "Все Камеры"
|
||||
|
||||
#: src/component/p-photo-search.vue:153
|
||||
#: src/component/p-photo-toolbar.vue:153
|
||||
msgid "All Categories"
|
||||
msgstr "Все Категории"
|
||||
|
||||
#: src/component/p-photo-search.vue:152
|
||||
#: src/component/p-photo-toolbar.vue:152
|
||||
msgid "All Colors"
|
||||
msgstr "Все Цвета"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:128 src/component/p-photo-search.vue:149
|
||||
#: src/component/p-album-toolbar.vue:128 src/component/p-photo-toolbar.vue:149
|
||||
msgid "All Countries"
|
||||
msgstr "Все Страны"
|
||||
|
||||
#: src/component/p-photo-search.vue:151
|
||||
#: src/component/p-photo-toolbar.vue:151
|
||||
msgid "All Lenses"
|
||||
msgstr "Все Объективы"
|
||||
|
||||
#: src/component/p-photo-search.vue:199
|
||||
#: src/component/p-photo-toolbar.vue:199
|
||||
msgid "All Years"
|
||||
msgstr "Все Года"
|
||||
|
||||
|
@ -111,7 +111,7 @@ msgid "Authentication required"
|
|||
msgstr "Требуется авторизация"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:151 src/component/p-photo-list.vue:53
|
||||
#: src/component/p-photo-search.vue:171 src/dialog/photo-edit/meta.vue:311
|
||||
#: src/component/p-photo-toolbar.vue:171 src/dialog/photo-edit/meta.vue:311
|
||||
#: src/dialog/photo-edit/todo.vue:34
|
||||
msgid "Camera"
|
||||
msgstr "Камера"
|
||||
|
@ -123,7 +123,7 @@ msgstr "Камера"
|
|||
msgid "Cancel"
|
||||
msgstr "Отменить"
|
||||
|
||||
#: src/component/p-photo-search.vue:175 src/dialog/photo-edit/meta.vue:315
|
||||
#: src/component/p-photo-toolbar.vue:175 src/dialog/photo-edit/meta.vue:315
|
||||
#: src/dialog/photo-edit/todo.vue:38
|
||||
msgid "Category"
|
||||
msgstr "Категория"
|
||||
|
@ -132,7 +132,7 @@ msgstr "Категория"
|
|||
msgid "Close"
|
||||
msgstr "Закрыть"
|
||||
|
||||
#: src/component/p-photo-search.vue:174 src/dialog/photo-edit/meta.vue:314
|
||||
#: src/component/p-photo-toolbar.vue:174 src/dialog/photo-edit/meta.vue:314
|
||||
#: src/dialog/photo-edit/todo.vue:37
|
||||
msgid "Color"
|
||||
msgstr "Цвет"
|
||||
|
@ -145,7 +145,7 @@ msgstr "Цвета"
|
|||
msgid "Convert RAW to JPEG"
|
||||
msgstr "Конвертировать RAW в JPEG"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:150 src/component/p-photo-search.vue:170
|
||||
#: src/component/p-album-toolbar.vue:150 src/component/p-photo-toolbar.vue:170
|
||||
#: src/dialog/photo-edit/meta.vue:310 src/dialog/photo-edit/todo.vue:33
|
||||
msgid "Country"
|
||||
msgstr "Страна"
|
||||
|
@ -171,7 +171,7 @@ msgstr "День"
|
|||
msgid "Delete"
|
||||
msgstr "Удалить"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:136 src/component/p-photo-search.vue:158
|
||||
#: src/component/p-album-toolbar.vue:136 src/component/p-photo-toolbar.vue:158
|
||||
#: src/dialog/p-photo-edit-dialog.vue:32
|
||||
msgid "Details"
|
||||
msgstr "Подробно"
|
||||
|
@ -255,7 +255,7 @@ msgstr "Метки"
|
|||
msgid "Language"
|
||||
msgstr "Язык"
|
||||
|
||||
#: src/component/p-photo-search.vue:172 src/dialog/photo-edit/meta.vue:312
|
||||
#: src/component/p-photo-toolbar.vue:172 src/dialog/photo-edit/meta.vue:312
|
||||
#: src/dialog/photo-edit/todo.vue:35
|
||||
msgid "Lens"
|
||||
msgstr "Объектив"
|
||||
|
@ -264,7 +264,7 @@ msgstr "Объектив"
|
|||
msgid "Library"
|
||||
msgstr "Библиотека"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:137 src/component/p-photo-search.vue:159
|
||||
#: src/component/p-album-toolbar.vue:137 src/component/p-photo-toolbar.vue:159
|
||||
msgid "List"
|
||||
msgstr "Список"
|
||||
|
||||
|
@ -308,7 +308,7 @@ msgstr "Более 20 меток найдено"
|
|||
msgid "More than 50 photos found"
|
||||
msgstr "Более 50 фотографий найдено"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:135 src/component/p-photo-search.vue:157
|
||||
#: src/component/p-album-toolbar.vue:135 src/component/p-photo-toolbar.vue:157
|
||||
msgid "Mosaic"
|
||||
msgstr "Мозайка"
|
||||
|
||||
|
@ -316,7 +316,7 @@ msgstr "Мозайка"
|
|||
msgid "Name"
|
||||
msgstr "Название"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:143 src/component/p-photo-search.vue:163
|
||||
#: src/component/p-album-toolbar.vue:143 src/component/p-photo-toolbar.vue:163
|
||||
msgid "Newest first"
|
||||
msgstr "Сначала новые"
|
||||
|
||||
|
@ -354,7 +354,7 @@ msgstr "Еще не реализованно"
|
|||
msgid "Nothing to see here yet. Be patient."
|
||||
msgstr "Пока что тут нечего смотреть. Будьте терпеливы."
|
||||
|
||||
#: src/component/p-album-toolbar.vue:144 src/component/p-photo-search.vue:164
|
||||
#: src/component/p-album-toolbar.vue:144 src/component/p-photo-toolbar.vue:164
|
||||
msgid "Oldest first"
|
||||
msgstr "Сначала старые"
|
||||
|
||||
|
@ -414,7 +414,7 @@ msgstr "Приоритет"
|
|||
msgid "Random"
|
||||
msgstr "Случайно"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:142 src/component/p-photo-search.vue:162
|
||||
#: src/component/p-album-toolbar.vue:142 src/component/p-photo-toolbar.vue:162
|
||||
msgid "Recently imported"
|
||||
msgstr "Недавно добавленные"
|
||||
|
||||
|
@ -438,7 +438,7 @@ msgstr "Восстановить"
|
|||
msgid "Save"
|
||||
msgstr "Сохранить"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:148 src/component/p-photo-search.vue:168
|
||||
#: src/component/p-album-toolbar.vue:148 src/component/p-photo-toolbar.vue:168
|
||||
#: src/dialog/photo-edit/meta.vue:308 src/dialog/photo-edit/todo.vue:31
|
||||
#: src/pages/albums.vue:175 src/pages/labels.vue:134 src/pages/places.vue:63
|
||||
msgid "Search"
|
||||
|
@ -480,7 +480,7 @@ msgstr "Похожие"
|
|||
msgid "Skip unchanged files"
|
||||
msgstr "Пропустить не измененные файлы"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:152 src/component/p-photo-search.vue:176
|
||||
#: src/component/p-album-toolbar.vue:152 src/component/p-photo-toolbar.vue:176
|
||||
#: src/dialog/photo-edit/meta.vue:316 src/dialog/photo-edit/todo.vue:39
|
||||
msgid "Sort By"
|
||||
msgstr "Сортировать по"
|
||||
|
@ -489,7 +489,7 @@ msgstr "Сортировать по"
|
|||
msgid "Story"
|
||||
msgstr "История"
|
||||
|
||||
#: src/component/p-photo-search.vue:178 src/dialog/photo-edit/meta.vue:318
|
||||
#: src/component/p-photo-toolbar.vue:178 src/dialog/photo-edit/meta.vue:318
|
||||
#: src/dialog/photo-edit/todo.vue:41
|
||||
msgid "Taken after"
|
||||
msgstr "Снято после"
|
||||
|
@ -498,7 +498,7 @@ msgstr "Снято после"
|
|||
msgid "Taken At"
|
||||
msgstr "Дата съемки"
|
||||
|
||||
#: src/component/p-photo-search.vue:177 src/dialog/photo-edit/meta.vue:317
|
||||
#: src/component/p-photo-toolbar.vue:177 src/dialog/photo-edit/meta.vue:317
|
||||
#: src/dialog/photo-edit/todo.vue:40
|
||||
msgid "Taken before"
|
||||
msgstr "Снято до"
|
||||
|
@ -569,7 +569,7 @@ msgstr "Загрузка фотографий..."
|
|||
msgid "Vibrant"
|
||||
msgstr "Вибрант"
|
||||
|
||||
#: src/component/p-album-toolbar.vue:149 src/component/p-photo-search.vue:169
|
||||
#: src/component/p-album-toolbar.vue:149 src/component/p-photo-toolbar.vue:169
|
||||
#: src/dialog/photo-edit/meta.vue:309 src/dialog/photo-edit/todo.vue:32
|
||||
msgid "View"
|
||||
msgstr "Обзор"
|
||||
|
@ -582,7 +582,7 @@ msgstr "Неделя"
|
|||
msgid "Width"
|
||||
msgstr "Ширина"
|
||||
|
||||
#: src/component/p-photo-search.vue:173 src/dialog/photo-edit/meta.vue:313
|
||||
#: src/component/p-photo-toolbar.vue:173 src/dialog/photo-edit/meta.vue:313
|
||||
#: src/dialog/photo-edit/todo.vue:36
|
||||
msgid "Year"
|
||||
msgstr "Год"
|
||||
|
|
|
@ -88,28 +88,28 @@ msgid "All "
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:136
|
||||
#: src/component/p-photo-search.vue:157
|
||||
#: src/component/p-photo-toolbar.vue:157
|
||||
msgid "All Cameras"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:160
|
||||
#: src/component/p-photo-toolbar.vue:160
|
||||
msgid "All Categories"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:159
|
||||
#: src/component/p-photo-toolbar.vue:159
|
||||
msgid "All Colors"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:140
|
||||
#: src/component/p-photo-search.vue:156
|
||||
#: src/component/p-photo-toolbar.vue:156
|
||||
msgid "All Countries"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:158
|
||||
#: src/component/p-photo-toolbar.vue:158
|
||||
msgid "All Lenses"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:208
|
||||
#: src/component/p-photo-toolbar.vue:208
|
||||
msgid "All Years"
|
||||
msgstr ""
|
||||
|
||||
|
@ -158,7 +158,7 @@ msgstr ""
|
|||
|
||||
#: src/component/p-album-toolbar.vue:166
|
||||
#: src/component/p-photo-list.vue:92
|
||||
#: src/component/p-photo-search.vue:180
|
||||
#: src/component/p-photo-toolbar.vue:180
|
||||
#: src/dialog/photo/details.vue:409
|
||||
msgid "Camera"
|
||||
msgstr ""
|
||||
|
@ -177,11 +177,11 @@ msgid "Cancel"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:148
|
||||
#: src/component/p-photo-search.vue:165
|
||||
#: src/component/p-photo-toolbar.vue:165
|
||||
msgid "Cards"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:184
|
||||
#: src/component/p-photo-toolbar.vue:184
|
||||
#: src/dialog/photo/details.vue:413
|
||||
msgid "Category"
|
||||
msgstr ""
|
||||
|
@ -198,7 +198,7 @@ msgstr ""
|
|||
msgid "Close"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:183
|
||||
#: src/component/p-photo-toolbar.vue:183
|
||||
#: src/dialog/photo/details.vue:412
|
||||
msgid "Color"
|
||||
msgstr ""
|
||||
|
@ -226,7 +226,7 @@ msgid "Convert RAW files"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:165
|
||||
#: src/component/p-photo-search.vue:179
|
||||
#: src/component/p-photo-toolbar.vue:179
|
||||
#: src/dialog/photo/details.vue:408
|
||||
msgid "Country"
|
||||
msgstr ""
|
||||
|
@ -365,7 +365,7 @@ msgid "General"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:157
|
||||
#: src/component/p-photo-search.vue:172
|
||||
#: src/component/p-photo-toolbar.vue:172
|
||||
msgid "Group by similarity"
|
||||
msgstr ""
|
||||
|
||||
|
@ -453,7 +453,7 @@ msgstr ""
|
|||
msgid "Language"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:181
|
||||
#: src/component/p-photo-toolbar.vue:181
|
||||
#: src/dialog/photo/details.vue:410
|
||||
msgid "Lens"
|
||||
msgstr ""
|
||||
|
@ -464,7 +464,7 @@ msgid "Library"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:149
|
||||
#: src/component/p-photo-search.vue:166
|
||||
#: src/component/p-photo-toolbar.vue:166
|
||||
msgid "List"
|
||||
msgstr ""
|
||||
|
||||
|
@ -516,12 +516,12 @@ msgid "More than 50 photos found"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:147
|
||||
#: src/component/p-photo-search.vue:164
|
||||
#: src/component/p-photo-toolbar.vue:164
|
||||
msgid "Mosaic"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:158
|
||||
#: src/component/p-photo-search.vue:173
|
||||
#: src/component/p-photo-toolbar.vue:173
|
||||
msgid "Most relevant"
|
||||
msgstr ""
|
||||
|
||||
|
@ -546,7 +546,7 @@ msgid "Never"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:155
|
||||
#: src/component/p-photo-search.vue:170
|
||||
#: src/component/p-photo-toolbar.vue:170
|
||||
msgid "Newest first"
|
||||
msgstr ""
|
||||
|
||||
|
@ -602,7 +602,7 @@ msgid "Nothing to see here yet. Be patient."
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:156
|
||||
#: src/component/p-photo-search.vue:171
|
||||
#: src/component/p-photo-toolbar.vue:171
|
||||
msgid "Oldest first"
|
||||
msgstr ""
|
||||
|
||||
|
@ -701,7 +701,7 @@ msgid "Re-index all originals, including already indexed and unchanged files."
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:154
|
||||
#: src/component/p-photo-search.vue:169
|
||||
#: src/component/p-photo-toolbar.vue:169
|
||||
msgid "Recently imported"
|
||||
msgstr ""
|
||||
|
||||
|
@ -745,7 +745,7 @@ msgid "Save"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:163
|
||||
#: src/component/p-photo-search.vue:177
|
||||
#: src/component/p-photo-toolbar.vue:177
|
||||
#: src/dialog/photo/details.vue:406
|
||||
#: src/dialog/photo/labels.vue:111
|
||||
#: src/pages/albums.vue:183
|
||||
|
@ -802,7 +802,7 @@ msgid "Size"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:167
|
||||
#: src/component/p-photo-search.vue:185
|
||||
#: src/component/p-photo-toolbar.vue:185
|
||||
#: src/dialog/photo/details.vue:414
|
||||
msgid "Sort By"
|
||||
msgstr ""
|
||||
|
@ -843,12 +843,12 @@ msgstr ""
|
|||
msgid "Taken"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:187
|
||||
#: src/component/p-photo-toolbar.vue:187
|
||||
#: src/dialog/photo/details.vue:416
|
||||
msgid "Taken after"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:186
|
||||
#: src/component/p-photo-toolbar.vue:186
|
||||
#: src/dialog/photo/details.vue:415
|
||||
msgid "Taken before"
|
||||
msgstr ""
|
||||
|
@ -935,7 +935,7 @@ msgid "Video"
|
|||
msgstr ""
|
||||
|
||||
#: src/component/p-album-toolbar.vue:164
|
||||
#: src/component/p-photo-search.vue:178
|
||||
#: src/component/p-photo-toolbar.vue:178
|
||||
#: src/dialog/photo/details.vue:407
|
||||
msgid "View"
|
||||
msgstr ""
|
||||
|
@ -944,7 +944,7 @@ msgstr ""
|
|||
msgid "Week"
|
||||
msgstr ""
|
||||
|
||||
#: src/component/p-photo-search.vue:182
|
||||
#: src/component/p-photo-toolbar.vue:182
|
||||
#: src/dialog/photo/details.vue:411
|
||||
msgid "Year"
|
||||
msgstr ""
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
//search + filters?
|
||||
|
||||
//clipboard?
|
||||
|
||||
//views ?
|
||||
|
||||
//clipboard yes!
|
||||
|
||||
//create + delete album
|
||||
|
||||
//Add description to album (Update)
|
||||
|
|
|
@ -2,4 +2,4 @@
|
|||
|
||||
//download files
|
||||
|
||||
//clipboard
|
||||
//clipboard yes
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
//views?
|
||||
|
||||
//clipboard?
|
||||
//clipboard yes
|
||||
|
||||
//show only high prio labels + check labels exist
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
//upload file + import
|
||||
//upload file + import from library and from navigation + delete later
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
import { Selector } from 'testcafe';
|
||||
import testcafeconfig from './testcafeconfig';
|
||||
import Page from "./page-model";
|
||||
import { RequestLogger } from 'testcafe';
|
||||
|
||||
const logger = RequestLogger( /http:\/\/localhost:2342\/api\/v1\/photos*/ , {
|
||||
logResponseHeaders: true,
|
||||
logResponseBody: true
|
||||
});
|
||||
|
||||
fixture`Test favorites page`
|
||||
.page `localhost:2342/favorites`
|
||||
.requestHooks(logger);
|
||||
|
||||
const page = new Page();
|
||||
|
||||
test('Like photo', async t => {
|
||||
|
||||
|
||||
}),
|
||||
|
||||
test('Dislike photo', async t => {
|
||||
|
||||
const FavoritesCount = await Selector('.t-like.t-on').count;
|
||||
await t
|
||||
.click(Selector('.t-like.t-on'));
|
||||
logger.clear();
|
||||
await t.navigateTo("../favorites");
|
||||
const request3 = await logger.requests[0].responseBody;
|
||||
|
||||
const FavoritesCountAfterDislike = await Selector('.t-like.t-on').count;
|
||||
await t
|
||||
.expect(FavoritesCountAfterDislike).eql(FavoritesCount - 1);
|
||||
});
|
|
@ -1,22 +0,0 @@
|
|||
import { Selector } from 'testcafe';
|
||||
import testcafeconfig from '../testcafeconfig';
|
||||
import Page from "../page-model";
|
||||
import { RequestLogger } from 'testcafe';
|
||||
|
||||
const logger = RequestLogger( /http:\/\/localhost:2342\/api\/v1\*/ , {
|
||||
logResponseHeaders: true,
|
||||
logResponseBody: true
|
||||
});
|
||||
|
||||
fixture`Library page`
|
||||
.page`localhost:2342/library`
|
||||
.requestHooks(logger);
|
||||
|
||||
const page = new Page();
|
||||
|
||||
|
||||
test('Upload image', async t => {
|
||||
await t
|
||||
//.click(Selector(button).withText('Upload'));
|
||||
|
||||
});
|
|
@ -1,23 +0,0 @@
|
|||
import { Selector } from 'testcafe';
|
||||
import testcafeconfig from '../testcafeconfig';
|
||||
import Page from "../page-model";
|
||||
|
||||
fixture`Use navigation`
|
||||
.page`${testcafeconfig.url}`;
|
||||
|
||||
const page = new Page();
|
||||
|
||||
test('Navigate', async t => {
|
||||
await page.openNav();
|
||||
await t
|
||||
.click('a[href="/albums"]')
|
||||
.expect(Selector('div.p-page-albums').exists, {timeout: 5000}).ok();
|
||||
await page.openNav();
|
||||
await t
|
||||
.click('a[href="/places"]')
|
||||
.expect(Selector('#map').exists).ok();
|
||||
await page.openNav();
|
||||
await t
|
||||
.click('a[href="/labels"]')
|
||||
.expect(Selector('div.p-page-labels').exists, {timeout: 5000}).ok();
|
||||
});
|
|
@ -1,35 +0,0 @@
|
|||
import { Selector } from 'testcafe';
|
||||
import testcafeconfig from '../testcafeconfig';
|
||||
import Page from "../page-model";
|
||||
|
||||
fixture`Test clipboard`
|
||||
.page`${testcafeconfig.url}`;
|
||||
|
||||
const page = new Page();
|
||||
|
||||
test('Test selecting photos and clear clipboard', async t => {
|
||||
const clipboardCount = await Selector('span.t-clipboard-count');
|
||||
|
||||
await page.selectNthPhoto(0);
|
||||
await page.selectNthPhoto(2);
|
||||
|
||||
await t
|
||||
.expect(clipboardCount.textContent).eql("2");
|
||||
await page.unselectPhoto(0);
|
||||
|
||||
await t
|
||||
.expect(clipboardCount.textContent).eql("1")
|
||||
|
||||
await page.openNav();
|
||||
await t
|
||||
.click('a[href="/labels"]')
|
||||
.expect(Selector('main .p-page-labels').exists, {timeout: 5000}).ok();
|
||||
await page.openNav();
|
||||
await t
|
||||
.click('a[href="/photos"]')
|
||||
.expect(clipboardCount.textContent).eql("1")
|
||||
.click(Selector('div.p-photo-clipboard'))
|
||||
.click(Selector('.p-photo-clipboard-clear'), {timeout: 15000});
|
||||
|
||||
await t.expect(Selector('#t-clipboard').exists).eql(false);
|
||||
});
|
|
@ -1,19 +0,0 @@
|
|||
import { Selector } from 'testcafe';
|
||||
import { ClientFunction } from 'testcafe';
|
||||
|
||||
const getLocation = ClientFunction(() => document.location.href);
|
||||
|
||||
fixture`Test places page`
|
||||
.page `localhost:2342/places`;
|
||||
|
||||
test('Test places', async t => {
|
||||
await t
|
||||
.expect(Selector('#map').exists, {timeout: 15000}).ok()
|
||||
.expect(Selector('div.p-map-control').visible).ok();
|
||||
await t
|
||||
.typeText(Selector('input[aria-label="Search"]'), 'Berlin')
|
||||
.pressKey('enter');
|
||||
await t
|
||||
.expect(Selector('div.p-map-control').visible).ok()
|
||||
.expect(getLocation()).contains('Berlin');
|
||||
});
|
|
@ -1,26 +0,0 @@
|
|||
import { Selector } from 'testcafe';
|
||||
import testcafeconfig from '../testcafeconfig';
|
||||
import Page from "../page-model";
|
||||
import { ClientFunction } from 'testcafe';
|
||||
|
||||
fixture`Scroll to top`
|
||||
.page`${testcafeconfig.url}`;
|
||||
|
||||
const page = new Page();
|
||||
const scroll = ClientFunction((x, y) => window.scrollTo(x, y));
|
||||
const getcurrentPosition = ClientFunction(() => window.pageYOffset);
|
||||
|
||||
test('Test scroll to top functionality', async t => {
|
||||
await t
|
||||
.expect(Selector('button.p-photo-scroll-top').exists).notOk()
|
||||
.expect(getcurrentPosition()).eql(0)
|
||||
.expect(Selector('div[class="v-image__image v-image__image--cover"]').nth(0).visible).ok();
|
||||
await scroll(0, 1200);
|
||||
await t
|
||||
.expect(getcurrentPosition()).eql(1200);
|
||||
await scroll(0, 900);
|
||||
await t
|
||||
.expect(getcurrentPosition()).eql(900)
|
||||
.click(Selector('button.p-photo-scroll-top'))
|
||||
.expect(getcurrentPosition()).eql(0);
|
||||
});
|
|
@ -272,27 +272,19 @@ func AddPhotosToAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
var added []entity.PhotoAlbum
|
||||
added := a.AddPhotos(photos.UIDs())
|
||||
|
||||
for _, p := range photos {
|
||||
pa := entity.PhotoAlbum{AlbumUID: a.AlbumUID, PhotoUID: p.PhotoUID, Hidden: false}
|
||||
|
||||
if err := pa.Save(); err != nil {
|
||||
log.Errorf("album: %s", err.Error())
|
||||
if len(added) > 0 {
|
||||
if len(added) == 1 {
|
||||
event.Success(fmt.Sprintf("one entry added to %s", txt.Quote(a.Title())))
|
||||
} else {
|
||||
added = append(added, pa)
|
||||
event.Success(fmt.Sprintf("%d entries added to %s", len(added), txt.Quote(a.Title())))
|
||||
}
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
}
|
||||
|
||||
if len(added) == 1 {
|
||||
event.Success(fmt.Sprintf("one photo added to %s", txt.Quote(a.AlbumTitle)))
|
||||
} else {
|
||||
event.Success(fmt.Sprintf("%d photos added to %s", len(added), txt.Quote(a.AlbumTitle)))
|
||||
}
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "photos added to album", "album": a, "added": added})
|
||||
c.JSON(http.StatusOK, gin.H{"message": "photos added to album", "album": a, "photos": photos.UIDs(), "added": added})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -324,18 +316,19 @@ func RemovePhotosFromAlbum(router *gin.RouterGroup, conf *config.Config) {
|
|||
return
|
||||
}
|
||||
|
||||
for _, photoUID := range f.Photos {
|
||||
pa := entity.PhotoAlbum{AlbumUID: a.AlbumUID, PhotoUID: photoUID, Hidden: true}
|
||||
logError("album", pa.Save())
|
||||
removed := a.RemovePhotos(f.Photos)
|
||||
|
||||
if len(removed) > 0 {
|
||||
if len(removed) == 1 {
|
||||
event.Success(fmt.Sprintf("one entry removed from %s", txt.Quote(a.Title())))
|
||||
} else {
|
||||
event.Success(fmt.Sprintf("%d entries removed from %s", len(removed), txt.Quote(txt.Quote(a.Title()))))
|
||||
}
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
}
|
||||
|
||||
// affected := entity.Db().Model(entity.PhotoAlbum{}).Where("album_uid = ? AND photo_uid IN (?)", a.AlbumUID, f.Photos).UpdateColumn("Hidden", true).RowsAffected
|
||||
|
||||
event.Success(fmt.Sprintf("entries removed from %s", a.AlbumTitle))
|
||||
|
||||
PublishAlbumEvent(EntityUpdated, a.AlbumUID, c)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "entries removed from album", "album": a, "photos": f.Photos})
|
||||
c.JSON(http.StatusOK, gin.H{"message": "entries removed from album", "album": a, "photos": f.Photos, "removed": removed})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -65,6 +65,11 @@ func StartImport(router *gin.RouterGroup, conf *config.Config) {
|
|||
opt = photoprism.ImportOptionsCopy(path)
|
||||
}
|
||||
|
||||
if len(f.Albums) > 0 {
|
||||
log.Debugf("import: files will be added to album %s", strings.Join(f.Albums, " and "))
|
||||
opt.Albums = f.Albums
|
||||
}
|
||||
|
||||
imp.Start(opt)
|
||||
|
||||
if subPath != "" && path != conf.ImportPath() && fs.IsEmpty(path) {
|
||||
|
@ -81,6 +86,10 @@ func StartImport(router *gin.RouterGroup, conf *config.Config) {
|
|||
event.Publish("import.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
event.Publish("index.completed", event.Data{"path": path, "seconds": elapsed})
|
||||
|
||||
for _, uid := range f.Albums {
|
||||
PublishAlbumEvent(EntityUpdated, uid, c)
|
||||
}
|
||||
|
||||
UpdateClientConfig(conf)
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": fmt.Sprintf("import completed in %d s", elapsed)})
|
||||
|
|
|
@ -201,7 +201,7 @@ func (c *Config) ClientConfig() ClientConfig {
|
|||
Select("MAX(photo_count) as label_max_photos, COUNT(*) AS labels").
|
||||
Where("photo_count > 0").
|
||||
Where("deleted_at IS NULL").
|
||||
Where("(label_priority >= 0 || label_favorite = 1)").
|
||||
Where("(label_priority >= 0 OR label_favorite = 1)").
|
||||
Take(&result.Count)
|
||||
|
||||
c.Db().Table("albums").
|
||||
|
|
|
@ -19,6 +19,8 @@ const (
|
|||
AccountSyncStatusSynced = "synced"
|
||||
)
|
||||
|
||||
type Accounts []Account
|
||||
|
||||
// Account represents a remote service account for uploading, downloading or syncing media files.
|
||||
type Account struct {
|
||||
ID uint `gorm:"primary_key"`
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
|
@ -13,6 +14,8 @@ import (
|
|||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
type Albums []Album
|
||||
|
||||
// Album represents a photo album
|
||||
type Album struct {
|
||||
ID uint `gorm:"primary_key" json:"ID" yaml:"-"`
|
||||
|
@ -40,13 +43,49 @@ type Album struct {
|
|||
DeletedAt *time.Time `sql:"index" json:"-" yaml:"-"`
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Album) BeforeCreate(scope *gorm.Scope) error {
|
||||
if rnd.IsUID(m.AlbumUID, 'a') {
|
||||
// AddPhotoToAlbums adds a photo UID to multiple albums and automatically creates them if needed.
|
||||
func AddPhotoToAlbums(photo string, albums []string) (err error) {
|
||||
if photo == "" || len(albums) == 0 {
|
||||
// Do nothing.
|
||||
return nil
|
||||
}
|
||||
|
||||
return scope.SetColumn("AlbumUID", rnd.PPID('a'))
|
||||
if !rnd.IsPPID(photo, 'p') {
|
||||
return fmt.Errorf("album: invalid photo uid %s", photo)
|
||||
}
|
||||
|
||||
for _, album := range albums {
|
||||
var aUID string
|
||||
|
||||
if album == "" {
|
||||
log.Debugf("album: empty album identifier while adding photo %s", photo)
|
||||
continue
|
||||
}
|
||||
|
||||
if rnd.IsPPID(album, 'a') {
|
||||
aUID = album
|
||||
} else {
|
||||
a := NewAlbum(album, TypeAlbum)
|
||||
|
||||
if err = a.Find(); err == nil {
|
||||
aUID = a.AlbumUID
|
||||
} else if err = a.Create(); err == nil {
|
||||
aUID = a.AlbumUID
|
||||
} else {
|
||||
log.Errorf("album: %s (add photo %s to albums)", err.Error(), photo)
|
||||
}
|
||||
}
|
||||
|
||||
if aUID != "" {
|
||||
entry := PhotoAlbum{AlbumUID: aUID, PhotoUID: photo, Hidden: false}
|
||||
|
||||
if err = entry.Save(); err != nil {
|
||||
log.Errorf("album: %s (add photo %s to albums)", err.Error(), photo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// NewAlbum creates a new album; default name is current month and year
|
||||
|
@ -58,7 +97,6 @@ func NewAlbum(albumTitle, albumType string) *Album {
|
|||
}
|
||||
|
||||
result := &Album{
|
||||
AlbumUID: rnd.PPID('a'),
|
||||
AlbumOrder: SortOrderOldest,
|
||||
AlbumType: albumType,
|
||||
CreatedAt: now,
|
||||
|
@ -79,7 +117,6 @@ func NewFolderAlbum(albumTitle, albumSlug, albumFilter string) *Album {
|
|||
now := time.Now().UTC()
|
||||
|
||||
result := &Album{
|
||||
AlbumUID: rnd.PPID('a'),
|
||||
AlbumOrder: SortOrderOldest,
|
||||
AlbumType: TypeFolder,
|
||||
AlbumTitle: albumTitle,
|
||||
|
@ -101,7 +138,6 @@ func NewMomentsAlbum(albumTitle, albumSlug, albumFilter string) *Album {
|
|||
now := time.Now().UTC()
|
||||
|
||||
result := &Album{
|
||||
AlbumUID: rnd.PPID('a'),
|
||||
AlbumOrder: SortOrderOldest,
|
||||
AlbumType: TypeMoment,
|
||||
AlbumTitle: albumTitle,
|
||||
|
@ -128,7 +164,6 @@ func NewMonthAlbum(albumTitle, albumSlug string, year, month int) *Album {
|
|||
now := time.Now().UTC()
|
||||
|
||||
result := &Album{
|
||||
AlbumUID: rnd.PPID('a'),
|
||||
AlbumOrder: SortOrderOldest,
|
||||
AlbumType: TypeMonth,
|
||||
AlbumTitle: albumTitle,
|
||||
|
@ -143,6 +178,59 @@ func NewMonthAlbum(albumTitle, albumSlug string, year, month int) *Album {
|
|||
return result
|
||||
}
|
||||
|
||||
// FindAlbumBySlug finds a matching album or returns nil.
|
||||
func FindAlbumBySlug(slug, albumType string) *Album {
|
||||
result := Album{}
|
||||
|
||||
if err := UnscopedDb().Where("album_slug = ? AND album_type = ?", slug, albumType).First(&result).Error; err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return &result
|
||||
}
|
||||
|
||||
// Find updates the entity with values from the database.
|
||||
func (m *Album) Find() error {
|
||||
if rnd.IsPPID(m.AlbumUID, 'a') {
|
||||
log.Debugf("IS PPID: %s", m.AlbumUID)
|
||||
if err := UnscopedDb().First(m, "album_uid = ?", m.AlbumUID).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := UnscopedDb().First(m, "album_slug = ? AND album_type = ?", m.AlbumSlug, m.AlbumType).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// BeforeCreate creates a random UID if needed before inserting a new row to the database.
|
||||
func (m *Album) BeforeCreate(scope *gorm.Scope) error {
|
||||
if rnd.IsUID(m.AlbumUID, 'a') {
|
||||
return nil
|
||||
}
|
||||
|
||||
return scope.SetColumn("AlbumUID", rnd.PPID('a'))
|
||||
}
|
||||
|
||||
// String returns the id or name as string.
|
||||
func (m *Album) String() string {
|
||||
if m.AlbumSlug != "" {
|
||||
return m.AlbumSlug
|
||||
}
|
||||
|
||||
if m.AlbumTitle != "" {
|
||||
return txt.Quote(m.AlbumTitle)
|
||||
}
|
||||
|
||||
if m.AlbumUID != "" {
|
||||
return m.AlbumUID
|
||||
}
|
||||
|
||||
return "[unknown album]"
|
||||
}
|
||||
|
||||
// Checks if the album is of type moment.
|
||||
func (m *Album) IsMoment() bool {
|
||||
return m.AlbumType == TypeMoment
|
||||
|
@ -213,13 +301,37 @@ func (m *Album) Create() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// FindAlbum finds a matching album or returns nil.
|
||||
func FindAlbum(slug, albumType string) *Album {
|
||||
result := Album{}
|
||||
// Returns the album title.
|
||||
func (m *Album) Title() string {
|
||||
return m.AlbumTitle
|
||||
}
|
||||
|
||||
if err := UnscopedDb().Where("album_slug = ? AND album_type = ?", slug, albumType).First(&result).Error; err != nil {
|
||||
return nil
|
||||
// AddPhotos adds photos to an existing album.
|
||||
func (m *Album) AddPhotos(UIDs []string) (added []PhotoAlbum) {
|
||||
for _, uid := range UIDs {
|
||||
entry := PhotoAlbum{AlbumUID: m.AlbumUID, PhotoUID: uid, Hidden: false}
|
||||
|
||||
if err := entry.Save(); err != nil {
|
||||
log.Errorf("album: %s (add to album %s)", err.Error(), m)
|
||||
} else {
|
||||
added = append(added, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return &result
|
||||
return added
|
||||
}
|
||||
|
||||
// RemovePhotos removes photos from an album.
|
||||
func (m *Album) RemovePhotos(UIDs []string) (removed []PhotoAlbum) {
|
||||
for _, uid := range UIDs {
|
||||
entry := PhotoAlbum{AlbumUID: m.AlbumUID, PhotoUID: uid, Hidden: true}
|
||||
|
||||
if err := entry.Save(); err != nil {
|
||||
log.Errorf("album: %s (remove from album %s)", err.Error(), m)
|
||||
} else {
|
||||
removed = append(removed, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return removed
|
||||
}
|
||||
|
|
|
@ -92,6 +92,24 @@ var AlbumFixtures = AlbumMap{
|
|||
UpdatedAt: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
DeletedAt: nil,
|
||||
},
|
||||
"import": {
|
||||
ID: 1000004,
|
||||
CoverUID: "",
|
||||
AlbumUID: "at6axuzitogaaiax",
|
||||
AlbumSlug: "import",
|
||||
AlbumType: TypeAlbum,
|
||||
AlbumTitle: "Import Album",
|
||||
AlbumDescription: "",
|
||||
AlbumNotes: "",
|
||||
AlbumOrder: "name",
|
||||
AlbumTemplate: "",
|
||||
AlbumFilter: "",
|
||||
AlbumFavorite: false,
|
||||
Links: []Link{},
|
||||
CreatedAt: time.Date(2020, 1, 1, 0, 0, 0, 0, time.UTC),
|
||||
UpdatedAt: time.Date(2020, 2, 1, 0, 0, 0, 0, time.UTC),
|
||||
DeletedAt: nil,
|
||||
},
|
||||
}
|
||||
|
||||
// CreateAlbumFixtures inserts known entities into the database for testing.
|
||||
|
|
|
@ -89,3 +89,31 @@ func TestAlbum_Save(t *testing.T) {
|
|||
})
|
||||
|
||||
}
|
||||
|
||||
func TestAddPhotoToAlbums(t *testing.T) {
|
||||
t.Run("success", func(t *testing.T) {
|
||||
err := AddPhotoToAlbums("pt9jtxrexxvl0yh0", []string{"at6axuzitogaaiax"})
|
||||
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
a := Album{AlbumUID: "at6axuzitogaaiax"}
|
||||
|
||||
if err := a.Find(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
var entries []PhotoAlbum
|
||||
|
||||
if err := Db().Where("album_uid = ? AND photo_uid = ?", "at6axuzitogaaiax", "pt9jtxrexxvl0yh0").Find(&entries).Error; err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
if len(entries) < 1 {
|
||||
t.Error("at least one album entry expected")
|
||||
}
|
||||
|
||||
// t.Logf("photo album entries: %+v", entries)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -41,4 +41,8 @@ const (
|
|||
RootDefault = ""
|
||||
RootImport = "import"
|
||||
RootPath = "/"
|
||||
|
||||
Updated = "updated"
|
||||
Created = "created"
|
||||
Deleted = "deleted"
|
||||
)
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
type Files []File
|
||||
|
||||
// File represents an image or sidecar file that belongs to a photo.
|
||||
type File struct {
|
||||
ID uint `gorm:"primary_key" json:"-" yaml:"-"`
|
||||
|
|
|
@ -15,6 +15,8 @@ import (
|
|||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
type Folders []Folder
|
||||
|
||||
// Folder represents a file system directory.
|
||||
type Folder struct {
|
||||
Path string `gorm:"type:varbinary(255);unique_index:idx_folders_path_root;" json:"Path" yaml:"Path"`
|
||||
|
|
|
@ -17,6 +17,20 @@ import (
|
|||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
// Default photo result slice for simple use cases.
|
||||
type Photos []Photo
|
||||
|
||||
// UIDs returns a slice of unique photo IDs.
|
||||
func (m Photos) UIDs() []string {
|
||||
result := make([]string, len(m))
|
||||
|
||||
for i, el := range m {
|
||||
result[i] = el.PhotoUID
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// Photo represents a photo, all its properties, and link to all its images and sidecar files.
|
||||
type Photo struct {
|
||||
ID uint `gorm:"primary_key" yaml:"-"`
|
||||
|
@ -31,8 +45,8 @@ type Photo struct {
|
|||
PhotoDescription string `gorm:"type:text;" json:"Description" yaml:"Description,omitempty"`
|
||||
DescriptionSrc string `gorm:"type:varbinary(8);" json:"DescriptionSrc" yaml:"DescriptionSrc,omitempty"`
|
||||
Details Details `json:"Details" yaml:"Details"`
|
||||
PhotoPath string `gorm:"type:varbinary(768);index;" yaml:"-"`
|
||||
PhotoName string `gorm:"type:varbinary(255);" json:"PhotoName" yaml:"-"`
|
||||
PhotoPath string `gorm:"type:varbinary(768);index;" json:"Path" yaml:"-"`
|
||||
PhotoName string `gorm:"type:varbinary(255);" json:"Name" yaml:"-"`
|
||||
OriginalName string `gorm:"type:varbinary(768);" json:"OriginalName" yaml:"OriginalName,omitempty"`
|
||||
PhotoFavorite bool `json:"Favorite" yaml:"Favorite,omitempty"`
|
||||
PhotoPrivate bool `json:"Private" yaml:"Private,omitempty"`
|
||||
|
@ -132,7 +146,7 @@ func SavePhotoForm(model Photo, form form.Photo, geoApi string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// String returns an entity identifier as string for use in logs.
|
||||
// String returns the id or name as string.
|
||||
func (m *Photo) String() string {
|
||||
if m.PhotoUID == "" {
|
||||
if m.PhotoName != "" {
|
||||
|
|
|
@ -1,12 +1,10 @@
|
|||
package entity
|
||||
|
||||
import (
|
||||
"path"
|
||||
"time"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/classify"
|
||||
"github.com/photoprism/photoprism/internal/maps"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
"gopkg.in/ugjka/go-tz.v2/tz"
|
||||
)
|
||||
|
||||
|
@ -117,7 +115,7 @@ func (m *Photo) UpdateLocation(geoApi string) (keywords []string, labels classif
|
|||
}
|
||||
|
||||
if m.UnknownCountry() {
|
||||
m.PhotoCountry = txt.CountryCode(path.Join(m.PhotoPath, m.PhotoName))
|
||||
m.EstimateCountry()
|
||||
}
|
||||
|
||||
if m.HasCountry() {
|
||||
|
|
|
@ -33,4 +33,15 @@ func TestPhoto_EstimateCountry(t *testing.T) {
|
|||
assert.Equal(t, "de", m.CountryCode())
|
||||
assert.Equal(t, "Germany", m.CountryName())
|
||||
})
|
||||
|
||||
t.Run("ca", func(t *testing.T) {
|
||||
m := Photo{PhotoTitle: "Port Lands / Gardiner Expressway / Toronto", PhotoPath: "2012/09", PhotoName: "20120910_231851_CA06E1AD", OriginalName: "demo/Toronto/port-lands--gardiner-expressway--toronto_7999515645_o.jpg"}
|
||||
assert.Equal(t, UnknownCountry.ID, m.CountryCode())
|
||||
assert.Equal(t, UnknownCountry.CountryName, m.CountryName())
|
||||
m.EstimateCountry()
|
||||
assert.Equal(t, "ca", m.CountryCode())
|
||||
assert.Equal(t, "Canada", m.CountryName())
|
||||
})
|
||||
|
||||
//OriginalName: "demo/Toronto/port-lands--gardiner-expressway--toronto_7999515645_o.jpg"
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package form
|
||||
|
||||
type ImportOptions struct {
|
||||
Path string `json:"path"`
|
||||
Move bool `json:"move"`
|
||||
Albums []string `json:"albums"`
|
||||
Path string `json:"path"`
|
||||
Move bool `json:"move"`
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package photoprism
|
||||
|
||||
type ImportOptions struct {
|
||||
Albums []string
|
||||
Path string
|
||||
Move bool
|
||||
RemoveDotFiles bool
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"path"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/event"
|
||||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
"github.com/photoprism/photoprism/pkg/txt"
|
||||
|
@ -129,6 +130,13 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
}
|
||||
|
||||
res := ind.MediaFile(related.Main, indexOpt, originalName)
|
||||
|
||||
if res.Success() {
|
||||
if err := entity.AddPhotoToAlbums(res.PhotoUID, opt.Albums); err != nil {
|
||||
log.Warn(err)
|
||||
}
|
||||
}
|
||||
|
||||
log.Infof("import: %s main %s file %s", res, related.Main.FileType(), txt.Quote(related.Main.RelativeName(ind.originalsPath())))
|
||||
done[related.Main.FileName()] = true
|
||||
} else {
|
||||
|
@ -149,6 +157,8 @@ func ImportWorker(jobs <-chan ImportJob) {
|
|||
|
||||
log.Infof("import: %s related %s file %s", res, f.FileType(), txt.Quote(f.RelativeName(ind.originalsPath())))
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -157,6 +157,14 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
}
|
||||
}
|
||||
|
||||
if originalName != "" {
|
||||
file.OriginalName = originalName
|
||||
|
||||
if file.FilePrimary && photo.OriginalName == "" {
|
||||
photo.OriginalName = originalName
|
||||
}
|
||||
}
|
||||
|
||||
if photo.PhotoQuality == -1 && file.FilePrimary {
|
||||
// restore photos that have been purged automatically
|
||||
photo.DeletedAt = nil
|
||||
|
@ -358,14 +366,6 @@ func (ind *Index) MediaFile(m *MediaFile, o IndexOptions, originalName string) (
|
|||
|
||||
photo.UpdateYearMonth()
|
||||
|
||||
if originalName != "" {
|
||||
file.OriginalName = originalName
|
||||
|
||||
if photo.OriginalName == "" {
|
||||
photo.OriginalName = originalName
|
||||
}
|
||||
}
|
||||
|
||||
file.FileSidecar = m.IsSidecar()
|
||||
file.FileVideo = m.IsVideo()
|
||||
file.FileRoot = fileRoot
|
||||
|
|
|
@ -75,7 +75,7 @@ func (m *Moments) Start() (err error) {
|
|||
Path: mom.Path,
|
||||
}
|
||||
|
||||
if a := entity.FindAlbum(mom.Slug(), entity.TypeFolder); a != nil {
|
||||
if a := entity.FindAlbumBySlug(mom.Slug(), entity.TypeFolder); a != nil {
|
||||
if a.DeletedAt != nil {
|
||||
// Nothing to do.
|
||||
log.Debugf("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
|
||||
|
@ -101,7 +101,7 @@ func (m *Moments) Start() (err error) {
|
|||
log.Errorf("moments: %s", err.Error())
|
||||
} else {
|
||||
for _, mom := range results {
|
||||
if a := entity.FindAlbum(mom.Slug(), entity.TypeMonth); a != nil {
|
||||
if a := entity.FindAlbumBySlug(mom.Slug(), entity.TypeMonth); a != nil {
|
||||
if a.DeletedAt != nil {
|
||||
// Nothing to do.
|
||||
log.Debugf("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
|
||||
|
@ -128,7 +128,7 @@ func (m *Moments) Start() (err error) {
|
|||
Year: mom.Year,
|
||||
}
|
||||
|
||||
if a := entity.FindAlbum(mom.Slug(), entity.TypeMoment); a != nil {
|
||||
if a := entity.FindAlbumBySlug(mom.Slug(), entity.TypeMoment); a != nil {
|
||||
if a.DeletedAt != nil {
|
||||
// Nothing to do.
|
||||
log.Debugf("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
|
||||
|
@ -158,7 +158,7 @@ func (m *Moments) Start() (err error) {
|
|||
State: mom.State,
|
||||
}
|
||||
|
||||
if a := entity.FindAlbum(mom.Slug(), entity.TypeMoment); a != nil {
|
||||
if a := entity.FindAlbumBySlug(mom.Slug(), entity.TypeMoment); a != nil {
|
||||
if a.DeletedAt != nil {
|
||||
// Nothing to do.
|
||||
log.Debugf("moments: %s was deleted (%s)", txt.Quote(a.AlbumTitle), a.AlbumFilter)
|
||||
|
@ -186,7 +186,7 @@ func (m *Moments) Start() (err error) {
|
|||
Label: mom.Label,
|
||||
}
|
||||
|
||||
if a := entity.FindAlbum(mom.Slug(), entity.TypeMoment); a != nil {
|
||||
if a := entity.FindAlbumBySlug(mom.Slug(), entity.TypeMoment); a != nil {
|
||||
log.Debugf("moments: %s already exists (%s)", txt.Quote(mom.Title()), f.Serialize())
|
||||
|
||||
if f.Serialize() == a.AlbumFilter || a.DeletedAt != nil {
|
||||
|
|
|
@ -5,10 +5,8 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/form"
|
||||
)
|
||||
|
||||
type Accounts []entity.Account
|
||||
|
||||
// AccountSearch returns a list of accounts.
|
||||
func AccountSearch(f form.AccountSearch) (result Accounts, err error) {
|
||||
func AccountSearch(f form.AccountSearch) (result entity.Accounts, err error) {
|
||||
s := Db().Where(&entity.Account{})
|
||||
|
||||
if f.Share {
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
)
|
||||
|
||||
// AccountUploads a list of files for uploading to a remote account.
|
||||
func AccountUploads(a entity.Account, limit int) (results []entity.File, err error) {
|
||||
func AccountUploads(a entity.Account, limit int) (results entity.Files, err error) {
|
||||
s := Db().Where("files.file_missing = 0").
|
||||
Where("files.id NOT IN (SELECT file_id FROM files_sync WHERE file_id > 0 AND account_id = ?)", a.ID)
|
||||
|
||||
|
|
|
@ -6,10 +6,8 @@ import (
|
|||
"github.com/photoprism/photoprism/internal/entity"
|
||||
)
|
||||
|
||||
type Files []entity.File
|
||||
|
||||
// FilesByPath returns a slice of files in a given originals folder.
|
||||
func FilesByPath(rootName, pathName string) (files Files, err error) {
|
||||
func FilesByPath(rootName, pathName string) (files entity.Files, err error) {
|
||||
if strings.HasPrefix(pathName, "/") {
|
||||
pathName = pathName[1:]
|
||||
}
|
||||
|
@ -26,7 +24,7 @@ func FilesByPath(rootName, pathName string) (files Files, err error) {
|
|||
}
|
||||
|
||||
// ExistingFiles returns not-missing and not-deleted file entities in the range of limit and offset sorted by id.
|
||||
func ExistingFiles(limit int, offset int, pathName string) (files Files, err error) {
|
||||
func ExistingFiles(limit int, offset int, pathName string) (files entity.Files, err error) {
|
||||
if strings.HasPrefix(pathName, "/") {
|
||||
pathName = pathName[1:]
|
||||
}
|
||||
|
@ -43,7 +41,7 @@ func ExistingFiles(limit int, offset int, pathName string) (files Files, err err
|
|||
}
|
||||
|
||||
// FilesByUID
|
||||
func FilesByUID(u []string, limit int, offset int) (files Files, err error) {
|
||||
func FilesByUID(u []string, limit int, offset int) (files entity.Files, err error) {
|
||||
if err := Db().Where("(photo_uid IN (?) AND file_primary = 1) OR file_uid IN (?)", u, u).Preload("Photo").Limit(limit).Offset(offset).Find(&files).Error; err != nil {
|
||||
return files, err
|
||||
}
|
||||
|
|
|
@ -7,17 +7,15 @@ import (
|
|||
"github.com/photoprism/photoprism/pkg/fs"
|
||||
)
|
||||
|
||||
type Folders []entity.Folder
|
||||
|
||||
// FoldersByPath returns a slice of folders in a given directory incl sub directories in recursive mode.
|
||||
func FoldersByPath(rootName, rootPath, path string, recursive bool) (folders Folders, err error) {
|
||||
func FoldersByPath(rootName, rootPath, path string, recursive bool) (folders entity.Folders, err error) {
|
||||
dirs, err := fs.Dirs(filepath.Join(rootPath, path), recursive)
|
||||
|
||||
if err != nil {
|
||||
return folders, err
|
||||
}
|
||||
|
||||
folders = make(Folders, len(dirs))
|
||||
folders = make(entity.Folders, len(dirs))
|
||||
|
||||
for i, dir := range dirs {
|
||||
folder := entity.FindFolder(rootName, filepath.Join(path, dir))
|
||||
|
@ -39,7 +37,7 @@ func FoldersByPath(rootName, rootPath, path string, recursive bool) (folders Fol
|
|||
}
|
||||
|
||||
// AlbumFolders returns folders that should be added as album.
|
||||
func AlbumFolders(threshold int) (folders Folders, err error) {
|
||||
func AlbumFolders(threshold int) (folders entity.Folders, err error) {
|
||||
db := UnscopedDb().Table("folders").
|
||||
Select("folders.*, COUNT(photos.id) AS photo_count").
|
||||
Joins("JOIN photos ON photos.photo_path = folders.path AND photos.deleted_at IS NULL AND photos.photo_quality >= 3").
|
||||
|
|
|
@ -73,7 +73,7 @@ func PhotoPreloadByUID(photoUID string) (photo entity.Photo, err error) {
|
|||
}
|
||||
|
||||
// PhotosMissing returns photo entities without existing files.
|
||||
func PhotosMissing(limit int, offset int) (entities Photos, err error) {
|
||||
func PhotosMissing(limit int, offset int) (entities entity.Photos, err error) {
|
||||
err = Db().
|
||||
Select("photos.*").
|
||||
Joins("JOIN files a ON photos.id = a.photo_id ").
|
||||
|
@ -94,7 +94,7 @@ func ResetPhotoQuality() error {
|
|||
}
|
||||
|
||||
// PhotosMaintenance returns photos selected for maintenance.
|
||||
func PhotosMaintenance(limit int, offset int) (entities Photos, err error) {
|
||||
func PhotosMaintenance(limit int, offset int) (entities entity.Photos, err error) {
|
||||
err = Db().
|
||||
Preload("Labels", func(db *gorm.DB) *gorm.DB {
|
||||
return db.Order("photos_labels.uncertainty ASC, photos_labels.label_id DESC")
|
||||
|
|
|
@ -11,8 +11,6 @@ import (
|
|||
"github.com/ulule/deepcopier"
|
||||
)
|
||||
|
||||
// Default photo result slice for simple use cases.
|
||||
type Photos []entity.Photo
|
||||
|
||||
// PhotoResult contains found photos and their main file plus other meta data.
|
||||
type PhotoResult struct {
|
||||
|
@ -26,6 +24,7 @@ type PhotoResult struct {
|
|||
TimeZone string `json:"TimeZone"`
|
||||
PhotoPath string `json:"Path"`
|
||||
PhotoName string `json:"Name"`
|
||||
OriginalName string `json:"OriginalName"`
|
||||
PhotoTitle string `json:"Title"`
|
||||
PhotoDescription string `json:"Description"`
|
||||
PhotoYear int `json:"Year"`
|
||||
|
|
|
@ -4,11 +4,12 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
)
|
||||
|
||||
// PhotoSelection queries all selected photos.
|
||||
func PhotoSelection(f form.Selection) (results Photos, err error) {
|
||||
func PhotoSelection(f form.Selection) (results entity.Photos, err error) {
|
||||
if f.Empty() {
|
||||
return results, errors.New("no items selected")
|
||||
}
|
||||
|
@ -47,7 +48,7 @@ func PhotoSelection(f form.Selection) (results Photos, err error) {
|
|||
}
|
||||
|
||||
// FileSelection queries all selected files e.g. for downloading.
|
||||
func FileSelection(f form.Selection) (results Files, err error) {
|
||||
func FileSelection(f form.Selection) (results entity.Files, err error) {
|
||||
if f.Empty() {
|
||||
return results, errors.New("no items selected")
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package query
|
|||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/photoprism/photoprism/internal/entity"
|
||||
"github.com/photoprism/photoprism/internal/form"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
@ -30,7 +31,7 @@ func TestPhotoSelection(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, 2, len(r))
|
||||
assert.IsType(t, Photos{}, r)
|
||||
assert.IsType(t, entity.Photos{}, r)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -57,6 +58,6 @@ func TestFileSelection(t *testing.T) {
|
|||
}
|
||||
|
||||
assert.Equal(t, 3, len(r))
|
||||
assert.IsType(t, Files{}, r)
|
||||
assert.IsType(t, entity.Files{}, r)
|
||||
})
|
||||
}
|
||||
|
|
|
@ -29,7 +29,7 @@ func Base(fileName string, stripSequence bool) string {
|
|||
}
|
||||
|
||||
// other common sequential naming schemes
|
||||
if end := strings.Index(basename, " ("); end != -1 {
|
||||
if end := strings.Index(basename, "("); end != -1 {
|
||||
// copies created by Chrome & Windows, example: IMG_1234 (2)
|
||||
basename = basename[:end]
|
||||
} else if end := strings.Index(basename, " copy"); end != -1 {
|
||||
|
@ -37,6 +37,8 @@ func Base(fileName string, stripSequence bool) string {
|
|||
basename = basename[:end]
|
||||
}
|
||||
|
||||
basename = strings.TrimSpace(basename)
|
||||
|
||||
return basename
|
||||
}
|
||||
|
||||
|
|
|
@ -67,6 +67,26 @@ func TestBase(t *testing.T) {
|
|||
result := Base("/testdata/Test (3).jpg", false)
|
||||
assert.Equal(t, "Test (3)", result)
|
||||
})
|
||||
t.Run("20180506_091537_DSC02122.JPG", func(t *testing.T) {
|
||||
result := Base("20180506_091537_DSC02122.JPG", true)
|
||||
assert.Equal(t, "20180506_091537_DSC02122", result)
|
||||
})
|
||||
t.Run("20180506_091537_DSC02122 (+3.3).JPG", func(t *testing.T) {
|
||||
result := Base("20180506_091537_DSC02122 (+3.3).JPG", true)
|
||||
assert.Equal(t, "20180506_091537_DSC02122", result)
|
||||
})
|
||||
t.Run("20180506_091537_DSC02122 (-2.7).JPG", func(t *testing.T) {
|
||||
result := Base("20180506_091537_DSC02122 (-2.7).JPG", true)
|
||||
assert.Equal(t, "20180506_091537_DSC02122", result)
|
||||
})
|
||||
t.Run("20180506_091537_DSC02122(+3.3).JPG", func(t *testing.T) {
|
||||
result := Base("20180506_091537_DSC02122(+3.3).JPG", true)
|
||||
assert.Equal(t, "20180506_091537_DSC02122", result)
|
||||
})
|
||||
t.Run("20180506_091537_DSC02122(-2.7).JPG", func(t *testing.T) {
|
||||
result := Base("20180506_091537_DSC02122(-2.7).JPG", true)
|
||||
assert.Equal(t, "20180506_091537_DSC02122", result)
|
||||
})
|
||||
}
|
||||
|
||||
func TestRelativeBase(t *testing.T) {
|
||||
|
|
|
@ -21,13 +21,43 @@ func IsPPID(s string, prefix byte) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
return s[0] == prefix
|
||||
return s[0] == prefix && IsLowerAlnum(s)
|
||||
}
|
||||
|
||||
// IsHex returns true if the string only contains hex numbers, dashes and letters without whitespace.
|
||||
func IsHex(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range s {
|
||||
if (r < 48 || r > 57) && (r < 97 || r > 102) && (r < 65 || r > 90) && r != 45{
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsLowerAlnum returns true if the string only contains alphanumeric ascii chars without whitespace.
|
||||
func IsLowerAlnum(s string) bool {
|
||||
if s == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, r := range s {
|
||||
if (r < 48 || r > 57) && (r < 97 || r > 122) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// IsUID returns true if string is a seemingly unique id.
|
||||
func IsUID(s string, prefix byte) bool {
|
||||
// Regular UUID.
|
||||
if len(s) == 36 {
|
||||
if len(s) == 36 && IsHex(s) {
|
||||
return true
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue