aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts348
-rw-r--r--src/client/util/CurrentUserUtils.ts57
-rw-r--r--src/client/util/SelectionManager.ts6
-rw-r--r--src/client/util/SnappingManager.ts49
-rw-r--r--src/client/views/ContextMenu.tsx89
-rw-r--r--src/client/views/DashboardView.tsx17
-rw-r--r--src/client/views/GestureOverlay.scss11
-rw-r--r--src/client/views/GestureOverlay.tsx20
-rw-r--r--src/client/views/Main.scss7
-rw-r--r--src/client/views/MainView.scss26
-rw-r--r--src/client/views/MainView.tsx32
-rw-r--r--src/client/views/_nodeModuleOverrides.scss15
-rw-r--r--src/client/views/collections/CollectionDockingView.scss10
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx2
-rw-r--r--src/client/views/collections/CollectionView.tsx1
-rw-r--r--src/client/views/collections/TreeView.tsx7
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx37
-rw-r--r--src/client/views/nodes/DocumentView.tsx24
-rw-r--r--src/fields/util.ts304
19 files changed, 579 insertions, 483 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 6699aa133..528a429d0 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,5 +1,5 @@
import v4 = require('uuid/v4');
-import v5 = require("uuid/v5");
+import v5 = require('uuid/v5');
import { ColorState } from 'react-color';
import { Socket } from 'socket.io';
import { Colors } from './client/views/global/globalEnums';
@@ -16,7 +16,7 @@ export namespace Utils {
return new Promise((resolve, reject) => {
temporaryFileReader.onerror = () => {
temporaryFileReader.abort();
- reject(new DOMException("Problem parsing input file."));
+ reject(new DOMException('Problem parsing input file.'));
};
temporaryFileReader.onload = () => {
@@ -34,7 +34,7 @@ export namespace Utils {
return v5(seed, v5.URL);
}
- export function GetScreenTransform(ele?: HTMLElement): { scale: number, translateX: number, translateY: number } {
+ export function GetScreenTransform(ele?: HTMLElement): { scale: number; translateX: number; translateY: number } {
if (!ele) {
return { scale: 1, translateX: 1, translateY: 1 };
}
@@ -50,12 +50,12 @@ export namespace Utils {
['log', 'warn'].forEach(function (method) {
const old = (console as any)[method];
(console as any)[method] = function () {
- let stack = new Error("").stack?.split(/\n/);
+ 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 message = (stack?.[1] || 'Stack undefined!').trim();
const args = ([] as any[]).slice.apply(arguments).concat([message]);
return old.apply(console, args);
};
@@ -79,7 +79,7 @@ export namespace Utils {
}
export function CorsProxy(url: string): string {
- return prepend("/corsProxy/") + encodeURIComponent(url);
+ return prepend('/corsProxy/') + encodeURIComponent(url);
}
export function CopyText(text: string) {
@@ -88,14 +88,13 @@ export namespace Utils {
export function decimalToHexString(number: number) {
if (number < 0) {
- number = 0xFFFFFFFF + number + 1;
+ number = 0xffffffff + number + 1;
}
- return (number < 16 ? "0" : "") + number.toString(16).toUpperCase();
+ return (number < 16 ? '0' : '') + number.toString(16).toUpperCase();
}
export function colorString(color: ColorState) {
- return color.hex.startsWith("#") ?
- color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : "ff") : color.hex;
+ return color.hex.startsWith('#') ? color.hex + (color.rgb.a ? decimalToHexString(Math.round(color.rgb.a * 255)) : 'ff') : color.hex;
}
export function fromRGBAstr(rgba: string) {
@@ -110,8 +109,8 @@ export namespace Utils {
return { r: r, g: g, b: b, a: a };
}
- const isTransparentFunctionHack = "isTransparent(__value__)";
- export const noRecursionHack = "__noRecursion";
+ const isTransparentFunctionHack = 'isTransparent(__value__)';
+ export const noRecursionHack = '__noRecursion';
export function IsRecursiveFilter(val: string) {
return !val.includes(noRecursionHack);
}
@@ -120,18 +119,18 @@ export namespace Utils {
}
export function IsTransparentFilter() {
// bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
- return `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
+ return `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 function IsOpaqueFilter() {
// bcz: isTransparent(__value__) is a hack. it would be nice to have acual functions be parsed, but now Doc.matchFieldValue is hardwired to recognize just this one
- return `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
+ return `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 PropUnsetFilter(prop: string) {
return `${prop}:any,${noRecursionHack}:unset`;
}
- 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 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) {
@@ -140,23 +139,35 @@ export namespace Utils {
// l /= 100;
const c = (1 - Math.abs(2 * l - 1)) * s,
- x = c * (1 - Math.abs((h / 60) % 2 - 1)),
+ 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;
+ r = c;
+ g = x;
+ b = 0;
} else if (60 <= h && h < 120) {
- r = x; g = c; b = 0;
+ r = x;
+ g = c;
+ b = 0;
} else if (120 <= h && h < 180) {
- r = 0; g = c; b = x;
+ r = 0;
+ g = c;
+ b = x;
} else if (180 <= h && h < 240) {
- r = 0; g = x; b = c;
+ r = 0;
+ g = x;
+ b = c;
} else if (240 <= h && h < 300) {
- r = x; g = 0; b = c;
+ r = x;
+ g = 0;
+ b = c;
} else if (300 <= h && h < 360) {
- r = c; g = 0; b = x;
+ r = c;
+ g = 0;
+ b = x;
}
r = Math.round((r + m) * 255);
g = Math.round((g + m) * 255);
@@ -221,18 +232,17 @@ export namespace Utils {
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)
+ //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)
+ //if (ys2 > ye)
return [Math.sqrt((ys2 - ye) * (ys2 - ye) + (x2 - x) * (x2 - x)), [x, ye, x, ye]];
}
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 };
@@ -245,30 +255,41 @@ export namespace Utils {
return {
point: {
x: ax + atob.x * t,
- y: ay + atob.y * t
+ y: ay + atob.y * t,
},
left: dot < 1,
dot: dot,
- t: t
+ t: t,
};
}
- export function closestPtBetweenRectangles(l: number, t: number, w: number, h: number,
- l1: number, t1: number, w1: number, h1: number,
- x: number, y: number) {
+ 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 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;
+ return res[0] < closest[0] ? res : closest;
}, res);
const near = project(x, y, fres[1][0], fres[1][1], fres[1][2], fres[1][3]);
@@ -279,8 +300,7 @@ export namespace Utils {
const r = l + w,
b = t + h;
- x = clamp(x, l, r),
- y = clamp(y, t, b);
+ (x = clamp(x, l, r)), (y = clamp(y, t, b));
const dl = Math.abs(x - l),
dr = Math.abs(x - r),
@@ -289,18 +309,18 @@ export namespace Utils {
const m = Math.min(dl, dr, dt, db);
- return (m === dt) ? [x, t] :
- (m === db) ? [x, b] :
- (m === dl) ? [l, y] : [r, y];
+ return m === dt ? [x, t] : m === db ? [x, b] : m === dl ? [l, y] : [r, y];
}
export function GetClipboardText(): string {
- const textArea = document.createElement("textarea");
+ const textArea = document.createElement('textarea');
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
- try { document.execCommand('paste'); } catch (err) { }
+ try {
+ document.execCommand('paste');
+ } catch (err) {}
const val = textArea.value;
document.body.removeChild(textArea);
@@ -318,7 +338,7 @@ export namespace Utils {
if (logFilter !== undefined && logFilter !== message.type) {
return;
}
- const idString = (message.id || "").padStart(36, ' ');
+ const idString = (message.id || '').padStart(36, ' ');
prefix = prefix.padEnd(16, ' ');
console.log(`${prefix}: ${idString}, ${receiving ? 'receiving' : 'sending'} ${messageName} with data ${JSON.stringify(message)} `);
}
@@ -331,14 +351,14 @@ export namespace Utils {
}
export function Emit<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T) {
- log("Emit", message.Name, args, false);
+ log('Emit', message.Name, args, false);
socket.emit(message.Message, args);
}
export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T): Promise<any>;
export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn: (args: any) => any): void;
export function EmitCallback<T>(socket: Socket | SocketIOClient.Socket, message: Message<T>, args: T, fn?: (args: any) => any): void | Promise<any> {
- log("Emit", message.Name, args, false);
+ log('Emit', message.Name, args, false);
if (fn) {
socket.emit(message.Message, args, loggingCallback('Receiving', fn, message.Name));
} else {
@@ -358,30 +378,33 @@ export namespace Utils {
}
export type RoomHandler = (socket: Socket, room: string) => any;
export type UsedSockets = Socket | SocketIOClient.Socket;
- export type RoomMessage = "create or join" | "created" | "joined";
+ export type RoomMessage = 'create or join' | 'created' | 'joined';
export function AddRoomHandler(socket: Socket, message: RoomMessage, handler: RoomHandler) {
socket.on(message, room => handler(socket, room));
}
}
-export function OmitKeys(obj: any, keys: string[], pattern?: string, addKeyFunc?: (dup: any) => void): { omit: any, extract: any } {
+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];
- });
+ 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 };
}
export function WithKeys(obj: any, keys: string[], addKeyFunc?: (dup: any) => void) {
const dup: any = {};
- keys.forEach(key => dup[key] = obj[key]);
+ keys.forEach(key => (dup[key] = obj[key]));
addKeyFunc && addKeyFunc(dup);
return dup;
}
@@ -402,31 +425,39 @@ export function timenow() {
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;
+ const copyNumStr = `{${1 + (numstr ? +numstr[2] : 0)}}`;
+ return (numstr ? title.replace(numstr[1], '') : title) + copyNumStr;
}
export function formatTime(time: number) {
time = Math.round(time);
const hours = Math.floor(time / 60 / 60);
- const minutes = Math.floor(time / 60) - (hours * 60);
+ const minutes = Math.floor(time / 60) - hours * 60;
const seconds = time % 60;
- return (hours ? hours.toString() + ":" : "") + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
+ return (hours ? hours.toString() + ':' : '') + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
}
// x is furthest left, y is furthest top, r is furthest right, b is furthest bottom
-export function aggregateBounds(boundsList: { x: number, y: number, width?: number, height?: number }[], xpad: number, ypad: number) {
- 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)
- }), { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE });
+export function aggregateBounds(boundsList: { x: number; y: number; width?: number; height?: number }[], xpad: number, ypad: number) {
+ 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),
+ }),
+ { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: -Number.MAX_VALUE, b: -Number.MAX_VALUE }
+ );
return {
- x: bounds.x !== Number.MAX_VALUE ? bounds.x - xpad : bounds.x, y: bounds.y !== Number.MAX_VALUE ? bounds.y - ypad : bounds.y,
- r: bounds.r !== -Number.MAX_VALUE ? bounds.r + xpad : bounds.r, b: bounds.b !== -Number.MAX_VALUE ? bounds.b + ypad : bounds.b
+ x: bounds.x !== Number.MAX_VALUE ? bounds.x - xpad : bounds.x,
+ y: bounds.y !== Number.MAX_VALUE ? bounds.y - ypad : bounds.y,
+ r: bounds.r !== -Number.MAX_VALUE ? bounds.r + xpad : bounds.r,
+ b: bounds.b !== -Number.MAX_VALUE ? bounds.b + ypad : bounds.b,
};
}
-export function intersectRect(r1: { left: number, top: number, width: number, height: number },
- r2: { left: number, top: number, width: number, height: number }) {
+export function intersectRect(r1: { left: number; top: number; width: number; height: number }, r2: { left: number; top: number; width: number; height: number }) {
return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
}
@@ -434,36 +465,63 @@ export function percent2frac(percent: string) {
return Number(percent.substr(0, percent.length - 1)) / 100;
}
-export function numberRange(num: number) { return num > 0 && num < 1000 ? Array.from(Array(num)).map((v, i) => i) : []; }
+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 returnTransparent() {
+ return 'transparent';
+}
-export function returnTrue() { return true; }
+export function returnTrue() {
+ return true;
+}
-export function returnFalse() { return false; }
+export function returnFalse() {
+ return false;
+}
-export function returnAll() { return "all"; }
+export function returnAll() {
+ return 'all';
+}
-export function returnNone() { return "none"; }
+export function returnNone() {
+ return 'none';
+}
-export function returnVal(val1?: number, val2?: number) { return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0; }
+export function returnVal(val1?: number, val2?: number) {
+ return val1 !== undefined ? val1 : val2 !== undefined ? val2 : 0;
+}
-export function returnOne() { return 1; }
+export function returnOne() {
+ return 1;
+}
-export function returnZero() { return 0; }
+export function returnZero() {
+ return 0;
+}
-export function returnEmptyString() { return ""; }
+export function returnEmptyString() {
+ return '';
+}
-export function returnEmptyFilter() { return [] as string[]; }
+export function returnEmptyFilter() {
+ return [] as string[];
+}
-export function returnEmptyDoclist() { return [] as any[]; }
+export function returnEmptyDoclist() {
+ return [] as any[];
+}
export let emptyPath = [];
-export function emptyFunction() { return undefined; }
-
-export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); }
+export function emptyFunction() {
+ return undefined;
+}
+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>>;
@@ -484,7 +542,6 @@ export function DeepCopy<K, V>(source: Map<K, V>, predicate?: Predicate<K, V>) {
}
export namespace JSONUtils {
-
export function tryParse(source: string) {
let results: any;
try {
@@ -494,7 +551,6 @@ export namespace JSONUtils {
}
return results;
}
-
}
const easeInOutQuad = (currentTime: number, start: number, change: number, duration: number) => {
@@ -509,52 +565,57 @@ const easeInOutQuad = (currentTime: number, start: number, change: number, durat
};
export function smoothScroll(duration: number, element: HTMLElement | HTMLElement[], to: number) {
- const elements = (element instanceof HTMLElement ? [element] : element);
+ const elements = element instanceof HTMLElement ? [element] : element;
const starts = elements.map(element => element.scrollTop);
const startDate = new Date().getTime();
const animateScroll = () => {
const currentDate = new Date().getTime();
const currentTime = currentDate - startDate;
- elements.map((element, i) => element.scrollTop = easeInOutQuad(currentTime, starts[i], to - starts[i], duration));
+ elements.map((element, i) => (element.scrollTop = easeInOutQuad(currentTime, starts[i], to - starts[i], duration)));
if (currentTime < duration) {
requestAnimationFrame(animateScroll);
} else {
- elements.forEach(element => element.scrollTop = to);
+ elements.forEach(element => (element.scrollTop = to));
}
};
animateScroll();
}
export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) {
- const elements = (element instanceof HTMLElement ? [element] : element);
+ 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 = easeInOutQuad(currentTime, starts[i], to - starts[i], duration));
+ elements.map((element, i) => (element.scrollLeft = easeInOutQuad(currentTime, starts[i], to - starts[i], duration)));
if (currentTime < duration) {
requestAnimationFrame(animateScroll);
} else {
- elements.forEach(element => element.scrollLeft = to);
+ elements.forEach(element => (element.scrollLeft = to));
}
};
animateScroll();
}
-export function addStyleSheet(styleType: string = "text/css") {
- const style = document.createElement("style");
+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 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) {
@@ -573,33 +634,35 @@ export function clearStyleSheetRules(sheet: any) {
export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) {
if (!element) return;
- ["pointerdown", "pointerup"].map(event => element.dispatchEvent(
- new PointerEvent(event, {
+ ['pointerdown', 'pointerup'].map(event => {
+ const me = new PointerEvent(event, {
view: window,
bubbles: true,
cancelable: true,
button: 2,
- pointerType: "mouse",
+ 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,
- });
+ 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);
}
@@ -607,27 +670,25 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe
export function DashColor(color: string) {
try {
- return color ? Color(color.toLowerCase()) : Color("transparent");
+ return color ? Color(color.toLowerCase()) : Color('transparent');
} catch (e) {
- console.log("COLOR error:", e);
- return Color("red");
+ console.log('COLOR error:', e);
+ return Color('red');
}
}
export function lightOrDark(color: any) {
- if (color === "transparent") return "gray";
- if (color.startsWith?.("linear")) return "black";
- const nonAlphaColor = color.startsWith("#") ? (color as string).substring(0, 7) :
- color.startsWith("rgba") ? color.replace(/,.[^,]*\)/, ")").replace("rgba", "rgb") : color;
+ if (color === 'transparent') return 'gray';
+ if (color.startsWith?.('linear')) return 'black';
+ const nonAlphaColor = color.startsWith('#') ? (color as string).substring(0, 7) : color.startsWith('rgba') ? color.replace(/,.[^,]*\)/, ')').replace('rgba', 'rgb') : color;
const col = DashColor(nonAlphaColor).rgb();
- const colsum = (col.red() + col.green() + col.blue());
+ const colsum = col.red() + col.green() + col.blue();
if (colsum / col.alpha() > 400 || col.alpha() < 0.25) return Colors.DARK_GRAY;
else return Colors.WHITE;
}
-
export function getWordAtPoint(elem: any, x: number, y: number): string | undefined {
- if (elem.tagName === "INPUT") return "input";
+ if (elem.tagName === 'INPUT') return 'input';
if (elem.nodeType === elem.TEXT_NODE) {
const range = elem.ownerDocument.createRange();
range.selectNodeContents(elem);
@@ -637,12 +698,11 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi
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
+ 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);
+ return ret;
}
currentPos += 1;
}
@@ -651,8 +711,7 @@ export function getWordAtPoint(elem: any, x: number, y: number): string | undefi
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) {
+ 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;
@@ -681,17 +740,17 @@ export function StopEvent(e: React.PointerEvent | React.MouseEvent) {
* Helper method for converting pixel string eg. '32px' into number eg. 32
* @param value: string with 'px' ending
* @returns value: number
- *
+ *
* Example:
* '32px' -> 32
*/
-export function numberValue(value: string | undefined):number {
+export function numberValue(value: string | undefined): number {
if (value == undefined) return 0;
return parseInt(value);
}
export function numbersAlmostEqual(num1: number, num2: number) {
- return Math.abs( num1 - num2 ) < 0.2;
+ return Math.abs(num1 - num2) < 0.2;
}
export function setupMoveUpEvents(
@@ -705,7 +764,7 @@ export function setupMoveUpEvents(
noDoubleTapTimeout?: () => void
) {
const doubleTapTimeout = 300;
- (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < doubleTapTimeout);
+ (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;
@@ -717,10 +776,9 @@ export function setupMoveUpEvents(
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);
+ 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;
@@ -743,18 +801,18 @@ export function setupMoveUpEvents(
}
(target as any)._noClick = clickEvent(e, (target as any)._doubleTap);
}
- document.removeEventListener("pointermove", _moveEvent);
- document.removeEventListener("pointerup", _upEvent);
+ document.removeEventListener('pointermove', _moveEvent);
+ document.removeEventListener('pointerup', _upEvent);
};
const _clickEvent = (e: MouseEvent): void => {
if ((target as any)._noClick) e.stopPropagation();
- document.removeEventListener("click", _clickEvent, true);
- }
+ document.removeEventListener('click', _clickEvent, true);
+ };
if (stopPropagation) {
e.stopPropagation();
e.preventDefault();
}
- document.addEventListener("pointermove", _moveEvent);
- document.addEventListener("pointerup", _upEvent);
- document.addEventListener("click", _clickEvent, true);
-} \ No newline at end of file
+ document.addEventListener('pointermove', _moveEvent);
+ document.addEventListener('pointerup', _upEvent);
+ document.addEventListener('click', _clickEvent, true);
+}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 02d43088d..6c80cf0f4 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,37 +1,28 @@
-import { computed, observable, reaction } from "mobx";
+import { reaction } from "mobx";
import * as rp from 'request-promise';
-import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
-import { Id } from "../../fields/FieldSymbols";
+import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { List } from "../../fields/List";
import { PrefetchProxy } from "../../fields/Proxy";
import { RichTextField } from "../../fields/RichTextField";
import { listSpec } from "../../fields/Schema";
-import { ComputedField, ScriptField } from "../../fields/ScriptField";
-import { Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types";
+import { ScriptField } from "../../fields/ScriptField";
+import { Cast, DateCast, DocCast, PromiseValue, StrCast } from "../../fields/Types";
import { nullAudio } from "../../fields/URLField";
-import { SharingPermissions } from "../../fields/util";
+import { SetCachedGroups, SharingPermissions } from "../../fields/util";
import { OmitKeys, Utils } from "../../Utils";
import { DocServer } from "../DocServer";
import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents";
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
-import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { TreeViewType } from "../views/collections/CollectionTreeView";
-import { CollectionView } from "../views/collections/CollectionView";
-import { TreeView } from "../views/collections/TreeView";
-import { DashboardView } from "../views/DashboardView";
import { Colors } from "../views/global/globalEnums";
import { MainView } from "../views/MainView";
import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox";
import { OverlayView } from "../views/OverlayView";
import { DragManager } from "./DragManager";
import { MakeTemplate } from "./DropConverter";
-import { HistoryUtil } from "./History";
import { LinkManager } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
-import { SelectionManager } from "./SelectionManager";
import { ColorScheme } from "./SettingsManager";
-import { SharingManager } from "./SharingManager";
-import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
interface Button {
@@ -488,7 +479,7 @@ export class CurrentUserUtils {
var myFilesystem = DocCast(doc[field]);
const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true });
- const newFolder = `makeTopLevelFolder()`;
+ const newFolder = `TreeView_addNewFolder()`;
const newFolderOpts: DocumentOptions = {
_forceActive: true, _stayInCollection: true, _hideContextMenu: true, _width: 30, _height: 30,
title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", system: true
@@ -638,16 +629,16 @@ export class CurrentUserUtils {
CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel,
CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
CollectionViewType.Grid]),
- title: "Perspective", toolTip: "View", width: 100,btnType: ButtonType.DropdownList,ignoreClick: true, scripts: { script: 'setView(value, _readOnly_)'}},
- { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}},
- { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}},
- { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setBackgroundColor(value, _readOnly_)'},funcs: {hidden: '!selectedDocumentType()'}}, // Only when a document is selected
- { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setHeaderColor(value, _readOnly_)'}, funcs: {hidden: '!selectedDocumentType()'}},
- { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, scripts: { onClick: 'toggleOverlay(_readOnly_)'}, funcs: {hidden: '!selectedDocumentType(undefined, "freeform", true)'}}, // Only when floating document is selected in freeform
- { title: "Text", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available
- { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available
- { title: "Web", icon: "web", subMenu: CurrentUserUtils.webTools(), funcs: {linearViewIsExpanded: `selectedDocumentType("${DocumentType.WEB}")`, hidden: `!selectedDocumentType("${DocumentType.WEB}")`} }, // Only when Web is selected
- { title: "Schema", icon: "schema", subMenu: CurrentUserUtils.schemaTools(), funcs: {linearViewIsExpanded: `selectedDocumentType(undefined, "${CollectionViewType.Schema}")`, hidden: `!selectedDocumentType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected
+ title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
+ { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform") || IsNoviceMode()'}, width: 20, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
+ { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, width: 20, scripts: { script: 'setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected
+ { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, funcs: {hidden: '!SelectionManager_selectedDocType()'}, ignoreClick: true, scripts: { script: 'setHeaderColor(value, _readOnly_)'}},
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, funcs: {hidden: '!SelectionManager_selectedDocType(undefined, "freeform", true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform
+ { title: "Text", icon: "text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), funcs: {hidden: 'false', linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.RTF}")`} }, // Always available
+ { title: "Ink", icon: "ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), funcs: {hidden: 'false', inearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.INK}")`} }, // Always available
+ { title: "Web", icon: "web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), funcs: {hidden: `!SelectionManager_selectedDocType("${DocumentType.WEB}")`, linearViewIsExpanded: `SelectionManager_selectedDocType("${DocumentType.WEB}")`, } }, // Only when Web is selected
+ { title: "Schema", icon: "schema", toolTip: "Schema functions", subMenu: CurrentUserUtils.schemaTools(), funcs: {hidden: `!SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`, linearViewIsExpanded: `SelectionManager_selectedDocType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected
];
}
@@ -812,7 +803,7 @@ export class CurrentUserUtils {
async () => {
const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
+ SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]);
}, { fireImmediately: true });
doc.system ?? (doc.system = true);
doc.title ?? (doc.title = Doc.CurrentUserEmail);
@@ -947,21 +938,7 @@ export class CurrentUserUtils {
ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
-ScriptingGlobals.add(function snapshotDashboard() { DashboardView.snapshotDashboard(); }, "creates a snapshot copy of a dashboard");
-ScriptingGlobals.add(function createNewDashboard() { return DashboardView.createNewDashboard(); }, "creates a new dashboard when called");
ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called");
ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called");
ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)");
ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
-ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard");
-ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { DashboardView.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards");
-ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { DashboardView.openDashboard( Doc.MakeAlias(dashboard)); }, "adds Dashboard to set of Dashboards");
-ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
- let selected = (sel => checkContext ? DocCast(sel?.context) : sel)(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
- return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
-});
-ScriptingGlobals.add(function makeTopLevelFolder() {
- TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
- const opts = { title: "Untitled folder", _stayInCollection: true, isFolder: true };
- return Doc.AddDocToList(Doc.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
-}); \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index d67ce6f6a..1c84af94a 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,8 +1,10 @@
import { action, observable, ObservableMap } from 'mobx';
import { computedFn } from 'mobx-utils';
import { Doc, Opt } from '../../fields/Doc';
+import { DocCast } from '../../fields/Types';
import { CollectionViewType, DocumentType } from '../documents/DocumentTypes';
import { DocumentView } from '../views/nodes/DocumentView';
+import { ScriptingGlobals } from './ScriptingGlobals';
export namespace SelectionManager {
class Manager {
@@ -98,3 +100,7 @@ export namespace SelectionManager {
return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking);
}
}
+ScriptingGlobals.add(function SelectionManager_selectedDocType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) {
+ let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
+ return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true;
+});
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 057843c68..2c0a1da8b 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,9 +1,8 @@
-import { observable, action, runInAction } from "mobx";
-import { computedFn } from "mobx-utils";
-import { Doc } from "../../fields/Doc";
+import { observable, action, runInAction } from 'mobx';
+import { computedFn } from 'mobx-utils';
+import { Doc } from '../../fields/Doc';
export namespace SnappingManager {
-
class Manager {
@observable IsDragging: boolean = false;
@observable public horizSnapLines: number[] = [];
@@ -16,28 +15,34 @@ export namespace SnappingManager {
this.horizSnapLines = horizLines;
this.vertSnapLines = vertLines;
}
-
- @observable cachedGroups: string[] = [];
- @action setCachedGroups(groups: string[]) { this.cachedGroups = groups; }
}
const manager = new Manager();
- export function clearSnapLines() { manager.clearSnapLines(); }
- export function setSnapLines(horizLines: number[], vertLines: number[]) { manager.setSnapLines(horizLines, vertLines); }
- export function horizSnapLines() { return manager.horizSnapLines; }
- export function vertSnapLines() { return manager.vertSnapLines; }
-
- export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); }
- export function GetIsDragging() { return manager.IsDragging; }
+ export function clearSnapLines() {
+ manager.clearSnapLines();
+ }
+ export function setSnapLines(horizLines: number[], vertLines: number[]) {
+ manager.setSnapLines(horizLines, vertLines);
+ }
+ export function horizSnapLines() {
+ return manager.horizSnapLines;
+ }
+ export function vertSnapLines() {
+ return manager.vertSnapLines;
+ }
- export function SetShowSnapLines(show: boolean) { runInAction(() => Doc.UserDoc().showSnapLines = show); }
- export function GetShowSnapLines() { return Doc.UserDoc().showSnapLines; }
+ export function SetIsDragging(dragging: boolean) {
+ runInAction(() => (manager.IsDragging = dragging));
+ }
+ export function GetIsDragging() {
+ return manager.IsDragging;
+ }
- /// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts
- // need to investigate further what caused the mobx update problems and move to a better location.
- const getCachedGroupByNameCache = computedFn(function (name: string) { return manager.cachedGroups.includes(name); }, true);
- export function GetCachedGroupByName(name: string) { return getCachedGroupByNameCache(name); }
- export function SetCachedGroups(groups: string[]) { manager.setCachedGroups(groups); }
+ export function SetShowSnapLines(show: boolean) {
+ runInAction(() => (Doc.UserDoc().showSnapLines = show));
+ }
+ export function GetShowSnapLines() {
+ return Doc.UserDoc().showSnapLines;
+ }
}
-
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index cffcd0f17..c9908b4b0 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,10 +1,10 @@
-import React = require("react");
+import React = require('react');
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, IReactionDisposer, observable } from "mobx";
-import { observer } from "mobx-react";
-import "./ContextMenu.scss";
-import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem";
-import { Utils } from "../../Utils";
+import { action, computed, IReactionDisposer, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import './ContextMenu.scss';
+import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from './ContextMenuItem';
+import { Utils } from '../../Utils';
@observer
export class ContextMenu extends React.Component {
@@ -14,7 +14,7 @@ export class ContextMenu extends React.Component {
@observable private _pageX: number = 0;
@observable private _pageY: number = 0;
@observable private _display: boolean = false;
- @observable private _searchString: string = "";
+ @observable private _searchString: string = '';
@observable private _showSearch: boolean = false;
// afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
@observable private _yRelativeToTop: boolean = true;
@@ -43,9 +43,10 @@ export class ContextMenu extends React.Component {
onPointerDown = (e: PointerEvent) => {
this._mouseX = e.clientX;
this._mouseY = e.clientY;
- }
+ };
@action
onPointerUp = (e: PointerEvent) => {
+ if (e.button !== 2 && !e.ctrlKey) return;
const curX = e.clientX;
const curY = e.clientY;
if (this._ignoreUp) {
@@ -63,27 +64,27 @@ export class ContextMenu extends React.Component {
this._display = true;
}
}
- }
+ };
componentWillUnmount() {
- document.removeEventListener("pointerdown", this.onPointerDown);
- document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener('pointerdown', this.onPointerDown, true);
+ document.removeEventListener('pointerup', this.onPointerUp);
this._reactionDisposer?.();
}
@action
componentDidMount = () => {
- document.addEventListener("pointerdown", this.onPointerDown);
- document.addEventListener("pointerup", this.onPointerUp);
- }
+ document.addEventListener('pointerdown', this.onPointerDown, true);
+ document.addEventListener('pointerup', this.onPointerUp);
+ };
@action
clearItems() {
this._items = [];
- this._defaultPrefix = "";
+ this._defaultPrefix = '';
this._defaultItem = undefined;
}
- _defaultPrefix: string = "";
+ _defaultPrefix: string = '';
_defaultItem: ((name: string) => void) | undefined;
findByDescription = (target: string, toLowerCase = false) => {
@@ -92,7 +93,7 @@ export class ContextMenu extends React.Component {
toLowerCase && (reference = reference.toLowerCase());
return reference === target;
});
- }
+ };
@action
addItem(item: ContextMenuProps) {
@@ -103,9 +104,9 @@ export class ContextMenu extends React.Component {
@action
moveAfter(item: ContextMenuProps, after: ContextMenuProps) {
if (after && this.findByDescription(after.description)) {
- const curInd = this._items.findIndex((i) => i.description === item.description);
+ const curInd = this._items.findIndex(i => i.description === item.description);
this._items.splice(curInd, 1);
- const afterInd = this._items.findIndex((i) => i.description === after.description);
+ const afterInd = this._items.findIndex(i => i.description === after.description);
this._items.splice(afterInd + 1, 0, item);
}
}
@@ -142,7 +143,7 @@ export class ContextMenu extends React.Component {
_onDisplay?: () => void = undefined;
@action
- displayMenu = (x: number, y: number, initSearch = "", showSearch = false, onDisplay?: () => void) => {
+ displayMenu = (x: number, y: number, initSearch = '', showSearch = false, onDisplay?: () => void) => {
//maxX and maxY will change if the UI/font size changes, but will work for any amount
//of items added to the menu
@@ -153,7 +154,7 @@ export class ContextMenu extends React.Component {
this._shouldDisplay = true;
this._onDisplay = onDisplay;
this._display = !onDisplay;
- }
+ };
@action
closeMenu = () => {
@@ -162,10 +163,10 @@ export class ContextMenu extends React.Component {
this._display = false;
this._shouldDisplay = false;
return wasOpen;
- }
+ };
@computed get filteredItems(): (OriginalMenuProps | string[])[] {
- const searchString = this._searchString.toLowerCase().split(" ");
+ const searchString = this._searchString.toLowerCase().split(' ');
const matches = (descriptions: string[]): boolean => {
return searchString.every(s => descriptions.some(desc => desc.toLowerCase().includes(s)));
};
@@ -176,7 +177,7 @@ export class ContextMenu extends React.Component {
for (const item of items) {
const description = item.description;
const path = groupFunc(description);
- if ("subitems" in item) {
+ if ('subitems' in item) {
const children = flattenItems(item.subitems, name => [...groupFunc(description), name]);
if (children.length || matches(path)) {
eles.push(path);
@@ -205,13 +206,14 @@ export class ContextMenu extends React.Component {
if (!this._searchString) {
return this._items.map((item, ind) => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={ind + item.description} closeMenu={this.closeMenu} />);
}
- return this.filteredItems.map((value, index) =>
- Array.isArray(value) ?
+ return this.filteredItems.map((value, index) =>
+ Array.isArray(value) ? (
<div className="contextMenu-group">
- <div className="contextMenu-description">{value.join(" -> ")}</div>
+ <div className="contextMenu-description">{value.join(' -> ')}</div>
</div>
- :
- <ContextMenuItem {...value} key={index+value.description} closeMenu={this.closeMenu} selected={index === this.selectedIndex} />
+ ) : (
+ <ContextMenuItem {...value} key={index + value.description} closeMenu={this.closeMenu} selected={index === this.selectedIndex} />
+ )
);
}
@@ -220,32 +222,34 @@ export class ContextMenu extends React.Component {
}
render() {
- return !this._display ? (null) :
- <div className="contextMenu-cont" style={{left: this.pageX, ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY })}}>
- {!this.itemsNeedSearch ? (null) :
- <span className={"search-icon"}>
+ return !this._display ? null : (
+ <div className="contextMenu-cont" style={{ left: this.pageX, ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY }) }}>
+ {!this.itemsNeedSearch ? null : (
+ <span className={'search-icon'}>
<span className="icon-background">
<FontAwesomeIcon icon="search" size="lg" />
</span>
<input className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus />
- </span>}
+ </span>
+ )}
{this.menuItems}
- </div>;
+ </div>
+ );
}
@action
onKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === "ArrowDown") {
+ if (e.key === 'ArrowDown') {
if (this.selectedIndex < this.flatItems.length - 1) {
this.selectedIndex++;
}
e.preventDefault();
- } else if (e.key === "ArrowUp") {
+ } else if (e.key === 'ArrowUp') {
if (this.selectedIndex > 0) {
this.selectedIndex--;
}
e.preventDefault();
- } else if (e.key === "Enter" || e.key === "Tab") {
+ } else if (e.key === 'Enter' || e.key === 'Tab') {
const item = this.flatItems[this.selectedIndex];
if (item) {
item.event({ x: this.pageX, y: this.pageY });
@@ -256,20 +260,19 @@ export class ContextMenu extends React.Component {
e.preventDefault();
e.stopPropagation();
}
- }
+ };
@action
onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this._searchString = e.target.value;
if (!this._searchString) {
this.selectedIndex = -1;
- }
- else {
+ } else {
if (this.selectedIndex === -1) {
this.selectedIndex = 0;
} else {
this.selectedIndex = Math.min(this.flatItems.length - 1, this.selectedIndex);
}
}
- }
-} \ No newline at end of file
+ };
+}
diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx
index c59c37488..526a32f5a 100644
--- a/src/client/views/DashboardView.tsx
+++ b/src/client/views/DashboardView.tsx
@@ -10,6 +10,7 @@ import { DocServer } from '../DocServer';
import { Docs, DocumentOptions } from '../documents/Documents';
import { CollectionViewType } from '../documents/DocumentTypes';
import { HistoryUtil } from '../util/History';
+import { ScriptingGlobals } from '../util/ScriptingGlobals';
import { SharingManager } from '../util/SharingManager';
import { undoBatch } from '../util/UndoManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
@@ -288,3 +289,19 @@ export class DashboardView extends React.Component {
export function AddToList(MySharedDocs: Doc, arg1: string, dash: any) {
throw new Error('Function not implemented.');
}
+
+ScriptingGlobals.add(function createNewDashboard() {
+ return DashboardView.createNewDashboard();
+}, 'creates a new dashboard when called');
+ScriptingGlobals.add(function shareDashboard(dashboard: Doc) {
+ SharingManager.Instance.open(undefined, dashboard);
+}, 'opens sharing dialog for Dashboard');
+ScriptingGlobals.add(function removeDashboard(dashboard: Doc) {
+ DashboardView.removeDashboard(dashboard);
+}, 'Remove Dashboard from Dashboards');
+ScriptingGlobals.add(function addToDashboards(dashboard: Doc) {
+ DashboardView.openDashboard(Doc.MakeAlias(dashboard));
+}, 'adds Dashboard to set of Dashboards');
+ScriptingGlobals.add(function snapshotDashboard() {
+ DashboardView.snapshotDashboard();
+}, 'creates a snapshot copy of a dashboard');
diff --git a/src/client/views/GestureOverlay.scss b/src/client/views/GestureOverlay.scss
index 16a4c74d1..f5cbbffb1 100644
--- a/src/client/views/GestureOverlay.scss
+++ b/src/client/views/GestureOverlay.scss
@@ -1,11 +1,8 @@
.gestureOverlay-cont {
width: 100vw;
height: 100vh;
- position: relative;
- top: 0;
- left: 0;
+ position: absolute;
touch-action: none;
- z-index: -1;
.pointerBubbles {
width: 100%;
@@ -18,7 +15,7 @@
width: 15px;
height: 15px;
border-radius: 100%;
- border: .5px solid grey;
+ border: 0.5px solid grey;
}
}
}
@@ -44,7 +41,7 @@
width: 100%;
height: 25px;
padding: 2.5px;
- border-bottom: .5px solid black;
+ border-bottom: 0.5px solid black;
}
}
@@ -62,4 +59,4 @@
position: absolute;
background-color: transparent;
border: 1px solid black;
-} \ No newline at end of file
+}
diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx
index 5a68e9091..5ec4de953 100644
--- a/src/client/views/GestureOverlay.tsx
+++ b/src/client/views/GestureOverlay.tsx
@@ -582,13 +582,15 @@ export class GestureOverlay extends Touchable {
})
);
}
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
- if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
- Doc.ActiveTool = InkTool.Write;
+ if (!(e.target as any)?.className?.startsWith('lm_')) {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) {
+ Doc.ActiveTool = InkTool.Write;
+ }
+ this._points.push({ X: e.clientX, Y: e.clientY });
+ setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
+ // if (Doc.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
}
- this._points.push({ X: e.clientX, Y: e.clientY });
- setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction);
- // if (Doc.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)");
}
};
@@ -965,7 +967,7 @@ export class GestureOverlay extends Touchable {
this._strokes.map((l, i) => {
const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(l, true);
return (
- <svg key={i} width={b.width} height={b.height} style={{ transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
+ <svg key={i} width={b.width} height={b.height} style={{ top: 0, left: 0, transform: `translate(${b.left}px, ${b.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
{InteractionUtils.CreatePolyline(
l,
b.left,
@@ -992,9 +994,9 @@ export class GestureOverlay extends Touchable {
);
}),
this._points.length <= 1 ? null : (
- <svg key="svg" width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
+ <svg key="svg" width={B.width} height={B.height} style={{ top: 0, left: 0, transform: `translate(${B.left}px, ${B.top}px)`, pointerEvents: 'none', position: 'absolute', zIndex: 30000, overflow: 'visible' }}>
{InteractionUtils.CreatePolyline(
- this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })),
+ this._points.map(p => ({ X: p.X - (rect?.x || 0), Y: p.Y - (rect?.y || 0) })),
B.left,
B.top,
ActiveInkColor(),
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index c8e64b5c4..c7a7614ac 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,5 +1,5 @@
-@import "global/globalCssVariables";
-@import "nodeModuleOverrides";
+@import 'global/globalCssVariables';
+@import 'nodeModuleOverrides';
:root {
--flyoutHandleWidth: 28px;
@@ -24,7 +24,6 @@ body {
// -ms-user-select: none;
// }
-
.jsx-parser {
width: 100%;
height: 100%;
@@ -70,4 +69,4 @@ button:hover {
.svg-inline--fa {
vertical-align: unset;
-} \ No newline at end of file
+}
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index a695577d0..ad87fb874 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -1,6 +1,5 @@
-@import "global/globalCssVariables";
-@import "nodeModuleOverrides";
-
+@import 'global/globalCssVariables';
+@import 'nodeModuleOverrides';
.dash-tooltip {
font-size: 11px;
@@ -63,6 +62,17 @@
}
}
+.mainView-container,
+.mainView-container-Dark {
+ .lm_header .lm_tab {
+ padding: 0px;
+ opacity: 0.7;
+ box-shadow: none;
+ height: 25px;
+ border-bottom: black solid;
+ }
+}
+
.mainView-container {
color: $dark-gray;
@@ -183,7 +193,6 @@
background-color: $light-gray;
}
}
-
}
.mainView-libraryHandle {
@@ -252,7 +261,6 @@
}
.buttonContainer {
-
position: absolute;
bottom: 0;
@@ -270,8 +278,6 @@
}
}
-
-
.mainView-logout {
position: absolute;
right: 0;
@@ -309,11 +315,10 @@
}
.mainView-libraryFlyout-out {
- transition: width .25s;
+ transition: width 0.25s;
box-shadow: rgb(156, 147, 150) 0.2vw 0.2vw 0.2vw;
}
-
.mainView-libraryHandle {
width: var(--flyoutHandleWidth);
height: 55px;
@@ -334,7 +339,6 @@
margin-right: 3px;
padding-top: 19px;
}
-
}
.mainView-dashboard {
@@ -371,4 +375,4 @@
display: block;
width: 500px;
height: 1000px;
-} \ No newline at end of file
+}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index edc16d9a6..b61cd3409 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -465,7 +465,7 @@ export class MainView extends React.Component {
window.addEventListener('drop', e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page
window.addEventListener('dragover', e => e.preventDefault(), false);
// document.addEventListener("pointermove", action(e => SearchBox.Instance._undoBackground = UndoManager.batchCounter ? "#000000a8" : undefined));
- document.addEventListener('pointerdown', this.globalPointerDown);
+ document.addEventListener('pointerdown', this.globalPointerDown, true);
document.addEventListener(
'click',
(e: MouseEvent) => {
@@ -590,19 +590,21 @@ export class MainView extends React.Component {
@computed get dockingContent() {
return (
- <div
- key="docking"
- className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
- onDrop={e => {
- e.stopPropagation();
- e.preventDefault();
- }}
- style={{
- minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
- transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined,
- }}>
- {!this.mainContainer ? null : this.mainDocView}
- </div>
+ <GestureOverlay>
+ <div
+ key="docking"
+ className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`}
+ onDrop={e => {
+ e.stopPropagation();
+ e.preventDefault();
+ }}
+ style={{
+ minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`,
+ transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined,
+ }}>
+ {!this.mainContainer ? null : this.mainDocView}
+ </div>
+ </GestureOverlay>
);
}
@@ -974,7 +976,7 @@ export class MainView extends React.Component {
<div style={{ position: 'relative', display: LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 1999 }}>
<CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} />
</div>
- <GestureOverlay> {this.mainDashboardArea} </GestureOverlay>
+ {this.mainDashboardArea}
</>
);
case 'home':
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
index fd0ac9d5c..17eff022f 100644
--- a/src/client/views/_nodeModuleOverrides.scss
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -1,4 +1,4 @@
-@import "./global/globalCssVariables";
+@import './global/globalCssVariables';
// this file is for overriding all the css from installed node modules
// goldenlayout stuff
@@ -56,16 +56,5 @@ div .lm_header {
font-family: $sans-serif !important;
}
-.lm_header .lm_controls {
- align-items: center;
- position: absolute;
- background-color: $dark-gray;
- border-radius: 5px;
- display: flex;
- justify-content: space-evenly;
- height: 23px;
- width: 65px;
-}
-
// @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out
-// why. Low priority for now but it's bugging me. --Julie \ No newline at end of file
+// why. Low priority for now but it's bugging me. --Julie
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 6d5a39bc2..091ba8e74 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -37,11 +37,11 @@
}
.lm_header .lm_tab {
- padding: 0px;
- opacity: 0.7;
- box-shadow: none;
- height: 25px;
- border-bottom: black solid;
+ // padding: 0px; // moved to MainView.scss, othwerise they get overridden by default stylings
+ // opacity: 0.7;
+ // box-shadow: none;
+ // height: 25px;
+ // border-bottom: black solid;
.collectionDockingView-gear {
display: none;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index d47dfbea0..42f9bb981 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -377,7 +377,7 @@ export class CollectionDockingView extends CollectionSubView() {
}
}
}
- if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
+ if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) {
e.stopPropagation();
}
};
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f38efe578..2ab5f6247 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -160,7 +160,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatab
onContextMenu = (e: React.MouseEvent): void => {
const cm = ContextMenu.Instance;
- if (e.nativeEvent.cancelBubble) return; // nested calls to React to render can cause the same event to trigger in the outer view even if the inner view has handled it. This avoid CollectionDockingView menu options from being added when the event has been handled by a sub-document.
if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== Doc.MainDocId) {
// need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
this.setupViewTypes(
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index eb5faf4e1..5a2103e98 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -31,6 +31,7 @@ import { CollectionTreeView, TreeViewType } from './CollectionTreeView';
import { CollectionView } from './CollectionView';
import './TreeView.scss';
import React = require('react');
+import { ScriptingGlobals } from '../../util/ScriptingGlobals';
export interface TreeViewProps {
treeView: CollectionTreeView;
@@ -1223,3 +1224,9 @@ export class TreeView extends React.Component<TreeViewProps> {
});
}
}
+
+ScriptingGlobals.add(function TreeView_addNewFolder() {
+ TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined };
+ const opts = { title: 'Untitled folder', _stayInCollection: true, isFolder: true };
+ return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id));
+});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 07ea26346..4174661d8 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -509,7 +509,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._downY = this._lastY = e.pageY;
if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
if (
- !e.nativeEvent.cancelBubble &&
!this.props.Document._isGroup && // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag
!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)
@@ -543,24 +542,22 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
handle1PointerDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble) {
- // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt = me.changedTouches[0];
- if (pt) {
- this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY));
- if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- if (Doc.ActiveTool === InkTool.None) {
- this._lastX = pt.pageX;
- this._lastY = pt.pageY;
- e.preventDefault();
- e.stopPropagation();
- } else {
- e.preventDefault();
- }
+ // const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
+ const pt = me.changedTouches[0];
+ if (pt) {
+ this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY));
+ if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ if (Doc.ActiveTool === InkTool.None) {
+ this._lastX = pt.pageX;
+ this._lastY = pt.pageY;
+ e.preventDefault();
+ e.stopPropagation();
+ } else {
+ e.preventDefault();
}
}
}
@@ -976,7 +973,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble && this.props.isContentActive(true)) {
+ if (this.props.isContentActive(true)) {
// const pt1: React.Touch | null = e.targetTouches.item(0);
// const pt2: React.Touch | null = e.targetTouches.item(1);
// // if (!pt1 || !pt2) return;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f89c65052..dea718a0d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -362,7 +362,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
};
handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (!e.nativeEvent.cancelBubble && !this.props.isSelected()) {
+ if (!this.props.isSelected()) {
e.stopPropagation();
e.preventDefault();
@@ -380,14 +380,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (touch) {
this._downX = touch.clientX;
this._downY = touch.clientY;
- if (!e.nativeEvent.cancelBubble) {
- if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) e.stopPropagation();
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
+ if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) {
e.stopPropagation();
}
+ this.removeMoveListeners();
+ this.addMoveListeners();
+ this.removeEndListeners();
+ this.addEndListeners();
+ e.stopPropagation();
}
};
@@ -510,7 +510,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
onKeyDown = (e: React.KeyboardEvent) => {
- if (e.altKey && !e.nativeEvent.cancelBubble) {
+ if (e.altKey) {
e.stopPropagation();
e.preventDefault();
if (e.key === '†' || e.key === 't') {
@@ -543,7 +543,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
});
};
onClick = action((e: React.MouseEvent | React.PointerEvent) => {
- if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (!this.Document.ignoreClick && this.props.renderDepth >= 0 && Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
let stopPropagate = true;
let preventDefault = true;
const isScriptBox = () => StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name);
@@ -643,11 +643,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
}
this._downX = e.clientX;
this._downY = e.clientY;
- if (
- (!e.nativeEvent.cancelBubble || this.onClickHandler || this.layoutDoc.onDragStart) &&
+ if (Doc.ActiveTool === InkTool.None && !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) {
// if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking
- !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))
- ) {
if (
(this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) &&
!this.props.onBrowseClick?.() &&
@@ -792,7 +789,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
@action
onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => {
- if (e?.nativeEvent.cancelBubble) return;
if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) {
e.preventDefault();
e.stopPropagation();
diff --git a/src/fields/util.ts b/src/fields/util.ts
index 8fb35981b..cbb560114 100644
--- a/src/fields/util.ts
+++ b/src/fields/util.ts
@@ -1,20 +1,41 @@
-import { UndoManager } from "../client/util/UndoManager";
-import { Doc, FieldResult, UpdatingFromServer, LayoutSym, AclPrivate, AclEdit, AclReadonly, AclAugment, AclSym, DataSym, DocListCast, AclAdmin, HeightSym, WidthSym, updateCachedAcls, AclUnset, DocListCastAsync, ForceServerWrite, Initializing, AclSelfEdit } from "./Doc";
-import { SerializationHelper } from "../client/util/SerializationHelper";
-import { ProxyField, PrefetchProxy } from "./Proxy";
-import { RefField } from "./RefField";
-import { ObjectField } from "./ObjectField";
-import { action, trace, } from "mobx";
-import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from "./FieldSymbols";
-import { DocServer } from "../client/DocServer";
-import { ComputedField } from "./ScriptField";
-import { ScriptCast, StrCast } from "./Types";
-import { returnZero } from "../Utils";
-import CursorField from "./CursorField";
-import { List } from "./List";
-import { SnappingManager } from "../client/util/SnappingManager";
-import { computedFn } from "mobx-utils";
-import { RichTextField } from "./RichTextField";
+import { UndoManager } from '../client/util/UndoManager';
+import {
+ Doc,
+ FieldResult,
+ UpdatingFromServer,
+ LayoutSym,
+ AclPrivate,
+ AclEdit,
+ AclReadonly,
+ AclAugment,
+ AclSym,
+ DataSym,
+ DocListCast,
+ AclAdmin,
+ HeightSym,
+ WidthSym,
+ updateCachedAcls,
+ AclUnset,
+ DocListCastAsync,
+ ForceServerWrite,
+ Initializing,
+ AclSelfEdit,
+} from './Doc';
+import { SerializationHelper } from '../client/util/SerializationHelper';
+import { ProxyField, PrefetchProxy } from './Proxy';
+import { RefField } from './RefField';
+import { ObjectField } from './ObjectField';
+import { action, observable, runInAction, trace } from 'mobx';
+import { Parent, OnUpdate, Update, Id, SelfProxy, Self } from './FieldSymbols';
+import { DocServer } from '../client/DocServer';
+import { ComputedField } from './ScriptField';
+import { ScriptCast, StrCast } from './Types';
+import { returnZero } from '../Utils';
+import CursorField from './CursorField';
+import { List } from './List';
+import { SnappingManager } from '../client/util/SnappingManager';
+import { computedFn } from 'mobx-utils';
+import { RichTextField } from './RichTextField';
function _readOnlySetter(): never {
throw new Error("Documents can't be modified in read-only mode");
@@ -44,7 +65,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
return true;
}
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
target[prop] = value;
return true;
}
@@ -76,15 +97,13 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
const writeMode = DocServer.getFieldWriteMode(prop as string);
const fromServer = target[UpdatingFromServer];
- const sameAuthor = fromServer || (receiver.author === Doc.CurrentUserEmail);
- const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (writeMode !== DocServer.WriteMode.LiveReadonly);
- const writeToServer =
- (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && (value instanceof RichTextField))) &&
- !DocServer.Control.isReadOnly();
+ const sameAuthor = fromServer || receiver.author === Doc.CurrentUserEmail;
+ const writeToDoc = sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || writeMode !== DocServer.WriteMode.LiveReadonly;
+ const writeToServer = (sameAuthor || effectiveAcl === AclEdit || effectiveAcl === AclAdmin || (effectiveAcl === AclSelfEdit && value instanceof RichTextField)) && !DocServer.Control.isReadOnly();
if (writeToDoc) {
if (value === undefined) {
- target.__fieldKeys && (delete target.__fieldKeys[prop]);
+ target.__fieldKeys && delete target.__fieldKeys[prop];
delete target.__fields[prop];
} else {
target.__fieldKeys && (target.__fieldKeys[prop] = true);
@@ -96,16 +115,17 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number
//if (typeof value === "object" && !(value instanceof ObjectField)) debugger;
if (writeToServer) {
- if (value === undefined) target[Update]({ '$unset': { ["fields." + prop]: "" } });
- else target[Update]({ '$set': { ["fields." + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : (value === undefined ? null : value) } });
+ if (value === undefined) target[Update]({ $unset: { ['fields.' + prop]: '' } });
+ else target[Update]({ $set: { ['fields.' + prop]: value instanceof ObjectField ? SerializationHelper.Serialize(value) : value === undefined ? null : value } });
} else {
DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue);
}
- !receiver[Initializing] && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
+ !receiver[Initializing] &&
+ (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) &&
UndoManager.AddEvent({
- redo: () => receiver[prop] = value,
- undo: () => receiver[prop] = curValue,
- prop: prop?.toString()
+ redo: () => (receiver[prop] = value),
+ undo: () => (receiver[prop] = curValue),
+ prop: prop?.toString(),
});
return true;
}
@@ -136,7 +156,6 @@ export function denormalizeEmail(email: string) {
// playgroundMode = !playgroundMode;
// }
-
/**
* Copies parent's acl fields to the child
*/
@@ -145,35 +164,37 @@ export function inheritParentAcls(parent: Doc, child: Doc) {
const dataDoc = parent[DataSym];
for (const key of Object.keys(dataDoc)) {
// if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private.
- const permission = (key === "acl-Public" && Doc.defaultAclPrivate) ? AclPrivate : dataDoc[key];
- key.startsWith("acl") && distributeAcls(key, permission, child);
+ const permission = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : dataDoc[key];
+ key.startsWith('acl') && distributeAcls(key, permission, child);
}
}
/**
* These are the various levels of access a user can have to a document.
- *
+ *
* Admin: a user with admin access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), as well as change others' access rights to that document.
- *
+ *
* Edit: a user with edit access to a document can remove/edit that document, add/remove/edit annotations (depending on permissions), but not change any access rights to that document.
- *
+ *
* Add: a user with add access to a document can augment documents/annotations to that document but cannot edit or delete anything.
- *
+ *
* View: a user with view access to a document can only view it - they cannot add/remove/edit anything.
- *
+ *
* None: the document is not shared with that user.
*/
export enum SharingPermissions {
- Admin = "Admin",
- Edit = "Edit",
- SelfEdit = "Self Edit",
- Augment = "Augment",
- View = "View",
- None = "Not Shared"
+ Admin = 'Admin',
+ Edit = 'Edit',
+ SelfEdit = 'Self Edit',
+ Augment = 'Augment',
+ View = 'View',
+ None = 'Not Shared',
}
// return acl from cache or cache the acl and return.
-const getEffectiveAclCache = computedFn(function (target: any, user?: string) { return getEffectiveAcl(target, user); }, true);
+const getEffectiveAclCache = computedFn(function (target: any, user?: string) {
+ return getEffectiveAcl(target, user);
+}, true);
/**
* Calculates the effective access right to a document for the current user.
@@ -183,33 +204,47 @@ export function GetEffectiveAcl(target: any, user?: string): symbol {
if (target[UpdatingFromServer]) return AclAdmin;
// authored documents are private until an ACL is set.
if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate;
- return getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
+ return getEffectiveAclCache(target, user); // all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable)
}
function getPropAcl(target: any, prop: string | symbol | number) {
- if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
+ if (prop === UpdatingFromServer || prop === Initializing || target[UpdatingFromServer] || prop === AclSym) return AclAdmin; // requesting the UpdatingFromServer prop or AclSym must always go through to keep the local DB consistent
if (prop && DocServer.IsPlaygroundField(prop.toString())) return AclEdit; // playground props are always editable
return GetEffectiveAcl(target);
}
let HierarchyMapping: Map<symbol, number> | undefined;
+let cachedGroups = observable([] as string[]);
+/// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts
+// need to investigate further what caused the mobx update problems and move to a better location.
+const getCachedGroupByNameCache = computedFn(function (name: string) {
+ return cachedGroups.includes(name);
+}, true);
+export function GetCachedGroupByName(name: string) {
+ return getCachedGroupByNameCache(name);
+}
+export function SetCachedGroups(groups: string[]) {
+ runInAction(() => cachedGroups.push(...groups));
+}
function getEffectiveAcl(target: any, user?: string): symbol {
const targetAcls = target[AclSym];
- const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
- const targetAuthor = (target.__fields?.author || target.author); // target may be a Doc of Proxy, so check __fields.author and .author
+ const userChecked = user || Doc.CurrentUserEmail; // if the current user is the author of the document / the current user is a member of the admin group
+ const targetAuthor = target.__fields?.author || target.author; // target may be a Doc of Proxy, so check __fields.author and .author
if (userChecked === targetAuthor || !targetAuthor) return AclAdmin;
- if (SnappingManager.GetCachedGroupByName("Admin")) return AclAdmin;
+ if (GetCachedGroupByName('Admin')) return AclAdmin;
if (targetAcls && Object.keys(targetAcls).length) {
- HierarchyMapping = HierarchyMapping || new Map<symbol, number>([
- [AclPrivate, 0],
- [AclReadonly, 1],
- [AclAugment, 2],
- [AclSelfEdit, 2.5],
- [AclEdit, 3],
- [AclAdmin, 4]
- ]);
+ HierarchyMapping =
+ HierarchyMapping ||
+ new Map<symbol, number>([
+ [AclPrivate, 0],
+ [AclReadonly, 1],
+ [AclAugment, 2],
+ [AclSelfEdit, 2.5],
+ [AclEdit, 3],
+ [AclAdmin, 4],
+ ]);
let effectiveAcl = AclPrivate;
for (const [key, value] of Object.entries(targetAcls)) {
@@ -217,14 +252,14 @@ function getEffectiveAcl(target: any, user?: string): symbol {
// as a result we need to restore them again during this comparison.
const entity = denormalizeEmail(key.substring(4)); // an individual or a group
if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) {
- if (SnappingManager.GetCachedGroupByName(entity) || userChecked === entity) {
+ if (GetCachedGroupByName(entity) || userChecked === entity) {
effectiveAcl = value as symbol;
}
}
}
// if there's an overriding acl set through the properties panel or sharing menu, that's what's returned if the user isn't an admin of the document
- const override = targetAcls["acl-Override"];
+ const override = targetAcls['acl-Override'];
if (override !== AclUnset && override !== undefined) effectiveAcl = override;
// if we're in playground mode, return AclEdit (or AclAdmin if that's the user's effectiveAcl)
@@ -246,12 +281,12 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
visited.push(target);
const HierarchyMapping = new Map<string, number>([
- ["Not Shared", 0],
- ["Can View", 1],
- ["Can Augment", 2],
- ["Self Edit", 2.5],
- ["Can Edit", 3],
- ["Admin", 4]
+ ['Not Shared', 0],
+ ['Can View', 1],
+ ['Can Augment', 2],
+ ['Self Edit', 2.5],
+ ['Can Edit', 3],
+ ['Admin', 4],
]);
let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym])
@@ -271,7 +306,6 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
}
if (dataDoc && (!inheritingFromCollection || !dataDoc[key] || HierarchyMapping.get(StrCast(dataDoc[key]))! > HierarchyMapping.get(acl)!)) {
-
if (GetEffectiveAcl(dataDoc) === AclAdmin) {
dataDoc[key] = acl;
dataDocChanged = true;
@@ -282,7 +316,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
links.forEach(link => distributeAcls(key, acl, link, inheritingFromCollection, visited));
// maps over the children of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? "-all" : "")]).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + (isDashboard ? '-all' : '')]).map(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
@@ -292,7 +326,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc
});
// maps over the annotations of the document
- DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + "-annotations"]).map(d => {
+ DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '-annotations']).map(d => {
distributeAcls(key, acl, d, inheritingFromCollection, visited);
// }
const data = d[DataSym];
@@ -311,11 +345,11 @@ export function setter(target: any, in_prop: string | symbol | number, value: an
const effectiveAcl = getPropAcl(target, prop);
if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin && !(effectiveAcl === AclSelfEdit && value instanceof RichTextField)) return true;
// if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't
- if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, "None"].includes(value))) return true;
+ if (typeof prop === 'string' && prop.startsWith('acl') && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined, 'None'].includes(value))) return true;
// if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Augment", "Can View", "Not Shared", undefined].includes(value)) return true;
- if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) {
- if (!prop.startsWith("__")) prop = prop.substring(1);
+ if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
+ if (!prop.startsWith('__')) prop = prop.substring(1);
if (target.__LAYOUT__) {
target.__LAYOUT__[prop] = value;
return true;
@@ -331,17 +365,18 @@ export function getter(target: any, in_prop: string | symbol | number, receiver:
let prop = in_prop;
if (in_prop === AclSym) return target[AclSym];
- if (in_prop === "toString" || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === "symbol")) return target.__fields[prop] || target[prop];
+ if (in_prop === 'toString' || (in_prop !== HeightSym && in_prop !== WidthSym && in_prop !== LayoutSym && typeof prop === 'symbol')) return target.__fields[prop] || target[prop];
if (GetEffectiveAcl(target) === AclPrivate) return prop === HeightSym || prop === WidthSym ? returnZero : undefined;
if (prop === LayoutSym) return target.__LAYOUT__;
- if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && prop.startsWith("_")) {
- if (!prop.startsWith("__")) prop = prop.substring(1);
+ if (typeof prop === 'string' && prop !== '__id' && prop !== '__fields' && prop.startsWith('_')) {
+ if (!prop.startsWith('__')) prop = prop.substring(1);
if (target.__LAYOUT__) return target.__LAYOUT__[prop];
}
- if (prop === "then") {//If we're being awaited
+ if (prop === 'then') {
+ //If we're being awaited
return undefined;
}
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
return target.__fields[prop] || target[prop];
}
if (SerializationHelper.IsSerializing()) {
@@ -362,22 +397,21 @@ function getFieldImpl(target: any, prop: string | number, receiver: any, ignoreP
field = res.value;
}
}
- if (field === undefined && !ignoreProto && prop !== "proto") {
- const proto = getFieldImpl(target, "proto", receiver, true);//TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
+ if (field === undefined && !ignoreProto && prop !== 'proto') {
+ const proto = getFieldImpl(target, 'proto', receiver, true); //TODO tfs: instead of receiver we could use target[SelfProxy]... I don't which semantics we want or if it really matters
if (proto instanceof Doc && GetEffectiveAcl(proto) !== AclPrivate) {
return getFieldImpl(proto[Self], prop, receiver, ignoreProto);
}
return undefined;
}
return field;
-
}
export function getField(target: any, prop: string | number, ignoreProto: boolean = false): any {
return getFieldImpl(target, prop, undefined, ignoreProto);
}
export function deleteProperty(target: any, prop: string | number | symbol) {
- if (typeof prop === "symbol") {
+ if (typeof prop === 'symbol') {
delete target[prop];
return true;
}
@@ -389,64 +423,68 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any
let lastValue = ObjectField.MakeCopy(value);
return (diff?: any) => {
const op =
- diff?.op === "$addToSet" ? { '$addToSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } } :
- diff?.op === "$remFromSet" ? { '$remFromSet': { ["fields." + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
- : { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
+ diff?.op === '$addToSet'
+ ? { $addToSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
+ : diff?.op === '$remFromSet'
+ ? { $remFromSet: { ['fields.' + prop]: SerializationHelper.Serialize(new List<Doc>(diff.items)) } }
+ : { $set: { ['fields.' + prop]: SerializationHelper.Serialize(value) } };
!op.$set && ((op as any).length = diff.length);
const prevValue = ObjectField.MakeCopy(lastValue as List<any>);
lastValue = ObjectField.MakeCopy(value);
const newValue = ObjectField.MakeCopy(value);
- if (!(value instanceof CursorField) && !(value?.some?.((v: any) => v instanceof CursorField))) {
- !receiver[UpdatingFromServer] && UndoManager.AddEvent(
- diff?.op === "$addToSet" ?
- {
- redo: () => {
- receiver[prop].push(...diff.items.map((item: any) => item.value ? item.value() : item));
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: action(() => {
- // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- prop: ""
- } :
- diff?.op === "$remFromSet" ?
- {
- redo: action(() => {
- diff.items.forEach((item: any) => {
- const ind = receiver[prop].indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].splice(ind, 1);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- }),
- undo: () => {
- // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
- diff.items.forEach((item: any) => {
- const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
- ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
- });
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: ""
- }
+ if (!(value instanceof CursorField) && !value?.some?.((v: any) => v instanceof CursorField)) {
+ !receiver[UpdatingFromServer] &&
+ UndoManager.AddEvent(
+ diff?.op === '$addToSet'
+ ? {
+ redo: () => {
+ receiver[prop].push(...diff.items.map((item: any) => (item.value ? item.value() : item)));
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ undo: action(() => {
+ // console.log("undo $add: " + prop, diff.items) // bcz: uncomment to log undo
+ diff.items.forEach((item: any) => {
+ const ind = receiver[prop].indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ }),
+ prop: '',
+ }
+ : diff?.op === '$remFromSet'
+ ? {
+ redo: action(() => {
+ diff.items.forEach((item: any) => {
+ const ind = receiver[prop].indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].splice(ind, 1);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ }),
+ undo: () => {
+ // console.log("undo $rem: " + prop, diff.items) // bcz: uncomment to log undo
+ diff.items.forEach((item: any) => {
+ const ind = (prevValue as List<any>).indexOf(item.value ? item.value() : item);
+ ind !== -1 && receiver[prop].indexOf(item.value ? item.value() : item) === -1 && receiver[prop].splice(ind, 0, item);
+ });
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ prop: '',
+ }
: {
- redo: () => {
- receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- undo: () => {
- // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
- receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
- lastValue = ObjectField.MakeCopy(receiver[prop]);
- },
- prop: ""
- });
+ redo: () => {
+ receiver[prop] = ObjectField.MakeCopy(newValue as List<any>);
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ undo: () => {
+ // console.log("undo list: " + prop, receiver[prop]) // bcz: uncomment to log undo
+ receiver[prop] = ObjectField.MakeCopy(prevValue as List<any>);
+ lastValue = ObjectField.MakeCopy(receiver[prop]);
+ },
+ prop: '',
+ }
+ );
}
target[Update](op);
};
-} \ No newline at end of file
+}