ps: add typings

Signed-off-by: Varun Patil <varunpatil@ucla.edu>
pull/504/head
Varun Patil 2023-03-19 03:09:28 -07:00
parent 09ab876ebd
commit c6478a195d
7 changed files with 121 additions and 66 deletions

31
package-lock.json generated
View File

@ -37,6 +37,7 @@
"@nextcloud/webpack-vue-config": "^5.4.0", "@nextcloud/webpack-vue-config": "^5.4.0",
"@playwright/test": "^1.31.2", "@playwright/test": "^1.31.2",
"@types/url-parse": "^1.4.8", "@types/url-parse": "^1.4.8",
"@types/videojs-contrib-quality-levels": "^2.0.1",
"playwright": "^1.31.2", "playwright": "^1.31.2",
"ts-loader": "^9.4.2", "ts-loader": "^9.4.2",
"typescript": "^4.9.5", "typescript": "^4.9.5",
@ -2534,6 +2535,21 @@
"integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==", "integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==",
"dev": true "dev": true
}, },
"node_modules/@types/video.js": {
"version": "7.3.51",
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.51.tgz",
"integrity": "sha512-xLlt/ZfCuWYBvG2MRn018RvaEplcK6dI63aOiVUeeAWFyjx3Br1hL749ndFgbrvNdY4m9FoHG1FQ/PB6IpfSAQ==",
"dev": true
},
"node_modules/@types/videojs-contrib-quality-levels": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.1.tgz",
"integrity": "sha512-a7vNjolI9zG269Ks8y6JC/Eut+BXex0/1haB8c2J7RCIn365EdTxgiT0udG2ObaCkQqZsa2+4KS7ZE1HMo113w==",
"dev": true,
"dependencies": {
"@types/video.js": "*"
}
},
"node_modules/@types/ws": { "node_modules/@types/ws": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@ -13244,6 +13260,21 @@
"integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==", "integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==",
"dev": true "dev": true
}, },
"@types/video.js": {
"version": "7.3.51",
"resolved": "https://registry.npmjs.org/@types/video.js/-/video.js-7.3.51.tgz",
"integrity": "sha512-xLlt/ZfCuWYBvG2MRn018RvaEplcK6dI63aOiVUeeAWFyjx3Br1hL749ndFgbrvNdY4m9FoHG1FQ/PB6IpfSAQ==",
"dev": true
},
"@types/videojs-contrib-quality-levels": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/videojs-contrib-quality-levels/-/videojs-contrib-quality-levels-2.0.1.tgz",
"integrity": "sha512-a7vNjolI9zG269Ks8y6JC/Eut+BXex0/1haB8c2J7RCIn365EdTxgiT0udG2ObaCkQqZsa2+4KS7ZE1HMo113w==",
"dev": true,
"requires": {
"@types/video.js": "*"
}
},
"@types/ws": { "@types/ws": {
"version": "8.5.3", "version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",

View File

@ -64,6 +64,7 @@
"@nextcloud/webpack-vue-config": "^5.4.0", "@nextcloud/webpack-vue-config": "^5.4.0",
"@playwright/test": "^1.31.2", "@playwright/test": "^1.31.2",
"@types/url-parse": "^1.4.8", "@types/url-parse": "^1.4.8",
"@types/videojs-contrib-quality-levels": "^2.0.1",
"playwright": "^1.31.2", "playwright": "^1.31.2",
"ts-loader": "^9.4.2", "ts-loader": "^9.4.2",
"typescript": "^4.9.5", "typescript": "^4.9.5",

View File

@ -1,9 +1,9 @@
import PhotoSwipe from "photoswipe"; import PhotoSwipe from "photoswipe";
import Slide from "photoswipe/dist/types/slide/slide";
import { isVideoContent } from "./PsVideo"; import { isVideoContent } from "./PsVideo";
import { isLiveContent } from "./PsLivePhoto"; import { isLiveContent } from "./PsLivePhoto";
import { fetchImage } from "../frame/XImgCache"; import { fetchImage } from "../frame/XImgCache";
import { PsContent, PsEvent, PsSlide } from "./types";
export default class ImageContentSetup { export default class ImageContentSetup {
private loading = 0; private loading = 0;
@ -17,11 +17,11 @@ export default class ImageContentSetup {
lightbox.addFilter("placeholderSrc", this.placeholderSrc.bind(this)); lightbox.addFilter("placeholderSrc", this.placeholderSrc.bind(this));
} }
isContentLoading(isLoading: boolean, content: any) { isContentLoading(isLoading: boolean, content: PsContent) {
return isLoading || this.loading > 0; return isLoading || this.loading > 0;
} }
onContentLoad(e) { onContentLoad(e: PsEvent) {
if (isVideoContent(e.content) || isLiveContent(e.content)) return; if (isVideoContent(e.content) || isLiveContent(e.content)) return;
// Insert image throgh XImgCache // Insert image throgh XImgCache
@ -29,16 +29,16 @@ export default class ImageContentSetup {
e.content.element = this.getXImgElem(e.content, () => e.content.onLoaded()); e.content.element = this.getXImgElem(e.content, () => e.content.onLoaded());
} }
onContentLoadImage(e) { onContentLoadImage(e: PsEvent) {
if (isVideoContent(e.content) || isLiveContent(e.content)) return; if (isVideoContent(e.content) || isLiveContent(e.content)) return;
e.preventDefault(); e.preventDefault();
} }
placeholderSrc(placeholderSrc: string, content: any) { placeholderSrc(placeholderSrc: string, content: PsContent) {
return content.data.msrc || placeholderSrc; return content.data.msrc || placeholderSrc;
} }
getXImgElem(content: any, onLoad: () => void): HTMLImageElement { getXImgElem(content: PsContent, onLoad: () => void): HTMLImageElement {
const img = document.createElement("img"); const img = document.createElement("img");
img.classList.add("pswp__img", "ximg"); img.classList.add("pswp__img", "ximg");
img.style.visibility = "hidden"; img.style.visibility = "hidden";
@ -63,7 +63,7 @@ export default class ImageContentSetup {
return img; return img;
} }
zoomPanUpdate({ slide }: { slide: Slide }) { zoomPanUpdate({ slide }: { slide: PsSlide }) {
if (!slide.data.highSrc || slide.data.highSrcCond !== "zoom") return; if (!slide.data.highSrc || slide.data.highSrcCond !== "zoom") return;
if (slide.currZoomLevel >= slide.zoomLevels.secondary) { if (slide.currZoomLevel >= slide.zoomLevels.secondary) {
@ -78,7 +78,7 @@ export default class ImageContentSetup {
} }
} }
loadFullImage(slide: Slide) { loadFullImage(slide: PsSlide) {
if (!slide.data.highSrc) return; if (!slide.data.highSrc) return;
// Get ximg element // Get ximg element

View File

@ -1,8 +1,9 @@
import PhotoSwipe from "photoswipe"; import PhotoSwipe from "photoswipe";
import PsImage from "./PsImage"; import PsImage from "./PsImage";
import * as utils from "../../services/Utils"; import * as utils from "../../services/Utils";
import { PsContent, PsEvent } from "./types";
export function isLiveContent(content): boolean { export function isLiveContent(content: PsContent): boolean {
// Do not play Live Photo if the slideshow is // Do not play Live Photo if the slideshow is
// playing in full screen mode. // playing in full screen mode.
if (document.fullscreenElement) { if (document.fullscreenElement) {
@ -21,7 +22,7 @@ class LivePhotoContentSetup {
} }
onContentLoad(e) { onContentLoad(e) {
const content = e.content; const content: PsContent = e.content;
if (!isLiveContent(content)) return; if (!isLiveContent(content)) return;
e.preventDefault(); e.preventDefault();
@ -50,7 +51,7 @@ class LivePhotoContentSetup {
content.element = div; content.element = div;
} }
onContentActivate({ content }) { onContentActivate({ content }: { content: PsContent }) {
if (isLiveContent(content)) { if (isLiveContent(content)) {
const video = content.element?.querySelector("video"); const video = content.element?.querySelector("video");
if (video) { if (video) {
@ -60,15 +61,15 @@ class LivePhotoContentSetup {
} }
} }
onContentDeactivate({ content }) { onContentDeactivate({ content }: PsEvent) {
if (isLiveContent(content)) { if (isLiveContent(content)) {
content.element?.querySelector("video")?.pause(); content.element?.querySelector("video")?.pause();
} }
} }
onContentDestroy(e) { onContentDestroy({ content }: PsEvent) {
if (isLiveContent(e.content)) { if (isLiveContent(content)) {
e.content.element?.remove(); content.element?.remove();
} }
} }
} }

View File

@ -1,12 +1,27 @@
import PhotoSwipe from "photoswipe"; import PhotoSwipe from "photoswipe";
import { loadState } from "@nextcloud/initial-state"; import { loadState } from "@nextcloud/initial-state";
import axios from "@nextcloud/axios";
import { showError } from "@nextcloud/dialogs"; import { showError } from "@nextcloud/dialogs";
import { translate as t } from "@nextcloud/l10n"; import { translate as t } from "@nextcloud/l10n";
import { getCurrentUser } from "@nextcloud/auth"; import { getCurrentUser } from "@nextcloud/auth";
import axios from "@nextcloud/axios";
import { API } from "../../services/API"; import { API } from "../../services/API";
import { IPhoto } from "../../types"; import { PsContent, PsEvent, PsSlide } from "./types";
import Player from "video.js/dist/types/player";
import { QualityLevelList } from "videojs-contrib-quality-levels";
type VideoContent = PsContent & {
videoElement: HTMLVideoElement;
videojs: Player & {
qualityLevels?: () => QualityLevelList;
};
plyr: globalThis.Plyr;
};
type PsVideoEvent = PsEvent & {
content: VideoContent;
};
const config_noTranscode = loadState( const config_noTranscode = loadState(
"memories", "memories",
@ -21,16 +36,18 @@ const config_video_default_quality = Number(
/** /**
* Check if slide has video content * Check if slide has video content
*
* @param {Slide|Content} content Slide or Content object
* @returns Boolean
*/ */
export function isVideoContent(content): boolean { export function isVideoContent(content: PsSlide | PsContent): boolean {
return content?.data?.type === "video"; return content?.data?.type === "video";
} }
class VideoContentSetup { class VideoContentSetup {
constructor(lightbox: PhotoSwipe, private options) { constructor(
lightbox: PhotoSwipe,
private options: {
preventDragOffset: number;
}
) {
this.initLightboxEvents(lightbox); this.initLightboxEvents(lightbox);
lightbox.on("init", () => { lightbox.on("init", () => {
this.initPswpEvents(lightbox); this.initPswpEvents(lightbox);
@ -53,10 +70,6 @@ class VideoContentSetup {
"useContentPlaceholder", "useContentPlaceholder",
this.useContentPlaceholder.bind(this) this.useContentPlaceholder.bind(this)
); );
lightbox.addFilter("domItemData", (itemData, element, linkEl) => {
return itemData;
});
} }
initPswpEvents(pswp: PhotoSwipe) { initPswpEvents(pswp: PhotoSwipe) {
@ -101,22 +114,19 @@ class VideoContentSetup {
}); });
pswp.on("close", () => { pswp.on("close", () => {
if (isVideoContent(pswp.currSlide.content)) { this.destroyVideo(pswp.currSlide.content as VideoContent);
// prevent more requests
this.destroyVideo(pswp.currSlide.content);
}
}); });
// Prevent closing when video fullscreen is active // Prevent closing when video fullscreen is active
pswp.on("pointerMove", (e) => { pswp.on("pointerMove", (e) => {
const plyr: Plyr = (<any>pswp.currSlide.content)?.plyr; const plyr = (<VideoContent>pswp.currSlide.content)?.plyr;
if (plyr?.fullscreen.active) { if (plyr?.fullscreen.active) {
e.preventDefault(); e.preventDefault();
} }
}); });
} }
getHLSsrc(content: any) { getHLSsrc(content: VideoContent) {
// Get base URL // Get base URL
const fileid = content.data.photo.fileid; const fileid = content.data.photo.fileid;
return { return {
@ -125,13 +135,13 @@ class VideoContentSetup {
}; };
} }
async initVideo(content: any) { async initVideo(content: VideoContent) {
if (!isVideoContent(content) || content.videojs || !config_videoIsSetup) { if (!isVideoContent(content) || content.videojs || !config_videoIsSetup) {
return; return;
} }
// Prevent double loading // Prevent double loading
content.videojs = {}; content.videojs = {} as any;
// Load videojs scripts // Load videojs scripts
if (!globalThis.vidjs) { if (!globalThis.vidjs) {
@ -142,20 +152,18 @@ class VideoContentSetup {
content.videoElement = document.createElement("video"); content.videoElement = document.createElement("video");
content.videoElement.className = "video-js"; content.videoElement.className = "video-js";
content.videoElement.setAttribute("poster", content.data.msrc); content.videoElement.setAttribute("poster", content.data.msrc);
if (this.options.videoAttributes) { content.videoElement.setAttribute("preload", "none");
for (let key in this.options.videoAttributes) { content.videoElement.setAttribute("controls", "");
content.videoElement.setAttribute( content.videoElement.setAttribute("playsinline", "");
key,
this.options.videoAttributes[key] || ""
);
}
}
// Add the video element to the actual container // Add the video element to the actual container
content.element.appendChild(content.videoElement); content.element.appendChild(content.videoElement);
// Create hls sources if enabled // Create hls sources if enabled
let sources: any[] = []; const sources: {
src: string;
type: string;
}[] = [];
if (!config_noTranscode) { if (!config_noTranscode) {
sources.push(this.getHLSsrc(content)); sources.push(this.getHLSsrc(content));
@ -239,7 +247,7 @@ class VideoContentSetup {
} }
} }
destroyVideo(content: any) { destroyVideo(content: VideoContent) {
if (isVideoContent(content)) { if (isVideoContent(content)) {
content.videojs?.dispose?.(); content.videojs?.dispose?.();
content.videojs = null; content.videojs = null;
@ -256,7 +264,7 @@ class VideoContentSetup {
} }
} }
initPlyr(content: any) { initPlyr(content: VideoContent) {
if (content.plyr) return; if (content.plyr) return;
content.videoElement = content.videojs?.el()?.querySelector("video"); content.videoElement = content.videojs?.el()?.querySelector("video");
@ -323,7 +331,7 @@ class VideoContentSetup {
} }
// Set the source to the original video // Set the source to the original video
if (content.videojs.src().includes("m3u8")) { if (content.videojs.src(undefined).includes("m3u8")) {
content.videojs.src({ content.videojs.src({
src: content.data.src, src: content.data.src,
type: "video/mp4", type: "video/mp4",
@ -332,7 +340,7 @@ class VideoContentSetup {
return; return;
} else { } else {
// Set source to HLS // Set source to HLS
if (!content.videojs.src().includes("m3u8")) { if (!content.videojs.src(undefined).includes("m3u8")) {
content.videojs.src(this.getHLSsrc(content)); content.videojs.src(this.getHLSsrc(content));
} }
} }
@ -402,16 +410,16 @@ class VideoContentSetup {
} }
} }
updateRotation(content: any, val?: number): boolean { updateRotation(content: VideoContent, val?: number): boolean {
if (!content.videojs) return; if (!content.videojs) return;
content.videoElement = content.videojs.el()?.querySelector("video"); content.videoElement = content.videojs.el()?.querySelector("video");
if (!content.videoElement) return; if (!content.videoElement) return;
const photo: IPhoto = content.data.photo; const photo = content.data.photo;
const exif = photo.imageInfo?.exif; const exif = photo.imageInfo?.exif;
const rotation = val ?? Number(exif?.Rotation || 0); const rotation = val ?? Number(exif?.Rotation || 0);
const shouldRotate = content.videojs?.src().includes("m3u8"); const shouldRotate = content.videojs?.src(undefined).includes("m3u8");
if (rotation && shouldRotate) { if (rotation && shouldRotate) {
let transform = `rotate(${rotation}deg)`; let transform = `rotate(${rotation}deg)`;
@ -437,7 +445,7 @@ class VideoContentSetup {
return false; return false;
} }
onContentDestroy({ content }) { onContentDestroy({ content }: PsVideoEvent) {
this.destroyVideo(content); this.destroyVideo(content);
} }
@ -466,25 +474,25 @@ class VideoContentSetup {
} }
} }
isContentZoomable(isZoomable: boolean, content) { isContentZoomable(isZoomable: boolean, content: PsContent) {
return !isVideoContent(content) && isZoomable; return !isVideoContent(content) && isZoomable;
} }
isKeepingPlaceholder(keep: boolean, content) { isKeepingPlaceholder(keep: boolean, content: PsContent) {
return isVideoContent(content) || keep; return isVideoContent(content) || keep;
} }
onContentActivate({ content }) { onContentActivate({ content }: PsVideoEvent) {
this.initVideo(content); this.initVideo(content);
} }
onContentDeactivate({ content }) { onContentDeactivate({ content }: PsVideoEvent) {
this.destroyVideo(content); this.destroyVideo(content);
} }
onContentLoad(e) { onContentLoad(e: PsVideoEvent) {
const content = e.content; const content: PsContent = e.content;
if (!isVideoContent(e.content)) return; if (!isVideoContent(content)) return;
// Stop default content load // Stop default content load
e.preventDefault(); e.preventDefault();
@ -508,7 +516,7 @@ class VideoContentSetup {
content.onLoaded(); content.onLoaded();
} }
useContentPlaceholder(usePlaceholder: boolean, content: any) { useContentPlaceholder(usePlaceholder: boolean, content: PsContent) {
return isVideoContent(content) || usePlaceholder; return isVideoContent(content) || usePlaceholder;
} }
} }

View File

@ -179,6 +179,7 @@
import { defineComponent } from "vue"; import { defineComponent } from "vue";
import { IDay, IPhoto, IRow, IRowType } from "../../types"; import { IDay, IPhoto, IRow, IRowType } from "../../types";
import { PsSlide } from "./types";
import UserConfig from "../../mixins/UserConfig"; import UserConfig from "../../mixins/UserConfig";
import NcActions from "@nextcloud/vue/dist/Components/NcActions"; import NcActions from "@nextcloud/vue/dist/Components/NcActions";
@ -584,19 +585,17 @@ export default defineComponent({
}); });
// Video support // Video support
this.psVideo = new PsVideo(<any>this.photoswipe, { this.psVideo = new PsVideo(<PhotoSwipe>this.photoswipe, {
videoAttributes: { controls: "", playsinline: "", preload: "none" },
autoplay: true,
preventDragOffset: 40, preventDragOffset: 40,
}); });
// Image support // Image support
this.psImage = new PsImage(<any>this.photoswipe); this.psImage = new PsImage(<PhotoSwipe>this.photoswipe);
// Live Photo support // Live Photo support
this.psLivePhoto = new PsLivePhoto( this.psLivePhoto = new PsLivePhoto(
<any>this.photoswipe, <PhotoSwipe>this.photoswipe,
<any>this.psImage <PsImage>this.psImage
); );
// Patch the close button to stop the slideshow // Patch the close button to stop the slideshow
@ -979,7 +978,7 @@ export default defineComponent({
/** Play the current live photo */ /** Play the current live photo */
playLivePhoto() { playLivePhoto() {
this.psLivePhoto.onContentActivate(this.photoswipe.currSlide); this.psLivePhoto.onContentActivate(this.photoswipe.currSlide as PsSlide);
}, },
/** Is the current photo a favorite */ /** Is the current photo a favorite */

View File

@ -0,0 +1,15 @@
import Content from "photoswipe/dist/types/slide/content";
import Slide, { _SlideData } from "photoswipe/dist/types/slide/slide";
import { IPhoto } from "../../types";
type PsAugment = {
data: _SlideData & {
photo?: IPhoto;
};
};
export type PsSlide = Slide & PsAugment;
export type PsContent = Content & PsAugment;
export type PsEvent = {
content: PsContent;
preventDefault: () => void;
};