refactor: improve typing and doc
Signed-off-by: Varun Patil <radialapps@gmail.com>pull/803/head
parent
e75aa4006f
commit
b1ab26e44a
|
@ -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(),
|
||||
]);
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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,
|
||||
});
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
});
|
||||
|
|
|
@ -61,7 +61,7 @@ export default defineComponent({
|
|||
},
|
||||
|
||||
methods: {
|
||||
onRecyclerScroll({ dynTopMatterVisible }: { dynTopMatterVisible: boolean }) {
|
||||
onRecyclerScroll({ dynTopMatterVisible }: utils.BusEvent['memories.recycler.scroll']) {
|
||||
this.dynamicVisible = dynTopMatterVisible;
|
||||
},
|
||||
},
|
||||
|
|
|
@ -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', '');
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 &
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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 */
|
||||
|
|
|
@ -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 = {
|
|||
};
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
export const bus = {
|
||||
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);
|
||||
},
|
||||
|
|
30
src/types.ts
30
src/types.ts
|
@ -106,11 +106,21 @@ export interface IImageInfo {
|
|||
address?: string;
|
||||
tags?: { [id: string]: string };
|
||||
|
||||
exif?: {
|
||||
exif?: IExif;
|
||||
|
||||
clusters?: {
|
||||
albums?: IAlbum[];
|
||||
recognize?: IFace[];
|
||||
facerecognition?: IFace[];
|
||||
};
|
||||
}
|
||||
|
||||
export interface IExif {
|
||||
Rotation?: number;
|
||||
Orientation?: number;
|
||||
ImageWidth?: number;
|
||||
ImageHeight?: number;
|
||||
Megapixels?: number;
|
||||
|
||||
Title?: string;
|
||||
Description?: string;
|
||||
|
@ -122,14 +132,18 @@ export interface IImageInfo {
|
|||
OffsetTime?: string;
|
||||
LocationTZID?: string;
|
||||
|
||||
[other: string]: unknown;
|
||||
};
|
||||
ExposureTime?: number;
|
||||
ShutterSpeed?: number;
|
||||
ShutterSpeedValue?: number;
|
||||
Aperture?: number;
|
||||
ApertureValue?: number;
|
||||
ISO?: number;
|
||||
FNumber?: number;
|
||||
FocalLength?: number;
|
||||
|
||||
clusters?: {
|
||||
albums?: IAlbum[];
|
||||
recognize?: IFace[];
|
||||
facerecognition?: IFace[];
|
||||
};
|
||||
GPSAltitude?: number;
|
||||
GPSLatitude?: number;
|
||||
GPSLongitude?: number;
|
||||
}
|
||||
|
||||
export interface IFolder extends IPhoto {
|
||||
|
|
Loading…
Reference in New Issue