aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-11-12 14:34:09 -0500
committerbobzel <zzzman@gmail.com>2023-11-12 14:34:09 -0500
commit7fcf4c54c42b7eaa427ea88c0b8586a78d7f1859 (patch)
treefb049b6364456e3b70c325c59efcee00c64d5557 /src
parentfb4521e4275248ba463164e40aaaed04df65b050 (diff)
cleaning up freeformview code.
Diffstat (limited to 'src')
-rw-r--r--src/client/cognitive_services/CognitiveServices.ts195
-rw-r--r--src/client/views/collections/CollectionDockingView.scss2
-rw-r--r--src/client/views/collections/CollectionMenu.tsx4
-rw-r--r--src/client/views/collections/CollectionTreeView.scss1
-rw-r--r--src/client/views/collections/CollectionView.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx75
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx60
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx303
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx11
-rw-r--r--src/client/views/global/globalCssVariables.scss2
-rw-r--r--src/client/views/global/globalCssVariables.scss.d.ts1
-rw-r--r--src/client/views/nodes/LinkBox.tsx2
13 files changed, 280 insertions, 379 deletions
diff --git a/src/client/cognitive_services/CognitiveServices.ts b/src/client/cognitive_services/CognitiveServices.ts
index 2b2931a97..408903324 100644
--- a/src/client/cognitive_services/CognitiveServices.ts
+++ b/src/client/cognitive_services/CognitiveServices.ts
@@ -1,29 +1,29 @@
-import * as request from "request-promise";
-import { Doc, Field } from "../../fields/Doc";
-import { Cast } from "../../fields/Types";
-import { Utils } from "../../Utils";
-import { InkData } from "../../fields/InkField";
-import { UndoManager } from "../util/UndoManager";
-import requestPromise = require("request-promise");
-import { List } from "../../fields/List";
-
-type APIManager<D> = { converter: BodyConverter<D>, requester: RequestExecutor };
+import * as request from 'request-promise';
+import { Doc, Field } from '../../fields/Doc';
+import { Cast } from '../../fields/Types';
+import { Utils } from '../../Utils';
+import { InkData } from '../../fields/InkField';
+import { UndoManager } from '../util/UndoManager';
+import requestPromise = require('request-promise');
+import { List } from '../../fields/List';
+
+type APIManager<D> = { converter: BodyConverter<D>; requester: RequestExecutor };
type RequestExecutor = (apiKey: string, body: string, service: Service) => Promise<string>;
type AnalysisApplier<D> = (target: Doc, relevantKeys: string[], data: D, ...args: any) => any;
type BodyConverter<D> = (data: D) => string;
type Converter = (results: any) => Field;
-type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field, external_recommendations: any, kp_string: string[] }>;
-type BingConverter = (results: any) => Promise<{ title_vals: string[], url_vals: string[] }>;
+type TextConverter = (results: any, data: string) => Promise<{ keyterms: Field; external_recommendations: any; kp_string: string[] }>;
+type BingConverter = (results: any) => Promise<{ title_vals: string[]; url_vals: string[] }>;
-export type Tag = { name: string, confidence: number };
-export type Rectangle = { top: number, left: number, width: number, height: number };
+export type Tag = { name: string; confidence: number };
+export type Rectangle = { top: number; left: number; width: number; height: number };
export enum Service {
- ComputerVision = "vision",
- Face = "face",
- Handwriting = "handwriting",
- Text = "text",
- Bing = "bing"
+ ComputerVision = 'vision',
+ Face = 'face',
+ Handwriting = 'handwriting',
+ Text = 'text',
+ Bing = 'bing',
}
export enum Confidence {
@@ -32,7 +32,7 @@ export enum Confidence {
Poor = 0.4,
Fair = 0.6,
Good = 0.8,
- Excellent = 0.95
+ Excellent = 0.95,
}
/**
@@ -41,13 +41,8 @@ export enum Confidence {
* various media types.
*/
export namespace CognitiveServices {
-
const ExecuteQuery = async <D>(service: Service, manager: APIManager<D>, data: D): Promise<any> => {
let apiKey = process.env[service.toUpperCase()];
- // A HACK FOR A DEMO VIDEO - syip2
- if (service === "handwriting") {
- apiKey = "61088486d76c4b12ba578775a5f55422";
- }
if (!apiKey) {
console.log(`No API key found for ${service}: ensure youe root directory has .env file with _CLIENT_${service.toUpperCase()}.`);
return undefined;
@@ -64,9 +59,7 @@ export namespace CognitiveServices {
};
export namespace Image {
-
export const Manager: APIManager<string> = {
-
converter: (imageUrl: string) => JSON.stringify({ url: imageUrl }),
requester: async (apiKey: string, body: string, service: Service) => {
@@ -77,18 +70,17 @@ export namespace CognitiveServices {
case Service.Face:
uriBase = 'face/v1.0/detect';
parameters = {
- 'returnFaceId': 'true',
- 'returnFaceLandmarks': 'false',
- 'returnFaceAttributes': 'age,gender,headPose,smile,facialHair,glasses,' +
- 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise'
+ returnFaceId: 'true',
+ returnFaceLandmarks: 'false',
+ returnFaceAttributes: 'age,gender,headPose,smile,facialHair,glasses,' + 'emotion,hair,makeup,occlusion,accessories,blur,exposure,noise',
};
break;
case Service.ComputerVision:
uriBase = 'vision/v2.0/analyze';
parameters = {
- 'visualFeatures': 'Categories,Description,Color,Objects,Tags,Adult',
- 'details': 'Celebrities,Landmarks',
- 'language': 'en',
+ visualFeatures: 'Categories,Description,Color,Objects,Tags,Adult',
+ details: 'Celebrities,Landmarks',
+ language: 'en',
};
break;
}
@@ -99,69 +91,63 @@ export namespace CognitiveServices {
body: body,
headers: {
'Content-Type': 'application/json',
- 'Ocp-Apim-Subscription-Key': apiKey
- }
+ 'Ocp-Apim-Subscription-Key': apiKey,
+ },
};
return request.post(options);
},
-
};
export namespace Appliers {
-
export const ProcessImage: AnalysisApplier<string> = async (target: Doc, keys: string[], url: string, service: Service, converter: Converter) => {
- const batch = UndoManager.StartBatch("Image Analysis");
+ const batch = UndoManager.StartBatch('Image Analysis');
const storageKey = keys[0];
- if (!url || await Cast(target[storageKey], Doc)) {
+ if (!url || (await Cast(target[storageKey], Doc))) {
return;
}
let toStore: any;
const results = await ExecuteQuery(service, Manager, url);
if (!results) {
- toStore = "Cognitive Services could not process the given image URL.";
+ toStore = 'Cognitive Services could not process the given image URL.';
} else {
if (!results.length) {
toStore = converter(results);
} else {
- toStore = results.length > 0 ? converter(results) : "Empty list returned.";
+ toStore = results.length > 0 ? converter(results) : 'Empty list returned.';
}
}
target[storageKey] = toStore;
batch.end();
};
-
}
- export type Face = { faceAttributes: any, faceId: string, faceRectangle: Rectangle };
-
+ export type Face = { faceAttributes: any; faceId: string; faceRectangle: Rectangle };
}
export namespace Inking {
-
export const Manager: APIManager<InkData[]> = {
-
converter: (inkData: InkData[]): string => {
let id = 0;
const strokes: AzureStrokeData[] = inkData.map(points => ({
id: id++,
- points: points.map(({ X: x, Y: y }) => `${x},${y}`).join(","),
- language: "en-US"
+ points: points.map(({ X: x, Y: y }) => `${x},${y}`).join(','),
+ language: 'en-US',
}));
return JSON.stringify({
version: 1,
- language: "en-US",
- unit: "mm",
- strokes
+ language: 'en-US',
+ unit: 'mm',
+ strokes,
});
},
requester: async (apiKey: string, body: string) => {
const xhttp = new XMLHttpRequest();
- const serverAddress = "https://api.cognitive.microsoft.com";
- const endpoint = serverAddress + "/inkrecognizer/v1.0-preview/recognize";
+ const serverAddress = 'https://api.cognitive.microsoft.com';
+ const endpoint = serverAddress + '/inkrecognizer/v1.0-preview/recognize';
return new Promise<string>((resolve, reject) => {
xhttp.onreadystatechange = function () {
@@ -177,7 +163,7 @@ export namespace CognitiveServices {
}
};
- xhttp.open("PUT", endpoint, true);
+ xhttp.open('PUT', endpoint, true);
xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
xhttp.setRequestHeader('Content-Type', 'application/json');
xhttp.send(body);
@@ -186,18 +172,17 @@ export namespace CognitiveServices {
};
export namespace Appliers {
-
export const ConcatenateHandwriting: AnalysisApplier<InkData[]> = async (target: Doc, keys: string[], inkData: InkData[]) => {
- const batch = UndoManager.StartBatch("Ink Analysis");
+ const batch = UndoManager.StartBatch('Ink Analysis');
let results = await ExecuteQuery(Service.Handwriting, Manager, inkData);
if (results) {
results.recognitionUnits && (results = results.recognitionUnits);
- target[keys[0]] = Doc.Get.FromJson({ data: results, title: "Ink Analysis" });
+ target[keys[0]] = Doc.Get.FromJson({ data: results, title: 'Ink Analysis' });
const recognizedText = results.map((item: any) => item.recognizedText);
const recognizedObjects = results.map((item: any) => item.recognizedObject);
- const individualWords = recognizedText.filter((text: string) => text && text.split(" ").length === 1);
- target[keys[1]] = individualWords.length ? individualWords.join(" ") : recognizedObjects.join(", ");
+ const individualWords = recognizedText.filter((text: string) => text && text.split(' ').length === 1);
+ target[keys[1]] = individualWords.length ? individualWords.join(' ') : recognizedObjects.join(', ');
}
batch.end();
@@ -224,7 +209,6 @@ export namespace CognitiveServices {
unit: string;
strokes: AzureStrokeData[];
}
-
}
export namespace BingSearch {
@@ -234,7 +218,7 @@ export namespace CognitiveServices {
},
requester: async (apiKey: string, query: string) => {
const xhttp = new XMLHttpRequest();
- const serverAddress = "https://api.cognitive.microsoft.com";
+ const serverAddress = 'https://api.cognitive.microsoft.com';
const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
const promisified = (resolve: any, reject: any) => {
xhttp.onreadystatechange = function () {
@@ -251,29 +235,26 @@ export namespace CognitiveServices {
};
if (apiKey) {
- xhttp.open("GET", endpoint, true);
+ xhttp.open('GET', endpoint, true);
xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
xhttp.setRequestHeader('Content-Type', 'application/json');
xhttp.send();
- }
- else {
- console.log("API key for BING unavailable");
+ } else {
+ console.log('API key for BING unavailable');
}
};
return new Promise<any>(promisified);
- }
-
+ },
};
export namespace Appliers {
export const analyzer = async (query: string, converter: BingConverter) => {
const results = await ExecuteQuery(Service.Bing, Manager, query);
- console.log("Bing results: ", results);
+ console.log('Bing results: ', results);
const { title_vals, url_vals } = await converter(results);
return { title_vals, url_vals };
};
}
-
}
export namespace HathiTrust {
@@ -283,7 +264,7 @@ export namespace CognitiveServices {
},
requester: async (apiKey: string, query: string) => {
const xhttp = new XMLHttpRequest();
- const serverAddress = "https://babel.hathitrust.org/cgi/htd/​";
+ const serverAddress = 'https://babel.hathitrust.org/cgi/htd/​';
const endpoint = serverAddress + '/bing/v5.0/search?q=' + encodeURIComponent(query);
const promisified = (resolve: any, reject: any) => {
xhttp.onreadystatechange = function () {
@@ -300,54 +281,52 @@ export namespace CognitiveServices {
};
if (apiKey) {
- xhttp.open("GET", endpoint, true);
+ xhttp.open('GET', endpoint, true);
xhttp.setRequestHeader('Ocp-Apim-Subscription-Key', apiKey);
xhttp.setRequestHeader('Content-Type', 'application/json');
xhttp.send();
- }
- else {
- console.log("API key for BING unavailable");
+ } else {
+ console.log('API key for BING unavailable');
}
};
return new Promise<any>(promisified);
- }
-
+ },
};
export namespace Appliers {
export const analyzer = async (query: string, converter: BingConverter) => {
const results = await ExecuteQuery(Service.Bing, Manager, query);
- console.log("Bing results: ", results);
+ console.log('Bing results: ', results);
const { title_vals, url_vals } = await converter(results);
return { title_vals, url_vals };
};
}
-
}
-
export namespace Text {
export const Manager: APIManager<string> = {
converter: (data: string) => {
return JSON.stringify({
- documents: [{
- id: 1,
- language: "en",
- text: data
- }]
+ documents: [
+ {
+ id: 1,
+ language: 'en',
+ text: data,
+ },
+ ],
});
},
requester: async (apiKey: string, body: string, service: Service) => {
- const serverAddress = "https://eastus.api.cognitive.microsoft.com";
- const endpoint = serverAddress + "/text/analytics/v2.1/keyPhrases";
+ const serverAddress = 'https://eastus.api.cognitive.microsoft.com';
+ const endpoint = serverAddress + '/text/analytics/v2.1/keyPhrases';
const sampleBody = {
- "documents": [
+ documents: [
{
- "language": "en",
- "id": 1,
- "text": "Hello world. This is some input text that I love."
- }
- ]
+ language: 'en',
+ id: 1,
+ text: 'Hello world. This is some input text that I love.',
+ },
+ ],
};
const actualBody = body;
const options = {
@@ -355,25 +334,23 @@ export namespace CognitiveServices {
body: actualBody,
headers: {
'Content-Type': 'application/json',
- 'Ocp-Apim-Subscription-Key': apiKey
- }
-
+ 'Ocp-Apim-Subscription-Key': apiKey,
+ },
};
return request.post(options);
- }
+ },
};
export namespace Appliers {
-
export async function vectorize(keyterms: any, dataDoc: Doc, mainDoc: boolean = false) {
- console.log("vectorizing...");
+ console.log('vectorizing...');
//keyterms = ["father", "king"];
- const args = { method: 'POST', uri: Utils.prepend("/recommender"), body: { keyphrases: keyterms }, json: true };
- await requestPromise.post(args).then(async (wordvecs) => {
+ const args = { method: 'POST', uri: Utils.prepend('/recommender'), body: { keyphrases: keyterms }, json: true };
+ await requestPromise.post(args).then(async wordvecs => {
if (wordvecs) {
const indices = Object.keys(wordvecs);
- console.log("successful vectorization!");
+ console.log('successful vectorization!');
const vectorValues = new List<number>();
indices.forEach((ind: any) => {
vectorValues.push(wordvecs[ind]);
@@ -381,15 +358,14 @@ export namespace CognitiveServices {
//ClientRecommender.Instance.processVector(vectorValues, dataDoc, mainDoc);
} // adds document to internal doc set
else {
- console.log("unsuccessful :( word(s) not in vocabulary");
+ console.log('unsuccessful :( word(s) not in vocabulary');
}
- }
- );
+ });
}
export const analyzer = async (dataDoc: Doc, target: Doc, keys: string[], data: string, converter: TextConverter, isMainDoc: boolean = false, isInternal: boolean = true) => {
const results = await ExecuteQuery(Service.Text, Manager, data);
- console.log("Cognitive Services keyphrases: ", results);
+ console.log('Cognitive Services keyphrases: ', results);
const { keyterms, external_recommendations, kp_string } = await converter(results, data);
target[keys[0]] = keyterms;
if (isInternal) {
@@ -400,10 +376,7 @@ export namespace CognitiveServices {
}
};
- // export async function countFrequencies()
+ // export async function countFrequencies()
}
-
}
-
-
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index c0530ab81..3c07f757e 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -484,8 +484,6 @@ ul.lm_tabs::before {
.collectiondockingview-container {
width: 100%;
height: 100%;
- border-style: solid;
- border-width: $COLLECTION_BORDER_WIDTH;
position: absolute;
top: 0;
left: 0;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index f722682c9..22f0f8a1f 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -40,8 +40,6 @@ import { CollectionDockingView } from './CollectionDockingView';
import { CollectionFreeFormView } from './collectionFreeForm';
import { CollectionLinearView } from './collectionLinear';
import './CollectionMenu.scss';
-import { COLLECTION_BORDER_WIDTH } from './CollectionView';
-import { TabDocView } from './TabDocView';
interface CollectionMenuProps {
panelHeight: () => number;
@@ -1242,7 +1240,7 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewMe
@undoBatch
togglePreview = () => {
const dividerWidth = 4;
- const borderWidth = Number(COLLECTION_BORDER_WIDTH);
+ const borderWidth = 0;
const panelWidth = this.props.docView.props.PanelWidth();
const previewWidth = NumCast(this.document.schema_previewWidth);
const tableWidth = panelWidth - 2 * borderWidth - dividerWidth - previewWidth;
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 2bf649caf..21efeba44 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -4,7 +4,6 @@
transform-origin: top left;
}
.collectionTreeView-dropTarget {
- border-width: $COLLECTION_BORDER_WIDTH;
border-color: transparent;
border-style: solid;
border-radius: inherit;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index f10d33f03..694f70903 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -33,7 +33,6 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from './CollectionTreeView';
import './CollectionView.scss';
-export const COLLECTION_BORDER_WIDTH = 2;
const path = require('path');
interface CollectionViewProps_ extends FieldViewProps {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
new file mode 100644
index 000000000..00505dbe3
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormBackgroundGrid.tsx
@@ -0,0 +1,75 @@
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { NumCast } from '../../../../fields/Types';
+import './CollectionFreeFormView.scss';
+import React = require('react');
+
+export interface CollectionFreeFormViewBackgroundGridProps {
+ panX: () => number;
+ panY: () => number;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ color: () => string;
+ isAnnotationOverlay?: boolean;
+ nativeDimScaling: () => number;
+ zoomScaling: () => number;
+ layoutDoc: Doc;
+ cachedCenteringShiftX: number;
+ cachedCenteringShiftY: number;
+}
+@observer
+export class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
+ chooseGridSpace = (gridSpace: number): number => {
+ if (!this.props.zoomScaling()) return gridSpace;
+ const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace;
+ return divisions < 90 ? gridSpace : this.chooseGridSpace(gridSpace * 2);
+ };
+ render() {
+ const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50));
+ const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
+ const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
+ const renderGridSpace = gridSpace * this.props.zoomScaling();
+ const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+ const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+ const strokeStyle = this.props.color();
+ return !this.props.nativeDimScaling() ? null : (
+ <canvas
+ className="collectionFreeFormView-grid"
+ width={w}
+ height={h}
+ style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
+ ref={el => {
+ const ctx = el?.getContext('2d');
+ if (ctx) {
+ const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
+ const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
+ ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
+ ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
+ ctx.clearRect(0, 0, w, h);
+ if (ctx) {
+ ctx.strokeStyle = strokeStyle;
+ ctx.fillStyle = strokeStyle;
+ ctx.beginPath();
+ if (this.props.zoomScaling() > 1) {
+ for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
+ ctx.moveTo(x, Cy - h);
+ ctx.lineTo(x, Cy + h);
+ }
+ for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+ ctx.moveTo(Cx - w, y);
+ ctx.lineTo(Cx + w, y);
+ }
+ } else {
+ for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace)
+ for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+ ctx.fillRect(Math.round(x), Math.round(y), 1, 1);
+ }
+ }
+ ctx.stroke();
+ }
+ }
+ }}
+ />
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 4484f664f..403fba67b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -155,7 +155,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
x: 0,
y: 0,
zIndex: 0,
- width: 0, // should make doc hidden in CollectionFreefromDocumentView
+ width: 0, // should make doc hidden in CollectionFreeFormDocumentView
height: 0,
pair,
replica: '',
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
new file mode 100644
index 000000000..856e195a3
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx
@@ -0,0 +1,60 @@
+import { computed } from 'mobx';
+import { observer } from 'mobx-react';
+import { Doc } from '../../../../fields/Doc';
+import { ScriptField } from '../../../../fields/ScriptField';
+import { PresBox } from '../../nodes/trails/PresBox';
+import './CollectionFreeFormView.scss';
+import React = require('react');
+import { CollectionFreeFormView } from './CollectionFreeFormView';
+
+export interface CollectionFreeFormPannableContentsProps {
+ rootDoc: Doc;
+ viewDefDivClick?: ScriptField;
+ children?: React.ReactNode | undefined;
+ transition?: string;
+ isAnnotationOverlay: boolean | undefined;
+ transform: () => string;
+ brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
+}
+
+@observer
+export class CollectionFreeFormPannableContents extends React.Component<CollectionFreeFormPannableContentsProps> {
+ @computed get presPaths() {
+ return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null;
+ }
+ // rectangle highlight used when following trail/link to a region of a collection that isn't a document
+ showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
+ !viewport ? null : (
+ <div
+ className="collectionFreeFormView-brushView"
+ style={{
+ transform: `translate(${viewport.panX}px, ${viewport.panY}px)`,
+ width: viewport.width,
+ height: viewport.height,
+ border: `orange solid ${viewport.width * 0.005}px`,
+ }}
+ />
+ );
+
+ render() {
+ return (
+ <div
+ className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
+ onScroll={e => {
+ const target = e.target as any;
+ if (getComputedStyle(target)?.overflow === 'visible') {
+ target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars
+ }
+ }}
+ style={{
+ transform: this.props.transform(),
+ transition: this.props.transition,
+ width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
+ }}>
+ {this.props.children}
+ {this.presPaths}
+ {this.showViewport(this.props.brushedView())}
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 27c3eaa93..e46a7bed7 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -9,10 +9,9 @@ import { DocData, Height, Width } from '../../../../fields/DocSymbols';
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
@@ -31,7 +30,6 @@ import { freeformScrollMode } from '../../../util/SettingsManager';
import { SnappingManager } from '../../../util/SnappingManager';
import { Transform } from '../../../util/Transform';
import { undoBatch, UndoManager } from '../../../util/UndoManager';
-import { COLLECTION_BORDER_WIDTH } from '../../../views/global/globalCssVariables.scss';
import { Timeline } from '../../animationtimeline/Timeline';
import { ContextMenu } from '../../ContextMenu';
import { GestureOverlay } from '../../GestureOverlay';
@@ -48,7 +46,9 @@ import { StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
import { TreeViewType } from '../CollectionTreeView';
import { TabDocView } from '../TabDocView';
+import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid';
import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines';
+import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents';
import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors';
import './CollectionFreeFormView.scss';
import { MarqueeView } from './MarqueeView';
@@ -82,9 +82,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _downX: number = 0;
private _downY: number = 0;
private _downTime = 0;
- private _inkToTextStartX: number | undefined;
- private _inkToTextStartY: number | undefined;
- private _wordPalette: Map<string, string> = new Map<string, string>();
private _clusterDistance: number = 75;
private _hitCluster: number = -1;
private _disposers: { [name: string]: IReactionDisposer } = {};
@@ -111,9 +108,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private get autoResetFieldKey() {
return (this.props.viewField ?? '') + '_freeform_autoReset';
}
- private get borderWidth() {
- return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
- }
@observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables
@observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0
@@ -123,8 +117,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable _deleteList: DocumentView[] = [];
@observable _timelineRef = React.createRef<Timeline>();
@observable _marqueeViewRef = React.createRef<MarqueeView>();
- @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined; // highlighted region of freeform canvas used by presentations to indicate a region
+ @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged.
@computed get views() {
const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele);
@@ -171,19 +165,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.layout_fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
- @computed get cachedGetLocalTransform(): Transform {
- return Transform.Identity()
- .scale(1 / this.zoomScaling())
- .translate(this.panX(), this.panY());
- }
- @computed get cachedGetContainerTransform(): Transform {
- return this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
+ @computed get panZoomXf() {
+ return new Transform(this.panX(), this.panY(), 1 / this.zoomScaling());
}
- @computed get cachedGetTransform(): Transform {
- return this.getContainerTransform()
+ @computed get screenToLocalXf() {
+ return this.props
+ .ScreenToLocalTransform()
.scale(this.props.isAnnotationOverlay ? 1 : 1 / this.nativeDim())
.translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY)
- .transform(this.cachedGetLocalTransform);
+ .transform(this.panZoomXf);
}
public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
@@ -248,11 +238,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1));
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1));
zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1));
- contentTransform = () =>
+ PanZoomCenterXf = () =>
this.props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
- getTransform = () => this.cachedGetTransform.copy();
- getLocalTransform = () => this.cachedGetLocalTransform.copy();
- getContainerTransform = () => this.cachedGetContainerTransform.copy();
+ ScreenToLocalXf = () => this.screenToLocalXf.copy();
getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout);
isAnyChildContentActive = () => this.props.isAnyChildContentActive();
addLiveTextBox = (newBox: Doc) => {
@@ -261,7 +249,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true));
};
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
@@ -332,19 +320,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
};
- getView = async (doc: Doc): Promise<Opt<DocumentView>> => {
- return new Promise<Opt<DocumentView>>(res => {
+ getView = async (doc: Doc): Promise<Opt<DocumentView>> =>
+ new Promise<Opt<DocumentView>>(res => {
if (doc.hidden && this._lightboxDoc !== doc) doc.hidden = false;
const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv));
findDoc(dv => res(dv));
});
- };
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number, yp: number) {
if (!super.onInternalDrop(e, de)) return false;
const refDoc = docDragData.droppedDocuments[0];
- const [xpo, ypo] = this.getContainerTransform().transformPoint(de.x, de.y);
+ const [xpo, ypo] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
const z = NumCast(refDoc.z);
const x = (z ? xpo : xp) - docDragData.offset[0];
const y = (z ? ypo : yp) - docDragData.offset[1];
@@ -452,14 +439,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
- const [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ const [xp, yp] = this.screenToLocalXf.transformPoint(de.x, de.y);
if (de.complete.annoDragData?.dragDocument && super.onInternalDrop(e, de)) return this.internalAnchorAnnoDrop(e, de.complete.annoDragData, xp, yp);
else if (de.complete.linkDragData) return this.internalLinkDrop(e, de, de.complete.linkDragData, xp, yp);
else if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, xp, yp);
return false;
};
- onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.getTransform().transformPoint(e.pageX, e.pageY));
+ onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToLocalXf.transformPoint(e.pageX, e.pageY));
static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) {
const doc2Layout = Doc.Layout(doc2);
@@ -500,7 +487,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'embed' : undefined);
de.moveDocument = this.props.moveDocument;
- de.offset = this.getTransform().transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
+ de.offset = this.screenToLocalXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top);
DragManager.StartDocumentDrag(
clusterDocs.map(v => v.ContentDiv!),
de,
@@ -597,7 +584,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
- getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
+ clusterStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => {
let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
switch (property) {
case StyleProp.BackgroundColor:
@@ -658,16 +645,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
) {
// prettier-ignore
switch (Doc.ActiveTool) {
- case InkTool.Highlighter: break;
+ case InkTool.Highlighter: break;
case InkTool.Write: break;
- case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
+ case InkTool.Pen: break; // the GestureOverlay handles ink stroke input -- either as gestures, or drying as ink strokes that are added to document views
case InkTool.Eraser:
this._batch = UndoManager.StartBatch('collectionErase');
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
break;
case InkTool.None:
if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
- this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
+ this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(e.clientX, e.clientY));
setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
}
break;
@@ -681,7 +668,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
// const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
const pt = me.changedTouches[0];
if (pt) {
- this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY));
+ this._hitCluster = this.pickCluster(this.screenToLocalXf.transformPoint(pt.clientX, pt.clientY));
if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) {
this.removeMoveListeners();
this.addMoveListeners();
@@ -711,8 +698,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case GestureUtils.Gestures.Triangle:
case GestureUtils.Gestures.Stroke:
const points = ge.points;
- const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
- console.log(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
+ const B = this.screenToLocalXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = Docs.Create.InkDocument(
ActiveInkColor(),
ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
@@ -739,69 +725,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
break;
case GestureUtils.Gestures.Rectangle:
- 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 => DocCast(s.proto)?.type === DocumentType.RTF && s.color);
- const sets = setDocs.map(sd => Cast(sd.text, 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) => st.split(',').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 + NumCast(doc._width);
- const t = NumCast(doc.y);
- const b = t + NumCast(doc._height);
- 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 })));
- }
+ const strokes = this.getActiveDocuments()
+ .filter(doc => doc.type === DocumentType.INK)
+ .map(i => {
+ const d = Cast(i.stroke, InkField);
+ const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0]));
+ const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0]));
+ return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y }));
});
- CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(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];
- }
+ CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {});
break;
case GestureUtils.Gestures.Text:
if (ge.text) {
- const B = this.getTransform().transformPoint(ge.points[0].X, ge.points[0].Y);
+ const B = this.screenToLocalXf.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();
}
@@ -824,7 +761,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.preventDefault();
} else if (this.isContentActive() && e.shiftKey) {
// reset zoom of freeform view to 1-to-1 on a shift + double click
- this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
+ this.zoomSmoothlyAboutPt(this.screenToLocalXf.transformPoint(e.clientX, e.clientY), 1);
e.stopPropagation();
e.preventDefault();
}
@@ -843,7 +780,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const shiftKey = e.shiftKey && !e.ctrlKey;
PresBox.Instance?.pauseAutoPres();
this.props.DocumentView?.().clearViewTransition();
- const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ const [dx, dy] = this.screenToLocalXf.transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
this.setPan(NumCast(this.Document[this.panXFieldKey]) - (ctrlKey ? 0 : dx), NumCast(this.Document[this.panYFieldKey]) - (shiftKey ? 0 : dy), 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
@@ -900,7 +837,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return true;
}
// pan the view if this is a regular collection, or it's an overlay and the overlay is zoomed (otherwise, there's nothing to pan)
- if (!this.props.isAnnotationOverlay || 1 - NumCast(this.rootDoc._freeform_scale_min, 1) / this.getLocalTransform().inverse().Scale) {
+ if (!this.props.isAnnotationOverlay || 1 - NumCast(this.rootDoc._freeform_scale_min, 1) / this.zoomScaling()) {
this.pan(e);
e.stopPropagation(); // if we are actually panning, stop propagation -- this will preven things like the overlayView from dragging the document while we're panning
}
@@ -1051,8 +988,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (this.Document._isGroup || this.Document[(this.props.viewField ?? '_') + 'freeform_noZoom']) return;
let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05;
if (deltaScale < 0) deltaScale = -deltaScale;
- const [x, y] = this.getTransform().transformPoint(pointX, pointY);
- const invTransform = this.getLocalTransform().inverse();
+ const [x, y] = this.screenToLocalXf.transformPoint(pointX, pointY);
+ const invTransform = this.panZoomXf.inverse();
if (deltaScale * invTransform.Scale > 20) {
deltaScale = 20 / invTransform.Scale;
}
@@ -1093,7 +1030,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (((!e.metaKey && !e.altKey) || Doc.UserDoc().freeformScrollMode === freeformScrollMode.Zoom) && this.props.isContentActive(true)) {
const deltaX = e.shiftKey ? e.deltaX : e.ctrlKey ? 0 : e.deltaX;
const deltaY = e.shiftKey ? 0 : e.ctrlKey ? e.deltaY : e.deltaY;
- this.scrollPan({ deltaX: -deltaX * this.getTransform().Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.getTransform().Scale });
+ this.scrollPan({ deltaX: -deltaX * this.screenToLocalXf.Scale, deltaY: e.shiftKey ? 0 : -deltaY * this.screenToLocalXf.Scale });
break;
}
default:
@@ -1146,7 +1083,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) {
this.setPanZoomTransition(panTime);
const minScale = NumCast(this.rootDoc._freeform_scale_min, 1);
- const scale = 1 - minScale / this.getLocalTransform().inverse().Scale;
+ const scale = 1 - minScale / this.zoomScaling();
const minPanX = NumCast(this.rootDoc._freeform_panX_min, 0);
const minPanY = NumCast(this.rootDoc._freeform_panY_min, 0);
const maxPanX = NumCast(this.rootDoc._freeform_panX_max, this.nativeWidth);
@@ -1227,11 +1164,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
if (this.Document._isGroup) return;
this.setPanZoomTransition(transitionTime);
- const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+ const screenXY = this.screenToLocalXf.inverse().transformPoint(docpt[0], docpt[1]);
this.layoutDoc[this.scaleFieldKey] = scale;
- const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
+ const newScreenXY = this.screenToLocalXf.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);
+ const newpan = this.screenToLocalXf.transformDirection(scrDelta.x, scrDelta.y);
this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
}
@@ -1340,7 +1277,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onKey={this.onKeyDown}
onDoubleClick={this.onChildDoubleClickHandler}
onBrowseClick={this.onBrowseClickHandler}
- ScreenToLocalTransform={childLayout.z ? this.getContainerTransform : this.getTransform}
+ ScreenToLocalTransform={childLayout.z ? this.props.ScreenToLocalTransform : this.ScreenToLocalXf}
PanelWidth={childLayout[Width]}
PanelHeight={childLayout[Height]}
childFilters={this.childDocFilters}
@@ -1356,7 +1293,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
pinToPres={this.props.pinToPres}
whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged}
docViewPath={this.props.docViewPath}
- styleProvider={this.getClusterColor}
+ styleProvider={this.clusterStyleProvider}
dragAction={(this.rootDoc.childDragAction ?? this.props.childDragAction) as dropActionType}
dataProvider={this.childDataProvider}
sizeProvider={this.childSizeProvider}
@@ -1378,7 +1315,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return (
(this.addDocument?.(
(doc instanceof Doc ? [doc] : doc).map(doc => {
- const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ const pt = this.screenToLocalXf.transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = pt[0];
doc.y = pt[1];
return doc;
@@ -1424,7 +1361,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
z: Cast(z, 'number'),
rotation,
color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(childDoc, this.props, StyleProp.BackgroundColor),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this.props, StyleProp.BackgroundColor),
opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
zIndex: Cast(zIndex, 'number'),
width: _width,
@@ -1531,11 +1468,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@observable _numLoaded = 1;
- _lastPoolSize = 0;
@action
doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => {
const array = Array.from(newPool.entries());
- this._lastPoolSize = array.length;
for (const entry of array) {
const lastPos = this._cachedPool.get(entry[0]); // last computed pos
const newPos = entry[1];
@@ -1753,7 +1688,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
promoteCollection = () => {
const childDocs = this.childDocs.slice();
childDocs.forEach(doc => {
- const scr = this.getTransform().inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
+ const scr = this.screenToLocalXf.inverse().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = scr?.[0];
doc.y = scr?.[1];
});
@@ -1876,7 +1811,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.dragStarting(snapToDraggedDoc, false, visited);
}
const activeDocs = this.getActiveDocuments();
- const size = this.getTransform().transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
+ const size = this.screenToLocalXf.transformDirection(this.props.PanelWidth(), this.props.PanelHeight());
const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] };
const docDims = (doc: Doc) => ({ left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) });
const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect);
@@ -1892,7 +1827,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const horizLines: number[] = [];
const vertLines: number[] = [];
- const invXf = this.getTransform().inverse();
+ const invXf = this.screenToLocalXf.inverse();
snappableDocs
.filter(doc => !doc._isGroup && (snapToDraggedDoc || (SnappingManager.GetIsResizing() !== doc && !DragManager.docsBeingDragged.includes(doc))))
.forEach(doc => {
@@ -1927,7 +1862,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@computed get placeholder() {
return (
- <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}>
+ <div className="collectionfreeformview-placeholder" style={{ background: this.props.styleProvider?.(this.Document, this.props, StyleProp.BackgroundColor) }}>
<span className="collectionfreeformview-placeholderSpan">{this.props.Document.annotationOn ? '' : this.props.Document.title?.toString()}</span>
</div>
);
@@ -1959,15 +1894,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
@computed get pannableContents() {
return (
- <CollectionFreeFormViewPannableContents
+ <CollectionFreeFormPannableContents
rootDoc={this.rootDoc}
brushedView={this.brushedView}
isAnnotationOverlay={this.isAnnotationOverlay}
- transform={this.contentTransform}
+ transform={this.PanZoomCenterXf}
transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.props.DocumentView?.()?.rootDoc._viewTransition, 'string', null))}
viewDefDivClick={this.props.viewDefDivClick}>
{this.children}
- </CollectionFreeFormViewPannableContents>
+ </CollectionFreeFormPannableContents>
);
}
@computed get marqueeView() {
@@ -1985,8 +1920,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
selectDocuments={this.selectDocuments}
addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform}
- getTransform={this.getTransform}
+ getContainerTransform={this.props.ScreenToLocalTransform}
+ getTransform={this.ScreenToLocalXf}
panXFieldKey={this.panXFieldKey}
panYFieldKey={this.panYFieldKey}
isAnnotationOverlay={this.isAnnotationOverlay}>
@@ -2109,128 +2044,6 @@ class CollectionFreeFormOverlayView extends React.Component<CollectionFreeFormOv
}
}
-interface CollectionFreeFormViewPannableContentsProps {
- rootDoc: Doc;
- viewDefDivClick?: ScriptField;
- children?: React.ReactNode | undefined;
- transition?: string;
- isAnnotationOverlay: boolean | undefined;
- transform: () => string;
- brushedView: () => { panX: number; panY: number; width: number; height: number } | undefined;
-}
-
-@observer
-class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps> {
- @computed get presPaths() {
- return CollectionFreeFormView.ShowPresPaths ? PresBox.Instance.pathLines(this.props.rootDoc) : null;
- }
- // rectangle highlight used when following trail/link to a region of a collection that isn't a document
- showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) =>
- !viewport ? null : (
- <div
- className="collectionFreeFormView-brushView"
- style={{
- transform: `translate(${viewport.panX}px, ${viewport.panY}px)`,
- width: viewport.width,
- height: viewport.height,
- border: `orange solid ${viewport.width * 0.005}px`,
- }}
- />
- );
-
- render() {
- return (
- <div
- className={'collectionfreeformview' + (this.props.viewDefDivClick ? '-viewDef' : '-none')}
- onScroll={e => {
- const target = e.target as any;
- if (getComputedStyle(target)?.overflow === 'visible') {
- target.scrollTop = target.scrollLeft = 0; // if collection is visible, scrolling messes things up since there are no scroll bars
- }
- }}
- style={{
- transform: this.props.transform(),
- transition: this.props.transition,
- width: this.props.isAnnotationOverlay ? undefined : 0, // if not an overlay, then this will be the size of the collection, but panning and zooming will move it outside the visible border of the collection and make it selectable. This problem shows up after zooming/panning on a background collection -- you can drag the collection by clicking on apparently empty space outside the collection
- }}>
- {this.props.children}
- {this.presPaths}
- {this.showViewport(this.props.brushedView())}
- </div>
- );
- }
-}
-
-interface CollectionFreeFormViewBackgroundGridProps {
- panX: () => number;
- panY: () => number;
- PanelWidth: () => number;
- PanelHeight: () => number;
- color: () => string;
- isAnnotationOverlay?: boolean;
- nativeDimScaling: () => number;
- zoomScaling: () => number;
- layoutDoc: Doc;
- cachedCenteringShiftX: number;
- cachedCenteringShiftY: number;
-}
-@observer
-class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
- chooseGridSpace = (gridSpace: number): number => {
- if (!this.props.zoomScaling()) return gridSpace;
- const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace;
- return divisions < 90 ? gridSpace : this.chooseGridSpace(gridSpace * 2);
- };
- render() {
- const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc['_backgroundGrid-spacing'], 50));
- const shiftX = (this.props.isAnnotationOverlay ? 0 : (-this.props.panX() % gridSpace) - gridSpace) * this.props.zoomScaling();
- const shiftY = (this.props.isAnnotationOverlay ? 0 : (-this.props.panY() % gridSpace) - gridSpace) * this.props.zoomScaling();
- const renderGridSpace = gridSpace * this.props.zoomScaling();
- const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
- const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
- const strokeStyle = this.props.color();
- return !this.props.nativeDimScaling() ? null : (
- <canvas
- className="collectionFreeFormView-grid"
- width={w}
- height={h}
- style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
- ref={el => {
- const ctx = el?.getContext('2d');
- if (ctx) {
- const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
- const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
- ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
- ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
- ctx.clearRect(0, 0, w, h);
- if (ctx) {
- ctx.strokeStyle = strokeStyle;
- ctx.fillStyle = strokeStyle;
- ctx.beginPath();
- if (this.props.zoomScaling() > 1) {
- for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
- ctx.moveTo(x, Cy - h);
- ctx.lineTo(x, Cy + h);
- }
- for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
- ctx.moveTo(Cx - w, y);
- ctx.lineTo(Cx + w, y);
- }
- } else {
- for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace)
- for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
- ctx.fillRect(Math.round(x), Math.round(y), 1, 1);
- }
- }
- ctx.stroke();
- }
- }
- }}
- />
- );
- }
-}
-
export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
const browseTransitionTime = 500;
SelectionManager.DeselectAll();
@@ -2245,7 +2058,7 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY
}
while (parFfview?.rootDoc._isGroup) parFfview = parFfview.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
const ffview = selfFfview && selfFfview.rootDoc[selfFfview.scaleFieldKey] !== 0.5 ? selfFfview : parFfview; // if focus doc is a freeform that is not at it's default 0.5 scale, then zoom out on it. Otherwise, zoom out on the parent ffview
- ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
+ ffview?.zoomSmoothlyAboutPt(ffview.screenToLocalXf.transformPoint(clientX, clientY), ffview?.isAnnotationOverlay ? 1 : 0.5, browseTransitionTime);
Doc.linkFollowHighlight(dv?.props.Document, false);
}
});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 5614c3d7b..f831478a7 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -78,15 +78,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const bounds: MarqueeViewBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
return bounds;
}
- get inkDoc() {
- return this.props.Document;
- }
- get ink() {
- return Cast(this.props.Document.ink, InkField);
- }
- set ink(value: Opt<InkField>) {
- this.props.Document.ink = value;
- }
componentDidMount() {
this.props.setPreviewCursor?.(this.setPreviewCursor);
@@ -267,8 +258,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (!e.shiftKey) {
SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
}
- // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
- // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
const docs = mselect.length ? mselect : [this.props.Document];
this.props.selectDocuments(docs);
}
diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss
index c129d29eb..44e8efe23 100644
--- a/src/client/views/global/globalCssVariables.scss
+++ b/src/client/views/global/globalCssVariables.scss
@@ -63,7 +63,6 @@ $standard-box-shadow: 0px 3px 4px rgba(0, 0, 0, 0.3);
$mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc
$docDecorations-zindex: 998; // then doc decorations appear over everything else
$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
-$COLLECTION_BORDER_WIDTH: 0;
$SCHEMA_DIVIDER_WIDTH: 4;
$MINIMIZED_ICON_SIZE: 24;
$MAX_ROW_HEIGHT: 44px;
@@ -80,7 +79,6 @@ $DATA_VIZ_TABLE_ROW_HEIGHT: 30;
:export {
contextMenuZindex: $contextMenu-zindex;
SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH;
- COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
SEARCH_THUMBNAIL_SIZE: $search-thumnail-size;
diff --git a/src/client/views/global/globalCssVariables.scss.d.ts b/src/client/views/global/globalCssVariables.scss.d.ts
index 3db498e77..bcbb1f068 100644
--- a/src/client/views/global/globalCssVariables.scss.d.ts
+++ b/src/client/views/global/globalCssVariables.scss.d.ts
@@ -1,7 +1,6 @@
interface IGlobalScss {
contextMenuZindex: string; // context menu shows up over everything
SCHEMA_DIVIDER_WIDTH: string;
- COLLECTION_BORDER_WIDTH: string;
MINIMIZED_ICON_SIZE: string;
MAX_ROW_HEIGHT: string;
SEARCH_THUMBNAIL_SIZE: string;
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index a4dd5e7ef..034eb4011 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -62,7 +62,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() {
const b = (this.anchor2 ?? this.anchor1)!;
const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView;
- const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform();
+ const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this.props.ScreenToLocalTransform();
const a_invXf = a.props.ScreenToLocalTransform().inverse();
const b_invXf = b.props.ScreenToLocalTransform().inverse();
const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) };