refactor: improve typing and doc

Signed-off-by: Varun Patil <radialapps@gmail.com>
pull/803/head
Varun Patil 2023-08-29 10:27:26 -07:00
parent e75aa4006f
commit b1ab26e44a
20 changed files with 110 additions and 72 deletions

View File

@ -171,8 +171,12 @@ class VideoController extends GenericApiController
// Requested only JSON info // Requested only JSON info
if ('json' === $format) { if ('json' === $format) {
// IPhoto object for the live video
return new JSONResponse([ return new JSONResponse([
'fileid' => $liveFile->getId(), 'fileid' => $liveFile->getId(),
'etag' => $liveFile->getEtag(),
'basename' => $liveFile->getName(),
'mimetype' => $liveFile->getMimeType(),
]); ]);
} }

View File

@ -91,7 +91,7 @@ import * as utils from '../services/utils';
import * as dav from '../services/dav'; import * as dav from '../services/dav';
import { API } from '../services/API'; import { API } from '../services/API';
import type { IAlbum, IFace, IImageInfo, IPhoto } from '../types'; import type { IAlbum, IFace, IImageInfo, IPhoto, IExif } from '../types';
interface TopField { interface TopField {
id?: string; id?: string;
@ -117,7 +117,7 @@ export default defineComponent({
data: () => ({ data: () => ({
fileid: null as number | null, fileid: null as number | null,
filename: '', filename: '',
exif: {} as NonNullable<IImageInfo['exif']>, exif: {} as IExif,
baseInfo: {} as IImageInfo, baseInfo: {} as IImageInfo,
loading: 0, loading: 0,
@ -198,12 +198,12 @@ export default defineComponent({
/** Title EXIF value */ /** Title EXIF value */
title(): string | null { title(): string | null {
return this.exif['Title'] || null; return this.exif.Title || null;
}, },
/** Description EXIF value */ /** Description EXIF value */
description(): string | null { description(): string | null {
return this.exif['Description'] || null; return this.exif.Description || null;
}, },
/** Date taken info */ /** Date taken info */
@ -252,7 +252,7 @@ export default defineComponent({
dateOriginalTime(): string[] | null { dateOriginalTime(): string[] | null {
if (!this.dateOriginal) return null; if (!this.dateOriginal) return null;
const fields = ['OffsetTimeOriginal', 'OffsetTime', 'LocationTZID']; const fields: (keyof IExif)[] = ['OffsetTimeOriginal', 'OffsetTime', 'LocationTZID'];
const hasTz = fields.some((key) => this.exif[key]); const hasTz = fields.some((key) => this.exif[key]);
const format = 't' + (hasTz ? ' ZZ' : ''); const format = 't' + (hasTz ? ' ZZ' : '');
@ -262,18 +262,18 @@ export default defineComponent({
/** Camera make and model info */ /** Camera make and model info */
camera(): string | null { camera(): string | null {
const make = this.exif['Make']; const make = this.exif.Make;
const model = this.exif['Model']; const model = this.exif.Model;
if (!make || !model) return null; if (!make || !model) return null;
if (model.startsWith(make)) return model; if (model.startsWith(make)) return model;
return `${make} ${model}`; return `${make} ${model}`;
}, },
cameraSub(): string[] { cameraSub(): string[] {
const f = this.exif['FNumber'] || this.exif['Aperture']; const f = this.exif.FNumber || this.exif.Aperture;
const s = this.shutterSpeed; const s = this.shutterSpeed;
const len = this.exif['FocalLength']; const len = this.exif.FocalLength;
const iso = this.exif['ISO']; const iso = this.exif.ISO;
const parts: string[] = []; const parts: string[] = [];
if (f) parts.push(`f/${f}`); if (f) parts.push(`f/${f}`);
@ -285,7 +285,7 @@ export default defineComponent({
/** Convert shutter speed decimal to 1/x format */ /** Convert shutter speed decimal to 1/x format */
shutterSpeed(): string | null { shutterSpeed(): string | null {
const speed = Number(this.exif['ShutterSpeedValue'] || this.exif['ShutterSpeed'] || this.exif['ExposureTime']); const speed = Number(this.exif.ShutterSpeedValue || this.exif.ShutterSpeed || this.exif.ExposureTime);
if (!speed) return null; if (!speed) return null;
if (speed < 1) { if (speed < 1) {
@ -311,7 +311,7 @@ export default defineComponent({
imageInfoSub(): string[] { imageInfoSub(): string[] {
let parts: string[] = []; let parts: string[] = [];
let mp = Number(this.exif['Megapixels']); let mp = Number(this.exif.Megapixels);
if (this.baseInfo.w && this.baseInfo.h) { if (this.baseInfo.w && this.baseInfo.h) {
parts.push(`${this.baseInfo.w}x${this.baseInfo.h}`); parts.push(`${this.baseInfo.w}x${this.baseInfo.h}`);
@ -341,11 +341,11 @@ export default defineComponent({
}, },
lat(): number { lat(): number {
return Number(this.exif['GPSLatitude']); return Number(this.exif.GPSLatitude);
}, },
lon(): number { lon(): number {
return Number(this.exif['GPSLongitude']); return Number(this.exif.GPSLongitude);
}, },
tagNames(): string[] { tagNames(): string[] {
@ -452,7 +452,7 @@ export default defineComponent({
} }
}, },
handleFileUpdated({ fileid }: { fileid: number }) { handleFileUpdated({ fileid }: utils.BusEvent['files:file:updated']) {
if (fileid && this.fileid === fileid) { if (fileid && this.fileid === fileid) {
this.refresh(); this.refresh();
} }

View File

@ -44,7 +44,7 @@ export default defineComponent({
}, },
methods: { methods: {
onScroll({ current, previous }: { current: number; previous: number }) { onScroll({ current, previous }: utils.BusEvent['memories.recycler.scroll']) {
this.isScrollDown = (this.isScrollDown && previous - current < 40) || current - previous > 40; // momentum scroll this.isScrollDown = (this.isScrollDown && previous - current < 40) || current - previous > 40; // momentum scroll
}, },
}, },

View File

@ -476,7 +476,7 @@ export default defineComponent({
}, },
/** Handle mouse leave */ /** Handle mouse leave */
mouseleave() { mouseleave(event: MouseEvent) {
this.interactend(); this.interactend();
this.moveHoverCursor(this.cursorY); this.moveHoverCursor(this.cursorY);
}, },
@ -539,7 +539,7 @@ export default defineComponent({
}, },
/** Handle touch */ /** Handle touch */
touchmove(event: any) { touchmove(event: TouchEvent) {
if (!this.scrollerRect) return; if (!this.scrollerRect) return;
let y = event.targetTouches[0].pageY - this.scrollerRect.top; let y = event.targetTouches[0].pageY - this.scrollerRect.top;
y = Math.max(this.topPadding, y + MOBILE_CURSOR_HH); // middle of touch finger y = Math.max(this.topPadding, y + MOBILE_CURSOR_HH); // middle of touch finger

View File

@ -21,7 +21,7 @@ const pendingUrls = new Map<string, BlobCallback[]>();
// Cache for preview images // Cache for preview images
const cacheName = 'memories-images'; const cacheName = 'memories-images';
let imageCache: Cache; let imageCache: Cache | undefined;
(async function openCache() { (async function openCache() {
try { try {
imageCache = await self.caches?.open(cacheName); imageCache = await self.caches?.open(cacheName);

View File

@ -11,7 +11,7 @@
@click="click($event, album)" @click="click($event, album)"
> >
<template #icon> <template #icon>
<XImg v-if="album.last_added_photo !== -1" class="album__image" :src="toCoverUrl(album.last_added_photo)" /> <XImg v-if="album.last_added_photo !== -1" class="album__image" :src="toCoverUrl(album)" />
<div v-else class="album__image album__image--placeholder"> <div v-else class="album__image album__image--placeholder">
<ImageMultipleIcon :size="32" /> <ImageMultipleIcon :size="32" />
</div> </div>
@ -81,10 +81,10 @@ export default defineComponent({
}; };
}, },
toCoverUrl(fileId: string | number) { toCoverUrl(album: IAlbum) {
return getPreviewUrl({ return getPreviewUrl({
photo: { photo: {
fileid: Number(fileId), fileid: Number(album.last_added_photo),
} as IPhoto, } as IPhoto,
sqsize: 256, sqsize: 256,
}); });

View File

@ -115,7 +115,7 @@ export default defineComponent({
}, },
isMobile(): boolean { isMobile(): boolean {
return globalThis.windowInnerWidth <= 768; return utils.isMobile();
}, },
}, },
@ -231,7 +231,7 @@ export default defineComponent({
}, },
refreshSidebar() { refreshSidebar() {
if (this.isMobile) return; if (utils.isMobile()) return;
globalThis.mSidebar.close(); globalThis.mSidebar.close();
globalThis.mSidebar.open(0, this.filename, true); globalThis.mSidebar.open(0, this.filename, true);
}, },

View File

@ -149,7 +149,7 @@ export default defineComponent({
computed: { computed: {
isAlbumList(): boolean { isAlbumList(): boolean {
return !Boolean(this.$route.params.name); return !this.$route.params.name;
}, },
canEditAlbum(): boolean { canEditAlbum(): boolean {
@ -158,7 +158,7 @@ export default defineComponent({
name(): string { name(): string {
// Album name is displayed in the dynamic top matter (timeline) // Album name is displayed in the dynamic top matter (timeline)
return this.$route.params.name ? '' : this.t('memories', 'Albums'); return this.isAlbumList ? this.t('memories', 'Albums') : String();
}, },
isMobile(): boolean { isMobile(): boolean {

View File

@ -10,7 +10,7 @@ if (title) {
let isHidden = false; // cache state to avoid unnecessary DOM updates let isHidden = false; // cache state to avoid unnecessary DOM updates
// Hide header when recycler is scrolled down // Hide header when recycler is scrolled down
utils.bus.on('memories.recycler.scroll', ({ dynTopMatterVisible }: { dynTopMatterVisible: boolean }) => { utils.bus.on('memories.recycler.scroll', ({ dynTopMatterVisible }) => {
if (dynTopMatterVisible === isHidden) return; if (dynTopMatterVisible === isHidden) return;
header.classList.toggle('hidden', (isHidden = dynTopMatterVisible)); header.classList.toggle('hidden', (isHidden = dynTopMatterVisible));
}); });

View File

@ -61,7 +61,7 @@ export default defineComponent({
}, },
methods: { methods: {
onRecyclerScroll({ dynTopMatterVisible }: { dynTopMatterVisible: boolean }) { onRecyclerScroll({ dynTopMatterVisible }: utils.BusEvent['memories.recycler.scroll']) {
this.dynamicVisible = dynTopMatterVisible; this.dynamicVisible = dynTopMatterVisible;
}, },
}, },

View File

@ -163,7 +163,7 @@ class VideoContentSetup {
// Create video element // Create video element
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!);
content.videoElement.setAttribute('preload', 'none'); content.videoElement.setAttribute('preload', 'none');
content.videoElement.setAttribute('controls', ''); content.videoElement.setAttribute('controls', '');
content.videoElement.setAttribute('playsinline', ''); content.videoElement.setAttribute('playsinline', '');

View File

@ -468,9 +468,8 @@ export default defineComponent({
arrowNextTitle: this.t('memories', 'Next'), arrowNextTitle: this.t('memories', 'Next'),
getViewportSizeFn: () => { getViewportSizeFn: () => {
// Ignore the sidebar if mobile or fullscreen // Ignore the sidebar if mobile or fullscreen
const isMobile = globalThis.windowInnerWidth < 768;
const isFullscreen = Boolean(document.fullscreenElement); const isFullscreen = Boolean(document.fullscreenElement);
const use = this.sidebarOpen && !isMobile && !isFullscreen; const use = this.sidebarOpen && !utils.isMobile() && !isFullscreen;
// Calculate the sidebar width to use and outer width // Calculate the sidebar width to use and outer width
const sidebarWidth = use ? globalThis.mSidebar.getWidth() : 0; const sidebarWidth = use ? globalThis.mSidebar.getWidth() : 0;
@ -780,7 +779,7 @@ export default defineComponent({
}, },
/** Get base data object */ /** Get base data object */
getItemData(photo: IPhoto) { getItemData(photo: IPhoto): PsContent['data'] {
let previewUrl = utils.getPreviewUrl({ photo, size: 'screen' }); let previewUrl = utils.getPreviewUrl({ photo, size: 'screen' });
const isvideo = photo.flag & this.c.FLAG_IS_VIDEO; const isvideo = photo.flag & this.c.FLAG_IS_VIDEO;

View File

@ -4,9 +4,16 @@ import { IPhoto } from '../../types';
type PsAugment = { type PsAugment = {
data: _SlideData & { data: _SlideData & {
/** The original source of the image.*/
src: string; src: string;
msrc: string; /** The original photo object. */
photo: IPhoto; photo: IPhoto;
/** The source of the high resolution image. */
highSrc: string | null;
/** The condition for loading the high resolution image. */
highSrcCond: 'always' | 'zoom' | 'never';
/** The type of content. */
type: 'image' | 'video';
}; };
}; };
export type PsSlide = Slide & export type PsSlide = Slide &

View File

@ -8,7 +8,7 @@ import * as utils from '../services/utils';
import { IConfig } from '../types'; import { IConfig } from '../types';
import staticConfig from '../services/static-config'; import staticConfig from '../services/static-config';
const eventName = 'memories:user-config-changed'; const eventName: keyof utils.BusEvent = 'memories:user-config-changed';
const localSettings: (keyof IConfig)[] = [ const localSettings: (keyof IConfig)[] = [
'square_thumbs', 'square_thumbs',

View File

@ -231,13 +231,9 @@ async function extendWithLivePhotos(photos: IPhoto[]) {
photos photos
.filter((p) => p.liveid && !p.liveid.startsWith('self__')) .filter((p) => p.liveid && !p.liveid.startsWith('self__'))
.map(async (p) => { .map(async (p) => {
const url = API.Q(utils.getLivePhotoVideoUrl(p, false), { format: 'json' });
try { try {
const response = await axios.get(url); const url = API.Q(utils.getLivePhotoVideoUrl(p, false), { format: 'json' });
const data = response.data; return (await axios.get<IPhoto>(url)).data;
return {
fileid: data.fileid,
} as IPhoto;
} catch (error) { } catch (error) {
console.error(error); console.error(error);
return null; return null;

View File

@ -67,8 +67,8 @@ function parseIdFromLocation(url: string): number {
url = url.substring(0, queryPos); url = url.substring(0, queryPos);
} }
const parts = url.split('/'); const parts: string[] = url.split('/');
let result; let result: string | undefined;
do { do {
result = parts[parts.length - 1]; result = parts[parts.length - 1];
parts.pop(); parts.pop();

View File

@ -66,11 +66,11 @@ export function randomChoice<T>(arr: T[]): T {
*/ */
export function randomSubarray<T>(arr: T[], size: number): T[] { export function randomSubarray<T>(arr: T[], size: number): T[] {
if (arr.length <= size) return arr; if (arr.length <= size) return arr;
var shuffled = arr.slice(0), let shuffled: T[] = arr.slice(0),
i = arr.length, i: number = arr.length,
min = i - size, min: number = i - size,
temp, temp: T,
index; index: number;
while (i-- > min) { while (i-- > min) {
index = Math.floor((i + 1) * Math.random()); index = Math.floor((i + 1) * Math.random());
temp = shuffled[index]; temp = shuffled[index];

View File

@ -17,7 +17,7 @@ export function dateToDayId(date: Date) {
} }
/** Get month name from number */ /** Get month name from number */
export function getShortDateStr(date: Date) { export function getShortDateStr(date: Date): string {
const dayId = dateToDayId(date); const dayId = dateToDayId(date);
if (!shortDateStrMemo.has(dayId)) { if (!shortDateStrMemo.has(dayId)) {
shortDateStrMemo.set( shortDateStrMemo.set(
@ -29,7 +29,7 @@ export function getShortDateStr(date: Date) {
}) })
); );
} }
return shortDateStrMemo.get(dayId); return shortDateStrMemo.get(dayId)!;
} }
/** Get long date string with optional year if same as current */ /** Get long date string with optional year if same as current */

View File

@ -1,7 +1,7 @@
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus'; import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus';
import { IConfig, IPhoto } from '../../types'; import { IConfig, IPhoto } from '../../types';
type BusEvent = { export type BusEvent = {
/** Open/close the navigation drawer */ /** Open/close the navigation drawer */
'toggle-navigation': { open: boolean }; 'toggle-navigation': { open: boolean };
/** File was created */ /** File was created */
@ -26,7 +26,10 @@ type BusEvent = {
value: IConfig[keyof IConfig]; value: IConfig[keyof IConfig];
} | null; } | null;
/** Delete these photos from the timeline */ /**
* Remove these photos from the timeline.
* Each photo object is required to have the `d` (day) property.
*/
'memories:timeline:deleted': IPhoto[]; 'memories:timeline:deleted': IPhoto[];
/** Viewer has requested fetching day */ /** Viewer has requested fetching day */
'memories:timeline:fetch-day': number; 'memories:timeline:fetch-day': number;
@ -45,18 +48,33 @@ type BusEvent = {
'memories:albums:update': IPhoto[]; 'memories:albums:update': IPhoto[];
}; };
/**
* Wrapper around Nextcloud's event bus.
*/
export const bus = {
/** /**
* Emit an event on the Nextcloud event bus. * Emit an event on the Nextcloud event bus.
* @param name Name of event * @param name Name of event
* @param data arguments * @param data arguments
*/ */
export const bus = {
emit<T extends keyof BusEvent>(name: T, data: BusEvent[T]): void { emit<T extends keyof BusEvent>(name: T, data: BusEvent[T]): void {
emit(name, data as any); emit(name, data as any);
}, },
/**
* Subscribe to an event on the Nextcloud event bus.
* @param name Name of event
* @param callback Callback to be called when the event is emitted
*/
on<T extends keyof BusEvent>(name: T, callback: (data: BusEvent[T]) => void): void { on<T extends keyof BusEvent>(name: T, callback: (data: BusEvent[T]) => void): void {
subscribe(name, callback); subscribe(name, callback);
}, },
/**
* Unsubscribe from an event on the Nextcloud event bus.
* @param name Name of event
* @param callback Same callback that was passed to `on`
*/
off<T extends keyof BusEvent>(name: T, callback: (data: BusEvent[T]) => void): void { off<T extends keyof BusEvent>(name: T, callback: (data: BusEvent[T]) => void): void {
unsubscribe(name, callback); unsubscribe(name, callback);
}, },

View File

@ -106,11 +106,21 @@ export interface IImageInfo {
address?: string; address?: string;
tags?: { [id: string]: string }; tags?: { [id: string]: string };
exif?: { exif?: IExif;
clusters?: {
albums?: IAlbum[];
recognize?: IFace[];
facerecognition?: IFace[];
};
}
export interface IExif {
Rotation?: number; Rotation?: number;
Orientation?: number; Orientation?: number;
ImageWidth?: number; ImageWidth?: number;
ImageHeight?: number; ImageHeight?: number;
Megapixels?: number;
Title?: string; Title?: string;
Description?: string; Description?: string;
@ -122,14 +132,18 @@ export interface IImageInfo {
OffsetTime?: string; OffsetTime?: string;
LocationTZID?: string; LocationTZID?: string;
[other: string]: unknown; ExposureTime?: number;
}; ShutterSpeed?: number;
ShutterSpeedValue?: number;
Aperture?: number;
ApertureValue?: number;
ISO?: number;
FNumber?: number;
FocalLength?: number;
clusters?: { GPSAltitude?: number;
albums?: IAlbum[]; GPSLatitude?: number;
recognize?: IFace[]; GPSLongitude?: number;
facerecognition?: IFace[];
};
} }
export interface IFolder extends IPhoto { export interface IFolder extends IPhoto {