remove class vue dep (2)
parent
07379d836c
commit
8d79151a30
|
@ -67,8 +67,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Emit, Mixins, Prop, Watch } from "vue-property-decorator";
|
import { defineComponent, PropType } from "vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
|
|
||||||
import { getPreviewUrl } from "../../services/FileUtils";
|
import { getPreviewUrl } from "../../services/FileUtils";
|
||||||
import { IDay, IPhoto } from "../../types";
|
import { IDay, IPhoto } from "../../types";
|
||||||
|
@ -80,38 +79,48 @@ import Star from "vue-material-design-icons/Star.vue";
|
||||||
import Video from "vue-material-design-icons/PlayCircleOutline.vue";
|
import Video from "vue-material-design-icons/PlayCircleOutline.vue";
|
||||||
import LivePhoto from "vue-material-design-icons/MotionPlayOutline.vue";
|
import LivePhoto from "vue-material-design-icons/MotionPlayOutline.vue";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "Photo",
|
||||||
components: {
|
components: {
|
||||||
CheckCircle,
|
CheckCircle,
|
||||||
Video,
|
Video,
|
||||||
Star,
|
Star,
|
||||||
LivePhoto,
|
LivePhoto,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class Photo extends Mixins(GlobalMixin) {
|
|
||||||
private touchTimer = 0;
|
|
||||||
private src = null;
|
|
||||||
private hasFaceRect = false;
|
|
||||||
|
|
||||||
@Prop() data: IPhoto;
|
props: {
|
||||||
@Prop() day: IDay;
|
data: {
|
||||||
|
type: Object as PropType<IPhoto>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
day: {
|
||||||
|
type: Object as PropType<IDay>,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
@Emit("select") emitSelect(data: IPhoto) {}
|
data() {
|
||||||
|
return {
|
||||||
|
touchTimer: 0,
|
||||||
|
src: null,
|
||||||
|
hasFaceRect: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
@Watch("data")
|
watch: {
|
||||||
onDataChange(newData: IPhoto, oldData: IPhoto) {
|
data(newData: IPhoto, oldData: IPhoto) {
|
||||||
// Copy flags relevant to this component
|
// Copy flags relevant to this component
|
||||||
if (oldData && newData) {
|
if (oldData && newData) {
|
||||||
newData.flag |=
|
newData.flag |=
|
||||||
oldData.flag & (this.c.FLAG_SELECTED | this.c.FLAG_LOAD_FAIL);
|
oldData.flag & (this.c.FLAG_SELECTED | this.c.FLAG_LOAD_FAIL);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
@Watch("data.etag")
|
"data.etag": function () {
|
||||||
onEtagChange() {
|
this.hasFaceRect = false;
|
||||||
this.hasFaceRect = false;
|
this.refresh();
|
||||||
this.refresh();
|
},
|
||||||
}
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.hasFaceRect = false;
|
this.hasFaceRect = false;
|
||||||
|
@ -122,136 +131,144 @@ export default class Photo extends Mixins(GlobalMixin) {
|
||||||
if (video) {
|
if (video) {
|
||||||
utils.setupLivePhotoHooks(video);
|
utils.setupLivePhotoHooks(video);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
get videoDuration() {
|
|
||||||
if (this.data.video_duration) {
|
|
||||||
return utils.getDurationStr(this.data.video_duration);
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
get videoUrl() {
|
|
||||||
if (this.data.liveid) {
|
|
||||||
return utils.getLivePhotoVideoUrl(this.data, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async refresh() {
|
|
||||||
this.src = await this.getSrc();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get src for image to show */
|
|
||||||
async getSrc() {
|
|
||||||
if (this.data.flag & this.c.FLAG_PLACEHOLDER) {
|
|
||||||
return null;
|
|
||||||
} else if (this.data.flag & this.c.FLAG_LOAD_FAIL) {
|
|
||||||
return errorsvg;
|
|
||||||
} else {
|
|
||||||
return this.url();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Get url of the photo */
|
|
||||||
url() {
|
|
||||||
let base = 256;
|
|
||||||
|
|
||||||
// Check if displayed size is larger than the image
|
|
||||||
if (this.data.dispH > base * 0.9 && this.data.dispW > base * 0.9) {
|
|
||||||
// Get a bigger image
|
|
||||||
// 1. No trickery here, just get one size bigger. This is to
|
|
||||||
// ensure that the images can be cached even after reflow.
|
|
||||||
// 2. Nextcloud only allows 4**x sized images, so technically
|
|
||||||
// this ends up being equivalent to 1024x1024.
|
|
||||||
base = 512;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Make the shorter dimension equal to base
|
|
||||||
let size = base;
|
|
||||||
if (this.data.w && this.data.h) {
|
|
||||||
size =
|
|
||||||
Math.floor(
|
|
||||||
(base * Math.max(this.data.w, this.data.h)) /
|
|
||||||
Math.min(this.data.w, this.data.h)
|
|
||||||
) - 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
return getPreviewUrl(this.data, false, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set src with overlay face rect */
|
|
||||||
async addFaceRect() {
|
|
||||||
if (!this.data.facerect || this.hasFaceRect) return;
|
|
||||||
this.hasFaceRect = true;
|
|
||||||
|
|
||||||
const canvas = document.createElement("canvas");
|
|
||||||
const context = canvas.getContext("2d");
|
|
||||||
const img = this.$refs.img as HTMLImageElement;
|
|
||||||
|
|
||||||
canvas.width = img.naturalWidth;
|
|
||||||
canvas.height = img.naturalHeight;
|
|
||||||
context.drawImage(img, 0, 0);
|
|
||||||
context.strokeStyle = "#00ff00";
|
|
||||||
context.lineWidth = 2;
|
|
||||||
context.strokeRect(
|
|
||||||
this.data.facerect.x * img.naturalWidth,
|
|
||||||
this.data.facerect.y * img.naturalHeight,
|
|
||||||
this.data.facerect.w * img.naturalWidth,
|
|
||||||
this.data.facerect.h * img.naturalHeight
|
|
||||||
);
|
|
||||||
|
|
||||||
canvas.toBlob(
|
|
||||||
(blob) => {
|
|
||||||
this.src = URL.createObjectURL(blob);
|
|
||||||
},
|
|
||||||
"image/jpeg",
|
|
||||||
0.95
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Post load tasks */
|
|
||||||
load() {
|
|
||||||
this.addFaceRect();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Error in loading image */
|
|
||||||
error(e: any) {
|
|
||||||
this.data.flag |= this.c.FLAG_LOAD_FAIL;
|
|
||||||
this.refresh();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Clear timers */
|
/** Clear timers */
|
||||||
beforeUnmount() {
|
beforeUnmount() {
|
||||||
clearTimeout(this.touchTimer);
|
clearTimeout(this.touchTimer);
|
||||||
}
|
},
|
||||||
|
|
||||||
toggleSelect() {
|
computed: {
|
||||||
if (this.data.flag & this.c.FLAG_PLACEHOLDER) return;
|
videoDuration() {
|
||||||
this.emitSelect(this.data);
|
if (this.data.video_duration) {
|
||||||
}
|
return utils.getDurationStr(this.data.video_duration);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
},
|
||||||
|
|
||||||
contextmenu(e: Event) {
|
videoUrl() {
|
||||||
e.preventDefault();
|
if (this.data.liveid) {
|
||||||
e.stopPropagation();
|
return utils.getLivePhotoVideoUrl(this.data, true);
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/** Start preview video */
|
methods: {
|
||||||
playVideo() {
|
emitSelect(data: IPhoto) {
|
||||||
if (this.$refs.video && !(this.data.flag & this.c.FLAG_SELECTED)) {
|
this.$emit("select", data);
|
||||||
const video = this.$refs.video as HTMLVideoElement;
|
},
|
||||||
video.currentTime = 0;
|
|
||||||
video.play();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Stop preview video */
|
async refresh() {
|
||||||
stopVideo() {
|
this.src = await this.getSrc();
|
||||||
if (this.$refs.video) {
|
},
|
||||||
const video = this.$refs.video as HTMLVideoElement;
|
|
||||||
video.pause();
|
/** Get src for image to show */
|
||||||
}
|
async getSrc() {
|
||||||
}
|
if (this.data.flag & this.c.FLAG_PLACEHOLDER) {
|
||||||
}
|
return null;
|
||||||
|
} else if (this.data.flag & this.c.FLAG_LOAD_FAIL) {
|
||||||
|
return errorsvg;
|
||||||
|
} else {
|
||||||
|
return this.url();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Get url of the photo */
|
||||||
|
url() {
|
||||||
|
let base = 256;
|
||||||
|
|
||||||
|
// Check if displayed size is larger than the image
|
||||||
|
if (this.data.dispH > base * 0.9 && this.data.dispW > base * 0.9) {
|
||||||
|
// Get a bigger image
|
||||||
|
// 1. No trickery here, just get one size bigger. This is to
|
||||||
|
// ensure that the images can be cached even after reflow.
|
||||||
|
// 2. Nextcloud only allows 4**x sized images, so technically
|
||||||
|
// this ends up being equivalent to 1024x1024.
|
||||||
|
base = 512;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make the shorter dimension equal to base
|
||||||
|
let size = base;
|
||||||
|
if (this.data.w && this.data.h) {
|
||||||
|
size =
|
||||||
|
Math.floor(
|
||||||
|
(base * Math.max(this.data.w, this.data.h)) /
|
||||||
|
Math.min(this.data.w, this.data.h)
|
||||||
|
) - 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPreviewUrl(this.data, false, size);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Set src with overlay face rect */
|
||||||
|
async addFaceRect() {
|
||||||
|
if (!this.data.facerect || this.hasFaceRect) return;
|
||||||
|
this.hasFaceRect = true;
|
||||||
|
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
const img = this.$refs.img as HTMLImageElement;
|
||||||
|
|
||||||
|
canvas.width = img.naturalWidth;
|
||||||
|
canvas.height = img.naturalHeight;
|
||||||
|
context.drawImage(img, 0, 0);
|
||||||
|
context.strokeStyle = "#00ff00";
|
||||||
|
context.lineWidth = 2;
|
||||||
|
context.strokeRect(
|
||||||
|
this.data.facerect.x * img.naturalWidth,
|
||||||
|
this.data.facerect.y * img.naturalHeight,
|
||||||
|
this.data.facerect.w * img.naturalWidth,
|
||||||
|
this.data.facerect.h * img.naturalHeight
|
||||||
|
);
|
||||||
|
|
||||||
|
canvas.toBlob(
|
||||||
|
(blob) => {
|
||||||
|
this.src = URL.createObjectURL(blob);
|
||||||
|
},
|
||||||
|
"image/jpeg",
|
||||||
|
0.95
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Post load tasks */
|
||||||
|
load() {
|
||||||
|
this.addFaceRect();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Error in loading image */
|
||||||
|
error(e: any) {
|
||||||
|
this.data.flag |= this.c.FLAG_LOAD_FAIL;
|
||||||
|
this.refresh();
|
||||||
|
},
|
||||||
|
|
||||||
|
toggleSelect() {
|
||||||
|
if (this.data.flag & this.c.FLAG_PLACEHOLDER) return;
|
||||||
|
this.emitSelect(this.data);
|
||||||
|
},
|
||||||
|
|
||||||
|
contextmenu(e: Event) {
|
||||||
|
e.preventDefault();
|
||||||
|
e.stopPropagation();
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Start preview video */
|
||||||
|
playVideo() {
|
||||||
|
if (this.$refs.video && !(this.data.flag & this.c.FLAG_SELECTED)) {
|
||||||
|
const video = this.$refs.video as HTMLVideoElement;
|
||||||
|
video.currentTime = 0;
|
||||||
|
video.play();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/** Stop preview video */
|
||||||
|
stopVideo() {
|
||||||
|
if (this.$refs.video) {
|
||||||
|
const video = this.$refs.video as HTMLVideoElement;
|
||||||
|
video.pause();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -29,99 +29,111 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Prop, Mixins, Emit } from "vue-property-decorator";
|
import { defineComponent, PropType } from "vue";
|
||||||
|
|
||||||
import { IAlbum, ITag } from "../../types";
|
import { IAlbum, ITag } from "../../types";
|
||||||
import { getPreviewUrl } from "../../services/FileUtils";
|
import { getPreviewUrl } from "../../services/FileUtils";
|
||||||
import { getCurrentUser } from "@nextcloud/auth";
|
import { getCurrentUser } from "@nextcloud/auth";
|
||||||
|
|
||||||
import NcCounterBubble from "@nextcloud/vue/dist/Components/NcCounterBubble";
|
import NcCounterBubble from "@nextcloud/vue/dist/Components/NcCounterBubble";
|
||||||
|
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
import { constants } from "../../services/Utils";
|
import { constants } from "../../services/Utils";
|
||||||
import { API } from "../../services/API";
|
import { API } from "../../services/API";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "Tag",
|
||||||
components: {
|
components: {
|
||||||
NcCounterBubble,
|
NcCounterBubble,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class Tag extends Mixins(GlobalMixin) {
|
|
||||||
@Prop() data: ITag;
|
|
||||||
@Prop() noNavigate: boolean;
|
|
||||||
|
|
||||||
/**
|
props: {
|
||||||
* Open tag event
|
data: {
|
||||||
* Unless noNavigate is set, the tag will be opened
|
type: Object as PropType<ITag>,
|
||||||
*/
|
required: true,
|
||||||
@Emit("open")
|
},
|
||||||
openTag(tag: ITag) {}
|
noNavigate: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
get previewUrl() {
|
computed: {
|
||||||
if (this.face) {
|
previewUrl() {
|
||||||
return API.FACE_PREVIEW(this.faceApp, this.face.fileid);
|
if (this.face) {
|
||||||
}
|
return API.FACE_PREVIEW(this.faceApp, this.face.fileid);
|
||||||
|
}
|
||||||
|
|
||||||
if (this.album) {
|
if (this.album) {
|
||||||
const mock = { fileid: this.album.last_added_photo, etag: "", flag: 0 };
|
const mock = { fileid: this.album.last_added_photo, etag: "", flag: 0 };
|
||||||
return getPreviewUrl(mock, true, 512);
|
return getPreviewUrl(mock, true, 512);
|
||||||
}
|
}
|
||||||
|
|
||||||
return API.TAG_PREVIEW(this.data.name);
|
return API.TAG_PREVIEW(this.data.name);
|
||||||
}
|
},
|
||||||
|
|
||||||
get subtitle() {
|
subtitle() {
|
||||||
if (this.album && this.album.user !== getCurrentUser()?.uid) {
|
if (this.album && this.album.user !== getCurrentUser()?.uid) {
|
||||||
return `(${this.album.user})`;
|
return `(${this.album.user})`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return "";
|
return "";
|
||||||
}
|
},
|
||||||
|
|
||||||
get face() {
|
face() {
|
||||||
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNIZE ||
|
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNIZE ||
|
||||||
this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
||||||
? this.data
|
? this.data
|
||||||
: null;
|
: null;
|
||||||
}
|
},
|
||||||
|
|
||||||
get faceApp() {
|
faceApp() {
|
||||||
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
return this.data.flag & constants.c.FLAG_IS_FACE_RECOGNITION
|
||||||
? "facerecognition"
|
? "facerecognition"
|
||||||
: "recognize";
|
: "recognize";
|
||||||
}
|
},
|
||||||
|
|
||||||
get album() {
|
album() {
|
||||||
return this.data.flag & constants.c.FLAG_IS_ALBUM
|
return this.data.flag & constants.c.FLAG_IS_ALBUM
|
||||||
? <IAlbum>this.data
|
? <IAlbum>this.data
|
||||||
: null;
|
: null;
|
||||||
}
|
},
|
||||||
|
|
||||||
/** Target URL to navigate to */
|
/** Target URL to navigate to */
|
||||||
get target() {
|
target() {
|
||||||
if (this.noNavigate) return {};
|
if (this.noNavigate) return {};
|
||||||
|
|
||||||
if (this.face) {
|
if (this.face) {
|
||||||
const name = this.face.name || this.face.fileid.toString();
|
const name = this.face.name || this.face.fileid.toString();
|
||||||
const user = this.face.user_id;
|
const user = this.face.user_id;
|
||||||
return { name: this.faceApp, params: { name, user } };
|
return { name: this.faceApp, params: { name, user } };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.album) {
|
if (this.album) {
|
||||||
const user = this.album.user;
|
const user = this.album.user;
|
||||||
const name = this.album.name;
|
const name = this.album.name;
|
||||||
return { name: "albums", params: { user, name } };
|
return { name: "albums", params: { user, name } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return { name: "tags", params: { name: this.data.name } };
|
return { name: "tags", params: { name: this.data.name } };
|
||||||
}
|
},
|
||||||
|
|
||||||
get error() {
|
error() {
|
||||||
return (
|
return (
|
||||||
Boolean(this.data.flag & this.c.FLAG_LOAD_FAIL) ||
|
Boolean(this.data.flag & this.c.FLAG_LOAD_FAIL) ||
|
||||||
Boolean(this.album && this.album.last_added_photo <= 0)
|
Boolean(this.album && this.album.last_added_photo <= 0)
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
methods: {
|
||||||
|
/**
|
||||||
|
* Open tag event
|
||||||
|
* Unless noNavigate is set, the tag will be opened
|
||||||
|
*/
|
||||||
|
openTag(tag: ITag) {
|
||||||
|
this.$emit("open", tag);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -20,8 +20,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Emit, Mixins } from "vue-property-decorator";
|
import { defineComponent } from "vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
|
|
||||||
import * as dav from "../../services/DavRequests";
|
import * as dav from "../../services/DavRequests";
|
||||||
import { showInfo } from "@nextcloud/dialogs";
|
import { showInfo } from "@nextcloud/dialogs";
|
||||||
|
@ -30,58 +29,65 @@ import { IAlbum, IPhoto } from "../../types";
|
||||||
import AlbumPicker from "./AlbumPicker.vue";
|
import AlbumPicker from "./AlbumPicker.vue";
|
||||||
import Modal from "./Modal.vue";
|
import Modal from "./Modal.vue";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "AddToAlbumModal",
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
AlbumPicker,
|
AlbumPicker,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class AddToAlbumModal extends Mixins(GlobalMixin) {
|
|
||||||
private show = false;
|
|
||||||
private photos: IPhoto[] = [];
|
|
||||||
private photosDone: number = 0;
|
|
||||||
private processing: boolean = false;
|
|
||||||
|
|
||||||
public open(photos: IPhoto[]) {
|
data() {
|
||||||
this.photosDone = 0;
|
return {
|
||||||
this.processing = false;
|
show: false,
|
||||||
this.show = true;
|
photos: [] as IPhoto[],
|
||||||
this.photos = photos;
|
photosDone: 0,
|
||||||
}
|
processing: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
@Emit("added")
|
methods: {
|
||||||
public added(photos: IPhoto[]) {}
|
open(photos: IPhoto[]) {
|
||||||
|
this.photosDone = 0;
|
||||||
|
this.processing = false;
|
||||||
|
this.show = true;
|
||||||
|
this.photos = photos;
|
||||||
|
},
|
||||||
|
|
||||||
@Emit("close")
|
added(photos: IPhoto[]) {
|
||||||
public close() {
|
this.$emit("added", photos);
|
||||||
this.photos = [];
|
},
|
||||||
this.processing = false;
|
|
||||||
this.show = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async selectAlbum(album: IAlbum) {
|
close() {
|
||||||
const name = album.name || album.album_id.toString();
|
this.photos = [];
|
||||||
const gen = dav.addToAlbum(album.user, name, this.photos);
|
this.processing = false;
|
||||||
this.processing = true;
|
this.show = false;
|
||||||
|
this.$emit("close");
|
||||||
|
},
|
||||||
|
|
||||||
for await (const fids of gen) {
|
async selectAlbum(album: IAlbum) {
|
||||||
this.photosDone += fids.filter((f) => f).length;
|
const name = album.name || album.album_id.toString();
|
||||||
this.added(this.photos.filter((p) => fids.includes(p.fileid)));
|
const gen = dav.addToAlbum(album.user, name, this.photos);
|
||||||
}
|
this.processing = true;
|
||||||
|
|
||||||
const n = this.photosDone;
|
for await (const fids of gen) {
|
||||||
showInfo(
|
this.photosDone += fids.filter((f) => f).length;
|
||||||
this.n(
|
this.added(this.photos.filter((p) => fids.includes(p.fileid)));
|
||||||
"memories",
|
}
|
||||||
"{n} item added to album",
|
|
||||||
"{n} items added to album",
|
const n = this.photosDone;
|
||||||
n,
|
showInfo(
|
||||||
{ n }
|
this.n(
|
||||||
)
|
"memories",
|
||||||
);
|
"{n} item added to album",
|
||||||
this.close();
|
"{n} items added to album",
|
||||||
}
|
n,
|
||||||
}
|
{ n }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
this.close();
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -141,8 +141,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Mixins, Prop, Watch } from "vue-property-decorator";
|
import { defineComponent, PropType } from "vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
|
|
||||||
import Magnify from "vue-material-design-icons/Magnify.vue";
|
import Magnify from "vue-material-design-icons/Magnify.vue";
|
||||||
import Close from "vue-material-design-icons/Close.vue";
|
import Close from "vue-material-design-icons/Close.vue";
|
||||||
|
@ -173,7 +172,8 @@ type Collaborator = {
|
||||||
type: Type;
|
type: Type;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "AddToAlbumModal",
|
||||||
components: {
|
components: {
|
||||||
Magnify,
|
Magnify,
|
||||||
Close,
|
Close,
|
||||||
|
@ -188,250 +188,270 @@ type Collaborator = {
|
||||||
NcPopover,
|
NcPopover,
|
||||||
NcEmptyContent,
|
NcEmptyContent,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class AddToAlbumModal extends Mixins(GlobalMixin) {
|
|
||||||
@Prop() private albumName: string;
|
|
||||||
@Prop() collaborators: Collaborator[];
|
|
||||||
@Prop() allowPublicLink: boolean;
|
|
||||||
|
|
||||||
private searchText = "";
|
props: {
|
||||||
private availableCollaborators: { [key: string]: Collaborator } = {};
|
albumName: {
|
||||||
private selectedCollaboratorsKeys: string[] = [];
|
type: String,
|
||||||
private currentSearchResults = [];
|
required: true,
|
||||||
private loadingAlbum = false;
|
},
|
||||||
private errorFetchingAlbum = null;
|
collaborators: {
|
||||||
private loadingCollaborators = false;
|
type: Array as PropType<Collaborator[]>,
|
||||||
private errorFetchingCollaborators = null;
|
required: true,
|
||||||
private randomId = Math.random().toString().substring(2, 10);
|
},
|
||||||
private publicLinkCopied = false;
|
allowPublicLink: {
|
||||||
private config = {
|
type: Boolean,
|
||||||
minSearchStringLength:
|
required: false,
|
||||||
parseInt(window.OC.config["sharing.minSearchStringLength"], 10) || 0,
|
},
|
||||||
};
|
},
|
||||||
|
|
||||||
get searchResults(): string[] {
|
data() {
|
||||||
return this.currentSearchResults
|
return {
|
||||||
.filter(({ id }) => id !== getCurrentUser()?.uid)
|
searchText: "",
|
||||||
.map(({ type, id }) => `${type}:${id}`)
|
availableCollaborators: {} as { [key: string]: Collaborator },
|
||||||
.filter(
|
selectedCollaboratorsKeys: [] as string[],
|
||||||
|
currentSearchResults: [] as Collaborator[],
|
||||||
|
loadingAlbum: false,
|
||||||
|
errorFetchingAlbum: null,
|
||||||
|
loadingCollaborators: false,
|
||||||
|
errorFetchingCollaborators: null,
|
||||||
|
randomId: Math.random().toString().substring(2, 10),
|
||||||
|
publicLinkCopied: false,
|
||||||
|
config: {
|
||||||
|
minSearchStringLength:
|
||||||
|
parseInt(window.OC.config["sharing.minSearchStringLength"], 10) || 0,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
computed: {
|
||||||
|
searchResults(): string[] {
|
||||||
|
return this.currentSearchResults
|
||||||
|
.filter(({ id }) => id !== getCurrentUser()?.uid)
|
||||||
|
.map(({ type, id }) => `${type}:${id}`)
|
||||||
|
.filter(
|
||||||
|
(collaboratorKey) =>
|
||||||
|
!this.selectedCollaboratorsKeys.includes(collaboratorKey)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
listableSelectedCollaboratorsKeys(): string[] {
|
||||||
|
return this.selectedCollaboratorsKeys.filter(
|
||||||
(collaboratorKey) =>
|
(collaboratorKey) =>
|
||||||
!this.selectedCollaboratorsKeys.includes(collaboratorKey)
|
this.availableCollaborators[collaboratorKey].type !==
|
||||||
|
Type.SHARE_TYPE_LINK
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
|
|
||||||
get listableSelectedCollaboratorsKeys(): string[] {
|
selectedCollaborators(): Collaborator[] {
|
||||||
return this.selectedCollaboratorsKeys.filter(
|
return this.selectedCollaboratorsKeys.map(
|
||||||
(collaboratorKey) =>
|
(collaboratorKey) => this.availableCollaborators[collaboratorKey]
|
||||||
this.availableCollaborators[collaboratorKey].type !==
|
);
|
||||||
Type.SHARE_TYPE_LINK
|
},
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get selectedCollaborators(): Collaborator[] {
|
isPublicLinkSelected(): boolean {
|
||||||
return this.selectedCollaboratorsKeys.map(
|
return this.selectedCollaboratorsKeys.includes(`${Type.SHARE_TYPE_LINK}`);
|
||||||
(collaboratorKey) => this.availableCollaborators[collaboratorKey]
|
},
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
get isPublicLinkSelected(): boolean {
|
publicLink(): Collaborator {
|
||||||
return this.selectedCollaboratorsKeys.includes(`${Type.SHARE_TYPE_LINK}`);
|
return this.availableCollaborators[Type.SHARE_TYPE_LINK];
|
||||||
}
|
},
|
||||||
|
},
|
||||||
get publicLink(): Collaborator {
|
watch: {
|
||||||
return this.availableCollaborators[Type.SHARE_TYPE_LINK];
|
collaborators(collaborators) {
|
||||||
}
|
this.populateCollaborators(collaborators);
|
||||||
|
},
|
||||||
@Watch("collaborators")
|
},
|
||||||
collaboratorsChanged(collaborators) {
|
|
||||||
this.populateCollaborators(collaborators);
|
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.searchCollaborators();
|
this.searchCollaborators();
|
||||||
this.populateCollaborators(this.collaborators);
|
this.populateCollaborators(this.collaborators);
|
||||||
}
|
},
|
||||||
|
|
||||||
/**
|
methods: {
|
||||||
* Fetch possible collaborators.
|
/**
|
||||||
*/
|
* Fetch possible collaborators.
|
||||||
async searchCollaborators() {
|
*/
|
||||||
if (this.searchText.length >= 1) {
|
async searchCollaborators() {
|
||||||
(<any>this.$refs.popover).$refs.popover.show();
|
if (this.searchText.length >= 1) {
|
||||||
}
|
(<any>this.$refs.popover).$refs.popover.show();
|
||||||
|
|
||||||
try {
|
|
||||||
if (this.searchText.length < this.config.minSearchStringLength) {
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadingCollaborators = true;
|
try {
|
||||||
const response = await axios.get(
|
if (this.searchText.length < this.config.minSearchStringLength) {
|
||||||
generateOcsUrl("core/autocomplete/get"),
|
return;
|
||||||
{
|
|
||||||
params: {
|
|
||||||
search: this.searchText,
|
|
||||||
itemType: "share-recipients",
|
|
||||||
shareTypes: [Type.SHARE_TYPE_USER, Type.SHARE_TYPE_GROUP],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.loadingCollaborators = true;
|
||||||
|
const response = await axios.get(
|
||||||
|
generateOcsUrl("core/autocomplete/get"),
|
||||||
|
{
|
||||||
|
params: {
|
||||||
|
search: this.searchText,
|
||||||
|
itemType: "share-recipients",
|
||||||
|
shareTypes: [Type.SHARE_TYPE_USER, Type.SHARE_TYPE_GROUP],
|
||||||
|
},
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.currentSearchResults = response.data.ocs.data.map(
|
||||||
|
(collaborator) => {
|
||||||
|
switch (collaborator.source) {
|
||||||
|
case "users":
|
||||||
|
return {
|
||||||
|
id: collaborator.id,
|
||||||
|
label: collaborator.label,
|
||||||
|
type: Type.SHARE_TYPE_USER,
|
||||||
|
};
|
||||||
|
case "groups":
|
||||||
|
return {
|
||||||
|
id: collaborator.id,
|
||||||
|
label: collaborator.label,
|
||||||
|
type: Type.SHARE_TYPE_GROUP,
|
||||||
|
};
|
||||||
|
default:
|
||||||
|
throw new Error(
|
||||||
|
`Invalid collaborator source ${collaborator.source}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
this.availableCollaborators = {
|
||||||
|
...this.availableCollaborators,
|
||||||
|
...this.currentSearchResults.reduce(this.indexCollaborators, {}),
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.errorFetchingCollaborators = error;
|
||||||
|
showError(this.t("photos", "Failed to fetch collaborators list."));
|
||||||
|
} finally {
|
||||||
|
this.loadingCollaborators = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Populate selectedCollaboratorsKeys and availableCollaborators.
|
||||||
|
*/
|
||||||
|
populateCollaborators(collaborators: Collaborator[]) {
|
||||||
|
const initialCollaborators = collaborators.reduce(
|
||||||
|
this.indexCollaborators,
|
||||||
|
{}
|
||||||
);
|
);
|
||||||
|
this.selectedCollaboratorsKeys = Object.keys(initialCollaborators);
|
||||||
this.currentSearchResults = response.data.ocs.data.map((collaborator) => {
|
|
||||||
switch (collaborator.source) {
|
|
||||||
case "users":
|
|
||||||
return {
|
|
||||||
id: collaborator.id,
|
|
||||||
label: collaborator.label,
|
|
||||||
type: Type.SHARE_TYPE_USER,
|
|
||||||
};
|
|
||||||
case "groups":
|
|
||||||
return {
|
|
||||||
id: collaborator.id,
|
|
||||||
label: collaborator.label,
|
|
||||||
type: Type.SHARE_TYPE_GROUP,
|
|
||||||
};
|
|
||||||
default:
|
|
||||||
throw new Error(
|
|
||||||
`Invalid collaborator source ${collaborator.source}`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
this.availableCollaborators = {
|
this.availableCollaborators = {
|
||||||
|
3: {
|
||||||
|
id: "",
|
||||||
|
label: this.t("photos", "Public link"),
|
||||||
|
type: Type.SHARE_TYPE_LINK,
|
||||||
|
},
|
||||||
...this.availableCollaborators,
|
...this.availableCollaborators,
|
||||||
...this.currentSearchResults.reduce(this.indexCollaborators, {}),
|
...initialCollaborators,
|
||||||
};
|
};
|
||||||
} catch (error) {
|
},
|
||||||
this.errorFetchingCollaborators = error;
|
|
||||||
showError(this.t("photos", "Failed to fetch collaborators list."));
|
|
||||||
} finally {
|
|
||||||
this.loadingCollaborators = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Populate selectedCollaboratorsKeys and availableCollaborators.
|
* @param {Object<string, Collaborator>} collaborators - Index of collaborators
|
||||||
*/
|
* @param {Collaborator} collaborator - A collaborator
|
||||||
populateCollaborators(collaborators: Collaborator[]) {
|
*/
|
||||||
const initialCollaborators = collaborators.reduce(
|
indexCollaborators(
|
||||||
this.indexCollaborators,
|
collaborators: { [s: string]: Collaborator },
|
||||||
{}
|
collaborator: Collaborator
|
||||||
);
|
) {
|
||||||
this.selectedCollaboratorsKeys = Object.keys(initialCollaborators);
|
return {
|
||||||
this.availableCollaborators = {
|
...collaborators,
|
||||||
3: {
|
[`${collaborator.type}${
|
||||||
|
collaborator.type === Type.SHARE_TYPE_LINK ? "" : ":"
|
||||||
|
}${collaborator.type === Type.SHARE_TYPE_LINK ? "" : collaborator.id}`]:
|
||||||
|
collaborator,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
|
async createPublicLinkForAlbum() {
|
||||||
|
this.selectEntity(`${Type.SHARE_TYPE_LINK}`);
|
||||||
|
await this.updateAlbumCollaborators();
|
||||||
|
try {
|
||||||
|
this.loadingAlbum = true;
|
||||||
|
this.errorFetchingAlbum = null;
|
||||||
|
|
||||||
|
const album = await dav.getAlbum(
|
||||||
|
getCurrentUser()?.uid.toString(),
|
||||||
|
this.albumName
|
||||||
|
);
|
||||||
|
this.populateCollaborators(album.collaborators);
|
||||||
|
} catch (error) {
|
||||||
|
if (error.response?.status === 404) {
|
||||||
|
this.errorFetchingAlbum = 404;
|
||||||
|
} else {
|
||||||
|
this.errorFetchingAlbum = error;
|
||||||
|
}
|
||||||
|
|
||||||
|
showError(this.t("photos", "Failed to fetch album."));
|
||||||
|
} finally {
|
||||||
|
this.loadingAlbum = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async deletePublicLink() {
|
||||||
|
this.unselectEntity(`${Type.SHARE_TYPE_LINK}`);
|
||||||
|
this.availableCollaborators[3] = {
|
||||||
id: "",
|
id: "",
|
||||||
label: this.t("photos", "Public link"),
|
label: this.t("photos", "Public link"),
|
||||||
type: Type.SHARE_TYPE_LINK,
|
type: Type.SHARE_TYPE_LINK,
|
||||||
},
|
};
|
||||||
...this.availableCollaborators,
|
this.publicLinkCopied = false;
|
||||||
...initialCollaborators,
|
await this.updateAlbumCollaborators();
|
||||||
};
|
},
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
async updateAlbumCollaborators() {
|
||||||
* @param {Object<string, Collaborator>} collaborators - Index of collaborators
|
try {
|
||||||
* @param {Collaborator} collaborator - A collaborator
|
const album = await dav.getAlbum(
|
||||||
*/
|
getCurrentUser()?.uid.toString(),
|
||||||
indexCollaborators(
|
this.albumName
|
||||||
collaborators: { [s: string]: Collaborator },
|
);
|
||||||
collaborator: Collaborator
|
await dav.updateAlbum(album, {
|
||||||
) {
|
albumName: this.albumName,
|
||||||
return {
|
properties: {
|
||||||
...collaborators,
|
collaborators: this.selectedCollaborators,
|
||||||
[`${collaborator.type}${
|
},
|
||||||
collaborator.type === Type.SHARE_TYPE_LINK ? "" : ":"
|
});
|
||||||
}${collaborator.type === Type.SHARE_TYPE_LINK ? "" : collaborator.id}`]:
|
} catch (error) {
|
||||||
collaborator,
|
showError(this.t("photos", "Failed to update album."));
|
||||||
};
|
} finally {
|
||||||
}
|
this.loadingAlbum = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
async createPublicLinkForAlbum() {
|
async copyPublicLink() {
|
||||||
this.selectEntity(`${Type.SHARE_TYPE_LINK}`);
|
await navigator.clipboard.writeText(
|
||||||
await this.updateAlbumCollaborators();
|
`${window.location.protocol}//${window.location.host}${generateUrl(
|
||||||
try {
|
`apps/photos/public/${this.publicLink.id}`
|
||||||
this.loadingAlbum = true;
|
)}`
|
||||||
this.errorFetchingAlbum = null;
|
|
||||||
|
|
||||||
const album = await dav.getAlbum(
|
|
||||||
getCurrentUser()?.uid.toString(),
|
|
||||||
this.albumName
|
|
||||||
);
|
);
|
||||||
this.populateCollaborators(album.collaborators);
|
this.publicLinkCopied = true;
|
||||||
} catch (error) {
|
setTimeout(() => {
|
||||||
if (error.response?.status === 404) {
|
this.publicLinkCopied = false;
|
||||||
this.errorFetchingAlbum = 404;
|
}, 10000);
|
||||||
} else {
|
},
|
||||||
this.errorFetchingAlbum = error;
|
|
||||||
|
selectEntity(collaboratorKey) {
|
||||||
|
if (this.selectedCollaboratorsKeys.includes(collaboratorKey)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
showError(this.t("photos", "Failed to fetch album."));
|
(<any>this.$refs.popover).$refs.popover.hide();
|
||||||
} finally {
|
this.selectedCollaboratorsKeys.push(collaboratorKey);
|
||||||
this.loadingAlbum = false;
|
},
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async deletePublicLink() {
|
unselectEntity(collaboratorKey) {
|
||||||
this.unselectEntity(`${Type.SHARE_TYPE_LINK}`);
|
const index = this.selectedCollaboratorsKeys.indexOf(collaboratorKey);
|
||||||
this.availableCollaborators[3] = {
|
|
||||||
id: "",
|
|
||||||
label: this.t("photos", "Public link"),
|
|
||||||
type: Type.SHARE_TYPE_LINK,
|
|
||||||
};
|
|
||||||
this.publicLinkCopied = false;
|
|
||||||
await this.updateAlbumCollaborators();
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateAlbumCollaborators() {
|
if (index === -1) {
|
||||||
try {
|
return;
|
||||||
const album = await dav.getAlbum(
|
}
|
||||||
getCurrentUser()?.uid.toString(),
|
|
||||||
this.albumName
|
|
||||||
);
|
|
||||||
await dav.updateAlbum(album, {
|
|
||||||
albumName: this.albumName,
|
|
||||||
properties: {
|
|
||||||
collaborators: this.selectedCollaborators,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
showError(this.t("photos", "Failed to update album."));
|
|
||||||
} finally {
|
|
||||||
this.loadingAlbum = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async copyPublicLink() {
|
this.selectedCollaboratorsKeys.splice(index, 1);
|
||||||
await navigator.clipboard.writeText(
|
},
|
||||||
`${window.location.protocol}//${window.location.host}${generateUrl(
|
},
|
||||||
`apps/photos/public/${this.publicLink.id}`
|
});
|
||||||
)}`
|
|
||||||
);
|
|
||||||
this.publicLinkCopied = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
this.publicLinkCopied = false;
|
|
||||||
}, 10000);
|
|
||||||
}
|
|
||||||
|
|
||||||
selectEntity(collaboratorKey) {
|
|
||||||
if (this.selectedCollaboratorsKeys.includes(collaboratorKey)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
(<any>this.$refs.popover).$refs.popover.hide();
|
|
||||||
this.selectedCollaboratorsKeys.push(collaboratorKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
unselectEntity(collaboratorKey) {
|
|
||||||
const index = this.selectedCollaboratorsKeys.indexOf(collaboratorKey);
|
|
||||||
|
|
||||||
if (index === -1) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.selectedCollaboratorsKeys.splice(index, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.manage-collaborators {
|
.manage-collaborators {
|
||||||
|
|
|
@ -21,8 +21,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Emit, Mixins } from "vue-property-decorator";
|
import { defineComponent } from "vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
|
|
||||||
import { showError } from "@nextcloud/dialogs";
|
import { showError } from "@nextcloud/dialogs";
|
||||||
import * as dav from "../../services/DavRequests";
|
import * as dav from "../../services/DavRequests";
|
||||||
|
@ -30,53 +29,59 @@ import * as dav from "../../services/DavRequests";
|
||||||
import Modal from "./Modal.vue";
|
import Modal from "./Modal.vue";
|
||||||
import AlbumForm from "./AlbumForm.vue";
|
import AlbumForm from "./AlbumForm.vue";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "AlbumCreateModal",
|
||||||
components: {
|
components: {
|
||||||
Modal,
|
Modal,
|
||||||
AlbumForm,
|
AlbumForm,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class AlbumCreateModal extends Mixins(GlobalMixin) {
|
|
||||||
private show = false;
|
|
||||||
private album: any = null;
|
|
||||||
|
|
||||||
/**
|
data() {
|
||||||
* Open the modal
|
return {
|
||||||
* @param edit If true, the modal will be opened in edit mode
|
show: false,
|
||||||
*/
|
album: null as any,
|
||||||
public async open(edit: boolean) {
|
};
|
||||||
if (edit) {
|
},
|
||||||
try {
|
|
||||||
this.album = await dav.getAlbum(
|
methods: {
|
||||||
this.$route.params.user,
|
/**
|
||||||
this.$route.params.name
|
* Open the modal
|
||||||
);
|
* @param edit If true, the modal will be opened in edit mode
|
||||||
} catch (e) {
|
*/
|
||||||
console.error(e);
|
async open(edit: boolean) {
|
||||||
showError(this.t("photos", "Could not load the selected album"));
|
if (edit) {
|
||||||
return;
|
try {
|
||||||
|
this.album = await dav.getAlbum(
|
||||||
|
this.$route.params.user,
|
||||||
|
this.$route.params.name
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showError(this.t("photos", "Could not load the selected album"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.album = null;
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
this.album = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
this.show = true;
|
this.show = true;
|
||||||
}
|
},
|
||||||
|
|
||||||
@Emit("close")
|
close() {
|
||||||
public close() {
|
this.show = false;
|
||||||
this.show = false;
|
this.$emit("close");
|
||||||
}
|
},
|
||||||
|
|
||||||
public done({ album }: any) {
|
done({ album }: any) {
|
||||||
if (!this.album || album.basename !== this.album.basename) {
|
if (!this.album || album.basename !== this.album.basename) {
|
||||||
const user = album.filename.split("/")[2];
|
const user = album.filename.split("/")[2];
|
||||||
const name = album.basename;
|
const name = album.basename;
|
||||||
this.$router.push({ name: "albums", params: { user, name } });
|
this.$router.push({ name: "albums", params: { user, name } });
|
||||||
}
|
}
|
||||||
this.close();
|
this.close();
|
||||||
}
|
},
|
||||||
}
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -23,70 +23,79 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Emit, Mixins, Watch } from "vue-property-decorator";
|
import { defineComponent } from "vue";
|
||||||
|
|
||||||
import NcButton from "@nextcloud/vue/dist/Components/NcButton";
|
import NcButton from "@nextcloud/vue/dist/Components/NcButton";
|
||||||
const NcTextField = () => import("@nextcloud/vue/dist/Components/NcTextField");
|
const NcTextField = () => import("@nextcloud/vue/dist/Components/NcTextField");
|
||||||
import { showError } from "@nextcloud/dialogs";
|
import { showError } from "@nextcloud/dialogs";
|
||||||
import { getCurrentUser } from "@nextcloud/auth";
|
import { getCurrentUser } from "@nextcloud/auth";
|
||||||
import Modal from "./Modal.vue";
|
import Modal from "./Modal.vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
import client from "../../services/DavClient";
|
import client from "../../services/DavClient";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "AlbumDeleteModal",
|
||||||
components: {
|
components: {
|
||||||
NcButton,
|
NcButton,
|
||||||
NcTextField,
|
NcTextField,
|
||||||
Modal,
|
Modal,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class AlbumDeleteModal extends Mixins(GlobalMixin) {
|
|
||||||
private user: string = "";
|
|
||||||
private name: string = "";
|
|
||||||
private show = false;
|
|
||||||
|
|
||||||
@Emit("close")
|
data() {
|
||||||
public close() {
|
return {
|
||||||
this.show = false;
|
show: false,
|
||||||
}
|
user: "",
|
||||||
|
name: "",
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
public open() {
|
watch: {
|
||||||
const user = this.$route.params.user || "";
|
$route: async function (from: any, to: any) {
|
||||||
if (this.$route.params.user !== getCurrentUser()?.uid) {
|
this.refreshParams();
|
||||||
showError(
|
},
|
||||||
this.t("memories", 'Only user "{user}" can delete this album', { user })
|
},
|
||||||
);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.show = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Watch("$route")
|
|
||||||
async routeChange(from: any, to: any) {
|
|
||||||
this.refreshParams();
|
|
||||||
}
|
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.refreshParams();
|
this.refreshParams();
|
||||||
}
|
},
|
||||||
|
|
||||||
public refreshParams() {
|
methods: {
|
||||||
this.user = this.$route.params.user || "";
|
close() {
|
||||||
this.name = this.$route.params.name || "";
|
this.show = false;
|
||||||
}
|
this.$emit("close");
|
||||||
|
},
|
||||||
|
|
||||||
public async save() {
|
open() {
|
||||||
try {
|
const user = this.$route.params.user || "";
|
||||||
await client.deleteFile(`/photos/${this.user}/albums/${this.name}`);
|
if (this.$route.params.user !== getCurrentUser()?.uid) {
|
||||||
this.$router.push({ name: "albums" });
|
showError(
|
||||||
this.close();
|
this.t("memories", 'Only user "{user}" can delete this album', {
|
||||||
} catch (error) {
|
user,
|
||||||
console.log(error);
|
})
|
||||||
showError(
|
);
|
||||||
this.t("photos", "Failed to delete {name}.", {
|
return;
|
||||||
name: this.name,
|
}
|
||||||
})
|
this.show = true;
|
||||||
);
|
},
|
||||||
}
|
|
||||||
}
|
refreshParams() {
|
||||||
}
|
this.user = this.$route.params.user || "";
|
||||||
|
this.name = this.$route.params.name || "";
|
||||||
|
},
|
||||||
|
|
||||||
|
async save() {
|
||||||
|
try {
|
||||||
|
await client.deleteFile(`/photos/${this.user}/albums/${this.name}`);
|
||||||
|
this.$router.push({ name: "albums" });
|
||||||
|
this.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error);
|
||||||
|
showError(
|
||||||
|
this.t("photos", "Failed to delete {name}.", {
|
||||||
|
name: this.name,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
|
@ -97,8 +97,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Emit, Mixins, Prop } from "vue-property-decorator";
|
import { defineComponent, PropType } from "vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
|
|
||||||
import { getCurrentUser } from "@nextcloud/auth";
|
import { getCurrentUser } from "@nextcloud/auth";
|
||||||
import NcButton from "@nextcloud/vue/dist/Components/NcButton";
|
import NcButton from "@nextcloud/vue/dist/Components/NcButton";
|
||||||
|
@ -112,7 +111,8 @@ import AlbumCollaborators from "./AlbumCollaborators.vue";
|
||||||
import Send from "vue-material-design-icons/Send.vue";
|
import Send from "vue-material-design-icons/Send.vue";
|
||||||
import AccountMultiplePlus from "vue-material-design-icons/AccountMultiplePlus.vue";
|
import AccountMultiplePlus from "vue-material-design-icons/AccountMultiplePlus.vue";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "AlbumForm",
|
||||||
components: {
|
components: {
|
||||||
NcButton,
|
NcButton,
|
||||||
NcLoadingIcon,
|
NcLoadingIcon,
|
||||||
|
@ -122,35 +122,48 @@ import AccountMultiplePlus from "vue-material-design-icons/AccountMultiplePlus.v
|
||||||
Send,
|
Send,
|
||||||
AccountMultiplePlus,
|
AccountMultiplePlus,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class AlbumForm extends Mixins(GlobalMixin) {
|
|
||||||
@Prop() private album: any;
|
|
||||||
@Prop() private displayBackButton: boolean;
|
|
||||||
|
|
||||||
private showCollaboratorView = false;
|
props: {
|
||||||
private albumName = "";
|
album: {
|
||||||
private albumLocation = "";
|
type: Object as PropType<any>,
|
||||||
private loading = false;
|
default: null,
|
||||||
|
},
|
||||||
|
displayBackButton: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
data() {
|
||||||
* @return Whether sharing is enabled.
|
return {
|
||||||
*/
|
showCollaboratorView: false,
|
||||||
get editMode(): boolean {
|
albumName: "",
|
||||||
return Boolean(this.album);
|
albumLocation: "",
|
||||||
}
|
loading: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
get saveText(): string {
|
computed: {
|
||||||
return this.editMode
|
/**
|
||||||
? this.t("photos", "Save")
|
* @return Whether sharing is enabled.
|
||||||
: this.t("photos", "Create album");
|
*/
|
||||||
}
|
editMode(): boolean {
|
||||||
|
return Boolean(this.album);
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
saveText(): string {
|
||||||
* @return Whether sharing is enabled.
|
return this.editMode
|
||||||
*/
|
? this.t("photos", "Save")
|
||||||
get sharingEnabled(): boolean {
|
: this.t("photos", "Create album");
|
||||||
return window.OC.Share !== undefined;
|
},
|
||||||
}
|
|
||||||
|
/**
|
||||||
|
* @return Whether sharing is enabled.
|
||||||
|
*/
|
||||||
|
sharingEnabled(): boolean {
|
||||||
|
return window.OC.Share !== undefined;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
if (this.editMode) {
|
if (this.editMode) {
|
||||||
|
@ -158,76 +171,79 @@ export default class AlbumForm extends Mixins(GlobalMixin) {
|
||||||
this.albumLocation = this.album.location;
|
this.albumLocation = this.album.location;
|
||||||
}
|
}
|
||||||
this.$nextTick(() => {
|
this.$nextTick(() => {
|
||||||
(<any>this.$refs.nameInput).$el.getElementsByTagName("input")[0].focus();
|
(<any>this.$refs.nameInput)?.$el.getElementsByTagName("input")[0].focus();
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
|
|
||||||
submit(collaborators = []) {
|
methods: {
|
||||||
if (this.albumName === "" || this.loading) {
|
submit(collaborators = []) {
|
||||||
return;
|
if (this.albumName === "" || this.loading) {
|
||||||
}
|
return;
|
||||||
if (this.editMode) {
|
|
||||||
this.handleUpdateAlbum();
|
|
||||||
} else {
|
|
||||||
this.handleCreateAlbum(collaborators);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleCreateAlbum(collaborators = []) {
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
|
||||||
let album = {
|
|
||||||
basename: this.albumName,
|
|
||||||
filename: `/photos/${getCurrentUser()?.uid}/albums/${this.albumName}`,
|
|
||||||
nbItems: 0,
|
|
||||||
location: this.albumLocation,
|
|
||||||
lastPhoto: -1,
|
|
||||||
date: moment().format("MMMM YYYY"),
|
|
||||||
collaborators,
|
|
||||||
};
|
|
||||||
await dav.createAlbum(album.basename);
|
|
||||||
|
|
||||||
if (this.albumLocation !== "" || collaborators.length !== 0) {
|
|
||||||
album = await dav.updateAlbum(album, {
|
|
||||||
albumName: this.albumName,
|
|
||||||
properties: {
|
|
||||||
location: this.albumLocation,
|
|
||||||
collaborators,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
if (this.editMode) {
|
||||||
this.$emit("done", { album });
|
this.handleUpdateAlbum();
|
||||||
} finally {
|
} else {
|
||||||
this.loading = false;
|
this.handleCreateAlbum(collaborators);
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleUpdateAlbum() {
|
|
||||||
try {
|
|
||||||
this.loading = true;
|
|
||||||
let album = { ...this.album };
|
|
||||||
if (this.album.basename !== this.albumName) {
|
|
||||||
album = await dav.renameAlbum(this.album, {
|
|
||||||
currentAlbumName: this.album.basename,
|
|
||||||
newAlbumName: this.albumName,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
if (this.album.location !== this.albumLocation) {
|
},
|
||||||
album.location = await dav.updateAlbum(this.album, {
|
|
||||||
albumName: this.albumName,
|
|
||||||
properties: { location: this.albumLocation },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
this.$emit("done", { album });
|
|
||||||
} finally {
|
|
||||||
this.loading = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Emit("back")
|
async handleCreateAlbum(collaborators = []) {
|
||||||
back() {}
|
try {
|
||||||
}
|
this.loading = true;
|
||||||
|
let album = {
|
||||||
|
basename: this.albumName,
|
||||||
|
filename: `/photos/${getCurrentUser()?.uid}/albums/${this.albumName}`,
|
||||||
|
nbItems: 0,
|
||||||
|
location: this.albumLocation,
|
||||||
|
lastPhoto: -1,
|
||||||
|
date: moment().format("MMMM YYYY"),
|
||||||
|
collaborators,
|
||||||
|
};
|
||||||
|
await dav.createAlbum(album.basename);
|
||||||
|
|
||||||
|
if (this.albumLocation !== "" || collaborators.length !== 0) {
|
||||||
|
album = await dav.updateAlbum(album, {
|
||||||
|
albumName: this.albumName,
|
||||||
|
properties: {
|
||||||
|
location: this.albumLocation,
|
||||||
|
collaborators,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.$emit("done", { album });
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async handleUpdateAlbum() {
|
||||||
|
try {
|
||||||
|
this.loading = true;
|
||||||
|
let album = { ...this.album };
|
||||||
|
if (this.album.basename !== this.albumName) {
|
||||||
|
album = await dav.renameAlbum(this.album, {
|
||||||
|
currentAlbumName: this.album.basename,
|
||||||
|
newAlbumName: this.albumName,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (this.album.location !== this.albumLocation) {
|
||||||
|
album.location = await dav.updateAlbum(this.album, {
|
||||||
|
albumName: this.albumName,
|
||||||
|
properties: { location: this.albumLocation },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.$emit("done", { album });
|
||||||
|
} finally {
|
||||||
|
this.loading = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
back() {
|
||||||
|
this.$emit("back");
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.album-form {
|
.album-form {
|
||||||
|
|
|
@ -57,8 +57,8 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Emit, Mixins } from "vue-property-decorator";
|
import { defineComponent } from "vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
import { getCurrentUser } from "@nextcloud/auth";
|
import { getCurrentUser } from "@nextcloud/auth";
|
||||||
|
|
||||||
import AlbumForm from "./AlbumForm.vue";
|
import AlbumForm from "./AlbumForm.vue";
|
||||||
|
@ -74,7 +74,8 @@ import { IAlbum, IPhoto } from "../../types";
|
||||||
import axios from "@nextcloud/axios";
|
import axios from "@nextcloud/axios";
|
||||||
import { API } from "../../services/API";
|
import { API } from "../../services/API";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "AlbumPicker",
|
||||||
components: {
|
components: {
|
||||||
AlbumForm,
|
AlbumForm,
|
||||||
Plus,
|
Plus,
|
||||||
|
@ -83,6 +84,7 @@ import { API } from "../../services/API";
|
||||||
NcListItem,
|
NcListItem,
|
||||||
NcLoadingIcon,
|
NcLoadingIcon,
|
||||||
},
|
},
|
||||||
|
|
||||||
filters: {
|
filters: {
|
||||||
toCoverUrl(fileId: string) {
|
toCoverUrl(fileId: string) {
|
||||||
return getPreviewUrl(
|
return getPreviewUrl(
|
||||||
|
@ -94,42 +96,48 @@ import { API } from "../../services/API";
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class AlbumPicker extends Mixins(GlobalMixin) {
|
data() {
|
||||||
private showAlbumCreationForm = false;
|
return {
|
||||||
private albums: IAlbum[] = [];
|
showAlbumCreationForm: false,
|
||||||
private loadingAlbums = true;
|
albums: [] as IAlbum[],
|
||||||
|
loadingAlbums: true,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.loadAlbums();
|
this.loadAlbums();
|
||||||
}
|
},
|
||||||
|
|
||||||
albumCreatedHandler() {
|
methods: {
|
||||||
this.showAlbumCreationForm = false;
|
albumCreatedHandler() {
|
||||||
this.loadAlbums();
|
this.showAlbumCreationForm = false;
|
||||||
}
|
this.loadAlbums();
|
||||||
|
},
|
||||||
|
|
||||||
getAlbumName(album: IAlbum) {
|
getAlbumName(album: IAlbum) {
|
||||||
if (album.user === getCurrentUser()?.uid) {
|
if (album.user === getCurrentUser()?.uid) {
|
||||||
return album.name;
|
return album.name;
|
||||||
}
|
}
|
||||||
return `${album.name} (${album.user})`;
|
return `${album.name} (${album.user})`;
|
||||||
}
|
},
|
||||||
|
|
||||||
async loadAlbums() {
|
async loadAlbums() {
|
||||||
try {
|
try {
|
||||||
const res = await axios.get<IAlbum[]>(API.ALBUM_LIST());
|
const res = await axios.get<IAlbum[]>(API.ALBUM_LIST());
|
||||||
this.albums = res.data;
|
this.albums = res.data;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
} finally {
|
} finally {
|
||||||
this.loadingAlbums = false;
|
this.loadingAlbums = false;
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
|
|
||||||
@Emit("select")
|
pickAlbum(album: IAlbum) {
|
||||||
pickAlbum(album: IAlbum) {}
|
this.$emit("select", album);
|
||||||
}
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|
|
@ -28,8 +28,7 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { Component, Emit, Mixins } from "vue-property-decorator";
|
import { defineComponent } from "vue";
|
||||||
import GlobalMixin from "../../mixins/GlobalMixin";
|
|
||||||
|
|
||||||
import NcButton from "@nextcloud/vue/dist/Components/NcButton";
|
import NcButton from "@nextcloud/vue/dist/Components/NcButton";
|
||||||
import NcLoadingIcon from "@nextcloud/vue/dist/Components/NcLoadingIcon";
|
import NcLoadingIcon from "@nextcloud/vue/dist/Components/NcLoadingIcon";
|
||||||
|
@ -39,47 +38,53 @@ import * as dav from "../../services/DavRequests";
|
||||||
import Modal from "./Modal.vue";
|
import Modal from "./Modal.vue";
|
||||||
import AlbumCollaborators from "./AlbumCollaborators.vue";
|
import AlbumCollaborators from "./AlbumCollaborators.vue";
|
||||||
|
|
||||||
@Component({
|
export default defineComponent({
|
||||||
|
name: "AlbumShareModal",
|
||||||
components: {
|
components: {
|
||||||
NcButton,
|
NcButton,
|
||||||
NcLoadingIcon,
|
NcLoadingIcon,
|
||||||
Modal,
|
Modal,
|
||||||
AlbumCollaborators,
|
AlbumCollaborators,
|
||||||
},
|
},
|
||||||
})
|
|
||||||
export default class AlbumShareModal extends Mixins(GlobalMixin) {
|
|
||||||
private album: any = null;
|
|
||||||
private show = false;
|
|
||||||
private loadingAddCollaborators = false;
|
|
||||||
|
|
||||||
@Emit("close")
|
data() {
|
||||||
public close() {
|
return {
|
||||||
this.show = false;
|
album: null as any,
|
||||||
this.album = null;
|
show: false,
|
||||||
}
|
loadingAddCollaborators: false,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
public async open() {
|
methods: {
|
||||||
this.show = true;
|
close() {
|
||||||
this.loadingAddCollaborators = true;
|
this.show = false;
|
||||||
const user = this.$route.params.user || "";
|
this.album = null;
|
||||||
const name = this.$route.params.name || "";
|
this.$emit("close");
|
||||||
this.album = await dav.getAlbum(user, name);
|
},
|
||||||
this.loadingAddCollaborators = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
async handleSetCollaborators(collaborators: any[]) {
|
async open() {
|
||||||
try {
|
this.show = true;
|
||||||
this.loadingAddCollaborators = true;
|
this.loadingAddCollaborators = true;
|
||||||
await dav.updateAlbum(this.album, {
|
const user = this.$route.params.user || "";
|
||||||
albumName: this.album.basename,
|
const name = this.$route.params.name || "";
|
||||||
properties: { collaborators },
|
this.album = await dav.getAlbum(user, name);
|
||||||
});
|
|
||||||
this.close();
|
|
||||||
} catch (error) {
|
|
||||||
console.error(error);
|
|
||||||
} finally {
|
|
||||||
this.loadingAddCollaborators = false;
|
this.loadingAddCollaborators = false;
|
||||||
}
|
},
|
||||||
}
|
|
||||||
}
|
async handleSetCollaborators(collaborators: any[]) {
|
||||||
|
try {
|
||||||
|
this.loadingAddCollaborators = true;
|
||||||
|
await dav.updateAlbum(this.album, {
|
||||||
|
albumName: this.album.basename,
|
||||||
|
properties: { collaborators },
|
||||||
|
});
|
||||||
|
this.close();
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
} finally {
|
||||||
|
this.loadingAddCollaborators = false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
Loading…
Reference in New Issue