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
if ('json' === $format) {
// IPhoto object for the live video
return new JSONResponse([
'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 { API } from '../services/API';
import type { IAlbum, IFace, IImageInfo, IPhoto } from '../types';
import type { IAlbum, IFace, IImageInfo, IPhoto, IExif } from '../types';
interface TopField {
id?: string;
@ -117,7 +117,7 @@ export default defineComponent({
data: () => ({
fileid: null as number | null,
filename: '',
exif: {} as NonNullable<IImageInfo['exif']>,
exif: {} as IExif,
baseInfo: {} as IImageInfo,
loading: 0,
@ -198,12 +198,12 @@ export default defineComponent({
/** Title EXIF value */
title(): string | null {
return this.exif['Title'] || null;
return this.exif.Title || null;
},
/** Description EXIF value */
description(): string | null {
return this.exif['Description'] || null;
return this.exif.Description || null;
},
/** Date taken info */
@ -252,7 +252,7 @@ export default defineComponent({
dateOriginalTime(): string[] | 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 format = 't' + (hasTz ? ' ZZ' : '');
@ -262,18 +262,18 @@ export default defineComponent({
/** Camera make and model info */
camera(): string | null {
const make = this.exif['Make'];
const model = this.exif['Model'];
const make = this.exif.Make;
const model = this.exif.Model;
if (!make || !model) return null;
if (model.startsWith(make)) return model;
return `${make} ${model}`;
},
cameraSub(): string[] {
const f = this.exif['FNumber'] || this.exif['Aperture'];
const f = this.exif.FNumber || this.exif.Aperture;
const s = this.shutterSpeed;
const len = this.exif['FocalLength'];
const iso = this.exif['ISO'];
const len = this.exif.FocalLength;
const iso = this.exif.ISO;
const parts: string[] = [];
if (f) parts.push(`f/${f}`);
@ -285,7 +285,7 @@ export default defineComponent({
/** Convert shutter speed decimal to 1/x format */
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 < 1) {
@ -311,7 +311,7 @@ export default defineComponent({
imageInfoSub(): string[] {
let parts: string[] = [];
let mp = Number(this.exif['Megapixels']);
let mp = Number(this.exif.Megapixels);
if (this.baseInfo.w && this.baseInfo.h) {
parts.push(`${this.baseInfo.w}x${this.baseInfo.h}`);
@ -341,11 +341,11 @@ export default defineComponent({
},
lat(): number {
return Number(this.exif['GPSLatitude']);
return Number(this.exif.GPSLatitude);
},
lon(): number {
return Number(this.exif['GPSLongitude']);
return Number(this.exif.GPSLongitude);
},
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) {
this.refresh();
}

View File

@ -44,7 +44,7 @@ export default defineComponent({
},
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
},
},

View File

@ -476,7 +476,7 @@ export default defineComponent({
},
/** Handle mouse leave */
mouseleave() {
mouseleave(event: MouseEvent) {
this.interactend();
this.moveHoverCursor(this.cursorY);
},
@ -539,7 +539,7 @@ export default defineComponent({
},
/** Handle touch */
touchmove(event: any) {
touchmove(event: TouchEvent) {
if (!this.scrollerRect) return;
let y = event.targetTouches[0].pageY - this.scrollerRect.top;
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
const cacheName = 'memories-images';
let imageCache: Cache;
let imageCache: Cache | undefined;
(async function openCache() {
try {
imageCache = await self.caches?.open(cacheName);

View File

@ -11,7 +11,7 @@
@click="click($event, album)"
>
<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">
<ImageMultipleIcon :size="32" />
</div>
@ -81,10 +81,10 @@ export default defineComponent({
};
},
toCoverUrl(fileId: string | number) {
toCoverUrl(album: IAlbum) {
return getPreviewUrl({
photo: {
fileid: Number(fileId),
fileid: Number(album.last_added_photo),
} as IPhoto,
sqsize: 256,
});

View File

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

View File

@ -149,7 +149,7 @@ export default defineComponent({
computed: {
isAlbumList(): boolean {
return !Boolean(this.$route.params.name);
return !this.$route.params.name;
},
canEditAlbum(): boolean {
@ -158,7 +158,7 @@ export default defineComponent({
name(): string {
// 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 {

View File

@ -10,7 +10,7 @@ if (title) {
let isHidden = false; // cache state to avoid unnecessary DOM updates
// 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;
header.classList.toggle('hidden', (isHidden = dynTopMatterVisible));
});

View File

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

View File

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

View File

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

View File

@ -4,9 +4,16 @@ import { IPhoto } from '../../types';
type PsAugment = {
data: _SlideData & {
/** The original source of the image.*/
src: string;
msrc: string;
/** The original photo object. */
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 &

View File

@ -8,7 +8,7 @@ import * as utils from '../services/utils';
import { IConfig } from '../types';
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)[] = [
'square_thumbs',

View File

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

View File

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

View File

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

View File

@ -17,7 +17,7 @@ export function dateToDayId(date: Date) {
}
/** Get month name from number */
export function getShortDateStr(date: Date) {
export function getShortDateStr(date: Date): string {
const dayId = dateToDayId(date);
if (!shortDateStrMemo.has(dayId)) {
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 */

View File

@ -1,7 +1,7 @@
import { emit, subscribe, unsubscribe } from '@nextcloud/event-bus';
import { IConfig, IPhoto } from '../../types';
type BusEvent = {
export type BusEvent = {
/** Open/close the navigation drawer */
'toggle-navigation': { open: boolean };
/** File was created */
@ -26,7 +26,10 @@ type BusEvent = {
value: IConfig[keyof IConfig];
} | 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[];
/** Viewer has requested fetching day */
'memories:timeline:fetch-day': number;
@ -46,17 +49,32 @@ type BusEvent = {
};
/**
* Emit an event on the Nextcloud event bus.
* @param name Name of event
* @param data arguments
* Wrapper around Nextcloud's event bus.
*/
export const bus = {
/**
* Emit an event on the Nextcloud event bus.
* @param name Name of event
* @param data arguments
*/
emit<T extends keyof BusEvent>(name: T, data: BusEvent[T]): void {
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 {
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 {
unsubscribe(name, callback);
},

View File

@ -106,24 +106,7 @@ export interface IImageInfo {
address?: string;
tags?: { [id: string]: string };
exif?: {
Rotation?: number;
Orientation?: number;
ImageWidth?: number;
ImageHeight?: number;
Title?: string;
Description?: string;
Make?: string;
Model?: string;
DateTimeEpoch?: number;
OffsetTimeOriginal?: string;
OffsetTime?: string;
LocationTZID?: string;
[other: string]: unknown;
};
exif?: IExif;
clusters?: {
albums?: IAlbum[];
@ -132,6 +115,37 @@ export interface IImageInfo {
};
}
export interface IExif {
Rotation?: number;
Orientation?: number;
ImageWidth?: number;
ImageHeight?: number;
Megapixels?: number;
Title?: string;
Description?: string;
Make?: string;
Model?: string;
DateTimeEpoch?: number;
OffsetTimeOriginal?: string;
OffsetTime?: string;
LocationTZID?: string;
ExposureTime?: number;
ShutterSpeed?: number;
ShutterSpeedValue?: number;
Aperture?: number;
ApertureValue?: number;
ISO?: number;
FNumber?: number;
FocalLength?: number;
GPSAltitude?: number;
GPSLatitude?: number;
GPSLongitude?: number;
}
export interface IFolder extends IPhoto {
/** Path to folder */
path: string;