memories/src/services/Utils.ts

256 lines
7.3 KiB
TypeScript
Raw Normal View History

2022-09-16 23:17:45 +00:00
import { getCanonicalLocale } from "@nextcloud/l10n";
2022-10-16 19:58:38 +00:00
import { getCurrentUser } from '@nextcloud/auth'
2022-10-16 20:18:47 +00:00
import { loadState } from '@nextcloud/initial-state'
2022-10-16 03:28:40 +00:00
import { IPhoto } from "../types";
2022-10-18 03:06:01 +00:00
import moment from 'moment';
2022-09-16 23:17:45 +00:00
2022-10-15 18:12:54 +00:00
// Memoize the result of short date conversions
// These operations are surprisingly expensive
// and we do them a lot because of scroller hover
const shortDateStrMemo = new Map<number, string>();
2022-09-12 19:15:28 +00:00
/** Get JS date object from dayId */
2022-09-13 01:33:24 +00:00
export function dayIdToDate(dayId: number){
return new Date(dayId*86400*1000);
2022-09-12 19:15:28 +00:00
}
2022-10-15 18:12:54 +00:00
/** Get Day ID from JS date */
export function dateToDayId(date: Date){
return Math.floor(date.getTime() / (86400*1000));
}
2022-09-12 19:15:28 +00:00
/** Get month name from number */
2022-09-16 23:17:45 +00:00
export function getShortDateStr(date: Date) {
2022-10-15 18:12:54 +00:00
const dayId = dateToDayId(date);
if (!shortDateStrMemo.has(dayId)) {
shortDateStrMemo.set(dayId,
date.toLocaleDateString(getCanonicalLocale(), {
month: 'short',
year: 'numeric',
timeZone: 'UTC',
}));
}
return shortDateStrMemo.get(dayId);
2022-09-16 23:17:45 +00:00
}
/** Get long date string with optional year if same as current */
2022-09-25 13:21:40 +00:00
export function getLongDateStr(date: Date, skipYear=false, time=false) {
2022-09-16 23:17:45 +00:00
return date.toLocaleDateString(getCanonicalLocale(), {
2022-10-12 19:30:42 +00:00
weekday: 'short',
month: 'short',
2022-09-16 23:17:45 +00:00
day: 'numeric',
year: (skipYear && date.getUTCFullYear() === new Date().getUTCFullYear()) ? undefined : 'numeric',
2022-09-12 19:15:28 +00:00
timeZone: 'UTC',
2022-09-25 13:21:40 +00:00
hour: time ? 'numeric' : undefined,
minute: time ? 'numeric' : undefined,
2022-09-12 19:15:28 +00:00
});
2022-10-06 23:28:35 +00:00
}
2022-10-18 03:06:01 +00:00
/** Get text like "5 years ago" from a date */
export function getFromNowStr(date: Date) {
// Get fromNow in correct locale
const text = moment(date).locale(getCanonicalLocale()).fromNow();
// Title case
return text.charAt(0).toUpperCase() + text.slice(1);
}
2022-10-06 23:28:35 +00:00
/**
* Returns a hash code from a string
* @param {String} str The string to hash.
* @return {Number} A 32bit integer
* @see http://werxltd.com/wp/2010/05/13/javascript-implementation-of-javas-string-hashcode-method/
*/
export function hashCode(str: string): number {
2022-10-06 23:28:35 +00:00
let hash = 0;
for (let i = 0, len = str.length; i < len; i++) {
let chr = str.charCodeAt(i);
hash = (hash << 5) - hash + chr;
hash |= 0; // Convert to 32bit integer
}
return hash;
}
/**
* Search for elem in a sorted array of objects
* If the object is not found, return the index where it should be inserted
*
* @param arr Array of objects to search
* @param elem Element to search for
* @param key Key to use for comparison
*/
export function binarySearch(arr: any, elem: any, key?: string) {
let minIndex = 0;
let maxIndex = arr.length - 1;
let currentIndex: number;
let currentElement: any;
while (minIndex <= maxIndex) {
currentIndex = (minIndex + maxIndex) / 2 | 0;
currentElement = key ? arr[currentIndex][key] : arr[currentIndex];
if (currentElement < elem) {
minIndex = currentIndex + 1;
}
else if (currentElement > elem) {
maxIndex = currentIndex - 1;
}
else {
return currentIndex;
}
}
return minIndex;
}
2022-10-16 02:55:53 +00:00
/**
* Round a number to N decimal places
* @param num Number to round
* @param places Number of decimal places
* @param floor If true, round down instead of to nearest
2022-10-16 02:55:53 +00:00
*/
export function round(num: number, places: number, floor=false) {
2022-10-16 02:55:53 +00:00
const pow = Math.pow(10, places);
const int = num * pow;
return (floor ? Math.floor : Math.round)(int) / pow;
2022-10-16 02:55:53 +00:00
}
/**
* Round to nearest 0.5. Useful for pixels.
* @param num Number to round
*/
export function roundHalf(num: number) {
return Math.round(num * 2) / 2;
}
2022-10-18 17:52:44 +00:00
/** Choose a random element from an array */
export function randomChoice(arr: any[]) {
return arr[Math.floor(Math.random() * arr.length)];
}
/**
* Choose a random sub array from an array
* https://stackoverflow.com/a/11935263/4745239
*/
export function randomSubarray(arr: any[], size: number) {
if (arr.length <= size) return arr;
var shuffled = arr.slice(0), i = arr.length, min = i - size, temp, index;
while (i-- > min) {
index = Math.floor((i + 1) * Math.random());
temp = shuffled[index];
shuffled[index] = shuffled[i];
shuffled[i] = temp;
}
return shuffled.slice(min);
}
2022-10-16 03:28:40 +00:00
/**
* Convert server-side flags to bitmask
* @param photo Photo to process
*/
export function convertFlags(photo: IPhoto) {
2022-10-23 18:53:00 +00:00
if (typeof photo.flag === "undefined") {
photo.flag = 0; // flags
}
2022-10-16 03:28:40 +00:00
if (photo.isvideo) {
photo.flag |= constants.c.FLAG_IS_VIDEO;
delete photo.isvideo;
}
if (photo.isfavorite) {
photo.flag |= constants.c.FLAG_IS_FAVORITE;
delete photo.isfavorite;
}
if (photo.isfolder) {
photo.flag |= constants.c.FLAG_IS_FOLDER;
delete photo.isfolder;
}
if (photo.isface) {
photo.flag |= constants.c.FLAG_IS_FACE;
delete photo.isface;
}
if (photo.istag) {
photo.flag |= constants.c.FLAG_IS_TAG;
delete photo.istag;
}
2022-10-26 22:48:46 +00:00
if (photo.isalbum) {
photo.flag |= constants.c.FLAG_IS_ALBUM;
delete photo.isalbum;
}
2022-10-16 03:28:40 +00:00
}
2022-10-25 01:34:46 +00:00
// Outside for set
const TagDayID = {
START: -(1 << 30),
FOLDERS: -(1 << 30) + 1,
TAGS: -(1 << 30) + 2,
FACES: -(1 << 30) + 3,
2022-10-26 22:48:46 +00:00
ALBUMS: -(1 << 30) + 4,
2022-10-25 01:34:46 +00:00
}
/** Global constants */
2022-10-06 23:28:35 +00:00
export const constants = {
c: {
FLAG_PLACEHOLDER: 1 << 0,
2022-10-16 19:01:49 +00:00
FLAG_LOAD_FAIL: 1 << 1,
FLAG_IS_VIDEO: 1 << 2,
FLAG_IS_FAVORITE: 1 << 3,
FLAG_IS_FOLDER: 1 << 4,
FLAG_IS_TAG: 1 << 5,
FLAG_IS_FACE: 1 << 6,
2022-10-26 22:48:46 +00:00
FLAG_IS_ALBUM: 1 << 7,
FLAG_SELECTED: 1 << 8,
FLAG_LEAVING: 1 << 9,
2022-10-06 23:28:35 +00:00
},
2022-10-25 01:34:46 +00:00
TagDayID: TagDayID,
TagDayIDValueSet: new Set(Object.values(TagDayID)),
2022-10-16 19:58:38 +00:00
}
/** Cache store */
2022-10-16 23:41:27 +00:00
let staticCache: Cache | null = null;
2022-10-16 20:18:47 +00:00
const cacheName = `memories-${loadState('memories', 'version')}-${getCurrentUser()!.uid}`;
2022-10-16 23:41:27 +00:00
openCache().then((cache) => { staticCache = cache });
2022-10-17 02:17:56 +00:00
// Clear all caches except the current one
2022-10-21 04:28:10 +00:00
window.caches?.keys().then((keys) => {
2022-10-17 02:17:56 +00:00
keys.filter((key) => key.startsWith('memories-') && key !== cacheName).forEach((key) => {
2022-10-21 04:28:10 +00:00
window.caches.delete(key);
2022-10-17 02:17:56 +00:00
});
2022-10-23 18:01:53 +00:00
});
2022-10-17 02:17:56 +00:00
2022-10-16 23:41:27 +00:00
/** Open the cache */
export async function openCache() {
2022-10-23 18:01:53 +00:00
try {
return await window.caches?.open(cacheName);
} catch {
console.warn('Failed to get cache', cacheName);
return null;
}
2022-10-16 23:41:27 +00:00
}
2022-10-16 19:58:38 +00:00
/** Get data from the cache */
2022-10-18 18:35:38 +00:00
export async function getCachedData<T>(url: string): Promise<T> {
2022-10-21 04:28:10 +00:00
if (!window.caches) return null;
2022-10-16 23:41:27 +00:00
const cache = staticCache || await openCache();
2022-10-23 18:01:53 +00:00
if (!cache) return null;
2022-10-16 23:41:27 +00:00
const cachedResponse = await cache.match(url);
2022-10-18 18:35:38 +00:00
if (!cachedResponse || !cachedResponse.ok) return undefined;
2022-10-16 19:58:38 +00:00
return await cachedResponse.json();
}
/** Store data in the cache */
2022-10-19 18:22:43 +00:00
export function cacheData(url: string, data: Object) {
2022-10-21 04:28:10 +00:00
if (!window.caches) return;
2022-10-19 18:22:43 +00:00
const str = JSON.stringify(data);
2022-10-23 18:01:53 +00:00
2022-10-19 18:22:43 +00:00
(async () => {
const cache = staticCache || await openCache();
2022-10-23 18:01:53 +00:00
if (!cache) return;
2022-10-19 18:22:43 +00:00
const response = new Response(str);
response.headers.set('Content-Type', 'application/json');
await cache.put(url, response);
})();
2022-09-12 19:15:28 +00:00
}