You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
214 lines
5.2 KiB
TypeScript
214 lines
5.2 KiB
TypeScript
// SPDX-FileCopyrightText: © 2017 EteSync Authors
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
|
|
import * as React from "react";
|
|
import * as ICAL from "ical.js";
|
|
import moment from "moment";
|
|
import { TaskPriorityType } from "./pim-types";
|
|
|
|
// Generic handling of input changes
|
|
export function handleInputChange(self: React.Component, part?: string) {
|
|
return (event: React.ChangeEvent<any>) => {
|
|
const name = event.target.name;
|
|
const value = event.target.value;
|
|
|
|
let newState;
|
|
|
|
if (event.target.type === "checkbox") {
|
|
newState = {
|
|
[name]: event.target.checked,
|
|
};
|
|
} else {
|
|
newState = {
|
|
[name]: value,
|
|
};
|
|
}
|
|
|
|
if (part === undefined) {
|
|
self.setState(newState);
|
|
} else {
|
|
self.setState({
|
|
[part]: {
|
|
...self.state[part],
|
|
...newState,
|
|
},
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
export function insertSorted<T>(array: T[] = [], newItem: T, key: string) {
|
|
if (array.length === 0) {
|
|
return [newItem];
|
|
}
|
|
|
|
for (let i = 0, len = array.length; i < len; i++) {
|
|
if (newItem[key] < array[i][key]) {
|
|
array.splice(i, 0, newItem);
|
|
return array;
|
|
}
|
|
}
|
|
|
|
array.push(newItem);
|
|
|
|
return array;
|
|
}
|
|
|
|
const allDayFormat = "dddd, LL";
|
|
const fullFormat = "LLLL";
|
|
|
|
export function formatDate(date: ICAL.Time) {
|
|
const mDate = moment(date.toJSDate());
|
|
if (date.isDate) {
|
|
return mDate.format(allDayFormat);
|
|
} else {
|
|
return mDate.format(fullFormat);
|
|
}
|
|
}
|
|
|
|
export function formatDateRange(start: ICAL.Time, end: ICAL.Time) {
|
|
const mStart = moment(start.toJSDate());
|
|
const mEnd = moment(end.toJSDate());
|
|
let strStart;
|
|
let strEnd;
|
|
|
|
// All day
|
|
if (start.isDate) {
|
|
if (mEnd.diff(mStart, "days", true) === 1) {
|
|
return mStart.format(allDayFormat);
|
|
} else {
|
|
strStart = mStart.format(allDayFormat);
|
|
strEnd = mEnd.clone().subtract(1, "day").format(allDayFormat);
|
|
}
|
|
} else if (mStart.isSame(mEnd, "day")) {
|
|
strStart = mStart.format(fullFormat);
|
|
strEnd = mEnd.format("LT");
|
|
|
|
if (mStart.isSame(mEnd)) {
|
|
return strStart;
|
|
}
|
|
} else {
|
|
strStart = mStart.format(fullFormat);
|
|
strEnd = mEnd.format(fullFormat);
|
|
}
|
|
|
|
return strStart + " - " + strEnd;
|
|
}
|
|
|
|
export function formatOurTimezoneOffset() {
|
|
let offset = new Date().getTimezoneOffset();
|
|
const prefix = (offset > 0) ? "-" : "+";
|
|
offset = Math.abs(offset);
|
|
const hours = Math.floor(offset / 60);
|
|
const minutes = offset % 60;
|
|
|
|
return `GMT${prefix}${hours.toString().padStart(2, "0")}:${minutes.toString().padStart(2, "0")}`;
|
|
}
|
|
|
|
export function getCurrentTimezone() {
|
|
return Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
}
|
|
|
|
export function mapPriority(priority: number): TaskPriorityType {
|
|
if (priority > 0 && priority < 5) {
|
|
return TaskPriorityType.High;
|
|
} else if (priority === 5) {
|
|
return TaskPriorityType.Medium;
|
|
} else if (priority > 5 && priority < 10) {
|
|
return TaskPriorityType.Low;
|
|
} else {
|
|
return TaskPriorityType.Undefined;
|
|
}
|
|
}
|
|
|
|
export function* arrayToChunkIterator<T>(arr: T[], size: number) {
|
|
for (let i = 0 ; i < arr.length ; i += size) {
|
|
yield arr.slice(i, i + size);
|
|
}
|
|
}
|
|
|
|
export function isPromise(x: any): x is Promise<any> {
|
|
return x && typeof x.then === "function";
|
|
}
|
|
|
|
export function isDefined<T>(x: T | undefined): x is T {
|
|
return x !== undefined;
|
|
}
|
|
|
|
export function startTask<T = any>(func: () => Promise<T> | T, delay = 0): Promise<T> {
|
|
return new Promise((resolve, reject) => {
|
|
setTimeout(
|
|
() => {
|
|
try {
|
|
const ret = func();
|
|
if (isPromise(ret)) {
|
|
ret.then(resolve)
|
|
.catch(reject);
|
|
} else {
|
|
resolve(ret);
|
|
}
|
|
} catch (e) {
|
|
reject(e);
|
|
}
|
|
},
|
|
delay);
|
|
});
|
|
}
|
|
|
|
export function usePromiseMemo<T>(promise: Promise<T> | undefined | null, deps: React.DependencyList, initial: T | undefined = undefined): T | undefined {
|
|
const [val, setVal] = React.useState<T>((promise as any)._returnedValue ?? initial);
|
|
React.useEffect(() => {
|
|
let cancel = false;
|
|
if (promise === undefined || promise === null) {
|
|
return undefined;
|
|
}
|
|
promise.then((val) => {
|
|
(promise as any)._returnedValue = val;
|
|
if (!cancel) {
|
|
setVal(val);
|
|
}
|
|
});
|
|
return () => {
|
|
cancel = true;
|
|
};
|
|
}, [...deps, promise]);
|
|
return val;
|
|
}
|
|
|
|
export function parseDate(prop: ICAL.Property) {
|
|
const value = prop.getFirstValue();
|
|
if ((value.day !== null) && (value.day !== undefined)) {
|
|
return {
|
|
day: value.day,
|
|
month: value.month - 1,
|
|
year: value.year ?? undefined,
|
|
};
|
|
} else {
|
|
const time = prop.toJSON()[3];
|
|
if (time.length === 6 && time.startsWith("--")) {
|
|
return {
|
|
day: parseInt(time.slice(4, 6)),
|
|
month: parseInt(time.slice(2, 4)) - 1,
|
|
};
|
|
} else if (time.length === 8) {
|
|
return {
|
|
day: parseInt(time.slice(6, 8)),
|
|
month: parseInt(time.slice(4, 6)) - 1,
|
|
year: parseInt(time.slice(0, 4)),
|
|
};
|
|
}
|
|
}
|
|
|
|
return {};
|
|
}
|
|
|
|
export const PASSWORD_MIN_LENGTH = 8;
|
|
|
|
export function enforcePasswordRules(password: string): string | undefined {
|
|
|
|
if (password.length < PASSWORD_MIN_LENGTH) {
|
|
return `Passwourds should be at least ${PASSWORD_MIN_LENGTH} digits long.`;
|
|
}
|
|
return undefined;
|
|
}
|