aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/DocumentDecorations.tsx35
-rw-r--r--src/client/views/InkControlPtHandles.tsx16
-rw-r--r--src/client/views/InkStrokeProperties.ts80
-rw-r--r--src/client/views/InkTangentHandles.tsx6
-rw-r--r--src/client/views/InkingStroke.tsx12
-rw-r--r--src/client/views/LightboxView.tsx4
-rw-r--r--src/client/views/MainView.scss17
-rw-r--r--src/client/views/MainView.tsx1
-rw-r--r--src/client/views/collections/CollectionDockingView.scss23
-rw-r--r--src/client/views/collections/CollectionSubView.tsx5
-rw-r--r--src/client/views/collections/TreeView.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx17
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx6
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx87
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx6
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx18
-rw-r--r--src/client/views/collections/collectionSchema/CollectionSchemaView.scss7
-rw-r--r--src/client/views/linking/LinkEditor.tsx28
-rw-r--r--src/client/views/linking/LinkMenuGroup.tsx1
-rw-r--r--src/client/views/linking/LinkMenuItem.tsx18
-rw-r--r--src/client/views/nodes/ComparisonBox.tsx14
-rw-r--r--src/client/views/nodes/DocumentView.tsx6
-rw-r--r--src/client/views/nodes/WebBox.tsx77
-rw-r--r--src/client/views/nodes/trails/PresBox.tsx39
24 files changed, 398 insertions, 137 deletions
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 207c7fcac..70dffd7a1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -195,15 +195,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
@action
onRotateDown = (e: React.PointerEvent): void => {
this._rotateUndo = UndoManager.StartBatch("rotatedown");
+ const pt = { x: (this.Bounds.x + this.Bounds.r) / 2, y: (this.Bounds.y + this.Bounds.b) / 2 };
setupMoveUpEvents(this, e,
(e: PointerEvent, down: number[], delta: number[]) => {
const docView = SelectionManager.Views()[0];
- const { left, top, right, bottom } = docView.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 };
+ const { left, top, right, bottom } = docView.getBounds() || { left: 0, top: 0, right: 0, bottom: 0 };
const centerPoint = { X: (left + right) / 2, Y: (top + bottom) / 2 };
const previousPoint = { X: e.clientX, Y: e.clientY };
const movedPoint = { X: e.clientX - delta[0], Y: e.clientY - delta[1] };
const angle = InkStrokeProperties.Instance?.angleChange(previousPoint, movedPoint, centerPoint);
- if (angle) InkStrokeProperties.Instance?.rotateInk(-angle);
+ const selectedInk = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+ angle && InkStrokeProperties.Instance?.rotateInk(selectedInk, -angle, pt);
return false;
},
() => {
@@ -322,8 +324,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
const doc = Document(docView.rootDoc);
const nwidth = docView.nativeWidth;
const nheight = docView.nativeHeight;
- const width = (doc._width || 0);
- let height = (doc._height || (nheight / nwidth * width));
+ const docheight = doc._height || 0;
+ const docwidth = doc._width || 0;
+ const width = docwidth;
+ let height = (docheight || (nheight / nwidth * width));
height = !height || isNaN(height) ? 20 : height;
const scale = docView.props.ScreenToLocalTransform().Scale;
const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable;
@@ -336,17 +340,18 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
else dW = dH * nwidth / nheight;
}
}
- const actualdW = Math.max(width + (dW * scale), 20);
- const actualdH = Math.max(height + (dH * scale), 20);
- doc.x = (doc.x || 0) + dX * (actualdW - width);
- doc.y = (doc.y || 0) + dY * (actualdH - height);
+ let actualdW = Math.max(width + (dW * scale), 20);
+ let actualdH = Math.max(height + (dH * scale), 20);
const fixedAspect = (nwidth && nheight);
if (fixedAspect) {
if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) {
if (dragRight && modifyNativeDim) {
doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc);
} else {
- if (!doc._fitWidth) doc._height = nheight / nwidth * actualdW;
+ if (!doc._fitWidth) {
+ actualdH = nheight / nwidth * actualdW;
+ doc._height = actualdH;
+ }
else if (!modifyNativeDim || dragBotRight) doc._height = actualdH;
}
doc._width = actualdW;
@@ -357,10 +362,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc);
doc._autoHeight = false;
} else {
- if (!doc._fitWidth) doc._width = nwidth / nheight * actualdH;
+ if (!doc._fitWidth) {
+ actualdW = nwidth / nheight * actualdH;
+ doc._width = actualdW;
+ }
else if (!modifyNativeDim || dragBotRight) doc._width = actualdW;
}
- if (!modifyNativeDim) doc._height = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+ if (!modifyNativeDim) {
+ actualdH = Math.min(nheight / nwidth * NumCast(doc._width), actualdH);
+ doc._height = actualdH;
+ }
else doc._height = actualdH;
}
} else {
@@ -368,6 +379,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P
dW && (doc._width = actualdW);
dH && (doc._autoHeight = false);
}
+ doc.x = (doc.x || 0) + dX * (actualdW - docwidth);
+ doc.y = (doc.y || 0) + dY * (actualdH - docheight);
doc._lastModified = new DateField();
}
const val = this._dragHeights.get(docView.layoutDoc);
diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx
index 0644488b3..73de4a3e0 100644
--- a/src/client/views/InkControlPtHandles.tsx
+++ b/src/client/views/InkControlPtHandles.tsx
@@ -3,18 +3,20 @@ import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../fields/Doc";
import { ControlPoint, InkData, PointData } from "../../fields/InkField";
+import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast } from "../../fields/Types";
import { setupMoveUpEvents } from "../../Utils";
import { Transform } from "../util/Transform";
import { UndoManager } from "../util/UndoManager";
import { Colors } from "./global/globalEnums";
-import { InkStrokeProperties } from "./InkStrokeProperties";
-import { List } from "../../fields/List";
import { InkingStroke } from "./InkingStroke";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { DocumentView } from "./nodes/DocumentView";
export interface InkControlProps {
inkDoc: Doc;
+ inkView: DocumentView;
inkCtrlPoints: InkData;
screenCtrlPoints: InkData;
screenSpaceLineWidth: number;
@@ -51,12 +53,12 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
setupMoveUpEvents(this, e,
action((e: PointerEvent, down: number[], delta: number[]) => {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("drag ink ctrl pt");
- InkStrokeProperties.Instance?.moveControlPtHandle(delta[0] * screenScale, delta[1] * screenScale, controlIndex);
+ InkStrokeProperties.Instance?.moveControlPtHandle(this.props.inkView, delta[0] * screenScale, delta[1] * screenScale, controlIndex);
return false;
}),
action(() => {
if (this.controlUndo) {
- InkStrokeProperties.Instance?.snapControl(this.props.inkDoc, controlIndex);
+ InkStrokeProperties.Instance?.snapControl(this.props.inkView, controlIndex);
}
this.controlUndo?.end();
this.controlUndo = undefined;
@@ -71,11 +73,11 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
} else {
if (brokenIndices?.includes(equivIndex)) {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
- InkStrokeProperties.Instance?.snapHandleTangent(equivIndex, handleIndexA, handleIndexB);
+ InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, equivIndex, handleIndexA, handleIndexB);
}
if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) {
if (!this.controlUndo) this.controlUndo = UndoManager.StartBatch("make smooth");
- InkStrokeProperties.Instance?.snapHandleTangent(controlIndex, handleIndexA, handleIndexB);
+ InkStrokeProperties.Instance?.snapHandleTangent(this.props.inkView, controlIndex, handleIndexA, handleIndexB);
}
}
this.controlUndo?.end();
@@ -98,7 +100,7 @@ export class InkControlPtHandles extends React.Component<InkControlProps> {
@action
onDelete = (e: KeyboardEvent) => {
if (["-", "Backspace", "Delete"].includes(e.key)) {
- InkStrokeProperties.Instance?.deletePoints();
+ InkStrokeProperties.Instance?.deletePoints(this.props.inkView);
e.stopPropagation();
}
}
diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts
index 712459125..156bb1a6a 100644
--- a/src/client/views/InkStrokeProperties.ts
+++ b/src/client/views/InkStrokeProperties.ts
@@ -1,17 +1,15 @@
import { Bezier } from "bezier-js";
-import { action, computed, observable, reaction } from "mobx";
-import { Doc } from "../../fields/Doc";
-import { DocumentView } from "./nodes/DocumentView";
-import { Document } from "../../fields/documentSchemas";
+import { action, observable, reaction } from "mobx";
+import { Doc, Opt } from "../../fields/Doc";
import { InkData, InkField, InkTool, PointData } from "../../fields/InkField";
import { List } from "../../fields/List";
import { listSpec } from "../../fields/Schema";
import { Cast, NumCast } from "../../fields/Types";
import { DocumentType } from "../documents/DocumentTypes";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
-import { SelectionManager } from "../util/SelectionManager";
import { undoBatch } from "../util/UndoManager";
import { InkingStroke } from "./InkingStroke";
+import { DocumentView } from "./nodes/DocumentView";
export class InkStrokeProperties {
static Instance: InkStrokeProperties | undefined;
@@ -26,21 +24,16 @@ export class InkStrokeProperties {
reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false));
}
- @computed get selectedInk() {
- const inks = SelectionManager.Views().filter(i => Document(i.rootDoc).type === DocumentType.INK);
- return inks.length ? inks : undefined;
- }
-
/**
* Helper function that enables other functions to be applied to a particular ink instance.
* @param func The inputted function.
* @param requireCurrPoint Indicates whether the current selected point is needed.
*/
- applyFunction = (func: (doc: Doc, ink: InkData, ptsXscale: number, ptsYscale: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
+ applyFunction = (strokes: Opt<DocumentView | DocumentView[]>, func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => {
var appliedFunc = false;
- this.selectedInk?.forEach(action(inkView => {
- if (this.selectedInk?.length === 1 && (!requireCurrPoint || this._currentPoint !== -1)) {
- const doc = Document(inkView.rootDoc);
+ (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(action(inkView => {
+ if (!requireCurrPoint || this._currentPoint !== -1) {
+ const doc = inkView.rootDoc;
if (doc.type === DocumentType.INK && doc.width && doc.height) {
const ink = Cast(doc.data, InkField)?.inkData;
if (ink) {
@@ -48,7 +41,7 @@ export class InkStrokeProperties {
const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1;
const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1;
- const newPoints = func(doc, ink, ptsXscale, ptsYscale);
+ const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth));
if (newPoints) {
const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X));
const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y));
@@ -74,8 +67,9 @@ export class InkStrokeProperties {
*/
@undoBatch
@action
- addPoints = (t: number, i: number, controls: { X: number, Y: number }[]) => {
- this.applyFunction((doc: Doc, ink: InkData) => {
+ addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number, Y: number }[]) => {
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+ const doc = view.rootDoc;
const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]];
const newsegs = new Bezier(array.map(p => ({ x: p.X, y: p.Y }))).split(t);
const splicepts = [...newsegs.left.points, ...newsegs.right.points];
@@ -144,9 +138,10 @@ export class InkStrokeProperties {
*/
@undoBatch
@action
- deletePoints = () => this.applyFunction((doc: Doc, ink: InkData) => {
+ deletePoints = (inkView: DocumentView) => this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+ const doc = view.rootDoc;
const newPoints: { X: number, Y: number }[] = [];
- const toRemove = Math.floor(((this._currentPoint + 2) / 4));
+ const toRemove = Math.floor((this._currentPoint + 2) / 4);
const last = this._currentPoint === ink.length - 1;
for (let i = 0; i < ink.length; i++) {
if (Math.floor((i + 2) / 4) !== toRemove && (toRemove !== 0 || i > 3)) {
@@ -165,17 +160,22 @@ export class InkStrokeProperties {
*/
@undoBatch
@action
- rotateInk = (angle: number) => {
- this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
- const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X));
- const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y));
- const centerPoint = { X: (oldXrange.min + oldXrange.max) / 2, Y: (oldYrange.min + oldYrange.max) / 2 };
+ rotateInk = (inkStrokes: DocumentView[], angle: number, scrpt: { x: number, y: number }) => {
+ this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => {
+ const oldXrangeMin = Math.min(...ink.map(p => p.X));
+ const oldYrangeMin = Math.min(...ink.map(p => p.Y));
+ const docViewCenterPt = view.screenToLocalTransform().transformPoint(scrpt.x, scrpt.y);
+ const inkCenterPt = {
+ X: (docViewCenterPt[0] - inkStrokeWidth / 2) / xScale + oldXrangeMin,
+ Y: (docViewCenterPt[1] - inkStrokeWidth / 2) / yScale + oldYrangeMin
+ };
const newPoints = ink.map(i => {
- const pt = { X: i.X - centerPoint.X, Y: i.Y - centerPoint.Y };
+ const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y };
const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale;
const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y;
- return { X: newX + centerPoint.X, Y: newY + centerPoint.Y };
+ return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y };
});
+ const doc = view.rootDoc;
doc.rotation = NumCast(doc.rotation) + angle;
return newPoints;
});
@@ -186,8 +186,8 @@ export class InkStrokeProperties {
*/
@undoBatch
@action
- moveControlPtHandle = (deltaX: number, deltaY: number, controlIndex: number) =>
- this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
+ moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number) =>
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => {
const order = controlIndex % 4;
const closed = InkingStroke.IsClosed(ink);
@@ -233,15 +233,19 @@ export class InkStrokeProperties {
return { distance, nearestT, nearestSeg, nearestPt };
}
- snapControl = (inkDoc: Doc, controlIndex: number) => {
+ /**
+ * Handles the movement/scaling of a control point.
+ */
+ snapControl = (inkView: DocumentView, controlIndex: number) => {
+ const inkDoc = inkView.rootDoc;
const ink = Cast(inkDoc.data, InkField)?.inkData;
if (ink) {
const { near, nearestPt, ptsXscale, ptsYscale } = this.snapWithinCurve(ink, inkDoc, controlIndex);
- if (near / (this.selectedInk?.lastElement().props.ScreenToLocalTransform().Scale || 1) < 10) {
+ if (near / (inkView.props.ScreenToLocalTransform().Scale || 1) < 10) {
const deltaX = (nearestPt.X - ink[controlIndex].X) * ptsXscale;
const deltaY = (nearestPt.Y - ink[controlIndex].Y) * ptsYscale;
- return this.moveControlPtHandle(deltaX, deltaY, controlIndex);
+ return this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex);
} else {
return this.snapBetweenCurves(ink, inkDoc, controlIndex);
}
@@ -265,14 +269,14 @@ export class InkStrokeProperties {
const { ptsXscale, ptsYscale } = this.inkToScreenScale(ink, inkDoc);
const near = Math.sqrt((nearestPt.X - refPt.X) * (nearestPt.X - refPt.X) * ptsXscale * ptsXscale +
(nearestPt.Y - refPt.Y) * (nearestPt.Y - refPt.Y) * ptsYscale * ptsYscale);
-
+
return { near, nearestPt, ptsXscale, ptsYscale };
}
snapBetweenCurves = (ink: InkData, inkDoc: Doc, controlIndex: number) => {
const inkContext = Cast(inkDoc.context, Doc, null);
// Cast(inkContext.data)
-
+
// .filter(doc => doc.type === DocumentType.INK)
// .map(doc => {
// if (InkStrokeProperties.Instance?._lock) {
@@ -297,8 +301,9 @@ export class InkStrokeProperties {
* @param handleIndexA The handle point that retains its current position.
* @param handleIndexB The handle point that is rotated to be 180 degrees from its opposite.
*/
- snapHandleTangent = (controlIndex: number, handleIndexA: number, handleIndexB: number) => {
- this.applyFunction((doc: Doc, ink: InkData) => {
+ snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => {
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData) => {
+ const doc = view.rootDoc;
const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []);
const ind = brokenIndices.findIndex(value => value === controlIndex);
if (ind !== -1) {
@@ -358,8 +363,9 @@ export class InkStrokeProperties {
*/
@undoBatch
@action
- moveTangentHandle = (deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
- this.applyFunction((doc: Doc, ink: InkData, xScale: number, yScale: number) => {
+ moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) =>
+ this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => {
+ const doc = view.rootDoc;
const closed = InkingStroke.IsClosed(ink);
const oldHandlePoint = ink[handleIndex];
const oppositeHandlePoint = ink[oppositeHandleIndex];
diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx
index df5bebf31..f88a20448 100644
--- a/src/client/views/InkTangentHandles.tsx
+++ b/src/client/views/InkTangentHandles.tsx
@@ -10,11 +10,13 @@ import { emptyFunction, setupMoveUpEvents } from "../../Utils";
import { Transform } from "../util/Transform";
import { UndoManager } from "../util/UndoManager";
import { Colors } from "./global/globalEnums";
-import { InkStrokeProperties } from "./InkStrokeProperties";
import { InkingStroke } from "./InkingStroke";
+import { InkStrokeProperties } from "./InkStrokeProperties";
+import { DocumentView } from "./nodes/DocumentView";
export interface InkHandlesProps {
inkDoc: Doc;
+ inkView: DocumentView;
screenCtrlPoints: InkData;
screenSpaceLineWidth: number;
ScreenToLocalTransform: () => Transform;
@@ -37,7 +39,7 @@ export class InkTangentHandles extends React.Component<InkHandlesProps> {
setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => {
if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent");
if (e.altKey) this.onBreakTangent(controlIndex);
- InkStrokeProperties.Instance?.moveTangentHandle(-delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
+ InkStrokeProperties.Instance?.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex);
return false;
}, () => {
controlUndo?.end();
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 67e797dcd..6ea3e108c 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,17 +1,17 @@
import React = require("react");
-import { Bezier } from "bezier-js";
import { action, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import { Doc } from "../../fields/Doc";
import { documentSchema } from "../../fields/documentSchemas";
import { InkData, InkField, InkTool } from "../../fields/InkField";
import { makeInterface } from "../../fields/Schema";
-import { Cast, NumCast, StrCast, BoolCast } from "../../fields/Types";
+import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types";
import { TraceMobx } from "../../fields/util";
import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils";
import { CognitiveServices } from "../cognitive_services/CognitiveServices";
import { CurrentUserUtils } from "../util/CurrentUserUtils";
import { InteractionUtils } from "../util/InteractionUtils";
+import { SnappingManager } from "../util/SnappingManager";
import { ContextMenu } from "./ContextMenu";
import { ViewBoxBaseComponent } from "./DocComponent";
import { Colors } from "./global/globalEnums";
@@ -20,7 +20,6 @@ import "./InkStroke.scss";
import { InkStrokeProperties } from "./InkStrokeProperties";
import { InkTangentHandles } from "./InkTangentHandles";
import { FieldView, FieldViewProps } from "./nodes/FieldView";
-import { SnappingManager } from "../util/SnappingManager";
import Color = require("color");
type InkDocument = makeInterface<[typeof documentSchema]>;
@@ -83,7 +82,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
InkStrokeProperties.Instance && (InkStrokeProperties.Instance._currentPoint = -1);
this._handledClick = true; // mark the double-click pseudo pointerevent so we can block the real mouse event from propagating to DocumentView
} else if (this._properties?._controlButton) {
- this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
+ this._nearestT && this._nearestSeg !== undefined && InkStrokeProperties.Instance?.addPoints(this.props.docViewPath().lastElement(), this._nearestT, this._nearestSeg, this.inkScaledData().inkData.slice());
}
}), this._properties?._controlButton, this._properties?._controlButton
);
@@ -166,6 +165,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier),
"none", startMarker, endMarker, StrCast(inkDoc.strokeDash), 1, 1, "", "none", 1.0, false)}
<InkControlPtHandles
+ inkView={this.props.docViewPath().lastElement()}
inkDoc={inkDoc}
inkCtrlPoints={inkData}
screenCtrlPoints={screenHdlPts}
@@ -173,6 +173,7 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
ScreenToLocalTransform={this.props.ScreenToLocalTransform} />
<InkTangentHandles
+ inkView={this.props.docViewPath().lastElement()}
inkDoc={inkDoc}
screenCtrlPoints={screenHdlPts}
screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth}
@@ -205,7 +206,8 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume
inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && (new Color(fillColor)).alpha() < 1 ? 6 : 15),
StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap),
StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" ? "none" : fillColor, startMarker, endMarker,
- undefined, inkScaleX, inkScaleY, "", this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted", 0.0, false);
+ undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0, false);
+ // Set of points rendered upon the ink that can be added if a user clicks on one.
return (
<svg className="inkStroke"
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx
index ec30a6a5d..ec3bf6c18 100644
--- a/src/client/views/LightboxView.tsx
+++ b/src/client/views/LightboxView.tsx
@@ -214,7 +214,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
LightboxView.SetLightboxDoc(undefined);
}
}} >
-
+
<div className="lightboxView-contents" style={{
left: this.leftBorder,
top: this.topBorder,
@@ -237,7 +237,7 @@ export class LightboxView extends React.Component<LightboxViewProps> {
DataDoc={undefined}
LayoutTemplate={LightboxView.LightboxDocTemplate}
addDocument={undefined}
- fitContentsToDoc={this.fitToBox}
+ // fitContentsToDoc={this.fitToBox} // bcz: why do we want this? when we initially open a colletion, we shrinkwrap it which allows for user navigation. if we later encounter a collection, it's not clear to me that we want to make it either shrinkwrap or fitContents...
isDocumentActive={returnFalse}
isContentActive={returnTrue}
addDocTab={this.addDocTab}
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index c3cdb7dde..15cd2c144 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -59,6 +59,10 @@
.mainView-container {
color: $dark-gray;
+ .lm_goldenlayout {
+ background: $medium-gray;
+ }
+
.lm_title {
background: $light-gray;
color: $dark-gray;
@@ -166,12 +170,14 @@
position: absolute;
z-index: 2;
background-color: $light-gray;
+
.editable-title {
background-color: $light-gray;
}
}
}
+
.mainView-libraryHandle {
background-color: $light-gray;
}
@@ -179,20 +185,24 @@
{
.propertiesView {
background-color: #252525;
+
input {
background-color: $medium-gray;
}
- .propertiesView-sharingTable
- {
+
+ .propertiesView-sharingTable {
background-color: $medium-gray;
}
+
.editable-title {
background-color: $medium-gray;
}
+
.propertiesView-field {
background-color: $medium-gray;
}
}
+
.mainView-propertiesDragger,
.mainView-libraryHandle {
background: #353535;
@@ -202,8 +212,9 @@
.contextMenu-cont {
background: $medium-gray;
color: $white;
+
input::placeholder {
- color:$white;
+ color: $white;
}
}
}
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 6d0d5eb39..9a885fbf8 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -466,6 +466,7 @@ export class MainView extends React.Component {
</div>;
}
+
expandFlyout = action((button: Doc) => {
// bcz: What's going on here!?
// Chrome(not firefox) seems to have a bug when the flyout expands and there's a zoomed freeform tab. All of the div below the CollectionFreeFormView's main div
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 77e7b86ea..b2ee33807 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -65,6 +65,29 @@
display: inline;
}
+.empty-tabs-message {
+ position: absolute;
+ width: 100%;
+ z-index: 1;
+ top: 50%;
+ z-index: 1;
+ text-align: center;
+ font-size: 18;
+ color: $dark-gray;
+
+ img {
+ position: relative;
+ top: -1px;
+ margin: 0 5px;
+ }
+}
+
+.lm_header,
+.lm_items {
+ z-index: 2;
+ position: relative;
+}
+
.lm_drag_tab {
padding: 0;
width: 15px !important;
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index e662f3ddf..5dffc65fc 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -378,7 +378,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// }
}
if (uriList) {
- console.log("Web URI = ", uriList);
// const existingWebDoc = await Hypothesis.findWebDoc(uriList);
// if (existingWebDoc) {
// const alias = Doc.MakeAlias(existingWebDoc);
@@ -390,7 +389,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// addDocument(alias);
// } else
{
- console.log("Adding ...");
const newDoc = Docs.Create.WebDocument(uriList.split("#annotations:")[0], {// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig)
...options,
title: uriList.split("#annotations:")[0],
@@ -399,8 +397,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
_nativeWidth: 850,
useCors: true
});
- console.log(" ... " + newDoc.title);
- console.log(" ... " + addDocument(newDoc) + " " + newDoc.title);
+ addDocument(newDoc);
}
return;
}
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx
index a3da0e0e4..7f2128230 100644
--- a/src/client/views/collections/TreeView.tsx
+++ b/src/client/views/collections/TreeView.tsx
@@ -820,7 +820,7 @@ export class TreeView extends React.Component<TreeViewProps> {
childDocs: Doc[],
treeView: CollectionTreeView,
parentTreeView: CollectionTreeView | TreeView | undefined,
- conainerCollection: Doc,
+ containerCollection: Doc,
dataDoc: Doc | undefined,
parentCollectionDoc: Doc | undefined,
containerPrevSibling: Doc | undefined,
@@ -846,16 +846,16 @@ export class TreeView extends React.Component<TreeViewProps> {
unobserveHeight: (ref: any) => void,
contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[])
) {
- const viewSpecScript = Cast(conainerCollection.viewSpecScript, ScriptField);
+ const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result);
}
- const docs = TreeView.sortDocs(childDocs, StrCast(conainerCollection.treeViewSortCriterion));
+ const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion));
const rowWidth = () => panelWidth() - treeBulletWidth();
const treeViewRefs = new Map<Doc, TreeView | undefined>();
return docs.filter(child => child instanceof Doc).map((child, i) => {
- const pair = Doc.GetLayoutDataDocPair(conainerCollection, dataDoc, child);
+ const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child);
if (!pair.layout || pair.data instanceof Promise) {
return (null);
}
@@ -883,7 +883,7 @@ export class TreeView extends React.Component<TreeViewProps> {
return <TreeView key={child[Id]} ref={r => treeViewRefs.set(child, r ? r : undefined)}
document={pair.layout}
dataDoc={pair.data}
- containerCollection={conainerCollection}
+ containerCollection={containerCollection}
prevSibling={docs[i]}
treeView={treeView}
indentDocument={indent}
@@ -891,7 +891,7 @@ export class TreeView extends React.Component<TreeViewProps> {
onCheckedClick={onCheckedClick}
onChildClick={onChildClick}
renderDepth={renderDepth}
- removeDoc={StrCast(conainerCollection.freezeChildren).includes("remove") ? undefined : remove}
+ removeDoc={StrCast(containerCollection.freezeChildren).includes("remove") ? undefined : remove}
addDocument={addDocument}
styleProvider={styleProvider}
panelWidth={rowWidth}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index f6c2707da..bb4cae8c6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -42,7 +42,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const { A, B, LinkDocs } = this.props;
const linkDoc = LinkDocs[0];
if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return;
- 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.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
@@ -180,15 +180,26 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
render() {
if (!this.renderData) return (null);
+
const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
LinkManager.currentLink = this.props.LinkDocs[0];
const linkRelationship = Field.toString(LinkManager.currentLink?.linkRelationship as any as Field); //get string representing relationship
const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
const linkColorList = Doc.UserDoc().linkColorList as List<string>;
+ const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List<number>;
+ const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
+
+ const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
+
//access stroke color using index of the relationship in the color list (default black)
- const strokeColor = linkRelationshipList.indexOf(linkRelationship) === -1 ? (CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "white" : "black") : linkColorList[linkRelationshipList.indexOf(linkRelationship)];
+ const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? "black" : linkColorList[currRelationshipIndex];
+
+ //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has)
+ //thickness varies linearly from 3px to 12px for increasing link count
+ const strokeWidth = linkSize === -1 ? "3px" : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + "px";
+
return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2", stroke: strokeColor }}
+ <path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, /*strokeDasharray: "2 2",*/ stroke, strokeWidth }}
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]}`} />
{textX === undefined ? (null) : <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
{Field.toString(this.props.LinkDocs[0].description as any as Field)}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b2db1168d..febccbfcc 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -75,7 +75,7 @@ export type collectionFreeformViewProps = {
scaleField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
- dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are trnasparent or not.
+ dontRenderDocuments?: boolean; // used for annotation overlays which need to distribute documents into different freeformviews with different mixBlendModes depending on whether they are transparent or not.
// However, this screws up interactions since only the top layer gets events. so we render the freeformview a 3rd time with all documents in order to get interaction events (eg., marquee) but we don't actually want to display the documents.
};
@@ -1036,7 +1036,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
showTitle={this.props.childShowTitle}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.backgroundActive || this.props.childPointerEvents ? "all" :
- (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : undefined}
+ (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents}
jitterRotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
//fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
/>;
@@ -1472,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getContainerTransform={this.getContainerTransform}
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
- <div ref={this._marqueeRef} style={{ display: this.props.dontRenderDocuments ? "none" : undefined }}>
+ <div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
{this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
<CollectionFreeFormViewPannableContents
isAnnotationOverlay={this.isAnnotationOverlay}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
index ed196349e..a439a7998 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaCells.tsx
@@ -38,27 +38,35 @@ export interface CellProps {
row: number;
col: number;
rowProps: CellInfo;
+ // currently unused
CollectionView: Opt<CollectionView>;
+ // currently unused
ContainingCollection: Opt<CollectionView>;
Document: Doc;
+ // column name
fieldKey: string;
+ // currently unused
renderDepth: number;
+ // called when a button is pressed on the node itself
addDocTab: (document: Doc, where: string) => boolean;
pinToPres: (document: Doc) => void;
moveDocument?: (document: Doc | Doc[], targetCollection: Doc | undefined,
addDocument: (document: Doc | Doc[]) => boolean) => boolean;
isFocused: boolean;
changeFocusedCellByIndex: (row: number, col: number) => void;
+ // set whether the cell is in the isEditing mode
setIsEditing: (isEditing: boolean) => void;
isEditable: boolean;
setPreviewDoc: (doc: Doc) => void;
setComputed: (script: string, doc: Doc, field: string, row: number, col: number) => boolean;
getField: (row: number, col?: number) => void;
+ // currnetly unused
showDoc: (doc: Doc | undefined, dataDoc?: any, screenX?: number, screenY?: number) => void;
}
@observer
export class CollectionSchemaCell extends React.Component<CellProps> {
+ // return a field key that is corrected for whether it COMMENT
public static resolvedFieldKey(column: string, rowDoc: Doc) {
const fieldKey = column;
if (fieldKey.startsWith("*")) {
@@ -72,7 +80,9 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@observable protected _isEditing: boolean = false;
protected _focusRef = React.createRef<HTMLDivElement>();
protected _rowDoc = this.props.rowProps.original;
+ // Gets the serialized data in proto form of the base proto that this document's proto inherits from
protected _rowDataDoc = Doc.GetProto(this.props.rowProps.original);
+ // methods for dragging and dropping
protected _dropDisposer?: DragManager.DragDropDisposer;
@observable contents: string = "";
@@ -81,6 +91,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@action
onKeyDown = (e: KeyboardEvent): void => {
+ // If a cell is editable and clicked, hitting enter shoudl allow the user to edit it
if (this.props.isFocused && this.props.isEditable && e.keyCode === KeyCodes.ENTER) {
document.removeEventListener("keydown", this.onKeyDown);
this._isEditing = true;
@@ -90,7 +101,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@action
isEditingCallback = (isEditing: boolean): void => {
+ // a general method that takes a boolean that determines whether the cell should be in
+ // is-editing mode
+ // remove the event listener if it's there
document.removeEventListener("keydown", this.onKeyDown);
+ // it's not already in is-editing mode, re-add the event listener
isEditing && document.addEventListener("keydown", this.onKeyDown);
this._isEditing = isEditing;
this.props.setIsEditing(isEditing);
@@ -99,13 +114,15 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@action
onPointerDown = async (e: React.PointerEvent): Promise<void> => {
+ // pan to the cell
this.onItemDown(e);
+ // focus on it
this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
this.props.setPreviewDoc(this.props.rowProps.original);
- console.log("click cell");
let url: string;
if (url = StrCast(this.props.rowProps.row.href)) {
+ // opens up the the doc in a new window, blurring the old one
try {
new URL(url);
const temp = window.open(url)!;
@@ -120,18 +137,25 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
@undoBatch
applyToDoc = (doc: Doc, row: number, col: number, run: (args?: { [name: string]: any }) => any) => {
+ // apply a specified change to the cell
const res = run({ this: doc, $r: row, $c: col, $: (r: number = 0, c: number = 0) => this.props.getField(r + row, c + col) });
if (!res.success) return false;
+ // change what is rendered to this new changed cell content
doc[this.renderFieldKey] = res.result;
return true;
+ // return whether the change was successful
}
private drop = (e: Event, de: DragManager.DropEvent) => {
+ // if the drag has data at its completion
if (de.complete.docDragData) {
+ // if only one doc was dragged
if (de.complete.docDragData.draggedDocuments.length === 1) {
+ // update the renderFieldKey
this._rowDataDoc[this.renderFieldKey] = de.complete.docDragData.draggedDocuments[0];
}
else {
+ // create schema document reflecting the new column arrangement
const coll = Docs.Create.SchemaDocument([new SchemaHeaderField("title", "#f1efeb")], de.complete.docDragData.draggedDocuments, {});
this._rowDataDoc[this.renderFieldKey] = coll;
}
@@ -140,7 +164,9 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
}
protected dropRef = (ele: HTMLElement | null) => {
+ // if the drop disposer is not undefined, run its function
this._dropDisposer?.();
+ // if ele is not null, give ele a non-undefined drop disposer
ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)));
}
@@ -164,33 +190,46 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
return <span style={{ color: contents ? "black" : "grey" }}>{contents ? contents?.valueOf() : "undefined"}</span>;
}
- @computed get renderFieldKey() { return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original); }
+ @computed get renderFieldKey() {
+ // gets the resolved field key of this cell
+ return CollectionSchemaCell.resolvedFieldKey(this.props.rowProps.column.id!, this.props.rowProps.original);
+ }
+
onItemDown = async (e: React.PointerEvent) => {
+ // if the document is a document used to change UI for search results in schema view
if (this.props.Document._searchDoc) {
const aliasdoc = await SearchUtil.GetAliasesOfDocument(this._rowDataDoc);
const targetContext = aliasdoc.length <= 0 ? undefined : Cast(aliasdoc[0].context, Doc, null);
+ // Jump to the this document
DocumentManager.Instance.jumpToDocument(this._rowDoc, false, emptyFunction, targetContext,
undefined, undefined, undefined, () => this.props.setPreviewDoc(this._rowDoc));
}
}
+
renderCellWithType(type: string | undefined) {
const dragRef: React.RefObject<HTMLDivElement> = React.createRef();
+ // the column
const fieldKey = this.renderFieldKey;
+ // the exact cell
const field = this._rowDoc[fieldKey];
const onPointerEnter = (e: React.PointerEvent): void => {
+ // e.buttons === 1 means the left moue pointer is down
if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) {
dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over";
}
};
const onPointerLeave = (e: React.PointerEvent): void => {
+ // change the class name to indicate that the cell is no longer being dragged
dragRef.current!.className = "collectionSchemaView-cellContainer";
};
let contents = Field.toString(field as Field);
+ // display 2 hyphens instead of a blank box for empty cells
contents = contents === "" ? "--" : contents;
+ // classname reflects the tatus of the cell
let className = "collectionSchemaView-cellWrapper";
if (this._isEditing) className += " editing";
if (this.props.isFocused && this.props.isEditable) className += " focused";
@@ -198,19 +237,23 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const positions = [];
if (StrCast(this.props.Document._searchString).toLowerCase() !== "") {
+ // term is ...promise pending... if the field is a Promise, otherwise it is the cell's contents
let term = (field instanceof Promise) ? "...promise pending..." : contents.toLowerCase();
const search = StrCast(this.props.Document._searchString).toLowerCase();
let start = term.indexOf(search);
let tally = 0;
+ // if search is found in term
if (start !== -1) {
positions.push(start);
}
+ // if search is found in term, continue finding all instances of search in term
while (start < contents?.length && start !== -1) {
term = term.slice(start + search.length + 1);
tally += start + search.length + 1;
start = term.indexOf(search);
positions.push(tally + start);
}
+ // remove the last position
if (positions.length > 1) {
positions.pop();
}
@@ -280,6 +323,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
//two options here: we can strip off outer quotes or we can figure out what's going on with the script
const script = CompileScript(inputAsString, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
const changeMade = inputAsString.length !== value.length || inputAsString.length - 2 !== value.length;
+ // change it if a change is made, otherwise, just compile using the old cell conetnts
script.compiled && (retVal = this.applyToDoc(changeMade ? this._rowDoc : this._rowDataDoc, this.props.row, this.props.col, script.run));
// handle numbers and expressions
} else if (inputIsNum || value.startsWith("=")) {
@@ -309,6 +353,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
return retVal;
})}
OnFillDown={async (value: string) => {
+ // computes all of the value preceded by :=
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
script.compiled && DocListCast(this.props.Document[this.props.fieldKey]).
forEach((doc, i) => value.startsWith(":=") ?
@@ -339,7 +384,10 @@ export class CollectionSchemaStringCell extends CollectionSchemaCell { render()
@observer
export class CollectionSchemaDateCell extends CollectionSchemaCell {
- @computed get _date(): Opt<DateField> { return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined; }
+ @computed get _date(): Opt<DateField> {
+ // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined.
+ return this._rowDoc[this.renderFieldKey] instanceof DateField ? DateCast(this._rowDoc[this.renderFieldKey]) : undefined;
+ }
@action
handleChange = (date: any) => {
@@ -378,8 +426,9 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
typecheck: true,
transformer: DocumentIconContainer.getTransformer()
});
-
+ // compile the script
const results = script.compiled && script.run();
+ // if the script was compiled and run
if (results && results.success) {
this._rowDoc[this.renderFieldKey] = results.result;
return true;
@@ -397,6 +446,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
@action
isEditingCallback = (isEditing: boolean): void => {
+ // the isEditingCallback from a general CollectionSchemaCell
document.removeEventListener("keydown", this.onKeyDown);
isEditing && document.addEventListener("keydown", this.onKeyDown);
this._isEditing = isEditing;
@@ -405,6 +455,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
}
render() {
+ // if there's a doc, render it
return !this._doc ? this.renderCellWithType("document") :
<div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1}
onPointerDown={this.onPointerDown}
@@ -440,11 +491,11 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
export class CollectionSchemaImageCell extends CollectionSchemaCell {
choosePath(url: URL) {
- if (url.protocol === "data") return url.href;
- if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href);
- if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href;//Why is this here
+ if (url.protocol === "data") return url.href; // if the url ises the data protocol, just return the href
+ if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); // otherwise, put it through the cors proxy erver
+ if (!/\.(png|jpg|jpeg|gif|webp)$/.test(url.href.toLowerCase())) return url.href;//Why is this here — good question
- const ext = path.extname(url.href);
+ const ext = path.extname(url.href); // the extension of the file
return url.href.replace(ext, "_o" + path.extname(url.href));
}
@@ -453,12 +504,13 @@ export class CollectionSchemaImageCell extends CollectionSchemaCell {
const alts = DocListCast(this._rowDoc[this.renderFieldKey + "-alternates"]); // retrieve alternate documents that may be rendered as alternate images
const altpaths = alts.map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url).filter(url => url).map(url => this.choosePath(url)); // access the primary layout data of the alternate documents
const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths;
+ // If there is a path, follow it; otherwise, follow a link to a default image icon
const url = paths.length ? paths : [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")];
- const aspect = Doc.NativeAspect(this._rowDoc);
- let width = Math.min(75, this.props.rowProps.width);
- const height = Math.min(75, width / aspect);
- width = height * aspect;
+ const aspect = Doc.NativeAspect(this._rowDoc); // aspect ratio
+ let width = Math.min(75, this.props.rowProps.width); // get a with that is no smaller than 75px
+ const height = Math.min(75, width / aspect); // get a height either proportional to that or 75 px
+ width = height * aspect; // increase the width of the image if necessary to maintain proportionality
const reference = React.createRef<HTMLDivElement>();
return <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}>
@@ -478,13 +530,13 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
@computed get _field() { return this._rowDoc[this.renderFieldKey]; }
@computed get _optionsList() { return this._field as List<any>; }
- @observable private _opened = false;
+ @observable private _opened = false; // whether the list is opened
@observable private _text = "select an item";
- @observable private _selectedNum = 0;
+ @observable private _selectedNum = 0; // the index of the list item selected
@action
onSetValue = (value: string) => {
- // change if its a document
+ // change if it's a document
this._optionsList[this._selectedNum] = this._text = value;
(this._field as List<any>).splice(this._selectedNum, 1, value);
@@ -492,6 +544,7 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
@action
onSelected = (element: string, index: number) => {
+ // if an item is selected, the private variables should update to reflect this
this._text = element;
this._selectedNum = index;
}
@@ -505,6 +558,7 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
const link = false;
const reference = React.createRef<HTMLDivElement>();
+ // if the list is not opened, don't display it; otherwise, do.
if (this._optionsList?.length) {
const options = !this._opened ? (null) :
<div>
@@ -572,6 +626,7 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell {
@observer
export class CollectionSchemaButtons extends CollectionSchemaCell {
+ // the navigation buttons for schema view when it is used for search.
render() {
return !this.props.Document._searchDoc || ![DocumentType.PDF, DocumentType.RTF].includes(StrCast(this._rowDoc.type) as DocumentType) ? <></> :
<div style={{ paddingTop: 8, paddingLeft: 3 }} >
@@ -583,4 +638,4 @@ export class CollectionSchemaButtons extends CollectionSchemaCell {
</button>
</div>;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
index c659f749e..1306b79cb 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx
@@ -24,6 +24,7 @@ export interface AddColumnHeaderProps {
@observer
export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHeaderProps> {
+ // the button that allows the user to add a column
render() {
return <button className="add-column" onClick={() => this.props.createColumn()}>
<FontAwesomeIcon icon="plus" size="sm" />
@@ -31,7 +32,6 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe
}
}
-
export interface ColumnMenuProps {
columnField: SchemaHeaderField;
// keyValue: string;
@@ -395,8 +395,8 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> {
this.closeResultsVisibility = "none";
}
for (let i = 0; i < (filters?.length ?? 0) - 1; i++) {
- if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i].split(":")[1]) === false) {
- keyOptions.push(filters![i + 1]);
+ if (filters[i] === this.props.col.heading && keyOptions.includes(filters[i].split(":")[1]) === false) {
+ keyOptions.push(filters[i + 1]);
}
}
const options = keyOptions.map(key => {
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
index 456c38c68..2df95ffd8 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaMovableColumn.tsx
@@ -21,27 +21,38 @@ export interface MovableColumnProps {
ScreenToLocalTransform: () => Transform;
}
export class MovableColumn extends React.Component<MovableColumnProps> {
+ // The header of the column
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
+ // The container of the function that is responsible for moving the column over to a new plac
private _colDropDisposer?: DragManager.DragDropDisposer;
+ // initial column position
private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 };
+ // sensitivity to being dragged, in pixels
private _sensitivity: number = 16;
+ // Column reference ID
private _dragRef: React.RefObject<HTMLDivElement> = React.createRef();
onPointerEnter = (e: React.PointerEvent): void => {
+ // if the column is left-clicked and it is being dragged
if (e.buttons === 1 && SnappingManager.GetIsDragging()) {
this._header!.current!.className = "collectionSchema-col-wrapper";
document.addEventListener("pointermove", this.onDragMove, true);
}
}
+
onPointerLeave = (e: React.PointerEvent): void => {
this._header!.current!.className = "collectionSchema-col-wrapper";
document.removeEventListener("pointermove", this.onDragMove, true);
!e.buttons && document.removeEventListener("pointermove", this.onPointerMove);
}
+
onDragMove = (e: PointerEvent): void => {
+ // only take into account the horizonal direction when a column is dragged
const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
const rect = this._header!.current!.getBoundingClientRect();
+ // Now store the point at the top center of the column when it was in its original position
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ // to be compared with its new horizontal position
const before = x[0] < bounds[0];
this._header!.current!.className = "collectionSchema-col-wrapper";
if (before) this._header!.current!.className += " col-before";
@@ -58,11 +69,15 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
colDrop = (e: Event, de: DragManager.DropEvent) => {
document.removeEventListener("pointermove", this.onDragMove, true);
+ // we only care about whether the column is shifted to the side
const x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ // get the dimensions of the smallest rectangle that bounds the header
const rect = this._header!.current!.getBoundingClientRect();
const bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left + ((rect.right - rect.left) / 2), rect.top);
+ // get whether the column was dragged before or after where it is now
const before = x[0] < bounds[0];
const colDragData = de.complete.columnDragData;
+ // if there is colDragData, which happen when the drag is complete, reorder the columns according to the established variables
if (colDragData) {
e.stopPropagation();
this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns);
@@ -85,8 +100,10 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
};
+ // if the left mouse button is the one being held
if (e.buttons === 1) {
const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y);
+ // If the movemnt of the drag exceeds the sensitivity value
if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) {
document.removeEventListener("pointermove", this.onPointerMove);
e.stopPropagation();
@@ -105,6 +122,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> {
onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => {
this._dragRef = ref;
const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY);
+ // If the cell thing dragged is not being edited
if (!(e.target as any)?.tagName.includes("INPUT")) {
this._startDragPosition = { x: dx, y: dy };
document.addEventListener("pointermove", this.onPointerMove);
diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
index 9ebe14d6c..b64e9dac1 100644
--- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
+++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss
@@ -132,6 +132,13 @@
min-height: 30px;
border: 0 !important;
}
+ .rt-tr-group:nth-of-type(even) {
+ direction: ltr;
+ flex: 0 1 auto;
+ min-height: 30px;
+ border: 0 !important;
+ background-color: red;
+ }
.rt-tr {
width: 100%;
min-height: 30px;
diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx
index 219f7d3a2..db331bb75 100644
--- a/src/client/views/linking/LinkEditor.tsx
+++ b/src/client/views/linking/LinkEditor.tsx
@@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { Tooltip } from "@material-ui/core";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, StrListCast, Field } from "../../../fields/Doc";
+import { Doc, NumListCast, StrListCast, Field } from "../../../fields/Doc";
import { DateCast, StrCast, Cast } from "../../../fields/Types";
import { LinkManager } from "../../util/LinkManager";
import { undoBatch } from "../../util/UndoManager";
@@ -42,14 +42,36 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
@undoBatch
setRelationshipValue = action((value: string) => {
if (LinkManager.currentLink) {
+ const prevRelationship = LinkManager.currentLink.linkRelationship as string;
+ LinkManager.currentLink.linkRelationship = value;
Doc.GetProto(LinkManager.currentLink).linkRelationship = value;
const linkRelationshipList = StrListCast(Doc.UserDoc().linkRelationshipList);
+ const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes);
const linkColorList = StrListCast(Doc.UserDoc().linkColorList);
+
// if the relationship does not exist in the list, add it and a corresponding unique randomly generated color
- if (linkRelationshipList && !linkRelationshipList.includes(value)) {
+ if (!linkRelationshipList?.includes(value)) {
linkRelationshipList.push(value);
+ linkRelationshipSizes.push(1);
const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")";
linkColorList.push(randColor);
+ // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes
+ } else if (linkRelationshipList && value !== prevRelationship) {
+ const index = linkRelationshipList.indexOf(value);
+ //increment size of new relationship size
+ if (index !== -1 && index < linkRelationshipSizes.length) {
+ const pvalue = linkRelationshipSizes[index];
+ linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1);
+ }
+ //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation)
+ if (linkRelationshipList.includes(prevRelationship)) {
+ const pindex = linkRelationshipList.indexOf(prevRelationship);
+ if (pindex !== -1 && pindex < linkRelationshipSizes.length) {
+ const pvalue = linkRelationshipSizes[pindex];
+ linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1));
+ }
+ }
+
}
this.relationshipButtonColor = "rgb(62, 133, 55)";
setTimeout(action(() => this.relationshipButtonColor = ""), 750);
@@ -141,6 +163,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
style={{ width: "100%" }}
id="input"
value={this.relationship}
+ autoComplete={"off"}
placeholder={"Enter link relationship"}
onKeyDown={this.onRelationshipKey}
onChange={this.handleRelationshipChange}
@@ -169,6 +192,7 @@ export class LinkEditor extends React.Component<LinkEditorProps> {
<div className="linkEditor-description-editing">
<input
style={{ width: "100%" }}
+ autoComplete={"off"}
id="input"
value={this.description}
placeholder={"Enter link description"}
diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx
index cb6571f92..03377ad4e 100644
--- a/src/client/views/linking/LinkMenuGroup.tsx
+++ b/src/client/views/linking/LinkMenuGroup.tsx
@@ -31,7 +31,6 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> {
if (RGBcolor) {
//set opacity to 0.25 by modifiying the rgb string
color = RGBcolor.slice(0, RGBcolor.length - 1) + ", 0.25)";
- console.log(color);
}
}
return color;
diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx
index 728956378..96cc6d600 100644
--- a/src/client/views/linking/LinkMenuItem.tsx
+++ b/src/client/views/linking/LinkMenuItem.tsx
@@ -165,19 +165,19 @@ export class LinkMenuItem extends React.Component<LinkMenuItemProps> {
<div className="linkMenu-item-buttons" ref={this._buttonRef} >
- <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Anchor" : "Hide Anchor"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.hidden ? "eye-slash" : "eye"} size="sm" /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.hidden ? "Show Link Anchor" : "Hide Link Anchor"}</div></>}>
+ <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.showAnchor} onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={"eye"} size="sm" /></div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkDisplay ? "Show link" : "Hide link"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={!this.props.linkDoc.linkDisplay ? "eye-slash" : "eye"} size="sm" /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkDisplay ? "Hide Link Line" : "Show Link Line"}</div></>}>
+ <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : this.props.linkDoc.linkDisplay ? "#4476f7"/* $medium-blue */ : "" }} onPointerDown={this.showLink} onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={"project-diagram"} size="sm" /></div>
</Tooltip>
- <Tooltip title={<><div className="dash-tooltip">{!this.props.linkDoc.linkAutoMove ? "Auto move dot" : "Freeze dot position"}</div></>}>
- <div className="button" ref={this._editRef} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}>
- <FontAwesomeIcon className="fa-icon" icon={this.props.linkDoc.linkAutoMove ? "play" : "pause"} size="sm" /></div>
+ <Tooltip title={<><div className="dash-tooltip">{this.props.linkDoc.linkAutoMove ? "Click to freeze link anchor position" : "Click to auto move link anchor"}</div></>}>
+ <div className="button" ref={this._editRef} style={{ background: this.props.linkDoc.hidden ? "gray" : !this.props.linkDoc.linkAutoMove ? "" : "#4476f7" /* $medium-blue */ }} onPointerDown={this.autoMove} onClick={e => e.stopPropagation()}>
+ <FontAwesomeIcon className="fa-icon" icon={"play"} size="sm" /></div>
</Tooltip>
<Tooltip title={<><div className="dash-tooltip">Edit Link</div></>}>
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index f23c68409..750213e67 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -88,15 +88,21 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
</div>;
};
const displayDoc = (which: string) => {
- var whichDoc = Cast(this.dataDoc[which], Doc, null);
- if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null);
+ const whichDoc = Cast(this.dataDoc[which], Doc, null);
+ //if (whichDoc?.type === DocumentType.MARKER)
+ const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc;
return whichDoc ? <>
- <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ <DocumentView
+ ref={(r) => {
+ whichDoc !== targetDoc && r?.focus(targetDoc);
+ }}
+ {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
isContentActive={returnFalse}
isDocumentActive={returnFalse}
styleProvider={this.docStyleProvider}
- Document={whichDoc}
+ Document={targetDoc}
DataDoc={undefined}
+ hideLinkButton={true}
pointerEvents={"none"} />
{clearButton(which)}
</> : // placeholder image if doc is missing
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 1ccf38de2..949e0e168 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -813,7 +813,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
- setHeight = (height: number) => this.layoutDoc._height = height;
+ setHeight = (height: number) => {
+ if (this.props.renderDepth !== -1) {
+ this.layoutDoc._height = height;
+ }
+ }
setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view);
isContentActive = (outsideReaction?: boolean) => {
return CurrentUserUtils.SelectedTool !== InkTool.None ||
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 37d716993..9956cc36b 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -58,8 +58,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
private _sidebarRef = React.createRef<SidebarAnnos>();
private _searchRef = React.createRef<HTMLInputElement>();
private _searchString = "";
+ @observable private _webUrl = ""; // url of the src parameter of the embedded iframe but not necessarily the rendered page - eg, when following a link, the rendered page changes but we don't wan the src parameter to also change as that would cause an unnecessary re-render.
+ @observable private _hackHide = false; // apparently changing the value of the 'sandbox' prop doesn't necessarily apply it to the active iframe. so thisforces the ifrmae to be rebuilt when allowScripts is toggled
@observable private _searching: boolean = false;
- @observable _showSidebar = false;
+ @observable private _showSidebar = false;
@observable private _scrollTimer: any;
@observable private _overlayAnnoInfo: Opt<Doc>;
@observable private _marqueeing: number[] | undefined;
@@ -79,6 +81,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
super(props);
Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || 850);
Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || this.Document[HeightSym]() / this.Document[WidthSym]() * 850);
+ runInAction(() => this._webUrl = this._url); // setting the weburl will change the src parameter of the embedded iframe and force a navigation to it.
}
@action
@@ -239,7 +242,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
MarqueeAnnotator.clearAnnotations(this._savedAnnotations);
this._marqueeing = [e.clientX * scale + mainContBounds.translateX,
e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._scrollTop) * scale];
- if (word) {
+ if (word || (e.target as any || "").className.includes("rangeslider") || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) {
setTimeout(action(() => this._marqueeing = undefined), 100); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it.
} else {
this._iframeClick = this._iframe ?? undefined;
@@ -269,9 +272,24 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
@action
iframeLoaded = (e: any) => {
const iframe = this._iframe;
- this._iframe?.contentDocument?.addEventListener("pointerup", this.iframeUp);
+ let requrlraw = decodeURIComponent(iframe?.contentWindow?.location.href.replace(Utils.prepend("") + "/corsProxy/", "") ?? this._url.toString());
+ if (requrlraw !== this._url.toString()) {
+ if (requrlraw.match(/q=.*&/)?.length && this._url.toString().match(/q=.*&/)?.length) {
+ const matches = requrlraw.match(/[^a-zA-z]q=[^&]*/g);
+ const newsearch = matches?.lastElement()!;
+ if (matches) {
+ requrlraw = requrlraw.substring(0, requrlraw.indexOf(newsearch));
+ for (let i = 1; i < Array.from(matches)?.length; i++) {
+ requrlraw = requrlraw.replace(matches[i], "");
+ }
+ }
+ requrlraw = requrlraw.replace(/q=[^&]*/, newsearch.substring(1)).replace("search&", "search?").replace("?gbv=1", "");
+ }
+ this.submitURL(requrlraw, undefined, true);
+ }
if (iframe?.contentDocument) {
- iframe?.contentDocument.addEventListener("pointerdown", this.iframeDown);
+ iframe.contentDocument.addEventListener("pointerup", this.iframeUp);
+ iframe.contentDocument.addEventListener("pointerdown", this.iframeDown);
this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument.body.scrollHeight);
setTimeout(action(() => this._scrollHeight = Math.max(this.scrollHeight, iframe?.contentDocument?.body.scrollHeight || 0)), 5000);
const initialScroll = this._initialScroll;
@@ -281,13 +299,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this._initialScroll = undefined;
}
iframe.setAttribute("enable-annotation", "true");
- iframe.contentDocument.addEventListener("click", undoBatch(action(e => {
+ iframe.contentDocument.addEventListener("click", undoBatch(action((e: MouseEvent) => {
let href = "";
- for (let ele = e.target; ele; ele = ele.parentElement) {
+ for (let ele = e.target as any; ele; ele = ele.parentElement) {
href = (typeof (ele.href) === "string" ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href;
}
- if (href && this.webField?.origin) {
- this.submitURL(href.replace(Utils.prepend(""), this.webField?.origin));
+ const origin = this.webField?.origin;
+ if (href && origin) {
+ e.stopPropagation();
+ setTimeout(() => this.submitURL(href.replace(Utils.prepend(""), origin)));
if (this._outerRef.current) {
this._outerRef.current.scrollTop = NumCast(this.layoutDoc._scrollTop);
this._outerRef.current.scrollLeft = 0;
@@ -338,8 +358,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"), []);
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
if (future.length) {
+ const curUrl = this._url;
this.dataDoc[this.fieldKey + "-history"] = new List<string>([...history, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(future.pop()!));
+ if (this._webUrl === this._url) {
+ this._webUrl = curUrl;
+ setTimeout(action(() => this._webUrl = this._url));
+ } else {
+ this._webUrl = this._url;
+ }
return true;
}
return false;
@@ -350,10 +377,16 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
const future = Cast(this.dataDoc[this.fieldKey + "-future"], listSpec("string"));
const history = Cast(this.dataDoc[this.fieldKey + "-history"], listSpec("string"), []);
if (history.length) {
+ const curUrl = this._url;
if (future === undefined) this.dataDoc[this.fieldKey + "-future"] = new List<string>([this._url]);
else this.dataDoc[this.fieldKey + "-future"] = new List<string>([...future, this._url]);
this.dataDoc[this.fieldKey] = new WebField(new URL(history.pop()!));
- console.log(this._urlHash);
+ if (this._webUrl === this._url) {
+ this._webUrl = curUrl;
+ setTimeout(action(() => this._webUrl = this._url));
+ } else {
+ this._webUrl = this._url;
+ }
return true;
}
return false;
@@ -362,9 +395,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
static urlHash = (s: string) => {
return Math.abs(s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0));
}
-
@action
- submitURL = (newUrl?: string, preview?: boolean) => {
+ submitURL = (newUrl?: string, preview?: boolean, dontUpdateIframe?: boolean) => {
if (!newUrl) return;
if (!newUrl.startsWith("http")) newUrl = "http://" + newUrl;
try {
@@ -376,7 +408,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
this.layoutDoc._scrollTop = 0;
future && (future.length = 0);
}
- if (!preview) this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
+ if (!preview) {
+ this.dataDoc[this.fieldKey] = new WebField(new URL(newUrl));
+ !dontUpdateIframe && (this._webUrl = this._url);
+ }
} catch (e) {
console.log("WebBox URL error:" + this._url);
}
@@ -430,6 +465,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
if (!cm.findByDescription("Options...")) {
!Doc.UserDoc().noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" });
funcs.push({
+ description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => {
+ this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts;
+ if (this._iframe) {
+ runInAction(() => this._hackHide = true);
+ setTimeout(action(() => this._hackHide = false));
+ }
+ }, icon: "snowflake"
+ });
+ funcs.push({
description: (!this.layoutDoc.forceReflow ? "Force" : "Prevent") + " Reflow", event: () => {
const nw = !this.layoutDoc.forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.scaling?.() || 1);
this.layoutDoc.forceReflow = !nw;
@@ -468,18 +512,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
}
@computed get urlContent() {
+ if (this._hackHide) return (null);
const field = this.dataDoc[this.props.fieldKey];
let view;
if (field instanceof HtmlField) {
view = <span className="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._url) : this._url;
+ const url = this.layoutDoc.useCors ? Utils.CorsProxy(this._webUrl) : this._webUrl;
view = <iframe className="webBox-iframe" enable-annotation={"true"}
style={{ pointerEvents: this._scrollTimer ? "none" : undefined }}
ref={action((r: HTMLIFrameElement | null) => this._iframe = r)} src={url} onLoad={this.iframeLoaded}
// the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page
// sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />;
- sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin"} />;
+ sandbox={`${this.layoutDoc.allowScripts ? "allow-scripts" : ""} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`} />;
} else {
view = <iframe className="webBox-iframe" enable-annotation={"true"}
style={{ pointerEvents: this._scrollTimer ? "none" : undefined }} // if we allow pointer events when scrolling is on, then reversing direction does not work smoothly
@@ -615,12 +660,12 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
moveDocument={this.moveDocument}
addDocument={this.sidebarAddDocument}
styleProvider={this.childStyleProvider}
- pointerEvents={CurrentUserUtils.SelectedTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
+ childPointerEvents={this.props.isContentActive() ? "all" : undefined}
+ pointerEvents={this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />;
return (
<div className="webBox" ref={this._mainCont}
style={{ pointerEvents: this.pointerEvents() }} >
<div className={`webBox-container`} style={{ pointerEvents }} onContextMenu={this.specificContextMenu}>
- <base target="_blank" />
<div className={"webBox-outerContent"} ref={this._outerRef}
style={{
width: `calc(${100 / scale}% - ${this.sidebarWidth() / scale * (this._previewWidth ? scale : 1)}px)`,
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 8e61a224c..2a153f256 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -404,7 +404,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
} else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) {
LightboxView.SetLightboxDoc(undefined);
//awaiting jump so that new scale can be found, since jumping is async
- await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right
+ await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right
}
// After navigating to the document, if it is added as a presPinView then it will
// adjust the pan and scale to that of the pinView when it was added.
@@ -1026,6 +1026,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
Array.from(this._selectedArray.keys()).forEach((doc) => doc.presTransition = timeInMS);
}
+ // Converts seconds to ms and updates presTransition
+ setZoom = (number: String, change?: number) => {
+ let scale = Number(number) / 100;
+ if (change) scale += change;
+ if (scale < 0.01) scale = 0.01;
+ if (scale > 1.5) scale = 1.5;
+ Array.from(this._selectedArray.keys()).forEach((doc) => doc.presZoom = scale);
+ }
+
// Converts seconds to ms and updates presDuration
setDurationTime = (number: String, change?: number) => {
let timeInMS = Number(number) * 1000;
@@ -1143,11 +1152,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@computed get transitionDropdown() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
- const type = targetDoc.type;
const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection);
const isPinWithView: boolean = BoolCast(activeItem.presPinView);
if (activeItem && targetDoc) {
+ const type = targetDoc.type;
const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5;
+ const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75;
let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2;
if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None';
@@ -1172,6 +1182,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
</div>
</div>
}
+ <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}>
+ <div className="presBox-subheading">Zoom (% screen filled)</div>
+ <div className="ribbon-property">
+ <input className="presBox-input"
+ type="number" value={zoom}
+ onChange={action((e) => this.setZoom(e.target.value))} />%
+ </div>
+ <div className="ribbon-propertyUpDown">
+ <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}>
+ <FontAwesomeIcon icon={"caret-up"} />
+ </div>
+ <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}>
+ <FontAwesomeIcon icon={"caret-down"} />
+ </div>
+ </div>
+ </div>
+ <input type="range" step="1" min="0" max="150" value={zoom}
+ className={`toolbar-slider ${activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}
+ id="toolbar-slider"
+ onPointerDown={() => this._batch = UndoManager.StartBatch("presZoom")}
+ onPointerUp={() => this._batch?.end()}
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
+ e.stopPropagation();
+ this.setZoom(e.target.value);
+ }} />
<div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}>
<div className="presBox-subheading">Movement Speed</div>
<div className="ribbon-property">