aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2020-04-28 17:32:59 -0400
committerBob Zeleznik <zzzman@gmail.com>2020-04-28 17:32:59 -0400
commitd020ab540abaf279414aa682c8930a4b280ace55 (patch)
tree2cab1b330659a97664af86e34f52d2d1b0ed49e1 /src/client/views/collections/collectionFreeForm
parent4ecf08b5c5cdc4ddb3a997e2f3a2188e921ff430 (diff)
parent6b2896756c55727ed397c223187cb03fe8a51a59 (diff)
merged with master
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx258
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx37
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx71
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx85
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss21
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx746
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx215
10 files changed, 867 insertions, 576 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index baf09fe5b..9a864078a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -1,4 +1,4 @@
-import { Doc, Field, FieldResult } from "../../../../new_fields/Doc";
+import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../new_fields/Doc";
import { NumCast, StrCast, Cast } from "../../../../new_fields/Types";
import { ScriptBox } from "../../ScriptBox";
import { CompileScript } from "../../../util/Scripting";
@@ -9,13 +9,15 @@ import React = require("react");
import { Id, ToString } from "../../../../new_fields/FieldSymbols";
import { ObjectField } from "../../../../new_fields/ObjectField";
import { RefField } from "../../../../new_fields/RefField";
+import { listSpec } from "../../../../new_fields/Schema";
export interface ViewDefBounds {
type: string;
- text?: string;
+ payload: any;
x: number;
y: number;
z?: number;
+ text?: string;
zIndex?: number;
width?: number;
height?: number;
@@ -23,19 +25,22 @@ export interface ViewDefBounds {
fontSize?: number;
highlight?: boolean;
color?: string;
- payload: any;
+ replica?: string;
+ pair?: { layout: Doc, data?: Doc };
}
export interface PoolData {
- x?: number,
- y?: number,
- z?: number,
- zIndex?: number,
- width?: number,
- height?: number,
- color?: string,
- transition?: string,
- highlight?: boolean,
+ x: number;
+ y: number;
+ z?: number;
+ zIndex?: number;
+ width?: number;
+ height?: number;
+ color?: string;
+ transition?: string;
+ highlight?: boolean;
+ replica: string;
+ pair: { layout: Doc, data?: Doc };
}
export interface ViewDefResult {
@@ -43,6 +48,11 @@ export interface ViewDefResult {
bounds?: ViewDefBounds;
}
function toLabel(target: FieldResult<Field>) {
+ if (typeof target === "number" || Number(target)) {
+ const truncated = Number(Number(target).toFixed(0));
+ const precise = Number(Number(target).toFixed(2));
+ return truncated === precise ? Number(target).toFixed(0) : Number(target).toFixed(2);
+ }
if (target instanceof ObjectField || target instanceof RefField) {
return target[ToString]();
}
@@ -58,47 +68,112 @@ function toLabel(target: FieldResult<Field>) {
*/
function getTextWidth(text: string, font: string): number {
// re-use canvas object for better performance
- var canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
- var context = canvas.getContext("2d");
+ const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas"));
+ const context = canvas.getContext("2d");
context.font = font;
- var metrics = context.measureText(text);
+ const metrics = context.measureText(text);
return metrics.width;
}
-interface pivotColumn {
- docs: Doc[],
- filters: string[]
+interface PivotColumn {
+ docs: Doc[];
+ replicas: string[];
+ filters: string[];
+}
+
+export function computerPassLayout(
+ poolData: Map<string, PoolData>,
+ pivotDoc: Doc,
+ childPairs: { layout: Doc, data?: Doc }[],
+ panelDim: number[],
+ viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+) {
+ const docMap = new Map<string, PoolData>();
+ childPairs.forEach(({ layout, data }, i) => {
+ docMap.set(layout[Id], {
+ x: NumCast(layout.x),
+ y: NumCast(layout.y),
+ width: layout[WidthSym](),
+ height: layout[HeightSym](),
+ pair: { layout, data },
+ replica: ""
+ });
+ });
+ return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
+}
+
+export function computerStarburstLayout(
+ poolData: Map<string, PoolData>,
+ pivotDoc: Doc,
+ childPairs: { layout: Doc, data?: Doc }[],
+ panelDim: number[],
+ viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
+) {
+ const docMap = new Map<string, PoolData>();
+ const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])];
+ const docScale = NumCast(pivotDoc._starburstDocScale);
+ const docSize = docScale * 100; // assume a icon sized at 100
+ const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize];
+ childPairs.forEach(({ layout, data }, i) => {
+ const deg = i / childPairs.length * Math.PI * 2;
+ docMap.set(layout[Id], {
+ x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2,
+ y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2,
+ width: docScale * layout[WidthSym](),
+ height: docScale * layout[HeightSym](),
+ pair: { layout, data },
+ replica: ""
+ });
+ });
+ return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []);
}
export function computePivotLayout(
poolData: Map<string, PoolData>,
pivotDoc: Doc,
- childDocs: Doc[],
- filterDocs: Doc[],
childPairs: { layout: Doc, data?: Doc }[],
panelDim: number[],
viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
) {
+ const docMap = new Map<string, PoolData>();
const fieldKey = "data";
- const pivotColumnGroups = new Map<FieldResult<Field>, pivotColumn>();
+ const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
const pivotFieldKey = toLabel(pivotDoc._pivotField);
- for (const doc of filterDocs) {
- const val = Field.toString(doc[pivotFieldKey] as Field);
- if (val) {
- !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] });
- pivotColumnGroups.get(val)!.docs.push(doc);
+ childPairs.map(pair => {
+ const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+ const val = Field.toString(pair.layout[pivotFieldKey] as Field);
+ if (lval) {
+ lval.forEach((val, i) => {
+ !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
+ pivotColumnGroups.get(val)!.docs.push(pair.layout);
+ pivotColumnGroups.get(val)!.replicas.push(i.toString());
+ });
+ } else if (val) {
+ !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
+ pivotColumnGroups.get(val)!.docs.push(pair.layout);
+ pivotColumnGroups.get(val)!.replicas.push("");
+ } else {
+ docMap.set(pair.layout[Id], {
+ x: 0,
+ y: 0,
+ zIndex: -99,
+ width: 0,
+ height: 0,
+ pair,
+ replica: ""
+ });
}
- }
+ });
let nonNumbers = 0;
- childDocs.map(doc => {
- const num = toNumber(doc[pivotFieldKey]);
+ childPairs.map(pair => {
+ const num = toNumber(pair.layout[pivotFieldKey]);
if (num === undefined || Number.isNaN(num)) {
nonNumbers++;
}
});
- const pivotNumbers = nonNumbers / childDocs.length < .1;
+ const pivotNumbers = nonNumbers / childPairs.length < .1;
if (pivotColumnGroups.size > 10) {
const arrayofKeys = Array.from(pivotColumnGroups.keys());
const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort();
@@ -110,6 +185,7 @@ export function computePivotLayout(
const newgrp = pivotColumnGroups.get(sortedKeys[j])!;
curgrp.docs.push(...newgrp.docs);
curgrp.filters.push(...newgrp.filters);
+ curgrp.replicas.push(...newgrp.replicas);
pivotColumnGroups.delete(sortedKeys[j]);
}
}
@@ -118,7 +194,7 @@ export function computePivotLayout(
const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`;
const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number);
const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2);
- let maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
+ const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1);
const colWidth = panelDim[0] / pivotColumnGroups.size;
const colHeight = panelDim[1] - max_text;
@@ -137,11 +213,11 @@ export function computePivotLayout(
}
}
- const docMap = new Map<Doc, ViewDefBounds>();
const groupNames: ViewDefBounds[] = [];
const expander = 1.05;
const gap = .15;
+ const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
let x = 0;
const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort();
sortedPivotKeys.forEach(key => {
@@ -159,7 +235,7 @@ export function computePivotLayout(
fontSize,
payload: val
});
- for (const doc of val.docs) {
+ val.docs.forEach((doc, i) => {
const layoutDoc = Doc.Layout(doc);
let wid = pivotAxisWidth;
let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth;
@@ -167,28 +243,27 @@ export function computePivotLayout(
hgt = pivotAxisWidth;
wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
- docMap.set(doc, {
- type: "doc",
+ docMap.set(doc[Id] + (val.replicas || ""), {
x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0),
y: -y + (pivotAxisWidth - hgt) / 2,
width: wid,
height: hgt,
- payload: undefined
+ pair: { layout: doc },
+ replica: val.replicas[i]
});
xCount++;
if (xCount >= numCols) {
xCount = 0;
y += pivotAxisWidth * expander;
}
- }
+ });
x += pivotAxisWidth * (numCols * expander + gap);
});
- const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols);
const dividers = sortedPivotKeys.map((key, i) =>
- ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap), y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
+ ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters }));
groupNames.push(...dividers);
- return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c)));
+ return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []);
}
function toNumber(val: FieldResult<Field>) {
@@ -198,35 +273,33 @@ function toNumber(val: FieldResult<Field>) {
export function computeTimelineLayout(
poolData: Map<string, PoolData>,
pivotDoc: Doc,
- childDocs: Doc[],
- filterDocs: Doc[],
childPairs: { layout: Doc, data?: Doc }[],
panelDim: number[],
viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[]
) {
const fieldKey = "data";
const pivotDateGroups = new Map<number, Doc[]>();
- const docMap = new Map<Doc, ViewDefBounds>();
+ const docMap = new Map<string, PoolData>();
const groupNames: ViewDefBounds[] = [];
const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field);
const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]);
const curTimeSpan = Cast(pivotDoc[fieldKey + "-timelineSpan"], "number", null);
- const minTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTimeSpan && (curTime - curTimeSpan);
- const maxTimeReq = curTime === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTimeSpan && (curTime + curTimeSpan);
+ const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTime && (curTime - curTimeSpan);
+ const maxTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTime && (curTime + curTimeSpan);
const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3));
const fontHeight = panelDim[1] > 58 ? 30 : panelDim[1] / 2;
const findStack = (time: number, stack: number[]) => {
const index = stack.findIndex(val => val === undefined || val < x);
return index === -1 ? stack.length : index;
- }
+ };
- let minTime = Number.MAX_VALUE;
- let maxTime = -Number.MAX_VALUE;
- filterDocs.map(doc => {
- const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey])));
- if (!(Number.isNaN(num) || (minTimeReq && num < minTimeReq) || (maxTimeReq && num > maxTimeReq))) {
+ let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq;
+ let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq;
+ childPairs.forEach(pair => {
+ const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey])));
+ if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) {
!pivotDateGroups.get(num) && pivotDateGroups.set(num, []);
- pivotDateGroups.get(num)!.push(doc);
+ pivotDateGroups.get(num)!.push(pair.layout);
minTime = Math.min(num, minTime);
maxTime = Math.max(num, maxTime);
}
@@ -254,38 +327,38 @@ export function computeTimelineLayout(
let prevKey = Math.floor(minTime);
if (sortedKeys.length && scaling * (sortedKeys[0] - prevKey) > 25) {
- groupNames.push({ type: "text", text: prevKey.toString(), x: x, y: 0, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: "text", text: toLabel(prevKey), x: x, y: 0, height: fontHeight, fontSize, payload: undefined });
}
if (!sortedKeys.length && curTime !== undefined) {
- groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined });
}
const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5);
- let stacking: number[] = [];
+ const stacking: number[] = [];
let zind = 0;
sortedKeys.forEach(key => {
if (curTime !== undefined && curTime > prevKey && curTime <= key) {
- groupNames.push({ type: "text", text: curTime.toString(), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key });
+ groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key });
}
const keyDocs = pivotDateGroups.get(key)!;
x += scaling * (key - prevKey);
const stack = findStack(x, stacking);
prevKey = key;
if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) {
- groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: "text", text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined });
}
layoutDocsAtTime(keyDocs, key);
});
if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) {
x = (curTime - minTime) * scaling;
- groupNames.push({ type: "text", text: curTime.toString(), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: "text", text: toLabel(curTime), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: undefined });
}
if (Math.ceil(maxTime - minTime) * scaling > x + 25) {
- groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
+ groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined });
}
- const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1, payload: undefined };
- return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c)));
+ const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined };
+ return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]);
function layoutDocsAtTime(keyDocs: Doc[], key: number) {
keyDocs.forEach(doc => {
@@ -297,58 +370,67 @@ export function computeTimelineLayout(
hgt = pivotAxisWidth;
wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth;
}
- docMap.set(doc, {
- type: "doc",
+ docMap.set(doc[Id], {
x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2,
- zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt, payload: undefined
+ zIndex: (curTime === key ? 1000 : zind++),
+ highlight: curTime === key,
+ width: wid / (Math.max(stack, 1)),
+ height: hgt / (Math.max(stack, 1)),
+ pair: { layout: doc },
+ replica: ""
});
stacking[stack] = x + pivotAxisWidth;
});
}
}
-function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>,
- poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[],
- extraDocs: Doc[]) {
-
+function normalizeResults(
+ panelDim: number[],
+ fontHeight: number,
+ docMap: Map<string, PoolData>,
+ poolData: Map<string, PoolData>,
+ viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[],
+ groupNames: ViewDefBounds[],
+ minWidth: number,
+ extras: ViewDefBounds[]
+): ViewDefResult[] {
const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds);
- const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds);
- const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0);
+ const docEles = Array.from(docMap.entries()).map(ele => ele[1]);
+ const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0);
aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x);
const wscale = panelDim[0] / (aggBounds.r - aggBounds.x);
let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale;
if (Number.isNaN(scale)) scale = 1;
- childPairs.filter(d => docMap.get(d.layout)).map(pair => {
- const newPosRaw = docMap.get(pair.layout);
+ Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => {
+ const newPosRaw = ele[1];
if (newPosRaw) {
const newPos = {
x: newPosRaw.x * scale,
y: newPosRaw.y * scale,
z: newPosRaw.z,
+ replica: newPosRaw.replica,
highlight: newPosRaw.highlight,
zIndex: newPosRaw.zIndex,
width: (newPosRaw.width || 0) * scale,
- height: newPosRaw.height! * scale
+ height: newPosRaw.height! * scale,
+ pair: ele[1].pair
};
- poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos });
+ poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos });
}
});
- extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 }));
- return {
- elements: viewDefsToJSX(extras.concat(groupNames.map(gname => ({
- type: gname.type,
- text: gname.text,
- x: gname.x * scale,
- y: gname.y * scale,
- color: gname.color,
- width: gname.width === undefined ? undefined : gname.width * scale,
- height: Math.max(fontHeight, (gname.height || 0) * scale),
- fontSize: gname.fontSize,
- payload: gname.payload
- }))))
- };
+ return viewDefsToJSX(extras.concat(groupNames).map(gname => ({
+ type: gname.type,
+ text: gname.text,
+ x: gname.x * scale,
+ y: gname.y * scale,
+ color: gname.color,
+ width: gname.width === undefined ? undefined : gname.width * scale,
+ height: gname.height === -1 ? 1 : gname.type === "text" ? Math.max(fontHeight * scale, (gname.height || 0) * scale) : (gname.height || 0) * scale,
+ fontSize: gname.fontSize,
+ payload: gname.payload
+ })));
}
export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 75af11537..05111adb4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -4,6 +4,7 @@
pointer-events: all;
stroke-width: 3px;
transition: opacity 0.5s ease-in;
+ fill: transparent;
}
.collectionfreeformlinkview-linkCircle {
stroke: rgb(0,0,0);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f04b79ea4..cf12ef382 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -25,9 +25,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
action(() => {
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
- setTimeout(action(() => this._opacity = 0.05), 750); // this will unhighlight the link line.
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
+ const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
const a = adiv.getBoundingClientRect();
@@ -43,11 +43,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const afield = StrCast(this.props.A.props.Document[StrCast(this.props.A.props.layoutKey, "layout")]).indexOf("anchor1") === -1 ? "anchor2" : "anchor1";
const bfield = afield === "anchor1" ? "anchor2" : "anchor1";
- // really hacky stuff to make the DocuLinkBox display where we want it to:
+ // really hacky stuff to make the LinkAnchorBox display where we want it to:
// if there's an element in the DOM with the id of the opposite anchor, then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = window.document.getElementById((this.props.LinkDocs[0][afield] as Doc)[Id]);
- const targetBhyperlink = window.document.getElementById((this.props.LinkDocs[0][bfield] as Doc)[Id]);
+ const targetAhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][afield] as Doc)[Id]);
+ const targetBhyperlink = window.document.getElementById(this.props.LinkDocs[0][Id] + (this.props.LinkDocs[0][bfield] as Doc)[Id]);
if (!targetBhyperlink) {
this.props.A.props.Document[afield + "_x"] = (apt.point.x - abounds.left) / abounds.width * 100;
this.props.A.props.Document[afield + "_y"] = (apt.point.y - abounds.top) / abounds.height * 100;
@@ -81,8 +81,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
}
render() {
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("docuLinkBox-cont") : [];
+ this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
+ const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
@@ -93,12 +94,26 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
apt.point.x, apt.point.y);
const pt1 = [apt.point.x, apt.point.y];
const pt2 = [bpt.point.x, bpt.point.y];
+ const pt1vec = [pt1[0] - (a.left + a.width / 2), pt1[1] - (a.top + a.height / 2)];
+ const pt2vec = [pt2[0] - (b.left + b.width / 2), pt2[1] - (b.top + b.height / 2)];
+ const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
+ const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
+ const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 3;
+ const pt1norm = [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
+ const pt2norm = [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- return !aActive && !bActive ? (null) :
- <line key="linkLine" className="collectionfreeformlinkview-linkLine"
+ const text = StrCast(this.props.A.props.Document.linkRelationship);
+ return !a.width || !b.width || ((!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
+ <text x={(pt1[0] + pt2[0]) / 2} y={(pt1[1] + pt2[1]) / 2}>
+ {text !== "-ungrouped-" ? text : ""}
+ </text>
+ <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
+ d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
+ {/* <line key="linkLine" className="collectionfreeformlinkview-linkLine"
style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
x1={`${pt1[0]}`} y1={`${pt1[1]}`}
- x2={`${pt2[0]}`} y2={`${pt2[1]}`} />;
+ x2={`${pt2[0]}`} y2={`${pt2[1]}`} /> */}
+ </>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 044d35eca..4b5e977df 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,4 +1,4 @@
-import { computed, IReactionDisposer } from "mobx";
+import { computed } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
@@ -7,69 +7,12 @@ import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
-import { Utils } from "../../../../Utils";
+import { Utils, emptyFunction } from "../../../../Utils";
import { SelectionManager } from "../../../util/SelectionManager";
import { DocumentType } from "../../../documents/DocumentTypes";
@observer
export class CollectionFreeFormLinksView extends React.Component {
-
- _brushReactionDisposer?: IReactionDisposer;
- componentDidMount() {
- // this._brushReactionDisposer = reaction(
- // () => {
- // let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
- // return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
- // },
- // () => {
- // let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
- // let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
- // views.forEach((dstDoc, i) => {
- // views.forEach((srcDoc, j) => {
- // let dstTarg = dstDoc;
- // let srcTarg = srcDoc;
- // let x1 = NumCast(srcDoc.x);
- // let x2 = NumCast(dstDoc.x);
- // let x1w = NumCast(srcDoc.width, -1);
- // let x2w = NumCast(dstDoc.width, -1);
- // if (x1w < 0 || x2w < 0 || i === j) { }
- // else {
- // let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
- // let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
- // return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
- // });
- // let brushAction = (field: (Doc | Promise<Doc>)[]) => {
- // let found = findBrush(field);
- // if (found !== -1) {
- // field.splice(found, 1);
- // }
- // };
- // if (Math.abs(x1 + x1w - x2) < 20) {
- // let linkDoc: Doc = new Doc();
- // linkDoc.title = "Histogram Brush";
- // linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
- // linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
-
- // brushAction = (field: (Doc | Promise<Doc>)[]) => {
- // if (findBrush(field) === -1) {
- // field.push(linkDoc);
- // }
- // };
- // }
- // if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>();
- // if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>();
- // let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
- // let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
- // brushAction(dstBrushDocs);
- // brushAction(srcBrushDocs);
- // }
- // });
- // });
- // });
- }
- componentWillUnmount() {
- this._brushReactionDisposer && this._brushReactionDisposer();
- }
@computed
get uniqueConnections() {
const connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
@@ -86,14 +29,16 @@ export class CollectionFreeFormLinksView extends React.Component {
}
return drawnPairs;
}, [] as { a: DocumentView, b: DocumentView, l: Doc[] }[]);
- return connections.filter(c => c.a.props.Document.type === DocumentType.LINK) // get rid of the filter to show links to documents in addition to document anchors
- .map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
+ return connections.filter(c =>
+ c.a.props.layoutKey && c.b.props.layoutKey && c.a.props.Document.type === DocumentType.LINK &&
+ c.a.props.bringToFront !== emptyFunction && c.b.props.bringToFront !== emptyFunction // bcz: this prevents links to be drawn to anchors in CollectionTree views -- this is a hack that should be fixed
+ ).map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
}
render() {
- return <div className="collectionfreeformlinksview-container">
+ return SelectionManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container">
<svg className="collectionfreeformlinksview-svgCanvas">
- {SelectionManager.GetIsDragging() ? (null) : this.uniqueConnections}
+ {this.uniqueConnections}
</svg>
{this.props.children}
</div>;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index bb9ae4326..92fa2781c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -8,74 +8,65 @@ import { CollectionViewProps } from "../CollectionSubView";
import "./CollectionFreeFormView.scss";
import React = require("react");
import v5 = require("uuid/v5");
+import { computed } from "mobx";
+import { FieldResult } from "../../../../new_fields/Doc";
+import { List } from "../../../../new_fields/List";
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
- protected getCursors(): CursorField[] {
+ @computed protected get cursors(): CursorField[] {
const doc = this.props.Document;
- const id = CurrentUserUtils.id;
- if (!id) {
+ let cursors: FieldResult<List<CursorField>>;
+ const { id } = CurrentUserUtils;
+ if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) {
return [];
}
-
- const cursors = Cast(doc.cursors, listSpec(CursorField));
-
const now = mobxUtils.now();
- // const now = Date.now();
- return (cursors || []).filter(cursor => cursor.data.metadata.id !== id && (now - cursor.data.metadata.timestamp) < 1000);
+ return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && (now - metadata.timestamp) < 1000);
}
- private crosshairs?: HTMLCanvasElement;
- drawCrosshairs = (backgroundColor: string) => {
- if (this.crosshairs) {
- const ctx = this.crosshairs.getContext('2d');
- if (ctx) {
- ctx.fillStyle = backgroundColor;
- ctx.fillRect(0, 0, 20, 20);
-
- ctx.fillStyle = "black";
- ctx.lineWidth = 0.5;
-
- ctx.beginPath();
+ @computed get renderedCursors() {
+ return this.cursors.map(({ data: { metadata, position: { x, y } } }) => {
+ return (
+ <div key={metadata.id} className="collectionFreeFormRemoteCursors-cont"
+ style={{ transform: `translate(${x - 10}px, ${y - 10}px)` }}
+ >
+ <canvas className="collectionFreeFormRemoteCursors-canvas"
+ ref={(el) => {
+ if (el) {
+ const ctx = el.getContext('2d');
+ if (ctx) {
+ ctx.fillStyle = "#" + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + "22";
+ ctx.fillRect(0, 0, 20, 20);
- ctx.moveTo(10, 0);
- ctx.lineTo(10, 8);
+ ctx.fillStyle = "black";
+ ctx.lineWidth = 0.5;
- ctx.moveTo(10, 20);
- ctx.lineTo(10, 12);
+ ctx.beginPath();
- ctx.moveTo(0, 10);
- ctx.lineTo(8, 10);
+ ctx.moveTo(10, 0);
+ ctx.lineTo(10, 8);
- ctx.moveTo(20, 10);
- ctx.lineTo(12, 10);
+ ctx.moveTo(10, 20);
+ ctx.lineTo(10, 12);
- ctx.stroke();
+ ctx.moveTo(0, 10);
+ ctx.lineTo(8, 10);
- // ctx.font = "10px Arial";
- // ctx.fillText(Doc.CurrentUserEmail[0].toUpperCase(), 10, 10);
- }
- }
- }
+ ctx.moveTo(20, 10);
+ ctx.lineTo(12, 10);
- get sharedCursors() {
- return this.getCursors().map(c => {
- const m = c.data.metadata;
- const l = c.data.position;
- this.drawCrosshairs("#" + v5(m.id, v5.URL).substring(0, 6).toUpperCase() + "22");
- return (
- <div key={m.id} className="collectionFreeFormRemoteCursors-cont"
- style={{ transform: `translate(${l.x - 10}px, ${l.y - 10}px)` }}
- >
- <canvas className="collectionFreeFormRemoteCursors-canvas"
- ref={(el) => { if (el) this.crosshairs = el; }}
+ ctx.stroke();
+ }
+ }
+ }}
width={20}
height={20}
/>
<p className="collectionFreeFormRemoteCursors-symbol">
- {m.identifier[0].toUpperCase()}
+ {metadata.identifier[0].toUpperCase()}
</p>
</div>
);
@@ -83,6 +74,6 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
}
render() {
- return this.sharedCursors;
+ return this.renderedCursors;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 0b5e44ccb..60c39c825 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -9,10 +9,21 @@
height: 100%;
transform-origin: left top;
border-radius: inherit;
+ touch-action: none;
+ border-radius: inherit;
+}
+
+.collectionfreeformview-viewdef {
+ > .collectionFreeFormDocumentView-container {
+ pointer-events: none;
+ .contentFittingDocumentDocumentView-previewDoc {
+ pointer-events: all;
+ }
+ }
}
.collectionfreeformview-ease {
- transition: transform 1s;
+ transition: transform 500ms;
}
.collectionfreeformview-none {
@@ -36,6 +47,8 @@
height: 100%;
display: flex;
align-items: center;
+ overflow: hidden;
+
.collectionfreeformview-placeholderSpan {
font-size: 32;
display: flex;
@@ -99,4 +112,10 @@
#prevCursor {
animation: blink 1s infinite;
+}
+
+.pullpane-indicator {
+ z-index: 99999;
+ background-color: rgba($color: #000000, $alpha: .4);
+ position: absolute;
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 81fca3b54..b9e80bb43 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,22 +1,26 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer, trace } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc";
+import { computedFn } from "mobx-utils";
+import { Doc, HeightSym, Opt, WidthSym, DocListCast } from "../../../../new_fields/Doc";
import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkTool, InkField, InkData } from "../../../../new_fields/InkField";
-import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
+import { InkData, InkField, InkTool } from "../../../../new_fields/InkField";
+import { List } from "../../../../new_fields/List";
+import { RichTextField } from "../../../../new_fields/RichTextField";
+import { createSchema, listSpec, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
-import { BoolCast, Cast, DateCast, NumCast, StrCast, ScriptCast } from "../../../../new_fields/Types";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
-import { aggregateBounds, emptyFunction, intersectRect, returnOne, Utils } from "../../../../Utils";
+import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../new_fields/Types";
+import { TraceMobx } from "../../../../new_fields/util";
+import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
+import { aggregateBounds, intersectRect, returnOne, Utils, returnZero, returnFalse } from "../../../../Utils";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
import { DocServer } from "../../../DocServer";
-import { Docs, DocUtils } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
+import { Docs } from "../../../documents/Documents";
import { DocumentManager } from "../../../util/DocumentManager";
-import { DragManager } from "../../../util/DragManager";
+import { DragManager, dropActionType } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
import { InteractionUtils } from "../../../util/InteractionUtils";
import { SelectionManager } from "../../../util/SelectionManager";
@@ -27,21 +31,20 @@ import { ContextMenu } from "../../ContextMenu";
import { ContextMenuProps } from "../../ContextMenuItem";
import { InkingControl } from "../../InkingControl";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
-import { DocumentViewProps } from "../../nodes/DocumentView";
-import { FormattedTextBox } from "../../nodes/FormattedTextBox";
+import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
import { pageSchema } from "../../nodes/ImageBox";
import PDFMenu from "../../pdf/PDFMenu";
+import { CollectionDockingView } from "../CollectionDockingView";
import { CollectionSubView } from "../CollectionSubView";
-import { computePivotLayout, ViewDefResult, computeTimelineLayout, PoolData, ViewDefBounds } from "./CollectionFreeFormLayoutEngines";
+import { computePivotLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult, computerStarburstLayout, computerPassLayout } from "./CollectionFreeFormLayoutEngines";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import { computedFn } from "mobx-utils";
-import { TraceMobx } from "../../../../new_fields/util";
+import { CollectionViewType } from "../CollectionView";
import { Timeline } from "../../animationtimeline/Timeline";
-import { GestureUtils } from "../../../../pen-gestures/GestureUtils";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -53,8 +56,8 @@ export const panZoomSchema = createSchema({
arrangeInit: ScriptField,
useClusters: "boolean",
fitToBox: "boolean",
- xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
- yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+ _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
+ _yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set
panTransformType: "string",
scrollHeight: "number",
fitX: "number",
@@ -65,34 +68,51 @@ export const panZoomSchema = createSchema({
type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof documentSchema, typeof positionSchema, typeof pageSchema]>;
const PanZoomDocument = makeInterface(panZoomSchema, documentSchema, positionSchema, pageSchema);
+export type collectionFreeformViewProps = {
+ forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
+ childClickScript?: ScriptField;
+ viewDefDivClick?: ScriptField;
+};
@observer
-export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
+export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, Partial<collectionFreeformViewProps>>(PanZoomDocument) {
private _lastX: number = 0;
private _lastY: number = 0;
+ private _downX: number = 0;
+ private _downY: number = 0;
+ private _inkToTextStartX: number | undefined;
+ private _inkToTextStartY: number | undefined;
+ private _wordPalette: Map<string, string> = new Map<string, string>();
private _clusterDistance: number = 75;
private _hitCluster = false;
private _layoutComputeReaction: IReactionDisposer | undefined;
- private _layoutPoolData = observable.map<string, any>();
+ private _layoutPoolData = new ObservableMap<string, PoolData>();
+ private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>();
+ private _cachedPool: Map<string, PoolData> = new Map();
+ @observable private _pullCoords: number[] = [0, 0];
+ @observable private _pullDirection: string = "";
public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@observable _clusterSets: (Doc[])[] = [];
@observable _timelineRef = React.createRef<Timeline>();
+ @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
@computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
- @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc.xPadding, 10), NumCast(this.layoutDoc.yPadding, 10)); }
- @computed get nativeWidth() { return this.Document._fitToContent ? 0 : NumCast(this.Document._nativeWidth); }
- @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight); }
+ @computed get contentBounds() { return aggregateBounds(this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); }
+ @computed get nativeWidth() { return this.fitToContent ? 0 : NumCast(this.Document._nativeWidth, this.props.NativeWidth()); }
+ @computed get nativeHeight() { return this.fitToContent ? 0 : NumCast(this.Document._nativeHeight, this.props.NativeHeight()); }
private get isAnnotationOverlay() { return this.props.isAnnotationOverlay; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private easing = () => this.props.Document.panTransformType === "Ease";
private panX = () => this.fitToContent ? (this.contentBounds.x + this.contentBounds.r) / 2 : this.Document._panX || 0;
private panY = () => this.fitToContent ? (this.contentBounds.y + this.contentBounds.b) / 2 : this.Document._panY || 0;
- private zoomScaling = () => (1 / this.parentScaling) * (this.fitToContent ?
- Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
+ private zoomScaling = () => (this.fitToContentScaling / this.parentScaling) * (this.fitToContent ?
+ Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y),
+ this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)) :
this.Document.scale || 1)
+
private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this.props.PanelWidth() / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections
private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this.props.PanelHeight() / 2 / this.parentScaling : 0;// shift so pan position is at center of window for non-overlay collections
private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
@@ -113,27 +133,27 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
- public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
+ public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
public getActiveDocuments = () => {
return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
}
@action
- onDrop = (e: React.DragEvent): Promise<void> => {
+ onExternalDrop = (e: React.DragEvent): Promise<void> => {
const pt = this.getTransform().transformPoint(e.pageX, e.pageY);
- return super.onDrop(e, { x: pt[0], y: pt[1] });
+ return super.onExternalDrop(e, { x: pt[0], y: pt[1] });
}
@undoBatch
@action
- drop = (e: Event, de: DragManager.DropEvent) => {
- if (this.props.Document.isBackground) return false;
+ onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
+ // if (this.props.Document.isBackground) return false;
const xf = this.getTransform();
const xfo = this.getTransformOverlay();
const [xp, yp] = xf.transformPoint(de.x, de.y);
const [xpo, ypo] = xfo.transformPoint(de.x, de.y);
- if (super.drop(e, de)) {
+ if (super.onInternalDrop(e, de)) {
if (de.complete.docDragData) {
if (de.complete.docDragData.droppedDocuments.length) {
const firstDoc = de.complete.docDragData.droppedDocuments[0];
@@ -154,7 +174,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const nh = NumCast(layoutDoc._nativeHeight);
layoutDoc._height = nw && nh ? nh / nw * NumCast(layoutDoc._width) : 300;
}
- this.bringToFront(d);
+ d.isBackground === undefined && this.bringToFront(d);
}));
(de.complete.docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(de.complete.docDragData.droppedDocuments);
@@ -209,6 +229,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@undoBatch
+ @action
updateClusters(useClusters: boolean) {
this.props.Document.useClusters = useClusters;
this._clusterSets.length = 0;
@@ -246,7 +267,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
docs.map(doc => this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc));
}
childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
- childLayouts.map(child => Doc.GetProto(child).clusterStr = child.cluster?.toString());
}
}
@@ -282,16 +302,16 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
getClusterColor = (doc: Doc) => {
- let clusterColor = "";
+ let clusterColor = this.props.backgroundColor?.(doc);
const cluster = NumCast(doc.cluster);
if (this.Document.useClusters) {
if (this._clusterSets.length <= cluster) {
setTimeout(() => this.updateCluster(doc), 0);
} else {
// choose a cluster color from a palette
- const colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"];
+ const colors = ["#da42429e", "#31ea318c", "rgba(197, 87, 20, 0.55)", "#4a7ae2c4", "rgba(216, 9, 255, 0.5)", "#ff7601", "#1dffff", "yellow", "rgba(27, 130, 49, 0.55)", "rgba(0, 0, 0, 0.268)"];
clusterColor = colors[cluster % colors.length];
- const set = this._clusterSets[cluster] && this._clusterSets[cluster].filter(s => s.backgroundColor && (s.backgroundColor !== s.defaultBackgroundColor));
+ const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor);
// override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document
set && set.filter(s => !s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
set && set.filter(s => s.isBackground).map(s => clusterColor = StrCast(s.backgroundColor));
@@ -312,43 +332,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
- // if physically using a pen or we're in pen or highlighter mode
- // if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) {
- // e.stopPropagation();
- // e.preventDefault();
- // const point = this.getTransform().transformPoint(e.pageX, e.pageY);
- // this._points.push({ X: point[0], Y: point[1] });
- // }
// if not using a pen and in no ink mode
if (InkingControl.Instance.selectedTool === InkTool.None) {
- this._lastX = e.pageX;
- this._lastY = e.pageY;
+ this._downX = this._lastX = e.pageX;
+ this._downY = this._lastY = e.pageY;
}
- // eraser or scrubber plus anything else mode
+ // eraser plus anything else mode
else {
e.stopPropagation();
e.preventDefault();
}
}
- // if (e.button === 0 && !e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) {
- // document.removeEventListener("pointermove", this.onPointerMove);
- // document.removeEventListener("pointerup", this.onPointerUp);
- // document.addEventListener("pointermove", this.onPointerMove);
- // document.addEventListener("pointerup", this.onPointerUp);
- // if (InkingControl.Instance.selectedTool === InkTool.None) {
- // this._lastX = e.pageX;
- // this._lastY = e.pageY;
- // }
- // else {
- // e.stopPropagation();
- // e.preventDefault();
-
- // if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
- // let point = this.getTransform().transformPoint(e.pageX, e.pageY);
- // this._points.push({ x: point[0], y: point[1] });
- // }
- // }
- // }
}
@action
@@ -413,11 +407,96 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
});
this.addDocument(Docs.Create.FreeformDocument(sel, { title: "nested collection", x: bounds.x, y: bounds.y, _width: bWidth, _height: bHeight, _panX: 0, _panY: 0 }));
sel.forEach(d => this.props.removeDocument(d));
+ e.stopPropagation();
break;
-
+ case GestureUtils.Gestures.StartBracket:
+ const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
+ this._inkToTextStartX = start[0];
+ this._inkToTextStartY = start[1];
+ console.log("start");
+ break;
+ case GestureUtils.Gestures.EndBracket:
+ console.log("end");
+ if (this._inkToTextStartX && this._inkToTextStartY) {
+ const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
+ const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "text" && s.color);
+ const sets = setDocs.map((sd) => {
+ return Cast(sd.data, RichTextField)?.Text as string;
+ });
+ if (sets.length && sets[0]) {
+ this._wordPalette.clear();
+ const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+ sets.forEach((st: string, i: number) => {
+ const words = st.split(",");
+ words.forEach(word => {
+ this._wordPalette.set(word, colors[i]);
+ });
+ });
+ }
+ const inks = this.getActiveDocuments().filter(doc => {
+ if (doc.type === "ink") {
+ const l = NumCast(doc.x);
+ const r = l + doc[WidthSym]();
+ const t = NumCast(doc.y);
+ const b = t + doc[HeightSym]();
+ const pass = !(this._inkToTextStartX! > r || end[0] < l || this._inkToTextStartY! > b || end[1] < t);
+ return pass;
+ }
+ return false;
+ });
+ // const inkFields = inks.map(i => Cast(i.data, InkField));
+ const strokes: InkData[] = [];
+ inks.forEach(i => {
+ const d = Cast(i.data, InkField);
+ const x = NumCast(i.x);
+ const y = NumCast(i.y);
+ const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]);
+ const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]);
+ if (d) {
+ strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
+ }
+ });
+
+ CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
+ console.log(results);
+ const wordResults = results.filter((r: any) => r.category === "inkWord");
+ for (const word of wordResults) {
+ const indices: number[] = word.strokeIds;
+ indices.forEach(i => {
+ const otherInks: Doc[] = [];
+ indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
+ inks[i].relatedInks = new List<Doc>(otherInks);
+ const uniqueColors: string[] = [];
+ Array.from(this._wordPalette.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
+ inks[i].alternativeColors = new List<string>(uniqueColors);
+ if (this._wordPalette.has(word.recognizedText.toLowerCase())) {
+ inks[i].color = this._wordPalette.get(word.recognizedText.toLowerCase());
+ }
+ else if (word.alternates) {
+ for (const alt of word.alternates) {
+ if (this._wordPalette.has(alt.recognizedString.toLowerCase())) {
+ inks[i].color = this._wordPalette.get(alt.recognizedString.toLowerCase());
+ break;
+ }
+ }
+ }
+ });
+ }
+ });
+ this._inkToTextStartX = end[0];
+ }
+ break;
+ case GestureUtils.Gestures.Text:
+ if (ge.text) {
+ const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
+ this.addDocument(Docs.Create.TextDocument(ge.text, { title: ge.text, x: B[0], y: B[1] }));
+ e.stopPropagation();
+ }
}
}
+ _lastTap = 0;
+
@action
onPointerUp = (e: PointerEvent): void => {
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) return;
@@ -428,39 +507,26 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.removeEndListeners();
}
+ onClick = (e: React.MouseEvent) => {
+ if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) {
+ if (Date.now() - this._lastTap < 300) {
+ const docpt = this.getTransform().transformPoint(e.clientX, e.clientY);
+ this.scaleAtPt(docpt, 1);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ this._lastTap = Date.now();
+ }
+ }
+
@action
pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
- // I think it makes sense for the marquee menu to go away when panned. -syip2
- MarqueeOptionsMenu.Instance.fadeOut(true);
+ // bcz: theres should be a better way of doing these than referencing these static instances directly
+ MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2
+ PDFMenu.Instance.fadeOut(true);
- let x = this.Document._panX || 0;
- let y = this.Document._panY || 0;
- const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.isMinimized).map(pair => pair.layout);
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- if (!this.isAnnotationOverlay && docs.length && this.childDataProvider(docs[0])) {
- PDFMenu.Instance.fadeOut(true);
- const minx = this.childDataProvider(docs[0]).x;//docs.length ? NumCast(docs[0].x) : 0;
- const miny = this.childDataProvider(docs[0]).y;//docs.length ? NumCast(docs[0].y) : 0;
- const maxx = this.childDataProvider(docs[0]).width + minx;//docs.length ? NumCast(docs[0].width) + minx : minx;
- const maxy = this.childDataProvider(docs[0]).height + miny;//docs.length ? NumCast(docs[0].height) + miny : miny;
- const ranges = docs.filter(doc => doc).filter(doc => this.childDataProvider(doc)).reduce((range, doc) => {
- const x = this.childDataProvider(doc).x;//NumCast(doc.x);
- const y = this.childDataProvider(doc).y;//NumCast(doc.y);
- const xe = this.childDataProvider(doc).width + x;//x + NumCast(layoutDoc.width);
- const ye = this.childDataProvider(doc).height + y; //y + NumCast(layoutDoc.height);
- return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
- [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
- }, [[minx, maxx], [miny, maxy]]);
-
- const cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
- const panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
- this.props.PanelHeight() / this.zoomScaling() * cscale);
- if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
- if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
- if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
- if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;
- }
- this.setPan(x - dx, y - dy);
+ this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
}
@@ -549,7 +615,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// use the centerx and centery as the "new mouse position"
const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- this.pan({ clientX: centerX, clientY: centerY });
+ // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
+
+ if (!this._pullDirection) { // if we are not bezel movement
+ this.pan({ clientX: centerX, clientY: centerY });
+ } else {
+ this._pullCoords = [centerX, centerY];
+ }
+
this._lastX = centerX;
this._lastY = centerY;
}
@@ -574,6 +647,27 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
this._lastX = centerX;
this._lastY = centerY;
+ const screenBox = this._mainCont?.getBoundingClientRect();
+
+
+ // determine if we are using a bezel movement
+ if (screenBox) {
+ if ((screenBox.right - centerX) < 100) {
+ this._pullCoords = [centerX, centerY];
+ this._pullDirection = "right";
+ } else if (centerX - screenBox.left < 100) {
+ this._pullCoords = [centerX, centerY];
+ this._pullDirection = "left";
+ } else if (screenBox.bottom - centerY < 100) {
+ this._pullCoords = [centerX, centerY];
+ this._pullDirection = "bottom";
+ } else if (centerY - screenBox.top < 100) {
+ this._pullCoords = [centerX, centerY];
+ this._pullDirection = "top";
+ }
+ }
+
+
this.removeMoveListeners();
this.addMoveListeners();
this.removeEndListeners();
@@ -584,12 +678,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
cleanUpInteractions = () => {
+ switch (this._pullDirection) {
+ case "left":
+ case "right":
+ case "top":
+ case "bottom":
+ CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([], { title: "New Collection" }), this._pullDirection);
+ }
+
+ this._pullDirection = "";
+ this._pullCoords = [0, 0];
+
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
this.removeMoveListeners();
this.removeEndListeners();
}
+
@action
zoom = (pointX: number, pointY: number, deltaY: number): void => {
let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1;
@@ -617,10 +723,33 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
e.stopPropagation();
this.zoom(e.clientX, e.clientY, e.deltaY);
}
+ this.props.Document.targetScale = NumCast(this.props.Document.scale);
}
@action
- setPan(panX: number, panY: number, panType: string = "None") {
+ setPan(panX: number, panY: number, panType: string = "None", clamp: boolean = false) {
+ if (!this.isAnnotationOverlay && clamp) {
+ // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds
+ const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout);
+ const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "")).map(doc => this.childDataProvider(doc, ""));
+ if (measuredDocs.length) {
+ const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content
+ ({
+ xrange: { min: Math.min(xrange.min, x), max: Math.max(xrange.max, x + width) },
+ yrange: { min: Math.min(yrange.min, y), max: Math.max(yrange.max, y + height) }
+ })
+ , {
+ xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE }
+ });
+
+ const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
+ if (ranges.xrange.min >= (panX + panelDim[0] / 2)) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
+ else if (ranges.xrange.max <= (panX - panelDim[0] / 2)) panX = ranges.xrange.min - panelDim[0] / 2;
+ if (ranges.yrange.min >= (panY + panelDim[1] / 2)) panY = ranges.yrange.max + panelDim[1] / 2;
+ else if (ranges.yrange.max <= (panY - panelDim[1] / 2)) panY = ranges.yrange.min - panelDim[1] / 2;
+ }
+ }
if (!this.Document.lockedTransform || this.Document.inOverlay) {
this.Document.panTransformType = panType;
const scale = this.getLocalTransform().inverse().Scale;
@@ -646,6 +775,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
+ scaleAtPt(docpt: number[], scale: number) {
+ const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+ this.Document.panTransformType = "Ease";
+ this.layoutDoc.scale = scale;
+ const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+ const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
+ const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
+ this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
+ this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
+ }
+
focusDocument = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => boolean) => {
const state = HistoryUtil.getState();
@@ -668,10 +808,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
if (!annotOn) {
this.props.focus(doc);
} else {
- const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn.height);
+ const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
const offset = annotOn && (contextHgt / 2 * 96 / 72);
this.props.Document.scrollY = NumCast(doc.y) - offset;
}
+
+ afterFocus && setTimeout(afterFocus, 1000);
} else {
const layoutdoc = Doc.Layout(doc);
const newPanX = NumCast(doc.x) + NumCast(layoutdoc._width) / 2;
@@ -682,44 +824,53 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType };
- if (!doc.z) this.setPan(newPanX, newPanY, "Ease"); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+ // if (!willZoom && DocumentView._focusHack.length) {
+ // Doc.BrushDoc(this.props.Document);
+ // !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
+ // } else {
+ if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
+ if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+ }
Doc.BrushDoc(this.props.Document);
this.props.focus(this.props.Document);
willZoom && this.setScaleToZoom(layoutdoc, scale);
Doc.linkFollowHighlight(doc);
+ //}
afterFocus && setTimeout(() => {
- if (afterFocus && afterFocus()) {
+ if (afterFocus?.()) {
this.Document._panX = savedState.px;
this.Document._panY = savedState.py;
this.Document.scale = savedState.s;
this.Document.panTransformType = savedState.pt;
}
- }, 1000);
+ }, 500);
}
}
- setScaleToZoom = (doc: Doc, scale: number = 0.5) => {
+ setScaleToZoom = (doc: Doc, scale: number = 0.75) => {
this.Document.scale = scale * Math.min(this.props.PanelWidth() / NumCast(doc._width), this.props.PanelHeight() / NumCast(doc._height));
}
- zoomToScale = (scale: number) => {
- this.Document.scale = scale;
- }
-
- getScale = () => this.Document.scale || 1;
-
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
-
+ @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
+ backgroundHalo = () => BoolCast(this.Document.useClusters);
+ @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+ parentActive = () => this.props.active() || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
return {
...this.props,
+ NativeHeight: returnZero,
+ NativeWidth: returnZero,
+ fitToBox: false,
DataDoc: childData,
Document: childLayout,
LibraryPath: this.libraryPath,
+ FreezeDimensions: this.props.freezeChildDimensions,
layoutKey: undefined,
+ rootSelected: childData ? this.rootSelected : returnFalse,
+ dropAction: StrCast(this.props.Document.childDropAction) as dropActionType,
//onClick: undefined, // this.props.onClick, // bcz: check this out -- I don't think we want to inherit click handlers, or we at least need a way to ignore them
onClick: this.onChildClickHandler,
ScreenToLocalTransform: childLayout.z ? this.getTransformOverlay : this.getTransform,
@@ -731,22 +882,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
ContainingCollectionDoc: this.props.Document,
focus: this.focusDocument,
backgroundColor: this.getClusterColor,
- parentActive: this.props.active,
+ backgroundHalo: this.backgroundHalo,
+ parentActive: this.parentActive,
bringToFront: this.bringToFront,
- zoomToScale: this.zoomToScale,
- getScale: this.getScale
+ addDocTab: this.addDocTab,
};
}
- getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
+ addDocTab = action((doc: Doc, where: string) => {
+ if (where === "inParent") {
+ const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = pt[0];
+ doc.y = pt[1];
+ this.props.addDocument(doc);
+ return true;
+ }
+ if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
+ this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]);
+ return true;
+ }
+ return this.props.addDocTab(doc, where);
+ });
+ getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
const result = this.Document.arrangeScript?.script.run(params, console.log);
if (result?.success) {
- return { ...result, transition: "transform 1s" };
+ return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
}
- const layoutDoc = Doc.Layout(params.doc);
+ const layoutDoc = Doc.Layout(params.pair.layout);
+ const { x, y, z, color, zIndex } = params.pair.layout;
return {
- x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), color: Cast(params.doc.color, "string"),
- zIndex: Cast(params.doc.zIndex, "number"), width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number")
+ x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
+ width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: ""
};
}
@@ -755,137 +921,127 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
onViewDefDivClick = (e: React.MouseEvent, payload: any) => {
- (this.props.Document.onViewDefDivClick as ScriptField)?.script.run({ this: this.props.Document, payload });
+ (this.props.viewDefDivClick || ScriptCast(this.props.Document.onViewDefDivClick))?.script.run({ this: this.props.Document, payload });
+ e.stopPropagation();
}
private viewDefToJSX(viewDef: ViewDefBounds): Opt<ViewDefResult> {
- const x = Cast(viewDef.x, "number");
- const y = Cast(viewDef.y, "number");
- const z = Cast(viewDef.z, "number");
- const highlight = Cast(viewDef.highlight, "boolean");
- const zIndex = Cast(viewDef.zIndex, "number");
- const color = Cast(viewDef.color, "string");
- const width = Cast(viewDef.width, "number", null);
- const height = Cast(viewDef.height, "number", null);
+ const { x, y, z } = viewDef;
+ const color = StrCast(viewDef.color);
+ const width = Cast(viewDef.width, "number");
+ const height = Cast(viewDef.height, "number");
+ const transform = `translate(${x}px, ${y}px)`;
if (viewDef.type === "text") {
const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
const fontSize = Cast(viewDef.fontSize, "number");
return [text, x, y].some(val => val === undefined) ? undefined :
{
- ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color}
- style={{ width, height, color, fontSize, transform: `translate(${x}px, ${y}px)` }}>
+ ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
{text}
</div>,
bounds: viewDef
};
} else if (viewDef.type === "div") {
- const backgroundColor = Cast(viewDef.color, "string");
return [x, y].some(val => val === undefined) ? undefined :
{
ele: <div className="collectionFreeform-customDiv" title={viewDef.payload?.join(" ")} key={"div" + x + y + z} onClick={e => this.onViewDefDivClick(e, viewDef)}
- style={{ width, height, backgroundColor, transform: `translate(${x}px, ${y}px)` }} />,
+ style={{ width, height, backgroundColor: color, transform }} />,
bounds: viewDef
};
}
}
- childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) {
- if (!doc) {
- console.log(doc);
- }
- return this._layoutPoolData.get(doc[Id]);
+ childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutPoolData.get(doc[Id] + (replica || ""));
+ }.bind(this));
+ childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) {
+ return this._layoutSizeData.get(doc[Id] + (replica || ""));
}.bind(this));
- doTimelineLayout(poolData: Map<string, any>) {
- return computeTimelineLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
- this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
- }
-
- doPivotLayout(poolData: Map<string, any>) {
- return computePivotLayout(poolData, this.props.Document, this.childDocs, this.filterDocs,
- this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
+ doEngineLayout(poolData: Map<string, PoolData>,
+ engine: (
+ poolData: Map<string, PoolData>,
+ pivotDoc: Doc,
+ childPairs: { layout: Doc, data?: Doc }[],
+ panelDim: number[],
+ viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]
+ ) {
+ return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX);
}
- _cachedPool: Map<string, any> = new Map();
- doFreeformLayout(poolData: Map<string, any>) {
+ doFreeformLayout(poolData: Map<string, PoolData>) {
const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
const state = initResult && initResult.success ? initResult.result.scriptState : undefined;
const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : [];
this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => {
- const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state });
+ const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state });
poolData.set(pair.layout[Id], pos);
});
- return { elements: elements };
+ return elements;
}
@computed get doInternalLayoutComputation() {
- const newPool = new Map<string, any>();
- switch (this.props.layoutEngine?.()) {
- case "timeline": return { newPool, computedElementData: this.doTimelineLayout(newPool) };
- case "pivot": return { newPool, computedElementData: this.doPivotLayout(newPool) };
+ TraceMobx();
+
+
+ const newPool = new Map<string, PoolData>();
+ const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine);
+ switch (engine) {
+ case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) };
+ case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
+ case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
+ case "starburst": return { newPool, computedElementData: this.doEngineLayout(newPool, computerStarburstLayout) };
}
return { newPool, computedElementData: this.doFreeformLayout(newPool) };
}
- @computed get filterDocs() {
- const docFilters = Cast(this.props.Document._docFilter, listSpec("string"), []);
- const clusters: { [key: string]: { [value: string]: string } } = {};
- for (let i = 0; i < docFilters.length; i += 3) {
- const [key, value, modifiers] = docFilters.slice(i, i + 3);
- const cluster = clusters[key];
- if (!cluster) {
- const child: { [value: string]: string } = {};
- child[value] = modifiers;
- clusters[key] = child;
- } else {
- cluster[value] = modifiers;
- }
- }
- const filteredDocs = docFilters.length ? this.childDocs.filter(d => {
- for (const key of Object.keys(clusters)) {
- const cluster = clusters[key];
- const satisfiesFacet = Object.keys(cluster).some(inner => {
- const modifier = cluster[inner];
- return (modifier === "x") !== Doc.matchFieldValue(d, key, inner);
- });
- if (!satisfiesFacet) {
- return false;
- }
- }
- return true;
- }) : this.childDocs;
- return filteredDocs;
- }
+ childLayoutDocFunc = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
get doLayoutComputation() {
const { newPool, computedElementData } = this.doInternalLayoutComputation;
runInAction(() =>
- Array.from(newPool.keys()).map(key => {
- const lastPos = this._cachedPool.get(key); // last computed pos
- const newPos = newPool.get(key);
- if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) {
- this._layoutPoolData.set(key, newPos);
+ Array.from(newPool.entries()).map(entry => {
+ const lastPos = this._cachedPool.get(entry[0]); // last computed pos
+ const newPos = entry[1];
+ if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) {
+ this._layoutPoolData.set(entry[0], newPos);
+ }
+ if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) {
+ this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height });
}
}));
this._cachedPool.clear();
- Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k)));
- this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair =>
- computedElementData.elements.push({
- ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)}
+ Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
+ const elements: ViewDefResult[] = computedElementData.slice();
+ const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
+ Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
+ elements.push({
+ ele: <CollectionFreeFormDocumentView
+ key={entry[1].pair.layout[Id] + (entry[1].replica || "")}
+ {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)}
+ replica={entry[1].replica}
dataProvider={this.childDataProvider}
- jitterRotation={NumCast(this.props.Document.jitterRotation)}
- fitToBox={this.props.fitToBox || this.props.layoutEngine !== undefined} />,
- bounds: this.childDataProvider(pair.layout)
+ sizeProvider={this.childSizeProvider}
+ LayoutDoc={this.childLayoutDocFunc}
+ pointerEvents={
+ this.backgroundActive ?
+ true :
+ (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined}
+ jitterRotation={NumCast(this.props.Document._jitterRotation)}
+ //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+ fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this
+ FreezeDimensions={BoolCast(this.props.freezeChildDimensions)}
+ />,
+ bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
}));
- return computedElementData;
+ return elements;
}
componentDidMount() {
super.componentDidMount();
- this._layoutComputeReaction = reaction(
- () => (this.doLayoutComputation),
- (computation) => this._layoutElements = computation?.elements.slice() || [],
+ this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
+ (elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
}
componentWillUnmount() {
@@ -899,6 +1055,15 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+ promoteCollection = undoBatch(action(() => {
+ this.childDocs.forEach(doc => {
+ const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ doc.x = scr?.[0];
+ doc.y = scr?.[1];
+ this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc);
+ });
+ this.props.ContainingCollectionView?.removeDocument(this.props.Document);
+ }));
layoutDocsInGrid = () => {
UndoManager.RunInBatch(() => {
const docs = this.childLayoutPairs;
@@ -923,74 +1088,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
private thumbIdentifier?: number;
- // @action
- // handleHandDown = (e: React.TouchEvent) => {
- // const fingers = InteractionUtils.GetMyTargetTouches(e, this.prevPoints, true);
- // const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]);
- // this.thumbIdentifier = thumb?.identifier;
- // const others = fingers.filter(f => f !== thumb);
- // const minX = Math.min(...others.map(f => f.clientX));
- // const minY = Math.min(...others.map(f => f.clientY));
- // const t = this.getTransform().transformPoint(minX, minY);
- // const th = this.getTransform().transformPoint(thumb.clientX, thumb.clientY);
-
- // const thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc));
- // if (thumbDoc) {
- // this._palette = <Palette x={t[0]} y={t[1]} thumb={th} thumbDoc={thumbDoc} />;
- // }
-
- // document.removeEventListener("touchmove", this.onTouch);
- // document.removeEventListener("touchmove", this.handleHandMove);
- // document.addEventListener("touchmove", this.handleHandMove);
- // document.removeEventListener("touchend", this.handleHandUp);
- // document.addEventListener("touchend", this.handleHandUp);
- // }
-
- // @action
- // handleHandMove = (e: TouchEvent) => {
- // for (let i = 0; i < e.changedTouches.length; i++) {
- // const pt = e.changedTouches.item(i);
- // if (pt?.identifier === this.thumbIdentifier) {
- // }
- // }
- // }
-
- // @action
- // handleHandUp = (e: TouchEvent) => {
- // this.onTouchEnd(e);
- // if (this.prevPoints.size < 3) {
- // this._palette = undefined;
- // document.removeEventListener("touchend", this.handleHandUp);
- // }
- // }
-
onContextMenu = (e: React.MouseEvent) => {
- const layoutItems: ContextMenuProps[] = [];
- const { Document } = this.props;
-
- layoutItems.push({
- description: "reset view", event: () => {
- Doc.resetView(Document);
- }, icon: "compress-arrows-alt"
- });
- layoutItems.push({
- description: "set view origin", event: () => {
- Doc.setView(Document);
- }, icon: "expand-arrows-alt"
- });
- layoutItems.push({
- description: "reset view to origin", event: () => {
- Doc.resetViewToOrigin(Document);
- }, icon: "expand-arrows-alt"
- });
-
- layoutItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
- layoutItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
- layoutItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- layoutItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+ if (this.props.children && this.props.annotationsKey) return;
+ const options = ContextMenu.Instance.findByDescription("Options...");
+ const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : [];
+
+ optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
+ optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" });
+ optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
+ optionItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+ this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
+ optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
// layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
- layoutItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = 10), icon: "paint-brush" });
- layoutItems.push({
+ optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" });
+ optionItems.push({
description: "Import document", icon: "upload", event: ({ x, y }) => {
const input = document.createElement("input");
input.type = "file";
@@ -1009,7 +1120,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
if (doc instanceof Doc) {
const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
doc.x = xx, doc.y = yy;
- this.props.addDocument && this.props.addDocument(doc);
+ this.props.addDocument?.(doc);
}
}
}
@@ -1017,28 +1128,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
input.click();
}
});
- //@ts-ignore
- const subitems: ContextMenuProps[] =
- DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
- description: (i + 1) + ": " + StrCast(note.title),
- event: () => console.log("Hi"),
- icon: "eye"
- }));
-
- layoutItems.push({
- description: "Add Note ...",
- subitems: DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data).map((note, i) => ({
- description: (i + 1) + ": " + StrCast(note.title),
- event: (args: { x: number, y: number }) => this.addLiveTextBox(Docs.Create.TextDocument("", { _width: 200, _height: 100, x: this.getTransform().transformPoint(args.x, args.y)[0], y: this.getTransform().transformPoint(args.x, args.y)[1], _autoHeight: true, layout: note, title: StrCast(note.title) })),
- icon: "eye"
- })) as ContextMenuProps[],
- icon: "eye"
- });
- ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
+ ContextMenu.Instance.addItem({ description: "Options ...", subitems: optionItems, icon: "eye" });
this._timelineRef.current!.timelineContextMenu(e);
}
-
private childViews = () => {
const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
return [
@@ -1047,13 +1140,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
];
}
- // @observable private _palette?: JSX.Element;
-
children = () => {
const eles: JSX.Element[] = [];
eles.push(...this.childViews());
- // this._palette && (eles.push(this._palette));
- // this.currentStroke && (eles.push(this.currentStroke));
eles.push(<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />);
return eles;
}
@@ -1062,23 +1151,40 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
</div>;
}
+
+ _nudgeTime = 0;
+ nudge = action((x: number, y: number) => {
+ if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform) { // bcz: this isn't ideal, but want to try it out...
+ this.setPan(NumCast(this.layoutDoc._panX) + this.props.PanelWidth() / 2 * x / this.zoomScaling(),
+ NumCast(this.layoutDoc._panY) + this.props.PanelHeight() / 2 * (-y) / this.zoomScaling(), "Ease", true);
+ this._nudgeTime = Date.now();
+ setTimeout(() => (Date.now() - this._nudgeTime >= 500) && (this.Document.panTransformType = undefined), 500);
+ return true;
+ }
+ return false;
+ });
@computed get marqueeView() {
- return <MarqueeView {...this.props} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
+ return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
- <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
- easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay}
+ easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{this.children}
</CollectionFreeFormViewPannableContents>
</MarqueeView>;
}
+
@computed get contentScaling() {
- if (this.props.annotationsKey) return 0;
- const hscale = this.nativeHeight ? this.props.PanelHeight() / this.nativeHeight : 1;
- const wscale = this.nativeWidth ? this.props.PanelWidth() / this.nativeWidth : 1;
+ if (this.props.annotationsKey && !this.props.forceScaling) return 0;
+ const nw = NumCast(this.Document._nativeWidth, this.props.NativeWidth());
+ const nh = NumCast(this.Document._nativeHeight, this.props.NativeHeight());
+ const hscale = nh ? this.props.PanelHeight() / nh : 1;
+ const wscale = nw ? this.props.PanelWidth() / nw : 1;
return wscale < hscale ? wscale : hscale;
}
+ @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); }
render() {
TraceMobx();
+ const clientRect = this._mainCont?.getBoundingClientRect();
// update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
// this.Document.fitX = this.contentBounds && this.contentBounds.x;
// this.Document.fitY = this.contentBounds && this.contentBounds.y;
@@ -1086,26 +1192,34 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// this.Document.fitH = this.contentBounds && (this.contentBounds.b - this.contentBounds.y);
// if isAnnotationOverlay is set, then children will be stored in the extension document for the fieldKey.
// otherwise, they are stored in fieldKey. All annotations to this document are stored in the extension document
- // let lodarea = this.Document[WidthSym]() * this.Document[HeightSym]() / this.props.ScreenToLocalTransform().Scale / this.props.ScreenToLocalTransform().Scale;
- return <div>
- <div className={"collectionfreeformview-container"}
- ref={this.createDashEventsTarget}
- onWheel={this.onPointerWheel}//pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}
+ return <div className={"collectionfreeformview-container"}
+ ref={this.createDashEventsTarget}
+ onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu}
+ style={{
+ pointerEvents: this.backgroundEvents ? "all" : undefined,
+ transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
+ transformOrigin: this.contentScaling ? "left top" : "",
+ width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
+ height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ }}>
+ {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
+ this.placeholder : this.marqueeView}
+ <CollectionFreeFormOverlayView elements={this.elementFunc} />
+
+ <div className={"pullpane-indicator"}
style={{
- pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined,
- transform: this.contentScaling ? `scale(${this.contentScaling})` : "",
- transformOrigin: this.contentScaling ? "left top" : "",
- width: this.contentScaling ? `${100 / this.contentScaling}%` : "",
- height: this.contentScaling ? `${100 / this.contentScaling}%` : this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ display: this._pullDirection ? "block" : "none",
+ top: clientRect ? this._pullDirection === "bottom" ? this._pullCoords[1] - clientRect.y : 0 : "auto",
+ // left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x - MainView.Instance.flyoutWidth : 0 : "auto",
+ left: clientRect ? this._pullDirection === "right" ? this._pullCoords[0] - clientRect.x : 0 : "auto",
+ width: clientRect ? this._pullDirection === "left" ? this._pullCoords[0] - clientRect.left : this._pullDirection === "right" ? clientRect.right - this._pullCoords[0] : clientRect.width : 0,
+ height: clientRect ? this._pullDirection === "top" ? this._pullCoords[1] - clientRect.top : this._pullDirection === "bottom" ? clientRect.bottom - this._pullCoords[1] : clientRect.height : 0,
+
}}>
- {/* <Timeline ref={this._timelineRef} {...this.props} /> */}
- {!this.Document._LODdisable && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ? // && this.props.CollectionView && lodarea < NumCast(this.Document.LODarea, 100000) ?
- this.placeholder : this.marqueeView}
- <CollectionFreeFormOverlayView elements={this.elementFunc} />
</div>
- <Timeline ref={this._timelineRef} {...this.props} />
- </div>;
+
+ </div >;
}
}
@@ -1116,7 +1230,7 @@ interface CollectionFreeFormOverlayViewProps {
@observer
class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOverlayViewProps>{
render() {
- return this.props.elements().filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele);
+ return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele);
}
}
@@ -1127,19 +1241,25 @@ interface CollectionFreeFormViewPannableContentsProps {
panY: () => number;
zoomScaling: () => number;
easing: () => boolean;
+ viewDefDivClick?: ScriptField;
children: () => JSX.Element[];
+ shifted: boolean;
}
@observer
class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
render() {
- const freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none");
+ const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : (this.props.easing() ? "-ease" : "-none"));
const cenx = this.props.centeringShiftX();
const ceny = this.props.centeringShiftY();
const panx = -this.props.panX();
const pany = -this.props.panY();
const zoom = this.props.zoomScaling();
- return <div className={freeformclass} style={{ touchAction: "none", borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)` }}>
+ return <div className={freeformclass}
+ style={{
+ width: this.props.shifted ? 0 : undefined, height: this.props.shifted ? 0 : undefined,
+ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}) translate(${panx}px, ${pany}px)`
+ }}>
{this.props.children()}
</div>;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 71f265484..db4b674b5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -11,6 +11,7 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
public showMarquee: () => void = unimplementedFunction;
public hideMarquee: () => void = unimplementedFunction;
@@ -43,6 +44,13 @@ export default class MarqueeOptionsMenu extends AntimodeMenu {
onPointerDown={this.delete}>
<FontAwesomeIcon icon="trash-alt" size="lg" />
</button>,
+ <button
+ className="antimodeMenu-button"
+ title="Change to Text"
+ key="inkToText"
+ onPointerDown={this.inkToText}>
+ <FontAwesomeIcon icon="font" size="lg" />
+ </button>,
];
return this.getElement(buttons);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 18d6da0da..1291e7dc1 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -6,7 +6,6 @@
width:100%;
height:100%;
overflow: hidden;
- pointer-events: inherit;
border-radius: inherit;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index e16f4011e..2d3bb6f3c 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,24 +1,26 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast } from "../../../../new_fields/Doc";
-import { InkField } from "../../../../new_fields/InkField";
+import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc";
+import { InkField, InkData } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
-import { listSpec } from "../../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
-import { ComputedField } from "../../../../new_fields/ScriptField";
-import { Cast, NumCast, StrCast } from "../../../../new_fields/Types";
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
+import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types";
import { Utils } from "../../../../Utils";
-import { Docs } from "../../../documents/Documents";
+import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
+import { ContextMenu } from "../../ContextMenu";
import { PreviewCursor } from "../../PreviewCursor";
-import { CollectionViewType } from "../CollectionView";
+import { SubCollectionViewProps } from "../CollectionSubView";
+import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import "./MarqueeView.scss";
import React = require("react");
-import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
-import { SubCollectionViewProps } from "../CollectionSubView";
+import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
+import { RichTextField } from "../../../../new_fields/RichTextField";
+import { CollectionView } from "../CollectionView";
+import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
+import { ScriptField } from "../../../../new_fields/ScriptField";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -30,6 +32,7 @@ interface MarqueeViewProps {
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
isAnnotationOverlay?: boolean;
+ nudge: (x: number, y: number) => boolean;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void;
}
@@ -45,7 +48,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
_commandExecuted = false;
componentDidMount() {
- this.props.setPreviewCursor && this.props.setPreviewCursor(this.setPreviewCursor);
+ this.props.setPreviewCursor?.(this.setPreviewCursor);
}
@action
@@ -66,7 +69,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
//make textbox and add it to this collection
// tslint:disable-next-line:prefer-const
let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
- if (e.key === "q" && e.ctrlKey) {
+ if (e.key === ":") {
+ DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y);
+
+ ContextMenu.Instance.displayMenu(this._downX, this._downY);
+ } else if (e.key === "q" && e.ctrlKey) {
e.preventDefault();
(async () => {
const text: string = await navigator.clipboard.readText();
@@ -100,13 +107,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
});
} else if (!e.ctrlKey) {
- this.props.addLiveTextDocument(
- Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" }));
- } else if (e.keyCode > 48 && e.keyCode <= 57) {
- const notes = DocListCast((CurrentUserUtils.UserDocument.noteTypes as Doc).data);
- const text = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
- text.layout = notes[(e.keyCode - 49) % notes.length];
- this.props.addLiveTextDocument(text);
+ FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
+ const tbox = Docs.Create.TextDocument("", { _width: 200, _height: 100, x: x, y: y, _autoHeight: true, title: "-typed text-" });
+ const template = FormattedTextBox.DefaultLayout;
+ if (template instanceof Doc) {
+ tbox._width = NumCast(template._width);
+ tbox.layoutKey = "layout_" + StrCast(template.title);
+ tbox[StrCast(tbox.layoutKey)] = template;
+ }
+ this.props.addLiveTextDocument(tbox);
}
e.stopPropagation();
}
@@ -204,6 +213,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
MarqueeOptionsMenu.Instance.createCollection = this.collection;
MarqueeOptionsMenu.Instance.delete = this.delete;
MarqueeOptionsMenu.Instance.summarize = this.summary;
+ MarqueeOptionsMenu.Instance.inkToText = this.syntaxHighlight;
MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
@@ -235,15 +245,16 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this._downX = x;
this._downY = y;
- PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument);
+ PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
}
});
@action
onClick = (e: React.MouseEvent): void => {
- if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ if (
+ Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
- this.setPreviewCursor(e.clientX, e.clientY, false);
+ !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false);
// let the DocumentView stopPropagation of this event when it selects this document
} else { // why do we get a click event when the cursor have moved a big distance?
// let's cut it off here so no one else has to deal with it.
@@ -299,17 +310,16 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.hideMarquee();
}
- getCollection = (selected: Doc[], asTemplate: boolean) => {
+ getCollection = (selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => {
const bounds = this.Bounds;
// const inkData = this.ink ? this.ink.inkData : undefined;
- const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument;
- const newCollection = creator(selected, {
+ const newCollection = (creator || Docs.Create.FreeformDocument)(selected, {
x: bounds.left,
y: bounds.top,
_panX: 0,
_panY: 0,
- backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : "white",
- defaultBackgroundColor: this.props.isAnnotationOverlay ? "#00000015" : "white",
+ isBackground,
+ backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined,
_width: bounds.width,
_height: bounds.height,
_LODdisable: true,
@@ -323,6 +333,18 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@action
+ pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ const selected = this.marqueeSelect(false);
+ SelectionManager.DeselectAll();
+ selected.forEach(d => this.props.removeDocument(d));
+ const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2);
+ this.props.addDocument(newCollection);
+ this.props.selectDocuments([newCollection], []);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ }
+
+ @action
collection = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
@@ -331,11 +353,11 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.props.removeDocument(d);
d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- d.displayTimecode = undefined;
+ d.displayTimecode = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection
return d;
});
}
- const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t");
+ const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined);
this.props.addDocument(newCollection);
this.props.selectDocuments([newCollection], []);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -343,11 +365,88 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@action
+ syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ const selected = this.marqueeSelect(false);
+ if (e instanceof KeyboardEvent ? e.key === "i" : true) {
+ const inks = selected.filter(s => s.proto?.type === "ink");
+ const setDocs = selected.filter(s => s.proto?.type === "text" && s.color);
+ const sets = setDocs.map((sd) => {
+ return Cast(sd.data, RichTextField)?.Text as string;
+ });
+ const colors = setDocs.map(sd => FieldValue(sd.color) as string);
+ const wordToColor = new Map<string, string>();
+ sets.forEach((st: string, i: number) => {
+ const words = st.split(",");
+ words.forEach(word => {
+ wordToColor.set(word, colors[i]);
+ });
+ });
+ const strokes: InkData[] = [];
+ inks.forEach(i => {
+ const d = Cast(i.data, InkField);
+ const x = NumCast(i.x);
+ const y = NumCast(i.y);
+ const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]);
+ const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]);
+ if (d) {
+ strokes.push(d.inkData.map(pd => ({ X: pd.X + x - left, Y: pd.Y + y - top })));
+ }
+ });
+ CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
+ // const wordResults = results.filter((r: any) => r.category === "inkWord");
+ // console.log(wordResults);
+ // console.log(results);
+ // for (const word of wordResults) {
+ // const indices: number[] = word.strokeIds;
+ // indices.forEach(i => {
+ // if (wordToColor.has(word.recognizedText.toLowerCase())) {
+ // inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
+ // }
+ // else {
+ // for (const alt of word.alternates) {
+ // if (wordToColor.has(alt.recognizedString.toLowerCase())) {
+ // inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
+ // break;
+ // }
+ // }
+ // }
+ // })
+ // }
+ // const wordResults = results.filter((r: any) => r.category === "inkWord");
+ // for (const word of wordResults) {
+ // const indices: number[] = word.strokeIds;
+ // indices.forEach(i => {
+ // const otherInks: Doc[] = [];
+ // indices.forEach(i2 => i2 !== i && otherInks.push(inks[i2]));
+ // inks[i].relatedInks = new List<Doc>(otherInks);
+ // const uniqueColors: string[] = [];
+ // Array.from(wordToColor.values()).forEach(c => uniqueColors.indexOf(c) === -1 && uniqueColors.push(c));
+ // inks[i].alternativeColors = new List<string>(uniqueColors);
+ // if (wordToColor.has(word.recognizedText.toLowerCase())) {
+ // inks[i].color = wordToColor.get(word.recognizedText.toLowerCase());
+ // }
+ // else if (word.alternates) {
+ // for (const alt of word.alternates) {
+ // if (wordToColor.has(alt.recognizedString.toLowerCase())) {
+ // inks[i].color = wordToColor.get(alt.recognizedString.toLowerCase());
+ // break;
+ // }
+ // }
+ // }
+ // });
+ // }
+ const lines = results.filter((r: any) => r.category === "line");
+ console.log(lines);
+ const text = lines.map((l: any) => l.recognizedText).join("\r\n");
+ this.props.addDocument(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
+ });
+ }
+ }
+
+ @action
summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const bounds = this.Bounds;
const selected = this.marqueeSelect(false);
- const newCollection = this.getCollection(selected, false);
-
selected.map(d => {
this.props.removeDocument(d);
d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
@@ -355,25 +454,26 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
d.page = -1;
return d;
});
- newCollection._chromeStatus = "disabled";
- const summary = Docs.Create.TextDocument("", { x: bounds.left, y: bounds.top, _width: 300, _height: 100, _autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
- Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
- newCollection.x = bounds.left + bounds.width;
- Doc.GetProto(newCollection).summaryDoc = summary;
- Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
- if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
- const container = Docs.Create.FreeformDocument([summary, newCollection], {
- x: bounds.left, y: bounds.top, _width: 300, _height: 200, _autoHeight: true,
- _viewType: CollectionViewType.Stacking, _chromeStatus: "disabled", title: "-summary-"
- });
- Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight"
- this.props.addLiveTextDocument(container);
- } else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
- Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight"
- this.props.addLiveTextDocument(summary);
- }
+ const summary = Docs.Create.TextDocument("", { x: bounds.left + bounds.width / 2, y: bounds.top + bounds.height / 2, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" });
+ const portal = Doc.MakeAlias(summary);
+ Doc.GetProto(summary)[Doc.LayoutFieldKey(summary) + "-annotations"] = new List<Doc>(selected);
+ Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations");
+ summary._backgroundColor = "#e2ad32";
+ portal.layoutKey = "layout_portal";
+ portal.title = "document collection";
+ DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing");
+
+ this.props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
}
+ @action
+ background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ const newCollection = this.getCollection([], undefined, true);
+ this.props.addDocument(newCollection);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ setTimeout(() => this.props.selectDocuments([newCollection], []), 0);
+ }
@undoBatch
@action
@@ -388,7 +488,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.delete();
e.stopPropagation();
}
- if (e.key === "c" || e.key === "t" || e.key === "s" || e.key === "S") {
+ if (e.key === "c" || e.key === "b" || e.key === "t" || e.key === "s" || e.key === "S" || e.key === "p") {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
@@ -396,10 +496,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (e.key === "c" || e.key === "t") {
this.collection(e);
}
-
if (e.key === "s" || e.key === "S") {
this.summary(e);
}
+ if (e.key === "b") {
+ this.background(e);
+ }
+ if (e.key === "p") {
+ this.pileup(e);
+ }
this.cleanupInteractions(false);
}
}
@@ -497,13 +602,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
* This contains the "C for collection, ..." text on marquees.
* Commented out by syip2 when the marquee menu was added.
*/
- return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} >
+ return <div className="marquee" style={{
+ transform: `translate(${p[0]}px, ${p[1]}px)`,
+ width: `${Math.abs(v[0])}`,
+ height: `${Math.abs(v[1])}`, zIndex: 2000
+ }} >
{/* <span className="marquee-legend" /> */}
</div>;
}
render() {
- return <div className="marqueeView" onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
+ return <div className="marqueeView"
+ style={{ overflow: StrCast(this.props.Document.overflow), }}
+ onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this._visible ? this.marqueeDiv : null}
{this.props.children}
</div>;