remove class vue dep (2)

vue3
Varun Patil 2022-12-10 01:27:04 -08:00
parent 07379d836c
commit 8d79151a30
9 changed files with 817 additions and 719 deletions

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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>

View File

@ -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 {

View File

@ -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>

View File

@ -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>