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",
"@playwright/test": "^1.31.2",
"@types/url-parse": "^1.4.8",
"@types/videojs-contrib-quality-levels": "^2.0.1",
"playwright": "^1.31.2",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",
@ -2534,6 +2535,21 @@
"integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==",
"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": {
"version": "8.5.3",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@ -13244,6 +13260,21 @@
"integrity": "sha512-zqqcGKyNWgTLFBxmaexGUKQyWqeG7HjXj20EuQJSJWwXe54BjX0ihIo5cJB9yAQzH8dNugJ9GvkBYMjPXs/PJw==",
"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": {
"version": "8.5.3",
"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",
"@playwright/test": "^1.31.2",
"@types/url-parse": "^1.4.8",
"@types/videojs-contrib-quality-levels": "^2.0.1",
"playwright": "^1.31.2",
"ts-loader": "^9.4.2",
"typescript": "^4.9.5",

View File

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

View File

@ -1,8 +1,9 @@
import PhotoSwipe from "photoswipe";
import PsImage from "./PsImage";
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
// playing in full screen mode.
if (document.fullscreenElement) {
@ -21,7 +22,7 @@ class LivePhotoContentSetup {
}
onContentLoad(e) {
const content = e.content;
const content: PsContent = e.content;
if (!isLiveContent(content)) return;
e.preventDefault();
@ -50,7 +51,7 @@ class LivePhotoContentSetup {
content.element = div;
}
onContentActivate({ content }) {
onContentActivate({ content }: { content: PsContent }) {
if (isLiveContent(content)) {
const video = content.element?.querySelector("video");
if (video) {
@ -60,15 +61,15 @@ class LivePhotoContentSetup {
}
}
onContentDeactivate({ content }) {
onContentDeactivate({ content }: PsEvent) {
if (isLiveContent(content)) {
content.element?.querySelector("video")?.pause();
}
}
onContentDestroy(e) {
if (isLiveContent(e.content)) {
e.content.element?.remove();
onContentDestroy({ content }: PsEvent) {
if (isLiveContent(content)) {
content.element?.remove();
}
}
}

View File

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

View File

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