diff options
Diffstat (limited to 'src/Utils.ts')
-rw-r--r-- | src/Utils.ts | 885 |
1 files changed, 117 insertions, 768 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 291d7c799..3af057d82 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -1,52 +1,12 @@ -import * as Color from 'color'; -import { ColorResult } from 'react-color'; -import * as rp from 'request-promise'; -import { Socket } from 'socket.io'; import * as uuid from 'uuid'; -import { DocumentType } from './client/documents/DocumentTypes'; -import { Colors } from './client/views/global/globalEnums'; -import { DocumentView } from './client/views/nodes/DocumentView'; -import { Message } from './server/Message'; +export function clamp(n: number, lower: number, upper: number) { + return Math.max(lower, Math.min(upper, n)); +} export namespace Utils { - export let CLICK_TIME = 300; - export let DRAG_THRESHOLD = 4; - export let SNAP_THRESHOLD = 10; - export function isClick(x: number, y: number, downX: number, downY: number, downTime: number) { - return Date.now() - downTime < Utils.CLICK_TIME && Math.abs(x - downX) < Utils.DRAG_THRESHOLD && Math.abs(y - downY) < Utils.DRAG_THRESHOLD; - } - - export function cleanDocumentType(type: DocumentType) { - switch (type) { - case DocumentType.IMG: - return 'Image'; - case DocumentType.AUDIO: - return 'Audio'; - case DocumentType.COL: - return 'Collection'; - case DocumentType.RTF: - return 'Text'; - default: - return type.charAt(0).toUpperCase() + type.slice(1); - } - } - - export function readUploadedFileAsText(inputFile: File) { - const temporaryFileReader = new FileReader(); - - return new Promise((resolve, reject) => { - temporaryFileReader.onerror = () => { - temporaryFileReader.abort(); - reject(new DOMException('Problem parsing input file.')); - }; - - temporaryFileReader.onload = () => { - resolve(temporaryFileReader.result); - }; - temporaryFileReader.readAsText(inputFile); - }); + export function GuestID() { + return '__guest__'; } - export function GenerateGuid(): string { return uuid.v4(); } @@ -55,399 +15,138 @@ export namespace Utils { return uuid.v5(seed, uuid.v5.URL); } - export function GuestID() { - return '__guest__'; - } + export const loggingEnabled = false; + export const logFilter: number | undefined = undefined; - /** - * Uploads an image buffer to the server and stores with specified filename. by default the image - * is stored at multiple resolutions each retrieved by using the filename appended with _o, _s, _m, _l (indicating original, small, medium, or large) - * @param imageUri the bytes of the image - * @param returnedFilename the base filename to store the image on the server - * @param nosuffix optionally suppress creating multiple resolution images - */ - export async function convertDataUri(imageUri: string, returnedFilename: string, nosuffix = false, replaceRootFilename?: string) { - try { - const posting = Utils.prepend('/uploadURI'); - const returnedUri = await rp.post(posting, { - body: { - uri: imageUri, - name: returnedFilename, - nosuffix, - replaceRootFilename, - }, - json: true, - }); - return returnedUri; - } catch (e) { - console.log('ConvertDataURI :' + e); + export function log(prefixIn: string, messageName: string, messageIn: any, receiving: boolean) { + let prefix = prefixIn; + let message = messageIn; + if (!loggingEnabled) { + return; } - } - - export function GetScreenTransform(ele?: HTMLElement | null): { scale: number; translateX: number; translateY: number } { - if (!ele) { - return { scale: 1, translateX: 1, translateY: 1 }; + message = message || {}; + if (logFilter !== undefined && logFilter !== message.type) { + return; } - const rect = ele.getBoundingClientRect(); - const scale = ele.offsetWidth === 0 && rect.width === 0 ? 1 : rect.width / ele.offsetWidth; - const translateX = rect.left; - const translateY = rect.top; + const idString = (message.id || '').padStart(36, ' '); + prefix = prefix.padEnd(16, ' '); + console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `); + } - return { scale, translateX, translateY }; + export function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) { + return (args: any) => { + log(prefix, messageName, args, true); + func(args); + }; } export function TraceConsoleLog() { - ['log', 'warn'].forEach(function (method) { + ['log', 'warn'].forEach(method => { const old = (console as any)[method]; - (console as any)[method] = function () { + (console as any)[method] = function (...args: any[]) { let stack = new Error('').stack?.split(/\n/); // Chrome includes a single "Error" line, FF doesn't. if (stack && stack[0].indexOf('Error') === 0) { stack = stack.slice(1); } const message = (stack?.[1] || 'Stack undefined!').trim(); - const args = ([] as any[]).slice.apply(arguments).concat([message]); - return old.apply(console, args); + const newArgs = args.slice().concat([message]); + return old.apply(console, newArgs); }; }); } - /** - * A convenience method. Prepends the full path (i.e. http://localhost:<port>) to the - * requested extension - * @param extension the specified sub-path to append to the window origin - */ - export function prepend(extension: string): string { - return window.location.origin + extension; - } - export function fileUrl(filename: string): string { - return prepend(`/files/${filename}`); - } - - export function shareUrl(documentId: string): string { - return prepend(`/doc/${documentId}?sharing=true`); - } - - export function CorsProxy(url: string): string { - return prepend('/corsProxy/') + encodeURIComponent(url); - } - - export function CopyText(text: string) { - navigator.clipboard.writeText(text); - } - - export function decimalToHexString(number: number) { - if (number < 0) { - number = 0xffffffff + number + 1; - } - return (number < 16 ? '0' : '') + number.toString(16).toUpperCase(); - } - - export function colorString(color: ColorResult) { - return color.hex.startsWith('#') && color.hex.length < 8 ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex; - } - - export function fromRGBAstr(rgba: string) { - const rm = rgba.match(/rgb[a]?\(([ 0-9]+)/); - const r = rm ? Number(rm[1]) : 0; - const gm = rgba.match(/rgb[a]?\([ 0-9]+,([ 0-9]+)/); - const g = gm ? Number(gm[1]) : 0; - const bm = rgba.match(/rgb[a]?\([ 0-9]+,[ 0-9]+,([ 0-9]+)/); - const b = bm ? Number(bm[1]) : 0; - const am = rgba.match(/rgba?\([ 0-9]+,[ 0-9]+,[ 0-9]+,([ .0-9]+)/); - const a = am ? Number(am[1]) : 1; - return { r: r, g: g, b: b, a: a }; - } - - const isTransparentFunctionHack = 'isTransparent(__value__)'; - export const noRecursionHack = '__noRecursion'; - - // special case filters - export const noDragDocsFilter = 'noDragDocs::any::check'; - export const TransparentBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::check`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field - export const OpaqueBackgroundFilter = `backgroundColor::${isTransparentFunctionHack},${noRecursionHack}::x`; // bcz: hack. noRecursion should probably be either another ':' delimited field, or it should be a modifier to the comparision (eg., check, x, etc) field - - export function IsRecursiveFilter(val: string) { - return !val.includes(noRecursionHack); - } - export function HasFunctionFilter(val: string) { - if (val.includes(isTransparentFunctionHack)) return (color: string) => color !== '' && DashColor(color).alpha() !== 1; - // add other function filters here... - return undefined; - } - - export function toRGBAstr(col: { r: number; g: number; b: number; a?: number }) { - return 'rgba(' + col.r + ',' + col.g + ',' + col.b + (col.a !== undefined ? ',' + col.a : '') + ')'; - } - - export function HSLtoRGB(h: number, s: number, l: number) { - // Must be fractions of 1 - // s /= 100; - // l /= 100; - - const c = (1 - Math.abs(2 * l - 1)) * s, - x = c * (1 - Math.abs(((h / 60) % 2) - 1)), - m = l - c / 2; - let r = 0, - g = 0, - b = 0; - if (0 <= h && h < 60) { - r = c; - g = x; - b = 0; - } else if (60 <= h && h < 120) { - r = x; - g = c; - b = 0; - } else if (120 <= h && h < 180) { - r = 0; - g = c; - b = x; - } else if (180 <= h && h < 240) { - r = 0; - g = x; - b = c; - } else if (240 <= h && h < 300) { - r = x; - g = 0; - b = c; - } else if (300 <= h && h < 360) { - r = c; - g = 0; - b = x; - } - r = Math.round((r + m) * 255); - g = Math.round((g + m) * 255); - b = Math.round((b + m) * 255); - return { r: r, g: g, b: b }; - } - - export function RGBToHSL(r: number, g: number, b: number) { - // Make r, g, and b fractions of 1 - r /= 255; - g /= 255; - b /= 255; - - // Find greatest and smallest channel values - const cmin = Math.min(r, g, b), - cmax = Math.max(r, g, b), - delta = cmax - cmin; - let h = 0, - s = 0, - l = 0; - // Calculate hue - - // No difference - if (delta === 0) h = 0; - // Red is max - else if (cmax === r) h = ((g - b) / delta) % 6; - // Green is max - else if (cmax === g) h = (b - r) / delta + 2; - // Blue is max - else h = (r - g) / delta + 4; - - h = Math.round(h * 60); - - // Make negative hues positive behind 360° - if (h < 0) h += 360; // Calculate lightness - - l = (cmax + cmin) / 2; - - // Calculate saturation - s = delta === 0 ? 0 : delta / (1 - Math.abs(2 * l - 1)); - - // Multiply l and s by 100 - // s = +(s * 100).toFixed(1); - // l = +(l * 100).toFixed(1); - - return { h: h, s: s, l: l }; - } - - export function scrollIntoView(targetY: number, targetHgt: number, scrollTop: number, contextHgt: number, minSpacing: number, scrollHeight: number) { - if (!targetHgt) return targetY; // if there's no height, then assume that - if (scrollTop + contextHgt < Math.min(scrollHeight, targetY + minSpacing + targetHgt)) { - return Math.ceil(targetY + minSpacing + targetHgt - contextHgt); - } - if (scrollTop >= Math.max(0, targetY - minSpacing)) { - return Math.max(0, Math.floor(targetY - minSpacing)); - } - } - - export function clamp(n: number, lower: number, upper: number) { - return Math.max(lower, Math.min(upper, n)); - } - - export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] { - if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]]; - if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]]; - //if (xs2 > xe) - return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]]; - } - export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] { - if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]]; - if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]]; - //if (ys2 > ye) - return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]]; - } - export function rotPt(x: number, y: number, radAng: number) { return { x: x * Math.cos(radAng) - y * Math.sin(radAng), y: x * Math.sin(radAng) + y * Math.cos(radAng) }; } - function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) { - if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 }; - const atob = { x: bx - ax, y: by - ay }; - const atop = { x: px - ax, y: py - ay }; - const len = atob.x * atob.x + atob.y * atob.y; - var dot = atop.x * atob.x + atop.y * atob.y; - const t = Math.min(1, Math.max(0, dot / len)); - - dot = (bx - ax) * (py - ay) - (by - ay) * (px - ax); - - return { - point: { - x: ax + atob.x * t, - y: ay + atob.y * t, - }, - left: dot < 1, - dot: dot, - t: t, - }; - } + export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, xIn: number, yIn: number) { + const r = l + w; + const b = t + h; - export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number, l1: number, t1: number, w1: number, h1: number, x: number, y: number) { - const r = l + w, - b = t + h; - const r1 = l1 + w1, - b1 = t1 + h1; - const hsegs = [ - [l, r, t, l1, r1, t1], - [l, r, b, l1, r1, t1], - [l, r, t, l1, r1, b1], - [l, r, b, l1, r1, b1], - ]; - const vsegs = [ - [l, t, b, l1, t1, b1], - [r, t, b, l1, t1, b1], - [l, t, b, r1, t1, b1], - [r, t, b, r1, t1, b1], - ]; - const res = hsegs.reduce( - (closest, seg) => { - const res = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]); - return res[0] < closest[0] ? res : closest; - }, - [Number.MAX_VALUE, []] as [number, number[]] - ); - const fres = vsegs.reduce((closest, seg) => { - const res = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]); - return res[0] < closest[0] ? res : closest; - }, res); + const x = clamp(xIn, l, r); + const y = clamp(yIn, t, b); - const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]); - return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]); - } - - export function getNearestPointInPerimeter(l: number, t: number, w: number, h: number, x: number, y: number) { - const r = l + w, - b = t + h; - - (x = clamp(x, l, r)), (y = clamp(y, t, b)); - - const dl = Math.abs(x - l), - dr = Math.abs(x - r), - dt = Math.abs(y - t), - db = Math.abs(y - b); + const dl = Math.abs(x - l); + const dr = Math.abs(x - r); + const dt = Math.abs(y - t); + const db = Math.abs(y - b); const m = Math.min(dl, dr, dt, db); return m === dt ? [x, t] : m === db ? [x, b] : m === dl ? [l, y] : [r, y]; } +} +export function decimalToHexString(numberIn: number) { + const number = numberIn < 0 ? 0xffffffff + numberIn + 1 : numberIn; + return (number < 16 ? '0' : '') + number.toString(16).toUpperCase(); +} - export function GetClipboardText(): string { - const textArea = document.createElement('textarea'); - document.body.appendChild(textArea); - textArea.focus(); - textArea.select(); - - try { - document.execCommand('paste'); - } catch (err) {} - - const val = textArea.value; - document.body.removeChild(textArea); - return val; - } - - export const loggingEnabled: Boolean = false; - export const logFilter: number | undefined = undefined; - - export function log(prefix: string, messageName: string, message: any, receiving: boolean) { - if (!loggingEnabled) { - return; - } - message = message || {}; - if (logFilter !== undefined && logFilter !== message.type) { - return; - } - const idString = (message.id || '').padStart(36, ' '); - prefix = prefix.padEnd(16, ' '); - console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `); - } - - export function loggingCallback(prefix: string, func: (args: any) => any, messageName: string) { - return (args: any) => { - log(prefix, messageName, args, true); - func(args); - }; - } - - export function Emit<T>(socket: Socket, message: Message<T>, args: T) { - log('Emit', message.Name, args, false); - socket.emit(message.Message, args); - } +export function distanceBetweenHorizontalLines(xs: number, xe: number, y: number, xs2: number, xe2: number, y2: number): [number, number[]] { + if ((xs2 <= xs && xe2 >= xs) || (xs2 <= xe && xe2 >= xe) || (xs2 >= xs && xe2 <= xe)) return [Math.abs(y - y2), [Math.max(xs, xs2), y, Math.min(xe, xe2), y]]; + if (xe2 <= xs) return [Math.sqrt((xe2 - xs) * (xe2 - xs) + (y2 - y) * (y2 - y)), [xs, y, xs, y]]; + // if (xs2 > xe) + return [Math.sqrt((xs2 - xe) * (xs2 - xe) + (y2 - y) * (y2 - y)), [xe, y, xe, y]]; +} +export function distanceBetweenVerticalLines(x: number, ys: number, ye: number, x2: number, ys2: number, ye2: number): [number, number[]] { + if ((ys2 <= ys && ye2 >= ys) || (ys2 <= ye && ye2 >= ye) || (ys2 >= ys && ye2 <= ye)) return [Math.abs(x - x2), [x, Math.max(ys, ys2), x, Math.min(ye, ye2)]]; + if (ye2 <= ys) return [Math.sqrt((ye2 - ys) * (ye2 - ys) + (x2 - x) * (x2 - x)), [x, ys, x, ys]]; + // if (ys2 > ye) + return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]]; +} - export function AddServerHandler<T>(socket: Socket, message: Message<T>, handler: (args: T) => any) { - socket.on(message.Message, loggingCallback('Incoming', handler, message.Name)); - } +function project(px: number, py: number, ax: number, ay: number, bx: number, by: number) { + if (ax === bx && ay === by) return { point: { x: ax, y: ay }, left: false, dot: 0, t: 0 }; + const atob = { x: bx - ax, y: by - ay }; + const atop = { x: px - ax, y: py - ay }; + const len = atob.x * atob.x + atob.y * atob.y; + let dot = atop.x * atob.x + atop.y * atob.y; + const t = Math.min(1, Math.max(0, dot / len)); - export function AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) { - socket.on(message.Message, (arg: T, fn: (res: any) => any) => { - log('S receiving', message.Name, arg, true); - handler([arg, loggingCallback('S sending', fn, message.Name)]); - }); - } - export type RoomHandler = (socket: Socket, room: string) => any; - export type UsedSockets = Socket; - export type RoomMessage = 'create or join' | 'created' | 'joined'; - export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) { - socket.on(message, (room: any) => handler(socket, room)); - } -} + dot = (bx - ax) * (py - ay) - (by - ay) * (px - ax); -export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any; extract: any } { - const omit: any = { ...obj }; - const extract: any = {}; - keys.forEach(key => { - extract[key] = omit[key]; - delete omit[key]; - }); - pattern && - Array.from(Object.keys(omit)) - .filter(key => key.match(pattern)) - .forEach(key => { - extract[key] = omit[key]; - delete omit[key]; - }); - addKeyFunc?.(omit); - return { omit, extract }; + return { + point: { + x: ax + atob.x * t, + y: ay + atob.y * t, + }, + left: dot < 1, + dot: dot, + t: t, + }; } -export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) { - const dup: any = {}; - keys.forEach(key => (dup[key] = obj[key])); - addKeyFunc && addKeyFunc(dup); - return dup; +export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number, l1: number, t1: number, w1: number, h1: number, x: number, y: number) { + const r = l + w; + const b = t + h; + const r1 = l1 + w1; + const b1 = t1 + h1; + const hsegs = [ + [l, r, t, l1, r1, t1], + [l, r, b, l1, r1, t1], + [l, r, t, l1, r1, b1], + [l, r, b, l1, r1, b1], + ]; + const vsegs = [ + [l, t, b, l1, t1, b1], + [r, t, b, l1, t1, b1], + [l, t, b, r1, t1, b1], + [r, t, b, r1, t1, b1], + ]; + const res = hsegs.reduce( + (closest, seg) => { + const dist = distanceBetweenHorizontalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]); + return dist[0] < closest[0] ? dist : closest; + }, + [Number.MAX_VALUE, []] as [number, number[]] + ); + const fres = vsegs.reduce((closest, seg) => { + const dist = distanceBetweenVerticalLines(seg[0], seg[1], seg[2], seg[3], seg[4], seg[5]); + return dist[0] < closest[0] ? dist : closest; + }, res); + + const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]); + return project(near.point.x, near.point.y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]); } export function timenow() { @@ -455,7 +154,6 @@ export function timenow() { let ampm = 'am'; let h = now.getHours(); let m: any = now.getMinutes(); - const s: any = now.getSeconds(); if (h >= 12) { if (h > 12) h -= 12; ampm = 'pm'; @@ -464,14 +162,8 @@ export function timenow() { return now.toLocaleDateString() + ' ' + h + ':' + m + ' ' + ampm; } -export function incrementTitleCopy(title: string) { - const numstr = title.match(/.*(\{([0-9]*)\})+/); - const copyNumStr = `{${1 + (numstr ? +numstr[2] : 0)}}`; - return (numstr ? title.replace(numstr[1], '') : title) + copyNumStr; -} - -export function formatTime(time: number) { - time = Math.round(time); +export function formatTime(timeIn: number) { + const time = Math.round(timeIn); const hours = Math.floor(time / 60 / 60); const minutes = Math.floor(time / 60) - hours * 60; const seconds = time % 60; @@ -483,11 +175,11 @@ export function aggregateBounds(boundsList: { x: number; y: number; width?: numb const bounds = boundsList .map(b => ({ x: b.x, y: b.y, r: b.x + (b.width || 0), b: b.y + (b.height || 0) })) .reduce( - (bounds, b) => ({ - x: Math.min(b.x, bounds.x), - y: Math.min(b.y, bounds.y), - r: Math.max(b.r, bounds.r), - b: Math.max(b.b, bounds.b), + (prevBounds, b) => ({ + x: Math.min(b.x, prevBounds.x), + y: Math.min(b.y, prevBounds.y), + r: Math.max(b.r, prevBounds.r), + b: Math.max(b.b, prevBounds.b), }), { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE } ); @@ -503,93 +195,31 @@ export function intersectRect(r1: { left: number; top: number; width: number; he } export function stringHash(s?: string) { - return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (a => a & a)((a << 5) - a + b.charCodeAt(0)), 0)); + // eslint-disable-next-line no-bitwise + return !s ? undefined : Math.abs(s.split('').reduce((a: any, b: any) => (n => n & n)((a << 5) - a + b.charCodeAt(0)), 0)); } export function percent2frac(percent: string) { return Number(percent.substr(0, percent.length - 1)) / 100; } +export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; + +export type Predicate<K, V> = (entry: [K, V]) => boolean; export function numberRange(num: number) { return num > 0 && num < 1000 ? Array.from(Array(num)).map((v, i) => i) : []; } -export function returnTransparent() { - return 'transparent'; -} - -export function returnTrue() { - return true; -} - -export function returnIgnore(): 'ignore' { - return 'ignore'; -} -export function returnAlways(): 'always' { - return 'always'; -} -export function returnNever(): 'never' { - return 'never'; -} - -export function returnDefault(): 'default' { - return 'default'; -} - -export function return18() { - return 18; -} - -export function returnFalse() { - return false; -} - -export function returnAll(): 'all' { - return 'all'; -} - -export function returnNone(): 'none' { - return 'none'; -} - -export function returnVal(val1?: number, val2?: number) { - return val1 ? val1 : val2 !== undefined ? val2 : 0; -} - -export function returnOne() { - return 1; -} - -export function returnZero() { - return 0; -} - -export function returnEmptyString() { - return ''; -} - -export function returnEmptyFilter() { - return [] as string[]; -} - -export function returnEmptyDoclist() { - return [] as any[]; -} - -export let emptyPath: DocumentView[] = []; - export function emptyFunction() { return undefined; } +export const emptyPath: any[] = []; + export function unimplementedFunction() { throw new Error('This function is not implemented, but should be.'); } -export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; - -export type Predicate<K, V> = (entry: [K, V]) => boolean; - export function DeepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) { const deepCopy = new Map<K, V>(); const entries = source.entries(); @@ -616,197 +246,6 @@ export namespace JSONUtils { } } -const easeFunc = (transition: 'ease' | 'linear' | undefined, currentTime: number, start: number, change: number, duration: number) => { - if (transition === 'linear') { - let newCurrentTime = currentTime / duration; // currentTime / (duration / 2); - return start + newCurrentTime * change; - } - - let newCurrentTime = currentTime / (duration / 2); - if (newCurrentTime < 1) { - return (change / 2) * newCurrentTime * newCurrentTime + start; - } - - newCurrentTime -= 1; - return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start; -}; - -export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number, transition: 'ease' | 'linear' | undefined, stopper?: () => void) { - stopper?.(); - const elements = element instanceof HTMLElement ? [element] : element; - const starts = elements.map(element => element.scrollTop); - const startDate = new Date().getTime(); - let _stop = false; - const stop = () => (_stop = true); - const animateScroll = () => { - const currentDate = new Date().getTime(); - const currentTime = currentDate - startDate; - const setScrollTop = (element: HTMLElement, value: number) => (element.scrollTop = value); - if (!_stop) { - if (currentTime < duration) { - elements.forEach((element, i) => currentTime && setScrollTop(element, easeFunc(transition, Math.min(currentTime, duration), starts[i], to - starts[i], duration))); - requestAnimationFrame(animateScroll); - } else { - elements.forEach(element => setScrollTop(element, to)); - } - } - }; - animateScroll(); - return stop; -} - -export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) { - const elements = element instanceof HTMLElement ? [element] : element; - const starts = elements.map(element => element.scrollLeft); - const startDate = new Date().getTime(); - - const animateScroll = () => { - const currentDate = new Date().getTime(); - const currentTime = currentDate - startDate; - elements.map((element, i) => (element.scrollLeft = easeFunc('ease', currentTime, starts[i], to - starts[i], duration))); - - if (currentTime < duration) { - requestAnimationFrame(animateScroll); - } else { - elements.forEach(element => (element.scrollLeft = to)); - } - }; - animateScroll(); -} - -export function addStyleSheet(styleType: string = 'text/css') { - const style = document.createElement('style'); - style.type = styleType; - const sheets = document.head.appendChild(style); - return (sheets as any).sheet; -} -export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = '.') { - const propText = - typeof css === 'string' - ? css - : Object.keys(css) - .map(p => p + ':' + (p === 'content' ? "'" + css[p] + "'" : css[p])) - .join(';'); - return sheet.insertRule(selectorPrefix + selector + '{' + propText + '}', sheet.cssRules.length); -} -export function removeStyleSheetRule(sheet: any, rule: number) { - if (sheet.rules.length) { - sheet.removeRule(rule); - return true; - } - return false; -} -export function clearStyleSheetRules(sheet: any) { - if (sheet.rules.length) { - numberRange(sheet.rules.length).map(n => sheet.removeRule(0)); - return true; - } - return false; -} - -export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) { - if (!element) return; - ['pointerdown', 'pointerup'].map(event => { - const me = new PointerEvent(event, { - view: window, - bubbles: true, - cancelable: true, - button: 2, - pointerType: 'mouse', - clientX: x, - clientY: y, - screenX: sx, - screenY: sy, - }); - (me as any).dash = true; - element.dispatchEvent(me); - }); - - if (rightClick) { - const me = new MouseEvent('contextmenu', { - view: window, - bubbles: true, - cancelable: true, - button: 2, - clientX: x, - clientY: y, - movementX: 0, - movementY: 0, - screenX: sx, - screenY: sy, - }); - (me as any).dash = true; - element.dispatchEvent(me); - } -} - -export function DashColor(color: string) { - try { - return color ? Color(color.toLowerCase()) : Color('transparent'); - } catch (e) { - if (color.includes('gradient')) console.log("using color 'white' in place of :" + color); - else console.log('COLOR error:', e); - return Color('white'); - } -} - -export function lightOrDark(color: any) { - if (color === 'transparent' || !color) return Colors.BLACK; - if (color.startsWith?.('linear')) return Colors.BLACK; - if (DashColor(color).isLight()) return Colors.BLACK; - return Colors.WHITE; -} - -export function getWordAtPoint(elem: any, x: number, y: number): string | undefined { - if (elem.tagName === 'INPUT') return 'input'; - if (elem.tagName === 'TEXTAREA') return 'textarea'; - if (elem.nodeType === elem.TEXT_NODE) { - const range = elem.ownerDocument.createRange(); - range.selectNodeContents(elem); - var currentPos = 0; - const endPos = range.endOffset; - while (currentPos + 1 <= endPos) { - range.setStart(elem, currentPos); - range.setEnd(elem, currentPos + 1); - const rangeRect = range.getBoundingClientRect(); - if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) { - range.expand?.('word'); // doesn't exist in firefox - const ret = range.toString(); - range.detach(); - return ret; - } - currentPos += 1; - } - } else { - for (const childNode of elem.childNodes) { - const range = childNode.ownerDocument.createRange(); - range.selectNodeContents(childNode); - const rangeRect = range.getBoundingClientRect(); - if (rangeRect.left <= x && rangeRect.right >= x && rangeRect.top <= y && rangeRect.bottom >= y) { - range.detach(); - const word = getWordAtPoint(childNode, x, y); - if (word) return word; - } else { - range.detach(); - } - } - } - return undefined; -} - -export function isTargetChildOf(ele: HTMLDivElement | null, target: Element | null) { - let entered = false; - for (let child = target; !entered && child; child = child.parentElement) { - entered = child === ele; - } - return entered; -} - -export function StopEvent(e: React.PointerEvent | React.MouseEvent) { - e.stopPropagation(); - e.preventDefault(); -} - /** * Helper method for converting pixel string eg. '32px' into number eg. 32 * @param value: string with 'px' ending @@ -816,100 +255,10 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) { * '32px' -> 32 */ export function numberValue(value: string | undefined): number { - if (value == undefined) return 0; + if (value === undefined) return 0; return parseInt(value); } export function numbersAlmostEqual(num1: number, num2: number) { return Math.abs(num1 - num2) < 0.2; } - -export function setupMoveUpEvents( - target: object, - e: React.PointerEvent, - moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean, - upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any, - clickEvent: (e: PointerEvent, doubleTap?: boolean) => any, - stopPropagation: boolean = true, - stopMovePropagation: boolean = true, - noDoubleTapTimeout?: () => void -) { - const doubleTapTimeout = 300; - (target as any)._doubleTap = Date.now() - (target as any)._lastTap < doubleTapTimeout; - (target as any)._lastTap = Date.now(); - (target as any)._downX = (target as any)._lastX = e.clientX; - (target as any)._downY = (target as any)._lastY = e.clientY; - (target as any)._noClick = false; - var moving = false; - - const _moveEvent = (e: PointerEvent): void => { - if (moving || Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) { - moving = true; - if ((target as any)._doubleTime) { - clearTimeout((target as any)._doubleTime); - (target as any)._doubleTime = undefined; - } - if (moveEvent(e, [(target as any)._downX, (target as any)._downY], [e.clientX - (target as any)._lastX, e.clientY - (target as any)._lastY])) { - document.removeEventListener('pointermove', _moveEvent); - document.removeEventListener('pointerup', _upEvent); - } - } - (target as any)._lastX = e.clientX; - (target as any)._lastY = e.clientY; - stopMovePropagation && e.stopPropagation(); - }; - const _upEvent = (e: PointerEvent): void => { - const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4; - upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick); - if (isClick) { - if (!(target as any)._doubleTime && noDoubleTapTimeout) { - (target as any)._doubleTime = setTimeout(() => { - noDoubleTapTimeout?.(); - (target as any)._doubleTime = undefined; - }, doubleTapTimeout); - } - if ((target as any)._doubleTime && (target as any)._doubleTap) { - clearTimeout((target as any)._doubleTime); - (target as any)._doubleTime = undefined; - } - (target as any)._noClick = clickEvent(e, (target as any)._doubleTap); - } - document.removeEventListener('pointermove', _moveEvent); - document.removeEventListener('pointerup', _upEvent, true); - }; - const _clickEvent = (e: MouseEvent): void => { - if ((target as any)._noClick) e.stopPropagation(); - document.removeEventListener('click', _clickEvent, true); - }; - if (stopPropagation) { - e.stopPropagation(); - e.preventDefault(); - } - document.addEventListener('pointermove', _moveEvent); - document.addEventListener('pointerup', _upEvent, true); - document.addEventListener('click', _clickEvent, true); -} - -export function DivHeight(ele: HTMLElement): number { - return Number(getComputedStyle(ele).height.replace('px', '')); -} -export function DivWidth(ele: HTMLElement): number { - return Number(getComputedStyle(ele).width.replace('px', '')); -} - -export function dateRangeStrToDates(dateStr: string) { - // dateStr in yyyy-mm-dd format - const dateRangeParts = dateStr.split('|'); // splits into from and to date - const fromParts = dateRangeParts[0].split('-'); - const toParts = dateRangeParts[1].split('-'); - - const fromYear = parseInt(fromParts[0]); - const fromMonth = parseInt(fromParts[1]) - 1; - const fromDay = parseInt(fromParts[2]); - - const toYear = parseInt(toParts[0]); - const toMonth = parseInt(toParts[1]) - 1; - const toDay = parseInt(toParts[2]); - - return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)]; -} |