WebDAV: Fix upload of complete albums #1376

This commit is contained in:
Michael Mayer 2022-02-27 17:32:54 +01:00
parent eb75a58f45
commit c256664a1b
116 changed files with 564 additions and 198 deletions

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package main

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -0,0 +1,108 @@
/*
Copyright (c) 2018 - 2022 Michael Mayer <hello@photoprism.app>
This program is free software: you can redistribute it and/or modify
it under Version 3 of the GNU Affero General Public License (the "AGPL"):
<https://docs.photoprism.app/license/agpl>
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.
The AGPL is supplemented by our Trademark and Brand Guidelines,
which describe how our Brand Assets may be used:
<https://photoprism.app/trademark>
Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
<https://docs.photoprism.app/developer-guide/>
*/
function isObject(val) {
return val && val instanceof Object;
}
function isModel(model) {
return (
model &&
typeof model.getId === "function" &&
typeof model.constructor.getCollectionResource === "function"
);
}
class Selection {
/**
* @param {Object?} items
*/
constructor(items) {
this.clear();
this.addItems(items);
}
clear() {
this.files = [];
this.photos = [];
this.albums = [];
this.labels = [];
this.places = [];
this.subjects = [];
return this;
}
/**
* @param {Object} items
*/
addItems(items) {
if (isObject(items) && Object.keys(items).length > 0) {
for (const [key, value] of Object.entries(items)) {
if (this.hasOwnProperty(key) && Array.isArray(value) && value.length > 0) {
if (this[key].length === 0 || this[key][0] !== value[0]) {
this[key].push(...value);
}
}
}
}
return this;
}
addModel(model) {
if (!isModel(model)) {
return;
}
const id = model.getId();
const key = model.constructor.getCollectionResource();
if (!id || !key || !this.hasOwnProperty(key)) {
return;
}
if (!this[key].includes(id)) {
this[key].push(id);
}
return this;
}
/**
* @returns {boolean}
*/
isEmpty() {
for (const items of Object.values(this)) {
if (Array.isArray(items) && items.length > 0) {
return false;
}
}
return true;
}
}
export default Selection;

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -100,7 +100,10 @@ import download from "common/download";
export default {
name: 'PAlbumClipboard',
props: {
selection: Array,
selection: {
type: Array,
default: () => [],
},
refresh: Function,
clearSelection: Function,
share: Function,

View File

@ -62,7 +62,7 @@
<p-share-dialog :show="dialog.share" :model="album" @upload="webdavUpload"
@close="dialog.share = false"></p-share-dialog>
<p-share-upload-dialog :show="dialog.upload" :selection="[album.getId()]" @cancel="dialog.upload = false"
<p-share-upload-dialog :show="dialog.upload" :items="{albums: album.getId()}" :model="album" @cancel="dialog.upload = false"
@confirm="dialog.upload = false"></p-share-upload-dialog>
<p-album-edit-dialog :show="dialog.edit" :album="album" @close="dialog.edit = false"></p-album-edit-dialog>
</v-form>
@ -75,9 +75,18 @@ import download from "common/download";
export default {
name: 'PAlbumToolbar',
props: {
album: Object,
filter: Object,
settings: Object,
album: {
type: Object,
default: () => {},
},
filter: {
type: Object,
default: () => {},
},
settings: {
type: Object,
default: () => {},
},
refresh: Function,
filterChange: Function,
},

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -69,7 +69,10 @@ import download from "common/download";
export default {
name: 'PFileClipboard',
props: {
selection: Array,
selection: {
type: Array,
default: () => [],
},
refresh: Function,
clearSelection: Function,
},

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -81,7 +81,10 @@ import download from "common/download";
export default {
name: 'PLabelClipboard',
props: {
selection: Array,
selection: {
type: Array,
default: () => [],
},
refresh: Function,
clearSelection: Function,
},

View File

@ -195,12 +195,21 @@ import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
export default {
name: 'PPhotoCards',
props: {
photos: Array,
photos: {
type: Array,
default: () => [],
},
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
album: {
type: Object,
default: () => {},
},
filter: {
type: Object,
default: () => {},
},
context: String,
selectMode: Boolean,
},

View File

@ -148,7 +148,7 @@
@confirm="batchDelete"></p-photo-delete-dialog>
<p-photo-album-dialog :show="dialog.album" @cancel="dialog.album = false"
@confirm="addToAlbum"></p-photo-album-dialog>
<p-share-upload-dialog :show="dialog.share" :selection="selection" :album="album" @cancel="dialog.share = false"
<p-share-upload-dialog :show="dialog.share" :items="{photos: selection}" :model="album" @cancel="dialog.share = false"
@confirm="onShared"></p-share-upload-dialog>
</div>
</template>
@ -162,9 +162,15 @@ import Photo from "model/photo";
export default {
name: 'PPhotoClipboard',
props: {
selection: Array,
selection: {
type: Array,
default: () => [],
},
refresh: Function,
album: Object,
album: {
type: Object,
default: () => {},
},
context: String,
},
data() {

View File

@ -117,12 +117,21 @@ import Notify from "common/notify";
export default {
name: 'PPhotoList',
props: {
photos: Array,
photos: {
type: Array,
default: () => [],
},
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
album: {
type: Object,
default: () => {},
},
filter: {
type: Object,
default: () => {},
},
context: String,
selectMode: Boolean,
},

View File

@ -121,11 +121,20 @@ import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
export default {
name: 'PPhotoMosaic',
props: {
photos: Array,
photos: {
type: Array,
default: () => [],
},
openPhoto: Function,
editPhoto: Function,
album: Object,
filter: Object,
album: {
type: Object,
default: () => {},
},
filter: {
type: Object,
default: () => {},
},
context: String,
selectMode: Boolean,
},

View File

@ -156,8 +156,14 @@ export default {
name: 'PPhotoToolbar',
props: {
dirty: Boolean,
filter: Object,
settings: Object,
filter: {
type: Object,
default: () => {},
},
settings: {
type: Object,
default: () => {},
},
refresh: Function,
filterChange: Function,
},

View File

@ -177,7 +177,7 @@ export default {
},
openPlayer(video) {
if (!video) {
this.$notify.error("no video selected");
this.$notify.error(this.$gettext("No video selected"));
return;
}

View File

@ -69,7 +69,10 @@ import download from "common/download";
export default {
name: 'PSubjectClipboard',
props: {
selection: Array,
selection: {
type: Array,
default: () => [],
},
refresh: Function,
clearSelection: Function,
},

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -274,7 +274,11 @@ export default {
props: {
show: Boolean,
scope: String,
model: Object,
model: {
type: Object,
default: () => {
},
},
},
data() {
const thumbs = this.$config.values.thumbs;
@ -366,7 +370,7 @@ export default {
},
sizes(thumbs) {
const result = [
{"text": this.$gettext("Original"), "value": ""}
{"text": this.$gettext("Originals"), "value": ""},
];
for (let i = 0; i < thumbs.length; i++) {

View File

@ -30,7 +30,10 @@ export default {
name: 'PAccountDeleteDialog',
props: {
show: Boolean,
model: Object,
model: {
type: Object,
default: () => {},
},
},
data() {
return {

View File

@ -100,7 +100,10 @@ export default {
name: 'PAlbumEditDialog',
props: {
show: Boolean,
album: Object,
album: {
type: Object,
default: () => {},
},
},
data() {
return {

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -128,8 +128,14 @@ export default {
props: {
index: Number,
show: Boolean,
selection: Array,
album: Object,
selection: {
type: Array,
default: () => [],
},
album: {
type: Object,
default: () => {},
},
},
data() {
return {
@ -217,7 +223,7 @@ export default {
}
if (!this.selection || !this.selection[index]) {
this.$notify.error("Invalid photo selected");
this.$notify.error(this.$gettext("Invalid photo selected"));
return;
}

View File

@ -231,7 +231,10 @@ import Util from "common/util";
export default {
name: 'PTabPhotoFiles',
props: {
model: Object,
model: {
type: Object,
default: () => {},
},
uid: String,
},
data() {

View File

@ -268,7 +268,10 @@ import * as options from "options/options";
export default {
name: 'PTabPhotoAdvanced',
props: {
model: Object,
model: {
type: Object,
default: () => {},
},
uid: String,
},
data() {

View File

@ -92,7 +92,10 @@ import Label from "model/label";
export default {
name: 'PTabPhotoLabels',
props: {
model: Object,
model: {
type: Object,
default: () => {},
},
uid: String,
},
data() {

View File

@ -108,7 +108,10 @@
export default {
name: 'PTabPhotoPeople',
props: {
model: Object,
model: {
type: Object,
default: () => {},
},
uid: String,
},
data() {

View File

@ -1,5 +1,5 @@
<template>
<v-dialog v-model="show" lazy persistent max-width="500" class="p-share-dialog" @keydown.esc="close">
<v-dialog :value="show" lazy persistent max-width="500" class="p-share-dialog" @keydown.esc="close">
<v-card raised elevation="24">
<v-card-title primary-title class="pb-0">
<v-layout row wrap>
@ -134,7 +134,10 @@ export default {
name: 'PShareDialog',
props: {
show: Boolean,
model: Object,
model: {
type: Object,
default: () => {},
},
},
data() {
return {
@ -181,9 +184,9 @@ export default {
try {
const url = link.url();
await Util.copyToMachineClipboard(url);
this.$notify.success(this.$gettext("Copied to clipboard"))
this.$notify.success(this.$gettext("Copied to clipboard"));
} catch (error) {
this.$notify.error(this.$gettext("Failed copying to clipboard"))
this.$notify.error(this.$gettext("Failed copying to clipboard"));
}
},
expires(link) {

View File

@ -1,5 +1,5 @@
<template>
<v-dialog v-model="show" lazy persistent max-width="400" class="p-share-upload-dialog" @keydown.esc="cancel">
<v-dialog :value="show" lazy persistent max-width="400" class="p-share-upload-dialog" @keydown.esc="cancel">
<v-card raised elevation="24">
<v-card-title primary-title class="pb-0">
<v-layout row wrap>
@ -68,12 +68,20 @@
</template>
<script>
import Account from "model/account";
import Selection from "common/selection";
export default {
name: 'PShareUploadDialog',
props: {
show: Boolean,
selection: Array,
items: {
type: Object,
default: null,
},
model: {
type: Object,
default: null,
}
},
data() {
return {
@ -82,6 +90,7 @@ export default {
search: null,
account: {},
accounts: [],
selection: new Selection({}),
path: "/",
paths: [
{"abs": "/"}
@ -107,6 +116,8 @@ export default {
show: function (show) {
if (show) {
this.load();
} else if (this.selection) {
this.selection.clear();
}
}
},
@ -129,7 +140,7 @@ export default {
this.loading = false;
if (files.length === 1) {
this.$notify.success("One file uploaded");
this.$notify.success(this.$gettext("One file uploaded"));
} else {
this.$notify.success(this.$gettextInterpolate(this.$gettext("%{n} files uploaded"), {n: files.length}));
}
@ -154,6 +165,18 @@ export default {
load() {
this.loading = true;
this.selection.clear().addItems(this.items);
if (this.selection.isEmpty()) {
this.selection.addModel(this.model);
}
if (this.selection.isEmpty()) {
this.loading = false;
this.$emit('cancel');
return;
}
const params = {
share: true,
count: 1000,

View File

@ -59,7 +59,7 @@ export default {
},
play(fullscreen) {
if (!this.video) {
this.$notify.error("no video selected");
this.$notify.error(this.$gettext("No video selected"));
return;
}

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
@ -75,10 +75,16 @@ export class Account extends RestModel {
);
}
Share(photos, dest) {
const values = { Photos: photos, Destination: dest };
Share(selection, folder) {
if (!selection) {
return;
}
return Api.post(this.getEntityResource() + "/share", values).then((response) =>
if (Array.isArray(selection)) {
selection = { Photos: selection };
}
return Api.post(this.getEntityResource() + "/share", { selection, folder }).then((response) =>
Promise.resolve(response.data)
);
}

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -198,7 +198,7 @@
</v-container>
<p-share-dialog :show="dialog.share" :model="model" @upload="webdavUpload"
@close="dialog.share = false"></p-share-dialog>
<p-share-upload-dialog :show="dialog.upload" :selection="selection" @cancel="dialog.upload = false"
<p-share-upload-dialog :show="dialog.upload" :items="{albums: selection}" :model="model" @cancel="dialog.upload = false"
@confirm="dialog.upload = false"></p-share-upload-dialog>
<p-album-edit-dialog :show="dialog.edit" :album="model" @close="dialog.edit = false"></p-album-edit-dialog>
</div>

View File

@ -135,7 +135,10 @@ import {ClickLong, ClickShort, Input, InputInvalid} from "common/input";
export default {
name: 'PPageFaces',
props: {
staticFilter: Object,
staticFilter: {
type: Object,
default: () => {},
},
active: Boolean,
},
data() {

View File

@ -180,7 +180,10 @@ import {ClickLong, ClickShort, Input, InputInvalid} from "common/input";
export default {
name: 'PPageSubjects',
props: {
staticFilter: Object,
staticFilter: {
type: Object,
default: () => {},
},
active: Boolean,
},
data() {

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -51,7 +51,10 @@ import download from "common/download";
export default {
name: 'PAlbumClipboard',
props: {
selection: Array,
selection: {
type: Array,
default: () => [],
},
refresh: Function,
clearSelection: Function,
context: String,

View File

@ -118,7 +118,10 @@ import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
export default {
name: 'PPageAlbums',
props: {
staticFilter: Object,
staticFilter: {
type: Object,
default: () => {},
},
view: String,
},
data() {

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -146,12 +146,21 @@ import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
export default {
name: 'PPhotoCards',
props: {
photos: Array,
photos: {
type: Array,
default: () => [],
},
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
album: {
type: Object,
default: () => {},
},
filter: {
type: Object,
default: () => {},
},
context: String,
selectMode: Boolean,
},

View File

@ -54,9 +54,15 @@ import Photo from "model/photo";
export default {
name: 'PPhotoClipboard',
props: {
selection: Array,
selection: {
type: Array,
default: () => [],
},
refresh: Function,
album: Object,
album: {
type: Object,
default: () => {},
},
context: String,
},
data() {

View File

@ -97,12 +97,21 @@ import Notify from "common/notify";
export default {
name: 'PPhotoList',
props: {
photos: Array,
photos: {
type: Array,
default: () => [],
},
openPhoto: Function,
editPhoto: Function,
openLocation: Function,
album: Object,
filter: Object,
album: {
type: Object,
default: () => {},
},
filter: {
type: Object,
default: () => {},
},
context: String,
selectMode: Boolean,
},

View File

@ -100,11 +100,20 @@ import {Input, InputInvalid, ClickShort, ClickLong} from "common/input";
export default {
name: 'PPhotoMosaic',
props: {
photos: Array,
photos: {
type: Array,
default: () => [],
},
openPhoto: Function,
editPhoto: Function,
album: Object,
filter: Object,
album: {
type: Object,
default: () => {},
},
filter: {
type: Object,
default: () => {},
},
context: String,
selectMode: Boolean,
},

View File

@ -19,7 +19,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package acl

View File

@ -117,6 +117,8 @@ func GetAccountFolders(router *gin.RouterGroup) {
})
}
// ShareWithAccount uploads files to the selected account.
//
// GET /api/v1/accounts/:id/share
//
// Parameters:
@ -146,8 +148,15 @@ func ShareWithAccount(router *gin.RouterGroup) {
return
}
dst := f.Destination
files, err := query.FilesByUID(f.Photos, 1000, 0)
folder := f.Folder
// Select files to be shared.
o := query.FileSelection{
Video: true,
OriginalsOnly: m.ShareOriginals(),
PrimaryOnly: !m.ShareOriginals(),
}
files, err := query.SelectedFiles(f.Selection, o)
if err != nil {
AbortEntityNotFound(c)
@ -157,7 +166,7 @@ func ShareWithAccount(router *gin.RouterGroup) {
var aliases = make(map[string]int)
for _, file := range files {
alias := path.Join(dst, file.ShareBase(0))
alias := path.Join(folder, file.ShareBase(0))
key := strings.ToLower(alias)
if seq := aliases[key]; seq > 0 {
@ -175,6 +184,8 @@ func ShareWithAccount(router *gin.RouterGroup) {
})
}
// CreateAccount creates a new remote account configuration.
//
// POST /api/v1/accounts
func CreateAccount(router *gin.RouterGroup) {
router.POST("/accounts", func(c *gin.Context) {
@ -219,6 +230,8 @@ func CreateAccount(router *gin.RouterGroup) {
})
}
// UpdateAccount updates a remote account configuration.
//
// PUT /api/v1/accounts/:id
//
// Parameters:
@ -288,6 +301,8 @@ func UpdateAccount(router *gin.RouterGroup) {
})
}
// DeleteAccount removes a remote account configuration.
//
// DELETE /api/v1/accounts/:id
//
// Parameters:

View File

@ -365,7 +365,8 @@ func AddPhotosToAlbum(router *gin.RouterGroup) {
return
}
photos, err := query.PhotoSelection(f)
// Fetch selection from index.
photos, err := query.SelectedPhotos(f)
if err != nil {
log.Errorf("album: %s", err)

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package api

View File

@ -44,7 +44,8 @@ func BatchPhotosArchive(router *gin.RouterGroup) {
log.Infof("photos: archiving %s", sanitize.Log(f.String()))
if service.Config().BackupYaml() {
photos, err := query.PhotoSelection(f)
// Fetch selection from index.
photos, err := query.SelectedPhotos(f)
if err != nil {
AbortEntityNotFound(c)
@ -107,7 +108,8 @@ func BatchPhotosRestore(router *gin.RouterGroup) {
log.Infof("photos: restoring %s", sanitize.Log(f.String()))
if service.Config().BackupYaml() {
photos, err := query.PhotoSelection(f)
// Fetch selection from index.
photos, err := query.SelectedPhotos(f)
if err != nil {
AbortEntityNotFound(c)
@ -168,7 +170,8 @@ func BatchPhotosApprove(router *gin.RouterGroup) {
log.Infof("photos: approving %s", sanitize.Log(f.String()))
photos, err := query.PhotoSelection(f)
// Fetch selection from index.
photos, err := query.SelectedPhotos(f)
if err != nil {
AbortEntityNotFound(c)
@ -267,7 +270,8 @@ func BatchPhotosPrivate(router *gin.RouterGroup) {
// Update precalculated photo and file counts.
logWarn("index", entity.UpdateCounts())
if photos, err := query.PhotoSelection(f); err == nil {
// Fetch selection from index.
if photos, err := query.SelectedPhotos(f); err == nil {
for _, p := range photos {
SavePhotoAsYaml(p)
}
@ -362,7 +366,8 @@ func BatchPhotosDelete(router *gin.RouterGroup) {
log.Infof("photos: deleting %s", sanitize.Log(f.String()))
photos, err := query.PhotoSelection(f)
// Fetch selection from index.
photos, err := query.SelectedPhotos(f)
if err != nil {
AbortEntityNotFound(c)

View File

@ -19,7 +19,7 @@ import (
//
// Parameters:
// hash: string The photo or video file hash as returned by the search API
// type: string Video type
// type: string Video format
func GetVideo(router *gin.RouterGroup) {
router.GET("/videos/:hash/:token/:type", func(c *gin.Context) {
if InvalidPreviewToken(c) {

View File

@ -25,6 +25,8 @@ import (
"github.com/photoprism/photoprism/pkg/sanitize"
)
// CreateZip creates a zip file archive for download.
//
// POST /api/v1/zip
func CreateZip(router *gin.RouterGroup) {
router.POST("/zip", func(c *gin.Context) {
@ -55,7 +57,8 @@ func CreateZip(router *gin.RouterGroup) {
return
}
files, err := query.FileSelection(f)
// Select files to be downloaded.
files, err := query.SelectedFiles(f, query.FileSelectionAll())
if err != nil {
Error(c, http.StatusBadRequest, err, i18n.ErrZipFailed)
@ -132,6 +135,8 @@ func CreateZip(router *gin.RouterGroup) {
})
}
// DownloadZip downloads a zip file archive.
//
// GET /api/v1/zip/:filename
func DownloadZip(router *gin.RouterGroup) {
router.GET("/zip/:filename", func(c *gin.Context) {
@ -159,6 +164,7 @@ func DownloadZip(router *gin.RouterGroup) {
})
}
// addFileToZip adds a file to a zip archive.
func addFileToZip(zipWriter *zip.Writer, fileName, fileAlias string) error {
fileToZip, err := os.Open(fileName)
if err != nil {

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package auto

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package classify

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package config

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package crop

View File

@ -137,3 +137,8 @@ func (m *Account) Save() error {
func (m *Account) Create() error {
return Db().Create(m).Error
}
// ShareOriginals tests if the unmodified originals should be shared.
func (m *Account) ShareOriginals() bool {
return m.ShareSize == ""
}

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package face

View File

@ -1,6 +1,6 @@
package form
type AccountShare struct {
Photos []string `json:"photos"`
Destination string `json:"destination"`
Selection Selection `json:"selection"`
Folder string `json:"folder"`
}

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package form

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package hub

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package places

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package i18n

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package maps

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package meta

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package migrate

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package nsfw

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package photoprism

View File

@ -0,0 +1,105 @@
package query
import (
"errors"
"fmt"
"github.com/photoprism/photoprism/internal/entity"
"github.com/photoprism/photoprism/internal/form"
)
// FileSelection represents a selection filter to include/exclude certain files.
type FileSelection struct {
Video bool
Sidecar bool
PrimaryOnly bool
OriginalsOnly bool
SizeLimit int
Include []string
Exclude []string
}
// FileSelectionAll returns options that include videos and sidecar files.
func FileSelectionAll() FileSelection {
return FileSelection{
Video: true,
Sidecar: true,
PrimaryOnly: false,
OriginalsOnly: false,
SizeLimit: 0,
Include: []string{},
Exclude: []string{},
}
}
// SelectedFiles finds files based on the given selection form, e.g. for downloading or sharing.
func SelectedFiles(f form.Selection, o FileSelection) (results entity.Files, err error) {
if f.Empty() {
return results, errors.New("no items selected")
}
var concat string
switch DbDialect() {
case MySQL:
concat = "CONCAT(a.path, '/%')"
case SQLite3:
concat = "a.path || '/%'"
default:
return results, fmt.Errorf("unknown sql dialect: %s", DbDialect())
}
where := fmt.Sprintf(`photos.photo_uid IN (?)
OR photos.place_id IN (?)
OR photos.photo_uid IN (SELECT photo_uid FROM files WHERE file_uid IN (?))
OR photos.photo_path IN (
SELECT a.path FROM folders a WHERE a.folder_uid IN (?) UNION
SELECT b.path FROM folders a JOIN folders b ON b.path LIKE %s WHERE a.folder_uid IN (?))
OR photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND album_uid IN (?))
OR files.file_uid IN (SELECT file_uid FROM %s m WHERE m.subj_uid IN (?))
OR photos.id IN (SELECT pl.photo_id FROM photos_labels pl JOIN labels l ON pl.label_id = l.id AND l.deleted_at IS NULL WHERE l.label_uid IN (?))
OR photos.id IN (SELECT pl.photo_id FROM photos_labels pl JOIN categories c ON c.label_id = pl.label_id JOIN labels lc ON lc.id = c.category_id AND lc.deleted_at IS NULL WHERE lc.label_uid IN (?))`,
concat, entity.Marker{}.TableName())
s := UnscopedDb().Table("files").
Select("files.*").
Joins("JOIN photos ON photos.id = files.photo_id").
Where("photos.deleted_at IS NULL").
Where("files.file_missing = 0").
Where(where, f.Photos, f.Places, f.Files, f.Files, f.Files, f.Albums, f.Subjects, f.Labels, f.Labels).
Group("files.id")
if o.OriginalsOnly {
s = s.Where("file_root = '/'")
}
if o.PrimaryOnly {
s = s.Where("file_primary = 1")
}
if !o.Sidecar {
s = s.Where("file_sidecar = 0")
}
if !o.Video {
s = s.Where("file_video = 0")
}
if o.SizeLimit > 0 {
s = s.Where("file_size < ?", o.SizeLimit)
}
if len(o.Include) > 0 {
s = s.Where("file_type IN (?)", o.Include)
}
if len(o.Exclude) > 0 {
s = s.Where("file_type NOT IN (?)", o.Exclude)
}
if result := s.Scan(&results); result.Error != nil {
return results, result.Error
}
return results, nil
}

View File

@ -8,8 +8,8 @@ import (
"github.com/photoprism/photoprism/internal/form"
)
// PhotoSelection queries all selected photos.
func PhotoSelection(f form.Selection) (results entity.Photos, err error) {
// SelectedPhotos finds photos based on the given selection form, e.g. for adding them to an album.
func SelectedPhotos(f form.Selection) (results entity.Photos, err error) {
if f.Empty() {
return results, errors.New("no items selected")
}
@ -47,47 +47,3 @@ func PhotoSelection(f form.Selection) (results entity.Photos, err error) {
return results, nil
}
// FileSelection queries all selected files e.g. for downloading.
func FileSelection(f form.Selection) (results entity.Files, err error) {
if f.Empty() {
return results, errors.New("no items selected")
}
var concat string
switch DbDialect() {
case MySQL:
concat = "CONCAT(a.path, '/%')"
case SQLite3:
concat = "a.path || '/%'"
default:
return results, fmt.Errorf("unknown sql dialect: %s", DbDialect())
}
where := fmt.Sprintf(`photos.photo_uid IN (?)
OR photos.place_id IN (?)
OR photos.photo_uid IN (SELECT photo_uid FROM files WHERE file_uid IN (?))
OR photos.photo_path IN (
SELECT a.path FROM folders a WHERE a.folder_uid IN (?) UNION
SELECT b.path FROM folders a JOIN folders b ON b.path LIKE %s WHERE a.folder_uid IN (?))
OR photos.photo_uid IN (SELECT photo_uid FROM photos_albums WHERE hidden = 0 AND album_uid IN (?))
OR files.file_uid IN (SELECT file_uid FROM %s m WHERE m.subj_uid IN (?))
OR photos.id IN (SELECT pl.photo_id FROM photos_labels pl JOIN labels l ON pl.label_id = l.id AND l.deleted_at IS NULL WHERE l.label_uid IN (?))
OR photos.id IN (SELECT pl.photo_id FROM photos_labels pl JOIN categories c ON c.label_id = pl.label_id JOIN labels lc ON lc.id = c.category_id AND lc.deleted_at IS NULL WHERE lc.label_uid IN (?))`,
concat, entity.Marker{}.TableName())
s := UnscopedDb().Table("files").
Select("files.*").
Joins("JOIN photos ON photos.id = files.photo_id").
Where("photos.deleted_at IS NULL").
Where("files.file_missing = 0").
Where(where, f.Photos, f.Places, f.Files, f.Files, f.Files, f.Albums, f.Subjects, f.Labels, f.Labels).
Group("files.id")
if result := s.Scan(&results); result.Error != nil {
return results, result.Error
}
return results, nil
}

View File

@ -14,7 +14,7 @@ func TestPhotoSelection(t *testing.T) {
Photos: []string{},
}
r, err := PhotoSelection(f)
r, err := SelectedPhotos(f)
assert.Equal(t, "no items selected", err.Error())
assert.Empty(t, r)
@ -24,7 +24,7 @@ func TestPhotoSelection(t *testing.T) {
Photos: []string{"pt9jtdre2lvl0yh7", "pt9jtdre2lvl0yh8"},
}
r, err := PhotoSelection(f)
r, err := SelectedPhotos(f)
if err != nil {
t.Fatal(err)
@ -41,7 +41,7 @@ func TestFileSelection(t *testing.T) {
Photos: []string{},
}
r, err := FileSelection(f)
r, err := SelectedFiles(f, FileSelectionAll())
assert.Equal(t, "no items selected", err.Error())
assert.Empty(t, r)
@ -51,7 +51,7 @@ func TestFileSelection(t *testing.T) {
Photos: []string{"pt9jtdre2lvl0yh7", "pt9jtdre2lvl0yh8"},
}
r, err := FileSelection(f)
r, err := SelectedFiles(f, FileSelectionAll())
if err != nil {
t.Fatal(err)

View File

@ -21,7 +21,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package query

View File

@ -24,7 +24,7 @@ Feel free to send an e-mail to hello@photoprism.app if you have questions,
want to support our work, or just want to say hello.
Additional information can be found in our Developer Guide:
https://docs.photoprism.app/developer-guide/
<https://docs.photoprism.app/developer-guide/>
*/
package remote

Some files were not shown because too many files have changed in this diff Show More