aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2023-05-22 11:25:32 -0400
committerbobzel <zzzman@gmail.com>2023-05-22 11:25:32 -0400
commitbed3309e1fda6597b2a8fea10ad82cd3a0402051 (patch)
treefe599bbdc5fca2c221e1e0f7a60995b7cd39f870 /src/client/views/collections/collectionFreeForm
parent887a4f7e0fc25fde87b20a5de2e7b0aee561cc78 (diff)
parent3d26d5b2654841a9b92f3d66b28d1dc8e36cca6a (diff)
merged physics with master
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx48
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx65
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx1083
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx97
6 files changed, 652 insertions, 645 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index 7dd9cdb8b..fee4705e6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -14,6 +14,7 @@ export interface ViewDefBounds {
x: number;
y: number;
z?: number;
+ rotation?: number;
text?: string;
zIndex?: number;
width?: number;
@@ -31,6 +32,7 @@ export interface PoolData {
x: number;
y: number;
z?: number;
+ rotation?: number;
zIndex?: number;
width?: number;
height?: number;
@@ -91,6 +93,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
width: layout[WidthSym](),
height: layout[HeightSym](),
pair: { layout, data },
+ transition: 'all .3s',
replica: '',
});
});
@@ -98,26 +101,28 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc
}
export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
- const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel
const docMap = new Map<string, PoolData>();
- const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75
- const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize];
- const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize];
+ const burstDiam = [NumCast(pivotDoc._width), NumCast(pivotDoc._height)];
+ const burstScale = NumCast(pivotDoc._starburstDocScale, 1);
childPairs.forEach(({ layout, data }, i) => {
- const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75
+ const aspect = layout[HeightSym]() / layout[WidthSym]();
+ const docSize = Math.min(Math.min(400, layout[WidthSym]()), Math.min(400, layout[WidthSym]()) / aspect) * burstScale;
const deg = (i / childPairs.length) * Math.PI * 2;
docMap.set(layout[Id], {
- x: Math.cos(deg) * burstRadius[0] - docSize / 2,
- y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2,
- width: docSize, //layout[WidthSym](),
- height: (docSize * layout[HeightSym]()) / layout[WidthSym](),
+ x: Math.min(burstDiam[0] / 2 - docSize, Math.max(-burstDiam[0] / 2, (Math.cos(deg) * burstDiam[0]) / 2 - docSize / 2)),
+ y: Math.min(burstDiam[1] / 2 - docSize * aspect, Math.max(-burstDiam[1] / 2, (Math.sin(deg) * burstDiam[1]) / 2 - (docSize / 2) * aspect)),
+ width: docSize,
+ height: docSize * aspect,
zIndex: NumCast(layout.zIndex),
pair: { layout, data },
replica: '',
+ color: 'white',
+ backgroundColor: 'white',
+ transition: 'all 0.3s',
});
});
- const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined };
- return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
+ const divider = { type: 'div', color: 'transparent', x: -burstDiam[0] / 2, y: -burstDiam[1] / 2, width: 15, height: 15, payload: undefined };
+ return normalizeResults(burstDiam, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]);
}
export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) {
@@ -128,20 +133,15 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
let nonNumbers = 0;
const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author';
childPairs.map(pair => {
- const lval =
- pivotFieldKey === '#' || pivotFieldKey === 'tags'
- ? Array.from(Object.keys(Doc.GetProto(pair.layout)))
- .filter(k => k.startsWith('#'))
- .map(k => k.substring(1))
- : Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
+ const listValue = Cast(pair.layout[pivotFieldKey], listSpec('string'), null);
const num = toNumber(pair.layout[pivotFieldKey]);
if (num === undefined || Number.isNaN(num)) {
nonNumbers++;
}
const val = Field.toString(pair.layout[pivotFieldKey] as Field);
- if (lval) {
- lval.forEach((val, i) => {
+ if (listValue) {
+ listValue.forEach((val, i) => {
!pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] });
pivotColumnGroups.get(val)!.docs.push(pair.layout);
pivotColumnGroups.get(val)!.replicas.push(i.toString());
@@ -154,8 +154,8 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do
docMap.set(pair.layout[Id], {
x: 0,
y: 0,
- zIndex: -99,
- width: 0,
+ zIndex: 0,
+ width: 0, // should make doc hidden in CollectionFreefromDocumentView
height: 0,
pair,
replica: '',
@@ -406,7 +406,7 @@ function normalizeResults(
.map(ele => {
const newPosRaw = ele[1];
if (newPosRaw) {
- const newPos = {
+ const newPos: PoolData = {
x: newPosRaw.x * scale,
y: newPosRaw.y * scale,
z: newPosRaw.z,
@@ -415,8 +415,12 @@ function normalizeResults(
zIndex: newPosRaw.zIndex,
width: (newPosRaw.width || 0) * scale,
height: newPosRaw.height! * scale,
+ backgroundColor: newPosRaw.backgroundColor,
+ opacity: newPosRaw.opacity,
+ color: newPosRaw.color,
pair: ele[1].pair,
};
+ if (newPosRaw.transition) newPos.transition = newPosRaw.transition;
poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos });
}
});
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 9811c239b..68ba3d368 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,6 +1,6 @@
import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
-import { Doc, Field } from '../../../../fields/Doc';
+import { CssSym, Doc, Field } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { List } from '../../../../fields/List';
import { Cast, NumCast, StrCast } from '../../../../fields/Types';
@@ -34,11 +34,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this._anchorDisposer = reaction(
() => [
this.props.A.props.ScreenToLocalTransform(),
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[CssSym],
this.props.B.props.ScreenToLocalTransform(),
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?.scrollTop,
- Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?._highlights,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop,
+ Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[CssSym],
],
action(() => {
this._start = Date.now();
@@ -58,7 +58,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
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)),
+ action(() => (!LinkDocs.length || !linkDoc.layout_linkDisplay) && (this._opacity = 0.05)),
750
); // this will unhighlight the link line.
const a = A.ContentDiv.getBoundingClientRect();
@@ -72,36 +72,36 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
// if there's an element in the DOM with a classname containing a link anchor's id,
// then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right
// otherwise, we just use the computed nearest point on the document boundary to the target Document
- const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor1 as Doc)[Id])).lastElement();
- const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.anchor2 as Doc)[Id])).lastElement();
+ const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_1 as Doc)[Id])).lastElement();
+ const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_2 as Doc)[Id])).lastElement();
if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return;
if (!targetAhyperlink) {
- if (linkDoc.linkAutoMove) {
- linkDoc.anchor1_x = ((apt.point.x - aleft) / awidth) * 100;
- linkDoc.anchor1_y = ((apt.point.y - atop) / aheight) * 100;
+ if (linkDoc.layout_autoMoveAnchors) {
+ linkDoc.link_anchor_1_x = ((apt.point.x - aleft) / awidth) * 100;
+ linkDoc.link_anchor_1_y = ((apt.point.y - atop) / aheight) * 100;
}
} else {
const m = targetAhyperlink.getBoundingClientRect();
const mp = A.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
const mpx = mp[0] / A.props.PanelWidth();
const mpy = mp[1] / A.props.PanelHeight();
- if (mpx >= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100;
- if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100;
+ if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100;
if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0;
else linkDoc.opacity = 1;
}
if (!targetBhyperlink) {
- if (linkDoc.linkAutoMove) {
- linkDoc.anchor2_x = ((bpt.point.x - bleft) / bwidth) * 100;
- linkDoc.anchor2_y = ((bpt.point.y - btop) / bheight) * 100;
+ if (linkDoc.layout_autoMoveAnchors) {
+ linkDoc.link_anchor_2_x = ((bpt.point.x - bleft) / bwidth) * 100;
+ linkDoc.link_anchor_2_y = ((bpt.point.y - btop) / bheight) * 100;
}
} else {
const m = targetBhyperlink.getBoundingClientRect();
const mp = B.props.ScreenToLocalTransform().transformPoint(m.right, m.top + 5);
const mpx = mp[0] / B.props.PanelWidth();
const mpy = mp[1] / B.props.PanelHeight();
- if (mpx >= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100;
- if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100;
+ if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100;
+ if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100;
if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0;
else linkDoc.opacity = 1;
}
@@ -112,8 +112,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
this,
e,
(e, down, delta) => {
- this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0];
- this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1];
+ this.props.LinkDocs[0].link_relationship_OffsetX = NumCast(this.props.LinkDocs[0].link_relationship_OffsetX) + delta[0];
+ this.props.LinkDocs[0].link_relationship_OffsetY = NumCast(this.props.LinkDocs[0].link_relationship_OffsetY) + delta[1];
return false;
},
emptyFunction,
@@ -136,6 +136,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
height = rect.height;
var el = el.parentNode;
while (el && el !== document.body) {
+ if (el.className === 'tabDocView-content') break;
rect = el.getBoundingClientRect?.();
if (rect?.width) {
if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom;
@@ -204,8 +205,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const bclipped = bleft !== b.left || btop !== b.top;
if (aclipped && bclipped) return undefined;
const clipped = aclipped || bclipped;
- const pt1inside = NumCast(LinkDocs[0].anchor1_x) % 100 !== 0 && NumCast(LinkDocs[0].anchor1_y) % 100 !== 0;
- const pt2inside = NumCast(LinkDocs[0].anchor2_x) % 100 !== 0 && NumCast(LinkDocs[0].anchor2_y) % 100 !== 0;
+ const pt1inside = NumCast(LinkDocs[0].link_anchor_1_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_1_y) % 100 !== 0;
+ const pt2inside = NumCast(LinkDocs[0].link_anchor_2_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_2_y) % 100 !== 0;
const pt1 = [aleft + a.width / 2, atop + a.height / 2];
const pt2 = [bleft + b.width / 2, btop + b.width / 2];
const pt2vec = pt2inside ? [-0.7071, 0.7071] : [(bDocBounds.left + bDocBounds.right) / 2 - pt2[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt2[1]];
@@ -222,8 +223,8 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const aActive = A.isSelected() || Doc.IsBrushed(A.rootDoc);
const bActive = B.isSelected() || Doc.IsBrushed(B.rootDoc);
- const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].linkOffsetX);
- const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].linkOffsetY);
+ const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX);
+ const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY);
return {
a,
b,
@@ -243,12 +244,12 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const link = this.props.LinkDocs[0];
const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
- const linkRelationship = Field.toString(link?.linkRelationship as any as Field); //get string representing relationship
- const linkRelationshipList = Doc.UserDoc().linkRelationshipList as List<string>;
+ const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship
+ const linkRelationshipList = Doc.UserDoc().link_relationshipList as List<string>;
const linkColorList = Doc.UserDoc().linkColorList as List<string>;
- const linkRelationshipSizes = Doc.UserDoc().linkRelationshipSizes as List<number>;
+ const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List<number>;
const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship);
- const linkDescription = Field.toString(link.description as any as Field);
+ const linkDescription = Field.toString(link.link_description as any as Field);
const linkSize = currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex];
@@ -260,11 +261,11 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
//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';
- if (link.linkDisplayArrow === undefined) {
- link.linkDisplayArrow = false;
+ if (link.layout_linkDisplayArrow === undefined) {
+ link.layout_linkDisplayArrow = false;
}
- return link.opacity === 0 || !a.width || !b.width || (!link.linkDisplay && !aActive && !bActive) ? null : (
+ return link.opacity === 0 || !a.width || !b.width || (!link.layout_linkDisplay && !aActive && !bActive) ? null : (
<>
<defs>
<marker id={`${link[Id] + 'arrowhead'}`} markerWidth="4" markerHeight="3" refX="0" refY="1.5" orient="auto">
@@ -293,7 +294,7 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
style={{ pointerEvents: 'visibleStroke', opacity: this._opacity, stroke, strokeWidth }}
onClick={this.onClickLine}
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]}`}
- markerEnd={link.linkDisplayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
+ markerEnd={link.layout_linkDisplayArrow ? `url(#${link[Id] + 'arrowhead'})` : ''}
/>
{textX === undefined || !linkDescription ? null : (
<text filter={`url(#${link[Id] + 'background'})`} className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown}>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 7a7ae3f40..c6419885b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -33,7 +33,7 @@
}
.collectionfreeformview-mask {
mix-blend-mode: multiply;
- background-color: rgba(0, 0, 0, 0.7);
+ background-color: rgba(0, 0, 0, 0.8);
}
.collectionfreeformview-viewdef {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 695d500e9..8b9698293 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,14 +1,13 @@
import { Bezier } from 'bezier-js';
import { Colors } from 'browndash-components';
-import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx';
import { observer } from 'mobx-react';
import { computedFn } from 'mobx-utils';
import { DateField } from '../../../../fields/DateField';
-import { DataSym, Doc, DocListCast, HeightSym, Opt, StrListCast, WidthSym } from '../../../../fields/Doc';
+import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc';
import { Id } from '../../../../fields/FieldSymbols';
import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
-import { ObjectField } from '../../../../fields/ObjectField';
import { RichTextField } from '../../../../fields/RichTextField';
import { listSpec } from '../../../../fields/Schema';
import { ScriptField } from '../../../../fields/ScriptField';
@@ -16,13 +15,12 @@ import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } fro
import { ImageField } from '../../../../fields/URLField';
import { TraceMobx } from '../../../../fields/util';
import { GestureUtils } from '../../../../pen-gestures/GestureUtils';
-import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils';
+import { aggregateBounds, emptyFunction, intersectRect, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils';
import { CognitiveServices } from '../../../cognitive_services/CognitiveServices';
import { Docs, DocUtils } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { DragManager, dropActionType } from '../../../util/DragManager';
-import { HistoryUtil } from '../../../util/History';
import { InteractionUtils } from '../../../util/InteractionUtils';
import { ReplayMovements } from '../../../util/ReplayMovements';
import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
@@ -34,15 +32,15 @@ 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 { DocumentDecorations } from '../../DocumentDecorations';
import { GestureOverlay } from '../../GestureOverlay';
import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveInkColor, SetActiveInkWidth } from '../../InkingStroke';
import { LightboxView } from '../../LightboxView';
import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView';
-import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere, ViewAdjustment, ViewSpecPrefix } from '../../nodes/DocumentView';
+import { DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../../nodes/DocumentView';
import { FieldViewProps } from '../../nodes/FieldView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { PresBox } from '../../nodes/trails/PresBox';
-import { VideoBox } from '../../nodes/VideoBox';
+import { PinProps, PresBox } from '../../nodes/trails/PresBox';
import { CreateImage } from '../../nodes/WebBoxRenderer';
import { StyleProp } from '../../StyleProvider';
import { CollectionSubView } from '../CollectionSubView';
@@ -55,13 +53,16 @@ import { MarqueeView } from './MarqueeView';
import React = require('react');
export type collectionFreeformViewProps = {
+ NativeWidth?: () => number;
+ NativeHeight?: () => number;
+ originTopLeft?: boolean;
annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: string;
- scaleField?: string;
+ viewField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
engineProps?: any;
- dontScaleFilter?: (doc: Doc) => boolean; // whether this collection should scale documents to fit their panel vs just scrolling them
+ getScrollHeight?: () => number | undefined;
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.
};
@@ -75,7 +76,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@observable
public static ShowPresPaths = false;
- private _lastNudge: any;
+ private _panZoomTransitionTimer: any;
private _lastX: number = 0;
private _lastY: number = 0;
private _downX: number = 0;
@@ -95,14 +96,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
private _brushtimer: any;
private _brushtimer1: any;
- // private isWritingMode: boolean = true;
- // private writingModeDocs: Doc[] = [];
-
private get isAnnotationOverlay() {
return this.props.isAnnotationOverlay;
}
- private get scaleFieldKey() {
- return this.props.scaleField || '_viewScale';
+ public get scaleFieldKey() {
+ return (this.props.viewField ?? '') + '_freeform_scale';
+ }
+ private get panXFieldKey() {
+ return (this.props.viewField ?? '') + '_freeform_panX';
+ }
+ private get panYFieldKey() {
+ return (this.props.viewField ?? '') + '_freeform_panY';
}
private get borderWidth() {
return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
@@ -112,7 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@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
@observable _hLines: number[] | undefined;
@observable _vLines: number[] | undefined;
- @observable _firstRender = true; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown.
+ @observable _firstRender = false; // this turns off rendering of the collection's content so that there's instant feedback when a tab is switched of what content will be shown. could be used for performance improvement
@observable _showAnimTimeline = false;
@observable _clusterSets: Doc[][] = [];
@observable _deleteList: DocumentView[] = [];
@@ -124,7 +128,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
constructor(props: any) {
super(props);
- this.props.setContentView?.(this);
}
@computed get views() {
@@ -136,11 +139,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@computed get fitToContentVals() {
return {
bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 },
- scale: !this.childDocs.length ? 1 : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
+ scale:
+ !this.childDocs.length || !Number.isFinite(this.contentBounds.b - this.contentBounds.y) || !Number.isFinite(this.contentBounds.r - this.contentBounds.x)
+ ? 1
+ : Math.min(this.props.PanelHeight() / (this.contentBounds.b - this.contentBounds.y), this.props.PanelWidth() / (this.contentBounds.r - this.contentBounds.x)),
};
}
@computed get fitContentsToBox() {
- return (this.props.fitContentsToBox?.() || this.Document._fitContentsToBox || this.Document.isGroup) && !this.isAnnotationOverlay;
+ return (this.props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay;
}
@computed get contentBounds() {
const cb = Cast(this.rootDoc.contentBounds, listSpec('number'));
@@ -148,27 +154,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] }
: this.props.contentBounds?.() ??
aggregateBounds(
- this._layoutElements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!),
+ this._layoutElements.filter(e => e.bounds && e.bounds.width && !e.bounds.z).map(e => e.bounds!),
NumCast(this.layoutDoc._xPadding, 10),
NumCast(this.layoutDoc._yPadding, 10)
);
}
@computed get nativeWidth() {
- return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this.props.NativeWidth?.() || (this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
}
@computed get nativeHeight() {
- return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null));
+ return this.props.NativeHeight?.() || (this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)));
}
@computed get cachedCenteringShiftX(): number {
const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
- return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ return this.props.isAnnotationOverlay || this.props.originTopLeft ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
}
@computed get cachedCenteringShiftY(): number {
const dv = this.props.DocumentView?.();
const scaling = this.fitContentsToBox || !this.nativeDimScaling ? 1 : this.nativeDimScaling;
// if freeform has a native aspect, then the panel height needs to be adjusted to match it
- const aspect = dv?.nativeWidth && dv?.nativeHeight && !dv.layoutDoc.fitWidth ? dv.nativeHeight / dv.nativeWidth : this.props.PanelHeight() / this.props.PanelWidth();
- return this.props.isAnnotationOverlay ? 0 : (aspect * this.props.PanelWidth()) / 2 / scaling; // shift so pan position is at center of window for non-overlay collections
+ 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()
@@ -182,7 +188,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.getContainerTransform().translate(-this.cachedCenteringShiftX, -this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform);
}
- _keyTimer: NodeJS.Timeout | undefined;
+ public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
+ if (timer) clearTimeout(timer);
+ return DocumentView.SetViewTransition(docs, 'all', duration, undefined, true);
+ }
+ public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
+ if (timer) clearTimeout(timer);
+ const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, undefined, true);
+ const timecode = Math.round(time);
+ docs.forEach(doc => {
+ CollectionFreeFormDocumentView.animFields.forEach(val => {
+ const findexed = Cast(doc[`${val.key}-indexed`], listSpec('number'), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any as number);
+ });
+ CollectionFreeFormDocumentView.animStringFields.forEach(val => {
+ const findexed = Cast(doc[`${val}-indexed`], listSpec('string'), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any as string);
+ });
+ CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => {
+ const findexed = Cast(doc[`${val}-indexed`], listSpec(InkField), null);
+ findexed?.length <= timecode + 1 && findexed.push(undefined as any);
+ });
+ });
+ return newTimer;
+ }
+
+ _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
changeKeyFrame = (back = false) => {
const currentFrame = Cast(this.Document._currentFrame, 'number', null);
if (currentFrame === undefined) {
@@ -190,10 +221,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
}
if (back) {
- this._keyTimer = CollectionFreeFormDocumentView.gotoKeyframe(this._keyTimer, this.childDocs.slice());
+ this._keyTimer = CollectionFreeFormView.gotoKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], 1000);
this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1);
} else {
- this._keyTimer = CollectionFreeFormDocumentView.updateKeyframe(this._keyTimer, this.childDocs, currentFrame || 0);
+ this._keyTimer = CollectionFreeFormView.updateKeyframe(this._keyTimer, [...this.childDocs, this.layoutDoc], currentFrame || 0);
this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1);
this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame));
}
@@ -208,21 +239,19 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
shrinkWrap = () => {
if (this.props.DocumentView?.().nativeWidth) return;
const vals = this.fitToContentVals;
- this.layoutDoc._panX = vals.bounds.cx;
- this.layoutDoc._panY = vals.bounds.cy;
- this.layoutDoc._viewScale = vals.scale;
+ this.layoutDoc._freeform_panX = vals.bounds.cx;
+ this.layoutDoc._freeform_panY = vals.bounds.cy;
+ this.layoutDoc._freeform_scale = vals.scale;
};
freeformData = (force?: boolean) => (!this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined);
reverseNativeScaling = () => (this.fitContentsToBox ? true : false);
- // panx, pany, zoomscale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
+ // freeform_panx, freeform_pany, freeform_scale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document.
// this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image
- panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panX, 1));
- panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY, NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.panY, 1));
+ 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 = () =>
- !this.cachedCenteringShiftX && !this.cachedCenteringShiftY && this.zoomScaling() === 1
- ? ''
- : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`;
+ 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();
@@ -234,7 +263,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectView(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())).map(dv => dv && SelectionManager.SelectView(dv, true));
};
addDocument = (newBox: Doc | Doc[]) => {
let retVal = false;
@@ -268,10 +297,44 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
isCurrent(doc: Doc) {
const dispTime = NumCast(doc._timecodeToShow, -1);
const endTime = NumCast(doc._timecodeToHide, dispTime + 1.5);
- const curTime = NumCast(this.Document._currentTimecode, -1);
+ const curTime = NumCast(this.Document._layout_currentTimecode, -1);
return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime);
}
+ groupFocus = (anchor: Doc, options: DocFocusOptions) => {
+ options.docTransform = new Transform(-NumCast(this.rootDoc[this.panXFieldKey]) + NumCast(anchor.x), -NumCast(this.rootDoc[this.panYFieldKey]) + NumCast(anchor.y), 1);
+ const res = this.props.focus(this.rootDoc, options);
+ options.docTransform = undefined;
+ return res;
+ };
+
+ focus = (anchor: Doc, options: DocFocusOptions) => {
+ if (this._lightboxDoc) return;
+ const xfToCollection = options?.docTransform ?? Transform.Identity();
+ const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
+ const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
+ const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined);
+
+ // focus on the document in the collection
+ const didMove = !cantTransform && !anchor.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
+ if (didMove) options.didMove = true;
+ // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
+ if (didMove) {
+ const focusTime = options?.instant ? 0 : options.zoomTime ?? 500;
+ (options.zoomScale ?? options.willZoomCentered) && scale && (this.Document[this.scaleFieldKey] = scale);
+ this.setPan(panX, panY, focusTime, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+ return focusTime;
+ }
+ };
+
+ getView = async (doc: Doc): Promise<Opt<DocumentView>> => {
+ return 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 (!de.embedKey && !this.ChildDrag && this.rootDoc._isGroup) return false;
@@ -285,9 +348,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
.map(pair => pair.layout)
.slice()
.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- zsorted.forEach((doc, index) => (doc.zIndex = doc.isInkMask ? 5000 : index + 1));
+ zsorted.forEach((doc, index) => (doc.zIndex = doc.stroke_isInkMask ? 5000 : index + 1));
const dvals = CollectionFreeFormDocumentView.getValues(refDoc, NumCast(refDoc.activeFrame, 1000));
const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)];
+
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
@@ -302,13 +366,35 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
d.x = x + NumCast(d.x) - dropPos[0];
d.y = y + NumCast(d.y) - dropPos[1];
}
- d._lastModified = new DateField();
+ d._layout_modificationDate = new DateField();
const nd = [Doc.NativeWidth(layoutDoc), Doc.NativeHeight(layoutDoc)];
layoutDoc._width = NumCast(layoutDoc._width, 300);
layoutDoc._height = NumCast(layoutDoc._height, nd[0] && nd[1] ? (nd[1] / nd[0]) * NumCast(layoutDoc._width) : 300);
(d._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : d._raiseWhenDragged) && (d.zIndex = zsorted.length + 1 + i); // bringToFront
}
+ if (this.layoutDoc._autoArrange || de.metaKey) {
+ const sorted = this.childLayoutPairs.slice().sort((a, b) => NumCast(a.layout.y) - NumCast(b.layout.y));
+ sorted.splice(
+ sorted.findIndex(pair => pair.layout === refDoc),
+ 1
+ );
+ if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) {
+ const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2;
+ const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0;
+ const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0;
+
+ let lastx = NumCast(refDoc.x);
+ let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height));
+ runInAction(() =>
+ sorted.slice(1).forEach((pair, i) => {
+ lastx = pair.layout.x = lastx + deltax;
+ lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height));
+ })
+ );
+ }
+ }
+
(docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments);
return true;
}
@@ -332,11 +418,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
internalLinkDrop(e: Event, de: DragManager.DropEvent, linkDragData: DragManager.LinkDragData, xp: number, yp: number) {
if (linkDragData.linkDragView.props.docViewPath().includes(this.props.docViewPath().lastElement())) {
// dragged document is a child of this collection
- if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.context !== this.props.Document) {
- // if the source doc view's context isn't this same freeformcollectionlinkDragData.dragDocument.context === this.props.Document
+ if (!linkDragData.linkDragView.props.CollectionFreeFormDocumentView?.() || linkDragData.dragDocument.embedContainer !== this.props.Document) {
+ // if the source doc view's embedContainer isn't this same freeformcollectionlinkDragData.dragDocument.embedContainer === this.props.Document
const source = Docs.Create.TextDocument('', { _width: 200, _height: 75, x: xp, y: yp, title: 'dropped annotation' });
this.props.addDocument?.(source);
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkDragData.linkSourceGetAnchor() }, { doc: source }, 'annotated by:annotation of', ''); // TODODO this is where in text links get passed
+ de.complete.linkDocument = DocUtils.MakeLink(linkDragData.linkSourceGetAnchor(), source, { link_relationship: 'annotated by:annotation of' }); // TODODO this is where in text links get passed
}
e.stopPropagation(); // do nothing if link is dropped into any freeform view parent of dragged document
return true;
@@ -360,13 +446,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return this.childLayoutPairs
.map(pair => pair.layout)
.reduce((cluster, cd) => {
- const grouping = this.props.Document._useClusters ? NumCast(cd.cluster, -1) : NumCast(cd.group, -1);
+ const grouping = this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1);
if (grouping !== -1) {
const layoutDoc = Doc.Layout(cd);
- const cx = NumCast(cd.x) - this._clusterDistance;
- const cy = NumCast(cd.y) - this._clusterDistance;
- const cw = NumCast(layoutDoc._width) + 2 * this._clusterDistance;
- const ch = NumCast(layoutDoc._height) + 2 * this._clusterDistance;
+ const cx = NumCast(cd.x) - this._clusterDistance / 2;
+ const cy = NumCast(cd.y) - this._clusterDistance / 2;
+ const cw = NumCast(layoutDoc._width) + this._clusterDistance;
+ const ch = NumCast(layoutDoc._height) + this._clusterDistance;
return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster;
}
return cluster;
@@ -377,10 +463,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (cluster !== -1) {
const ptsParent = e instanceof PointerEvent ? e : e.targetTouches.item(0);
if (ptsParent) {
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === cluster);
- const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!);
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster);
+ const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.DocumentView?.())!);
const { left, top } = clusterDocs[0].getBounds() || { left: 0, top: 0 };
- const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? 'alias' : undefined);
+ 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);
DragManager.StartDocumentDrag(
@@ -397,10 +483,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
return false;
}
- @undoBatch
@action
- updateClusters(_useClusters: boolean) {
- this.props.Document._useClusters = _useClusters;
+ updateClusters(_freeform_useClusters: boolean) {
+ this.props.Document._freeform_useClusters = _freeform_useClusters;
this._clusterSets.length = 0;
this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c));
}
@@ -408,70 +493,74 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
updateClusterDocs(docs: Doc[]) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._useClusters) {
+ if (this.props.Document._freeform_useClusters) {
const docFirst = docs[0];
docs.map(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)));
- const preferredInd = NumCast(docFirst.cluster);
- docs.map(doc => (doc.cluster = -1));
+ const preferredInd = NumCast(docFirst.layout_cluster);
+ docs.map(doc => (doc.layout_cluster = -1));
docs.map(doc =>
this._clusterSets.map((set, i) =>
set.map(member => {
- if (docFirst.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- docFirst.cluster = i;
+ if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ docFirst.layout_cluster = i;
}
})
)
);
- if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
- docFirst.cluster = preferredInd;
+ if (
+ docFirst.layout_cluster === -1 &&
+ preferredInd !== -1 &&
+ this._clusterSets.length > preferredInd &&
+ (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)
+ ) {
+ docFirst.layout_cluster = preferredInd;
}
this._clusterSets.map((set, i) => {
- if (docFirst.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
- docFirst.cluster = i;
+ if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ docFirst.layout_cluster = i;
}
});
- if (docFirst.cluster === -1) {
+ if (docFirst.layout_cluster === -1) {
docs.map(doc => {
- doc.cluster = this._clusterSets.length;
+ doc.layout_cluster = this._clusterSets.length;
this._clusterSets.push([doc]);
});
} else if (this._clusterSets.length) {
- for (let i = this._clusterSets.length; i <= NumCast(docFirst.cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
- docs.map(doc => this._clusterSets[(doc.cluster = NumCast(docFirst.cluster))].push(doc));
+ for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ docs.map(doc => this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc));
}
- childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.cluster === i) && this.updateCluster(child));
+ childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child));
}
}
- @undoBatch
@action
updateCluster(doc: Doc) {
const childLayouts = this.childLayoutPairs.map(pair => pair.layout);
- if (this.props.Document._useClusters) {
+ if (this.props.Document._freeform_useClusters) {
this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1));
- const preferredInd = NumCast(doc.cluster);
- doc.cluster = -1;
+ const preferredInd = NumCast(doc.layout_cluster);
+ doc.layout_cluster = -1;
this._clusterSets.forEach((set, i) =>
set.forEach(member => {
- if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
- doc.cluster = i;
+ if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) {
+ doc.layout_cluster = i;
}
})
);
- if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
- doc.cluster = preferredInd;
+ if (doc.layout_cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) {
+ doc.layout_cluster = preferredInd;
}
this._clusterSets.forEach((set, i) => {
- if (doc.cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
- doc.cluster = i;
+ if (doc.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) {
+ doc.layout_cluster = i;
}
});
- if (doc.cluster === -1) {
- doc.cluster = this._clusterSets.length;
+ if (doc.layout_cluster === -1) {
+ doc.layout_cluster = this._clusterSets.length;
this._clusterSets.push([doc]);
} else if (this._clusterSets.length) {
- for (let i = this._clusterSets.length; i <= doc.cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
- this._clusterSets[doc.cluster].push(doc);
+ for (let i = this._clusterSets.length; i <= doc.layout_cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]);
+ this._clusterSets[doc.layout_cluster ?? 0].push(doc);
}
}
}
@@ -480,8 +569,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1
switch (property) {
case StyleProp.BackgroundColor:
- const cluster = NumCast(doc?.cluster);
- if (this.Document._useClusters) {
+ const cluster = NumCast(doc?.layout_cluster);
+ if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) {
if (this._clusterSets.length <= cluster) {
setTimeout(() => doc && this.updateCluster(doc));
} else {
@@ -494,6 +583,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
}
}
break;
+ case StyleProp.FillColor:
+ if (doc && this.Document._currentFrame !== undefined) {
+ return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor;
+ }
}
return styleProp;
};
@@ -501,7 +594,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
trySelectCluster = (addToSel: boolean) => {
if (this._hitCluster !== -1) {
!addToSel && SelectionManager.DeselectAll();
- const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._useClusters ? NumCast(cd.cluster) : NumCast(cd.group, -1)) === this._hitCluster);
+ const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.props.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster);
this.selectDocuments(eles);
return true;
}
@@ -530,14 +623,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) &&
!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)
) {
+ // prettier-ignore
switch (Doc.ActiveTool) {
- case InkTool.Highlighter:
- break;
- // TODO: nda - this where we want to create the new "writingDoc" collection that we add strokes to
- 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.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.Eraser:
this._batch = UndoManager.StartBatch('collectionErase');
setupMoveUpEvents(this, e, this.onEraserMove, this.onEraserUp, emptyFunction);
@@ -545,7 +635,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
case InkTool.None:
if (!(this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) {
this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY));
- setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, false);
+ setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1 ? true : false, false);
}
break;
}
@@ -591,7 +681,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const B = this.getTransform().transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height);
const inkDoc = Docs.Create.InkDocument(
ActiveInkColor(),
- Doc.ActiveTool,
ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
ActiveInkBezierApprox(),
ActiveFillColor(),
@@ -602,8 +691,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
ActiveIsInkMask(),
{
title: 'ink stroke',
- x: B.x - ActiveInkWidth() / 2,
- y: B.y - ActiveInkWidth() / 2,
+ x: B.x - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
+ y: B.y - (ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale) / 2,
_width: B.width + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
_height: B.height + ActiveInkWidth() * this.props.ScreenToLocalTransform().Scale,
}
@@ -693,7 +782,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._batch?.end();
};
+ @action
onClick = (e: React.MouseEvent) => {
+ if (this._lightboxDoc) this._lightboxDoc = undefined;
if (this.onBrowseClickHandler()) {
if (this.props.DocumentView?.()) {
this.onBrowseClickHandler().script.run({ documentView: this.props.DocumentView(), clientX: e.clientX, clientY: e.clientY });
@@ -701,7 +792,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
e.stopPropagation();
e.preventDefault();
} else if (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3) {
- if (e.shiftKey) {
+ if (e.shiftKey && (this.props.renderDepth === 0 || this.isContentActive())) {
if (Date.now() - this._lastTap < 300) {
// reset zoom of freeform view to 1-to-1 on a shift + double click
this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1);
@@ -715,20 +806,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => {
+ PresBox.Instance?.pauseAutoPres();
const dx = e.deltaX;
const dy = e.deltaY;
- this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+ this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
};
@action
pan = (e: PointerEvent | React.Touch | { clientX: number; clientY: number }): void => {
+ PresBox.Instance?.pauseAutoPres();
this.props.DocumentView?.().clearViewTransition();
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
+ this.setPan(NumCast(this.Document[this.panXFieldKey]) - dx, NumCast(this.Document[this.panYFieldKey]) - dy, 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
};
+ _eraserLock = 0;
/**
* Erases strokes by intersecting them with an invisible "eraser stroke".
* By default this iterates through all intersected ink strokes, determines their segmentation, draws back the non-intersected segments,
@@ -738,19 +832,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onEraserMove = (e: PointerEvent, down: number[], delta: number[]) => {
const currPoint = { X: e.clientX, Y: e.clientY };
+ if (this._eraserLock) return false; // bcz: should be fixed by putting it on a queue to be processed after the last eraser movement is processed.
this.getEraserIntersections({ X: currPoint.X - delta[0], Y: currPoint.Y - delta[1] }, currPoint).forEach(intersect => {
if (!this._deleteList.includes(intersect.inkView)) {
this._deleteList.push(intersect.inkView);
- SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.strokeWidth?.toString()) || '1');
+ SetActiveInkWidth(StrCast(intersect.inkView.rootDoc.stroke_width?.toString()) || '1');
SetActiveInkColor(StrCast(intersect.inkView.rootDoc.color?.toString()) || 'black');
// create a new curve by appending all curves of the current segment together in order to render a single new stroke.
if (!e.shiftKey) {
+ this._eraserLock++;
this.segmentInkStroke(intersect.inkView, intersect.t).forEach(segment =>
- GestureOverlay.Instance.dispatchGesture(
+ this.forceStrokeGesture(
+ e,
GestureUtils.Gestures.Stroke,
segment.reduce((data, curve) => [...data, ...curve.points.map(p => intersect.inkView.ComponentView?.ptToScreen?.({ X: p.x, Y: p.y }) ?? { X: 0, Y: 0 })], [] as PointData[])
)
);
+ setTimeout(() => this._eraserLock--);
}
// Lower ink opacity to give the user a visual indicator of deletion.
intersect.inkView.layoutDoc.opacity = 0.5;
@@ -759,17 +857,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
});
return false;
};
+ forceStrokeGesture = (e: PointerEvent, gesture: GestureUtils.Gestures, points: InkData, text?: any) => {
+ this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text));
+ };
@action
onPointerMove = (e: PointerEvent): boolean => {
if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return false;
if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) {
Doc.ActiveTool = InkTool.None;
- if (this.props.isContentActive(true)) e.stopPropagation();
- } else if (!e.cancelBubble) {
+ } else {
if (this.tryDragCluster(e, this._hitCluster)) {
+ e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over
return true;
- } else this.pan(e);
+ }
+ // 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) {
+ 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
+ }
}
return false;
};
@@ -783,7 +889,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) };
return this.childDocs
- .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView))
+ .map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.()))
.filter(inkView => inkView?.ComponentView instanceof InkingStroke)
.map(inkView => ({ inkViewBounds: inkView!.getBounds(), inkStroke: inkView!.ComponentView as InkingStroke, inkView: inkView! }))
.filter(
@@ -837,7 +943,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const docCurveTVal = t + Math.floor(i / 4);
if (excludeT < startSegmentT || excludeT > docCurveTVal) {
const localStartTVal = startSegmentT - Math.floor(i / 4);
- segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
+ t !== (localStartTVal < 0 ? 0 : localStartTVal) && segment.push(inkSegment.split(localStartTVal < 0 ? 0 : localStartTVal, t));
segment.length && segments.push(segment);
}
// start a new segment from the intersection t value
@@ -877,7 +983,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this.childDocs
.filter(doc => doc.type === DocumentType.INK && !doc.dontIntersect)
.forEach(doc => {
- const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)?.ComponentView as InkingStroke;
+ const otherInk = DocumentManager.Instance.getDocumentView(doc, this.props.DocumentView?.())?.ComponentView as InkingStroke;
const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] };
const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point));
const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt));
@@ -887,106 +993,26 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (ink?.Document === otherInk.props.Document && neighboringSegment) continue;
const otherCurve = new Bezier(otherCtrlPts.slice(j, j + 4).map(p => ({ x: p.X, y: p.Y })));
+ const c0 = otherCurve.get(0);
+ const c1 = otherCurve.get(1);
+ const apt = curve.project(c0);
+ const bpt = curve.project(c1);
+ if (apt.d !== undefined && apt.d < 1 && apt.t !== undefined && !tVals.includes(apt.t)) {
+ tVals.push(apt.t);
+ }
this.bintersects(curve, otherCurve).forEach((val: string | number, i: number) => {
// Converting the Bezier.js Split type to a t-value number.
const t = +val.toString().split('/')[0];
if (i % 2 === 0 && !tVals.includes(t)) tVals.push(t); // bcz: Hack! don't know why but intersection points are doubled from bezier.js (but not identical).
});
+ if (bpt.d !== undefined && bpt.d < 1 && bpt.t !== undefined && !tVals.includes(bpt.t)) {
+ tVals.push(bpt.t);
+ }
}
});
return tVals;
};
- handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- if (myTouches[0]) {
- if (Doc.ActiveTool === InkTool.None) {
- if (this.tryDragCluster(e, this._hitCluster)) {
- e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
- e.preventDefault();
- document.removeEventListener('pointermove', this.onPointerMove);
- return;
- }
- // TODO: nda - this allows us to pan collections with finger -> only want to do this when collection is selected'
- this.pan(myTouches[0]);
- }
- }
- // e.stopPropagation();
- e.preventDefault();
- }
- };
-
- handle2PointersMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => {
- // pinch zooming
- if (!e.cancelBubble) {
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
-
- if (this.prevPoints.size === 2) {
- const oldPoint1 = this.prevPoints.get(pt1.identifier);
- const oldPoint2 = this.prevPoints.get(pt2.identifier);
- if (oldPoint1 && oldPoint2) {
- const dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2);
-
- // if zooming, zoom
- if (dir !== 0) {
- const d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2));
- const d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2));
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
-
- // calculate the raw delta value
- const rawDelta = dir * (d1 + d2);
-
- // this floors and ceils the delta value to prevent jitteriness
- const delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 8);
- this.zoom(centerX, centerY, delta * window.devicePixelRatio);
- this.prevPoints.set(pt1.identifier, pt1);
- this.prevPoints.set(pt2.identifier, pt2);
- }
- // this is not zooming. derive some form of panning from it.
- else {
- // use the centerx and centery as the "new mouse position"
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- // const transformed = this.getTransform().inverse().transformPoint(centerX, centerY);
-
- this._lastX = centerX;
- this._lastY = centerY;
- }
- }
- }
- // e.stopPropagation();
- e.preventDefault();
- }
- };
-
- @action
- handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => {
- if (this.props.isContentActive(true)) {
- // const pt1: React.Touch | null = e.targetTouches.item(0);
- // const pt2: React.Touch | null = e.targetTouches.item(1);
- // // if (!pt1 || !pt2) return;
- const myTouches = InteractionUtils.GetMyTargetTouches(me, this.prevPoints, true);
- const pt1 = myTouches[0];
- const pt2 = myTouches[1];
- if (pt1 && pt2) {
- const centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
- const centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
- this._lastX = centerX;
- this._lastY = centerY;
-
- this.removeMoveListeners();
- this.addMoveListeners();
- this.removeEndListeners();
- this.addEndListeners();
- e.stopPropagation();
- }
- }
- };
-
cleanUpInteractions = () => {
this.removeMoveListeners();
this.removeEndListeners();
@@ -1002,51 +1028,42 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (deltaScale * invTransform.Scale > 20) {
deltaScale = 20 / invTransform.Scale;
}
- if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._viewScaleMin, 1) && this.isAnnotationOverlay) {
- deltaScale = NumCast(this.rootDoc._viewScaleMin, 1) / invTransform.Scale;
+ if (deltaScale < 1 && invTransform.Scale <= NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
+ this.setPan(0, 0);
+ return;
+ }
+ if (deltaScale * invTransform.Scale < NumCast(this.rootDoc._freeform_scale_min, 1) && this.isAnnotationOverlay) {
+ deltaScale = NumCast(this.rootDoc._freeform_scale_min, 1) / invTransform.Scale;
}
- const localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
+ const localTransform = invTransform.scaleAbout(deltaScale, x, y);
if (localTransform.Scale >= 0.05 || localTransform.Scale > this.zoomScaling()) {
const safeScale = Math.min(Math.max(0.05, localTransform.Scale), 20);
this.props.Document[this.scaleFieldKey] = Math.abs(safeScale);
- this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
+ this.setPan(-localTransform.TranslateX / safeScale, (this.props.originTopLeft ? undefined : NumCast(this.props.Document.layout_scrollTop) * safeScale) || -localTransform.TranslateY / safeScale);
}
};
@action
onPointerWheel = (e: React.WheelEvent): void => {
- if (this.layoutDoc._Transform || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
+ if (this.Document._isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom
+ PresBox.Instance?.pauseAutoPres();
+ if (this.layoutDoc._Transform || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return;
e.stopPropagation();
- e.preventDefault();
+ const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+ const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
switch (!e.ctrlKey ? Doc.UserDoc().freeformScrollMode : freeformScrollMode.Pan) {
case freeformScrollMode.Pan:
- // if shift is selected then zoom
- if (e.ctrlKey) {
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
- // things that can scroll vertically should do that instead of zooming
- } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
- !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
- }
- // otherwise pan
- } else if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
- // things that can scroll vertically should do that instead of zooming
- } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
- const dx = -e.deltaX;
- const dy = -e.deltaY;
- if (e.shiftKey) {
- !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dy, deltaY: 0 });
- } else {
- !this.props.isAnnotationOverlayScrollable && this.scrollPan({ deltaX: dx, deltaY: dy });
- }
+ // if ctrl is selected then zoom
+ if (!e.ctrlKey && this.props.isContentActive(true)) {
+ this.scrollPan({ deltaX: -e.deltaX, deltaY: e.shiftKey ? 0 : -Math.max(-1, Math.min(1, e.deltaY)) });
+ break;
}
- break;
default:
case freeformScrollMode.Zoom:
- if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) {
- // things that can scroll vertically should do that instead of zooming
- } else if (this.props.isContentActive(true) && !this.Document._isGroup) {
- !this.props.isAnnotationOverlayScrollable && this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ if ((e.ctrlKey || !scrollable) && this.props.isContentActive(true)) {
+ this.zoom(e.clientX, e.clientY, Math.max(-1, Math.min(1, e.deltaY))); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc?
+ e.preventDefault();
}
break;
}
@@ -1074,40 +1091,61 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) },
}),
{
- xrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
- yrange: { min: Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ xrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
+ yrange: { min: this.props.originTopLeft ? 0 : Number.MAX_VALUE, max: -Number.MAX_VALUE },
}
);
- const panelDim = [this.props.PanelWidth() / this.zoomScaling(), this.props.PanelHeight() / this.zoomScaling()];
- if (ranges.xrange.min >= panX + panelDim[0] / 2) panX = ranges.xrange.max + panelDim[0] / 2; // snaps pan position of range of content goes out of bounds
- else if (ranges.xrange.max <= panX - panelDim[0] / 2) panX = ranges.xrange.min - panelDim[0] / 2;
- if (ranges.yrange.min >= panY + panelDim[1] / 2) panY = ranges.yrange.max + panelDim[1] / 2;
- else if (ranges.yrange.max <= panY - panelDim[1] / 2) panY = ranges.yrange.min - panelDim[1] / 2;
+ const panelWidMax = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelWidMin = (this.props.PanelWidth() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+ const panelHgtMax = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 2 / this.nativeDimScaling : 1);
+ const panelHgtMin = (this.props.PanelHeight() / this.zoomScaling()) * (this.props.originTopLeft ? 0 : 1);
+ if (ranges.xrange.min >= panX + panelWidMax / 2) panX = ranges.xrange.max + (this.props.originTopLeft ? 0 : panelWidMax / 2);
+ else if (ranges.xrange.max <= panX - panelWidMin / 2) panX = ranges.xrange.min - (this.props.originTopLeft ? panelWidMax / 2 : panelWidMin / 2);
+ if (ranges.yrange.min >= panY + panelHgtMax / 2) panY = ranges.yrange.max + (this.props.originTopLeft ? 0 : panelHgtMax / 2);
+ else if (ranges.yrange.max <= panY - panelHgtMin / 2) panY = ranges.yrange.min - (this.props.originTopLeft ? panelHgtMax / 2 : panelHgtMin / 2);
}
}
- if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc || DocListCast(Doc.MyOverlayDocs?.data).includes(this.Document)) {
- this._panZoomTransition = panTime;
- const scale = this.getLocalTransform().inverse().Scale;
- const minScale = NumCast(this.rootDoc._viewScaleMin, 1);
- const minPanX = NumCast(this.rootDoc._panXMin, 0);
- const minPanY = NumCast(this.rootDoc._panYMin, 0);
- const newPanX = Math.min(minPanX + (1 - minScale / scale) * NumCast(this.rootDoc._panXMax, this.nativeWidth), Math.max(minPanX, panX));
- const newPanY = Math.min(this.props.Document.scrollHeight !== undefined ? NumCast(this.Document.scrollHeight) : minPanY + (1 - minScale / scale) * NumCast(this.rootDoc._panYMax, this.nativeHeight), Math.max(minPanY, panY));
- !this.Document._verticalScroll && (this.Document._panX = this.isAnnotationOverlay ? newPanX : panX);
- !this.Document._horizontalScroll && (this.Document._panY = this.isAnnotationOverlay ? newPanY : panY);
+ 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 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);
+ const newPanX = Math.min(minPanX + scale * maxPanX, Math.max(minPanX, panX));
+ const fitYscroll = (((this.nativeHeight / this.nativeWidth) * this.props.PanelWidth() - this.props.PanelHeight()) * this.props.ScreenToLocalTransform().Scale) / minScale;
+ const nativeHeight = (this.props.PanelHeight() / this.props.PanelWidth() / (this.nativeHeight / this.nativeWidth)) * this.nativeHeight;
+ const maxScrollTop = this.nativeHeight / this.props.ScreenToLocalTransform().Scale - this.props.PanelHeight();
+ const maxPanY =
+ minPanY + // minPanY + scrolling introduced by view scaling + scrolling introduced by layout_fitWidth
+ scale * NumCast(this.rootDoc._panY_max, nativeHeight) +
+ (!this.props.getScrollHeight?.() ? fitYscroll : 0); // when not zoomed, scrolling is handled via a scrollbar, not panning
+ let newPanY = Math.max(minPanY, Math.min(maxPanY, panY));
+ if (false && NumCast(this.rootDoc.layout_scrollTop) && NumCast(this.rootDoc._freeform_scale, minScale) !== minScale) {
+ const relTop = NumCast(this.rootDoc.layout_scrollTop) / maxScrollTop;
+ this.rootDoc.layout_scrollTop = undefined;
+ newPanY = minPanY + relTop * (maxPanY - minPanY);
+ } else if (fitYscroll > 2 && this.rootDoc.layout_scrollTop === undefined && NumCast(this.rootDoc._freeform_scale, minScale) === minScale) {
+ const maxPanY = minPanY + fitYscroll;
+ const relTop = (panY - minPanY) / (maxPanY - minPanY);
+ setTimeout(() => (this.rootDoc.layout_scrollTop = relTop * maxScrollTop), 10);
+ newPanY = minPanY;
+ }
+ !this.Document._verticalScroll && (this.Document[this.panXFieldKey] = this.isAnnotationOverlay ? newPanX : panX);
+ !this.Document._horizontalScroll && (this.Document[this.panYFieldKey] = this.isAnnotationOverlay ? newPanY : panY);
}
}
@action
nudge = (x: number, y: number, nudgeTime: number = 500) => {
- if (this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform || this.props.ContainingCollectionDoc._panX !== undefined) {
- // bcz: this isn't ideal, but want to try it out...
- this.setPan(NumCast(this.layoutDoc._panX) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), NumCast(this.layoutDoc._panY) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(), nudgeTime, true);
- this._lastNudge && clearTimeout(this._lastNudge);
- this._lastNudge = setTimeout(
- action(() => (this._panZoomTransition = 0)),
- nudgeTime
+ const collectionDoc = this.props.docViewPath().lastElement().rootDoc;
+ if (collectionDoc?._type_collection !== CollectionViewType.Freeform || collectionDoc._freeform_ !== undefined) {
+ this.setPan(
+ NumCast(this.layoutDoc[this.panXFieldKey]) + ((this.props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale
+ NumCast(this.layoutDoc[this.panYFieldKey]) + ((this.props.PanelHeight() / 2) * -y) / this.zoomScaling(),
+ nudgeTime,
+ true
);
return true;
}
@@ -1117,117 +1155,61 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
bringToFront = (doc: Doc, sendToBack?: boolean) => {
if (sendToBack) {
- doc.zIndex = 0;
- } else if (doc.isInkMask) {
+ const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
+ docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
+ let zfirst = docs.length ? NumCast(docs[0].zIndex) : 0;
+ doc.zIndex = zfirst - 1;
+ } else if (doc.stroke_isInkMask) {
doc.zIndex = 5000;
} else {
const docs = this.childLayoutPairs.map(pair => pair.layout).slice();
docs.sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex));
- let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1;
- if (zlast - docs.length > 100) {
- for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
- zlast = docs.length + 1;
+ let zlast = docs.length ? Math.max(docs.length, NumCast(docs.lastElement().zIndex)) : 1;
+ if (docs.lastElement() !== doc) {
+ if (zlast - docs.length > 100) {
+ for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1;
+ zlast = docs.length + 1;
+ }
+ doc.zIndex = zlast + 1;
}
- doc.zIndex = zlast + 1;
}
};
@action
+ setPanZoomTransition = (transitionTime: number) => {
+ this._panZoomTransition = transitionTime;
+ this._panZoomTransitionTimer && clearTimeout(this._panZoomTransitionTimer);
+ this._panZoomTransitionTimer = setTimeout(
+ action(() => (this._panZoomTransition = 0)),
+ transitionTime
+ );
+ };
+
+ @action
zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) {
if (this.Document._isGroup) return;
- this._panZoomTransition = transitionTime;
+ this.setPanZoomTransition(transitionTime);
const screenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
this.layoutDoc[this.scaleFieldKey] = scale;
const newScreenXY = this.getTransform().inverse().transformPoint(docpt[0], docpt[1]);
const scrDelta = { x: screenXY[0] - newScreenXY[0], y: screenXY[1] - newScreenXY[1] };
const newpan = this.getTransform().transformDirection(scrDelta.x, scrDelta.y);
- this.layoutDoc._panX = NumCast(this.layoutDoc._panX) - newpan[0];
- this.layoutDoc._panY = NumCast(this.layoutDoc._panY) - newpan[1];
- return new Promise<number>(res => setTimeout(() => res(runInAction(() => (this._panZoomTransition = 0))), this._panZoomTransition)); // set transition to be smooth, then reset
+ this.layoutDoc[this.panXFieldKey] = NumCast(this.layoutDoc[this.panXFieldKey]) - newpan[0];
+ this.layoutDoc[this.panYFieldKey] = NumCast(this.layoutDoc[this.panYFieldKey]) - newpan[1];
}
- _focusCount = 0;
- focusDocument = (doc: Doc, options: DocFocusOptions) => {
- const state = HistoryUtil.getState();
-
- // TODO This technically isn't correct if type !== "doc", as
- // currently nothing is done, but we should probably push a new state
- if (state.type === 'doc' && this.Document._panX !== undefined && this.Document._panY !== undefined) {
- const init = state.initializers![this.Document[Id]];
- if (!init) {
- state.initializers![this.Document[Id]] = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY) };
- HistoryUtil.pushState(state);
- } else if (init.panX !== this.Document._panX || init.panY !== this.Document._panY) {
- init.panX = NumCast(this.Document._panX);
- init.panY = NumCast(this.Document._panY);
- HistoryUtil.pushState(state);
- }
- }
- // if (SelectionManager.Views().length !== 1 || SelectionManager.Views()[0].Document !== doc) {
- // SelectionManager.DeselectAll();
- // }
- if (this.props.Document.scrollHeight || this.props.Document.scrollTop !== undefined || this.props.Document.currentTimecode !== undefined || doc.type === DocumentType.MARKER) {
- this.props.focus(doc, options);
- } else {
- const xfToCollection = options?.docTransform ?? Transform.Identity();
- const savedState = { panX: NumCast(this.Document._panX), panY: NumCast(this.Document._panY), scale: options?.willPanZoom ? this.Document[this.scaleFieldKey] : undefined };
- const newState = HistoryUtil.getState();
- const cantTransform = (this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc;
- const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willPanZoom) ? savedState : this.calculatePanIntoView(doc, xfToCollection, options?.willPanZoom ? options?.zoomScale || 0.75 : undefined);
- if (!cantTransform) {
- // only pan and zoom to focus on a document if the document is not an annotation in an annotation overlay collection
- newState.initializers![this.Document[Id]] = { panX, panY };
- HistoryUtil.pushState(newState);
- }
- // focus on the document in the collection
- const didMove = !cantTransform && !doc.z && (panX !== savedState.panX || panY !== savedState.panY || scale !== savedState.scale);
- const focusSpeed = options?.instant ? 0 : didMove ? options.zoomTime ?? 500 : 0;
- // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
- if (didMove) {
- options.zoomScale && scale && (this.Document[this.scaleFieldKey] = scale);
- this.setPan(panX, panY, focusSpeed, true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
- }
-
- const focusCount = ++this._focusCount;
- const startTime = Date.now();
- // focus on this collection within its parent view. the parent view after focusing determines whether to reset the view change within the collection
- const endFocus = async (moved: boolean) => {
- doc.hidden && Doc.UnHighlightDoc(doc);
- const resetView = options?.afterFocus ? await options?.afterFocus(moved) : ViewAdjustment.doNothing;
- if (resetView) {
- const restoreState = savedState;
- if (typeof restoreState !== 'boolean') {
- this.Document._panX = restoreState.panX;
- this.Document._panY = restoreState.panY;
- this.Document[this.scaleFieldKey] = restoreState.scale;
- }
- }
- this._focusCount === focusCount && didMove && runInAction(() => (this._panZoomTransition = 0));
- return resetView;
- };
- const xf = !cantTransform
- ? Transform.Identity()
- : this.props.isAnnotationOverlay
- ? new Transform(NumCast(this.rootDoc.x), NumCast(this.rootDoc.y), this.rootDoc[WidthSym]() / Doc.NativeWidth(this.rootDoc))
- : new Transform(NumCast(this.rootDoc.x) + this.rootDoc[WidthSym]() / 2 - NumCast(this.rootDoc._panX), NumCast(this.rootDoc.y) + this.rootDoc[HeightSym]() / 2 - NumCast(this.rootDoc._panY), 1);
-
- this.props.focus(!cantTransform ? this.rootDoc : doc, {
- ...options,
- docTransform: xf,
- afterFocus: (didFocus: boolean) => new Promise<ViewAdjustment>(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))),
- });
- }
- };
-
calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => {
const layoutdoc = Doc.Layout(doc);
const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y));
const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]());
const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] };
- if (scale) {
+ if (scale !== undefined) {
const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context
- const newScale = Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
+ const newScale =
+ scale === 0
+ ? NumCast(this.layoutDoc[this.scaleFieldKey])
+ : Math.min(maxZoom, (1 / (this.nativeDimScaling || 1)) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height)));
return {
panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2,
panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2,
@@ -1237,10 +1219,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const panelWidth = this.props.isAnnotationOverlay ? this.nativeWidth : this.props.PanelWidth();
const panelHeight = this.props.isAnnotationOverlay ? this.nativeHeight : this.props.PanelHeight();
- const pw = panelWidth / NumCast(this.layoutDoc._viewScale, 1);
- const ph = panelHeight / NumCast(this.layoutDoc._viewScale, 1);
- const cx = NumCast(this.layoutDoc._panX) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
- const cy = NumCast(this.layoutDoc._panY) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
+ const pw = panelWidth / NumCast(this.layoutDoc._freeform_scale, 1);
+ const ph = panelHeight / NumCast(this.layoutDoc._freeform_scale, 1);
+ const cx = NumCast(this.layoutDoc[this.panXFieldKey]) + (this.props.isAnnotationOverlay ? pw / 2 : 0);
+ const cy = NumCast(this.layoutDoc[this.panYFieldKey]) + (this.props.isAnnotationOverlay ? ph / 2 : 0);
const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 };
const maxYShift = Math.max(0, screen.bot - screen.top - (bounds.bot - bounds.top));
const phborder = bounds.top < screen.top || bounds.bot > screen.bot ? Math.min(ph / 10, maxYShift / 2) : 0;
@@ -1252,8 +1234,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
}
return {
- panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panX) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
- panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc._panY) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
+ panX: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panXFieldKey]) : cx) + Math.min(0, bounds.left - pw / 10 - screen.left) + Math.max(0, bounds.right + pw / 10 - screen.right),
+ panY: (this.props.isAnnotationOverlay ? NumCast(this.layoutDoc[this.panYFieldKey]) : cy) + Math.min(0, bounds.top - phborder - screen.top) + Math.max(0, bounds.bot + phborder - screen.bot),
};
};
@@ -1263,17 +1245,17 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => {
const docView = fieldProps.DocumentView?.();
- if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._singleLine) && ['Tab', 'Enter'].includes(e.key)) {
+ if (docView && (e.metaKey || e.ctrlKey || e.altKey || docView.rootDoc._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) {
e.stopPropagation?.();
const below = !e.altKey && e.key !== 'Tab';
- const layoutKey = StrCast(docView.LayoutFieldKey);
+ const layout_fieldKey = StrCast(docView.LayoutFieldKey);
const newDoc = Doc.MakeCopy(docView.rootDoc, true);
const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)];
newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined;
if (below) newDoc.y = NumCast(docView.rootDoc.y) + NumCast(docView.rootDoc._height) + 10;
else newDoc.x = NumCast(docView.rootDoc.x) + NumCast(docView.rootDoc._width) + 10;
- if (layoutKey !== 'layout' && docView.rootDoc[layoutKey] instanceof Doc) {
- newDoc[layoutKey] = docView.rootDoc[layoutKey];
+ if (layout_fieldKey !== 'layout' && docView.rootDoc[layout_fieldKey] instanceof Doc) {
+ newDoc[layout_fieldKey] = docView.rootDoc[layout_fieldKey];
}
Doc.GetProto(newDoc).text = undefined;
FormattedTextBox.SelectOnLoad = newDoc[Id];
@@ -1282,8 +1264,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
};
pointerEvents = () => {
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
- const pointerEvents =
- this.props.isContentActive() === false ? 'none' : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
+ const pointerEvents = DocumentDecorations.Instance.Interacting
+ ? 'none'
+ : this.props.childPointerEvents ?? (this.props.viewDefDivClick || (engine === computePassLayout.name && !this.props.isSelected(true)) ? 'none' : this.props.pointerEvents?.());
return pointerEvents;
};
getChildDocView(entry: PoolData) {
@@ -1298,12 +1281,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
replica={entry.replica}
suppressSetHeight={this.layoutEngine ? true : false}
renderCutoffProvider={this.renderCutoffProvider}
- ContainingCollectionView={this.props.CollectionView}
- ContainingCollectionDoc={this.props.Document}
CollectionFreeFormView={this}
LayoutTemplate={childLayout.z ? undefined : this.props.childLayoutTemplate}
LayoutTemplateString={childLayout.z ? undefined : this.props.childLayoutString}
rootSelected={childData ? this.rootSelected : returnFalse}
+ waitForDoubleClickToClick={this.props.waitForDoubleClickToClick}
onClick={this.onChildClickHandler}
onKey={this.onKeyDown}
onDoubleClick={this.onChildDoubleClickHandler}
@@ -1315,8 +1297,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
docRangeFilters={this.childDocRangeFilters}
searchFilterDocs={this.searchFilterDocs}
isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
- isContentActive={emptyFunction}
- focus={this.focusDocument}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
+ focus={this.Document._isGroup ? this.groupFocus : this.isAnnotationOverlay ? this.props.focus : this.focus}
addDocTab={this.addDocTab}
addDocument={this.props.addDocument}
removeDocument={this.props.removeDocument}
@@ -1330,23 +1312,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
sizeProvider={this.childSizeProvider}
dropAction={StrCast(this.props.Document.childDropAction) as dropActionType}
bringToFront={this.bringToFront}
- showTitle={this.props.childShowTitle}
- dontScaleFilter={this.props.dontScaleFilter}
+ layout_showTitle={this.props.childlayout_showTitle}
dontRegisterView={this.props.dontRenderDocuments || this.props.dontRegisterView}
pointerEvents={this.pointerEvents}
- rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
+ //rotation={this.props.styleProvider?.(childLayout, this.props, StyleProp.JitterRotation) || 0}
//fitContentsToBox={this.props.fitContentsToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this
/>
);
}
addDocTab = action((doc: Doc, where: OpenWhere) => {
+ if (this.props.isAnnotationOverlay) return this.props.addDocTab(doc, where);
switch (where) {
case OpenWhere.inParent:
return this.props.addDocument?.(doc) || false;
case OpenWhere.inParentFromScreen:
- const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.context);
+ const docContext = DocCast((doc instanceof Doc ? doc : doc?.[0])?.embedContainer);
return (
- (this.props.addDocument?.(
+ (this.addDocument?.(
(doc instanceof Doc ? [doc] : doc).map(doc => {
const pt = this.getTransform().transformPoint(NumCast(doc.x), NumCast(doc.y));
doc.x = pt[0];
@@ -1357,32 +1339,43 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
(!docContext || this.props.removeDocument?.(docContext))) ||
false
);
- case OpenWhere.inPlace:
- if (this.layoutDoc.isInPlaceContainer) {
- this.dataDoc[this.props.fieldKey] = new List<Doc>(doc instanceof Doc ? [doc] : doc);
+ case undefined:
+ case OpenWhere.lightbox:
+ if (this.layoutDoc._isLightbox) {
+ this._lightboxDoc = doc;
+ return true;
+ }
+ if (this.childDocList?.includes(doc)) {
+ if (doc.hidden) doc.hidden = false;
return true;
}
}
return this.props.addDocTab(doc, where);
});
+ @observable _lightboxDoc: Opt<Doc>;
getCalculatedPositions(params: { pair: { layout: Doc; data?: Doc }; index: number; collection: Doc }): PoolData {
- const curFrame = Cast(this.Document._currentFrame, 'number');
const childDoc = params.pair.layout;
const childDocLayout = Doc.Layout(childDoc);
+ const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values
+ const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed
const { z, zIndex } = childDoc;
- const { backgroundColor, color } = curFrame === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, curFrame);
- const { x, y, opacity } = curFrame === undefined ? { x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, curFrame);
+ const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber);
+ const { x, y, _width, _height, opacity, _rotation } =
+ layoutFrameNumber === undefined
+ ? { _width: Cast(childDocLayout._width, 'number'), _height: Cast(childDocLayout._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this.props.childOpacity?.() }
+ : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber);
return {
x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x),
y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y),
z: Cast(z, 'number'),
+ rotation: Cast(_rotation, 'number'),
color: Cast(color, 'string') ? StrCast(color) : this.props.styleProvider?.(childDoc, this.props, StyleProp.Color),
- backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.props.styleProvider?.(childDoc, this.props, StyleProp.BackgroundColor),
- opacity: this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.props.styleProvider?.(childDoc, this.props, StyleProp.Opacity),
+ backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.getClusterColor(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: Cast(childDocLayout._width, 'number'),
- height: Cast(childDocLayout._height, 'number'),
+ width: _width,
+ height: _height,
transition: StrCast(childDocLayout.dataTransition),
pair: params.pair,
replica: '',
@@ -1479,7 +1472,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const newPool = new Map<string, PoolData>();
// prettier-ignore
switch (this.layoutEngine) {
- case computePassLayout.name : return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) };
+ case computePassLayout.name : return { newPool, computedElementData: this.doEngineLayout(newPool, computePassLayout) };
case computeTimelineLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) };
case computePivotLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) };
case computeStarburstLayout.name: return { newPool, computedElementData: this.doEngineLayout(newPool, computeStarburstLayout) };
@@ -1503,6 +1496,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
newPos.x !== lastPos.x ||
newPos.y !== lastPos.y ||
newPos.z !== lastPos.z ||
+ newPos.rotation !== lastPos.rotation ||
newPos.zIndex !== lastPos.zIndex ||
newPos.transition !== lastPos.transition
) {
@@ -1518,70 +1512,36 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const elements = computedElementData.slice();
Array.from(newPool.entries())
.filter(entry => this.isCurrent(entry[1].pair.layout))
- .forEach((entry, i) =>
+ .forEach((entry, i) => {
+ const childData: ViewDefBounds = this.childDataProvider(entry[1].pair.layout, entry[1].replica);
+ const childSize = this.childSizeProvider(entry[1].pair.layout, entry[1].replica);
elements.push({
ele: this.getChildDocView(entry[1]),
- bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica),
- inkMask: BoolCast(entry[1].pair.layout.isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
- })
- );
+ bounds: childData.opacity === 0 ? { ...childData, width: 0, height: 0 } : { ...childData, width: childSize.width, height: childSize.height },
+ inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1,
+ });
+ });
if (this.props.isAnnotationOverlay && this.props.Document[this.scaleFieldKey]) {
// don't zoom out farther than 1-1 if it's a bounded item (image, video, pdf), otherwise don't allow zooming in closer than 1-1 if it's a text sidebar
- if (this.props.scaleField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
+ if (this.props.viewField) this.props.Document[this.scaleFieldKey] = Math.min(1, this.zoomScaling());
else this.props.Document[this.scaleFieldKey] = Math.max(1, this.zoomScaling()); // NumCast(this.props.Document[this.scaleFieldKey]));
}
- this.Document._useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
+ this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true);
return elements;
}
- @action
- setViewSpec = (anchor: Doc, preview: boolean) => {
- if (preview) {
- this._focusFilters = StrListCast(Doc.GetProto(anchor).docFilters);
- this._focusRangeFilters = StrListCast(Doc.GetProto(anchor).docRangeFilters);
- } else {
- if (anchor.docFilters) {
- this.layoutDoc._docFilters = new List<string>(StrListCast(anchor.docFilters));
- }
- if (anchor.docRangeFilters) {
- this.layoutDoc._docRangeFilters = new List<string>(StrListCast(anchor.docRangeFilters));
- }
- }
- return 0;
- };
+ getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+ // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type)
+ const anchor = Docs.Create.CollectionConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presTransition: 500, annotationOn: this.rootDoc });
+ PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document._isGroup, type_collection: true, filters: true } }, this.rootDoc);
- @action
- scrollFocus = (anchor: Doc, options: DocFocusOptions) => {
- const focusSpeed = options.instant ? 0 : options.zoomTime ?? 500;
- return PresBox.restoreTargetDocView(
- this.props.DocumentView?.(), //
- { pinDocLayout: BoolCast(anchor.presPinDocLayout) },
- anchor,
- focusSpeed,
- {
- pannable: anchor.presPinData ? true : false,
- }
- )
- ? focusSpeed
- : undefined;
- }; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document
-
- getAnchor = (addAsAnnotation: boolean) => {
- if (this.props.Document.annotationOn) {
- return this.rootDoc;
- }
- const anchor = Docs.Create.TextanchorDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._viewType), presTransition: 500, annotationOn: this.rootDoc });
- PresBox.pinDocView(anchor, { pinData: { pannable: true } }, this.rootDoc);
- const proto = Doc.GetProto(anchor);
- proto[ViewSpecPrefix + '_viewType'] = this.layoutDoc._viewType;
- proto.docFilters = ObjectField.MakeCopy(this.layoutDoc.docFilters as ObjectField) || new List<string>([]);
if (addAsAnnotation) {
- if (Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), null) !== undefined) {
- Cast(this.dataDoc[this.props.fieldKey + '-annotations'], listSpec(Doc), []).push(anchor);
+ if (Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) {
+ Cast(this.dataDoc[this.props.fieldKey + '_annotations'], listSpec(Doc), []).push(anchor);
} else {
- this.dataDoc[this.props.fieldKey + '-annotations'] = new List<Doc>([anchor]);
+ this.dataDoc[this.props.fieldKey + '_annotations'] = new List<Doc>([anchor]);
}
}
return anchor;
@@ -1589,6 +1549,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
@action
componentDidMount() {
+ this.props.setContentView?.(this);
super.componentDidMount?.();
this.props.setBrushViewer?.(this.brushView);
setTimeout(
@@ -1596,7 +1557,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
this._firstRender = false;
this._disposers.groupBounds = reaction(
() => {
- if (this.props.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
+ if (this.Document._isGroup && this.childDocs.length === this.childDocList?.length) {
const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: cd[WidthSym](), height: cd[HeightSym]() }));
return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding));
}
@@ -1605,7 +1566,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
cbounds => {
if (cbounds) {
const c = [NumCast(this.layoutDoc.x) + this.layoutDoc[WidthSym]() / 2, NumCast(this.layoutDoc.y) + this.layoutDoc[HeightSym]() / 2];
- const p = [NumCast(this.layoutDoc._panX), NumCast(this.layoutDoc._panY)];
+ const p = [NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(this.layoutDoc[this.panYFieldKey])];
const pbounds = {
x: cbounds.x - p[0] + c[0],
y: cbounds.y - p[1] + c[1],
@@ -1615,8 +1576,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
if (Number.isFinite(pbounds.r - pbounds.x) && Number.isFinite(pbounds.b - pbounds.y)) {
this.layoutDoc._width = pbounds.r - pbounds.x;
this.layoutDoc._height = pbounds.b - pbounds.y;
- this.layoutDoc._panX = (cbounds.r + cbounds.x) / 2;
- this.layoutDoc._panY = (cbounds.b + cbounds.y) / 2;
+ this.layoutDoc[this.panXFieldKey] = (cbounds.r + cbounds.x) / 2;
+ this.layoutDoc[this.panYFieldKey] = (cbounds.b + cbounds.y) / 2;
this.layoutDoc.x = pbounds.x;
this.layoutDoc.y = pbounds.y;
}
@@ -1649,7 +1610,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
} else {
const canvas = oldDiv;
const img = document.createElement('img'); // create a Image Element
- img.src = canvas.toDataURL(); //image source
+ try {
+ img.src = canvas.toDataURL(); //image source
+ } catch (e) {
+ console.log(e);
+ }
img.style.width = canvas.style.width;
img.style.height = canvas.style.height;
const newCan = newDiv as HTMLCanvasElement;
@@ -1676,8 +1641,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
'',
(iconFile, nativeWidth, nativeHeight) => {
this.dataDoc.icon = new ImageField(iconFile);
- this.dataDoc['icon-nativeWidth'] = nativeWidth;
- this.dataDoc['icon-nativeHeight'] = nativeHeight;
+ this.dataDoc['icon_nativeWidth'] = nativeWidth;
+ this.dataDoc['icon_nativeHeight'] = nativeHeight;
}
);
@@ -1703,7 +1668,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const nativeHeight = height;
return CreateImage(Utils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight)
.then(async (data_url: any) => {
- const returnedFilename = await VideoBox.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
+ const returnedFilename = await Utils.convertDataUri(data_url, filename, noSuffix, replaceRootFilename);
cb(returnedFilename as string, nativeWidth, nativeHeight);
})
.catch(function (error: any) {
@@ -1734,8 +1699,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
if (deltaX !== 0 || deltaY !== 0) {
- this.Document._panY = NumCast(this.Document._panY) + deltaY / 2;
- this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+ this.Document[this.panYFieldKey] = NumCast(this.Document[this.panYFieldKey]) + deltaY / 2;
+ this.Document[this.panXFieldKey] = NumCast(this.Document[this.panXFieldKey]) + deltaX / 2;
}
}
e.stopPropagation();
@@ -1759,8 +1724,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const height = Math.max(...docs.map(doc => NumCast(doc._height))) + 20;
const dim = Math.ceil(Math.sqrt(docs.length));
docs.forEach((doc, i) => {
- doc.x = NumCast(this.Document._panX) + (i % dim) * width - (width * dim) / 2;
- doc.y = NumCast(this.Document._panY) + Math.floor(i / dim) * height - (height * dim) / 2;
+ doc.x = NumCast(this.Document[this.panXFieldKey]) + (i % dim) * width - (width * dim) / 2;
+ doc.y = NumCast(this.Document[this.panYFieldKey]) + Math.floor(i / dim) * height - (height * dim) / 2;
});
};
@@ -1775,20 +1740,25 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
appearanceItems.push({
description: 'Reset View',
event: () => {
- this.props.Document._panX = this.props.Document._panY = 0;
+ this.props.Document[this.panXFieldKey] = this.props.Document[this.panYFieldKey] = 0;
this.props.Document[this.scaleFieldKey] = 1;
},
icon: 'compress-arrows-alt',
});
+ !Doc.noviceMode &&
+ appearanceItems.push({
+ description: 'Toggle auto arrange',
+ event: () => (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange),
+ icon: 'compress-arrows-alt',
+ });
+ if (this.props.setContentView === emptyFunction) {
+ !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' });
+ return;
+ }
!Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: 'Reset default note style', event: () => (Doc.UserDoc().defaultTextLayout = undefined), icon: 'eye' });
- appearanceItems.push({
- description: `${this.fitContentsToBox ? 'Make Zoomable' : 'Scale to Window'}`,
- event: () => (this.Document._fitContentsToBox = !this.fitContentsToBox),
- icon: !this.fitContentsToBox ? 'expand-arrows-alt' : 'compress-arrows-alt',
- });
appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, { pinViewport: MarqueeView.CurViewBounds(this.rootDoc, this.props.PanelWidth(), this.props.PanelHeight()) }), icon: 'map-pin' });
- //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" });
- appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
+ !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: 'compress-arrows-alt' });
+ this.props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' });
this.props.Document._isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: () => this.transcribeStrokes(false), icon: 'font' });
@@ -1802,7 +1772,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!Doc.noviceMode
? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? 'Hide' : 'Show') + ' Snap Lines', event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: 'compress-arrows-alt' })
: null;
- !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._useClusters), icon: 'braille' }) : null;
+ !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null;
!viewctrls && ContextMenu.Instance.addItem({ description: 'UI Controls...', subitems: viewCtrlItems, icon: 'eye' });
const options = ContextMenu.Instance.findByDescription('Options...');
@@ -1817,31 +1787,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
!options && ContextMenu.Instance.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' });
const mores = ContextMenu.Instance.findByDescription('More...');
const moreItems = mores && 'subitems' in mores ? mores.subitems : [];
- if (!Doc.noviceMode) {
- e.persist();
- moreItems.push({ description: 'Export collection', icon: 'download', event: async () => Doc.Zip(this.props.Document) });
- moreItems.push({ description: 'Import exported collection', icon: 'upload', event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) });
- }
!mores && ContextMenu.Instance.addItem({ description: 'More...', subitems: moreItems, icon: 'eye' });
};
- importDocument = (x: number, y: number) => {
- const input = document.createElement('input');
- input.type = 'file';
- input.accept = '.zip';
- input.onchange = _e => {
- input.files &&
- Doc.importDocument(input.files[0]).then(doc => {
- if (doc instanceof Doc) {
- const [xx, yy] = this.getTransform().transformPoint(x, y);
- (doc.x = xx), (doc.y = yy);
- this.props.addDocument?.(doc);
- }
- });
- };
- input.click();
- };
-
@undoBatch
@action
transcribeStrokes = (math: boolean) => {
@@ -1852,7 +1800,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const lines = text.split('\n');
const height = 30 + 15 * lines.length;
- this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
+ this.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height }));
}
}
};
@@ -1890,20 +1838,20 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
incrementalRender = action(() => {
if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
- const unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
+ const layout_unrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id]));
const loadIncrement = 5;
- for (var i = 0; i < Math.min(unrendered.length, loadIncrement); i++) {
- this._renderCutoffData.set(unrendered[i][Id] + '', true);
+ for (var i = 0; i < Math.min(layout_unrendered.length, loadIncrement); i++) {
+ this._renderCutoffData.set(layout_unrendered[i][Id] + '', true);
}
}
this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
});
- children = () => {
+ get children() {
this.incrementalRender();
- const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : [];
+ const children = typeof this.props.children === 'function' ? ((this.props.children as any)() as JSX.Element[]) : this.props.children ? [this.props.children] : [];
return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
- };
+ }
@computed get placeholder() {
return (
@@ -1940,13 +1888,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
r?.addEventListener('dashDragAutoScroll', this.onDragAutoScroll as any);
}}
style={{ opacity: this.props.dontRenderDocuments ? 0.7 : undefined }}>
- {this.layoutDoc._backgroundGridShow ? (
+ {this.layoutDoc._freeform_backgroundGrid ? (
<div>
<CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render whenn taking snapshot of a dashboard and the background grid is on!!?
PanelWidth={this.props.PanelWidth}
PanelHeight={this.props.PanelHeight}
panX={this.panX}
panY={this.panY}
+ nativeDimScaling={this.nativeDim}
zoomScaling={this.zoomScaling}
layoutDoc={this.layoutDoc}
isAnnotationOverlay={this.isAnnotationOverlay}
@@ -1963,7 +1912,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
zoomScaling={this.zoomScaling}
presPaths={this.showPresPaths}
presPinView={BoolCast(this.Document.presPinView)}
- transition={this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', null)}
+ 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>
@@ -1979,8 +1928,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
const nh = this.nativeHeight;
const hscale = nh ? this.props.PanelHeight() / nh : 1;
const wscale = nw ? this.props.PanelWidth() / nw : 1;
- return wscale < hscale || this.layoutDoc.fitWidth ? wscale : hscale;
+ return wscale < hscale || this.layoutDoc.layout_fitWidth ? wscale : hscale;
}
+ nativeDim = () => this.nativeDimScaling;
private groupDropDisposer?: DragManager.DragDropDisposer;
protected createGroupEventsTarget = (ele: HTMLDivElement) => {
@@ -2007,13 +1957,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
1000
);
};
-
+ lightboxPanelWidth = () => Math.max(0, this.props.PanelWidth() - 30);
+ lightboxPanelHeight = () => Math.max(0, this.props.PanelHeight() - 30);
+ lightboxScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-15, -15);
+ onPassiveWheel = (e: WheelEvent) => {
+ const docHeight = NumCast(this.rootDoc[Doc.LayoutFieldKey(this.rootDoc) + '_nativeHeight'], this.nativeHeight);
+ const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this.props.PanelHeight() / this.nativeDimScaling;
+ this.props.isSelected() && !scrollable && e.preventDefault();
+ };
+ _oldWheel: any;
render() {
TraceMobx();
return (
<div
- className={'collectionfreeformview-container'}
- ref={this.createDashEventsTarget}
+ className="collectionfreeformview-container"
+ ref={r => {
+ this.createDashEventsTarget(r);
+ this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel);
+ this._oldWheel = r;
+ // prevent wheel events from passivly propagating up through containers
+ r?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ }}
onWheel={this.onPointerWheel}
onClick={this.onClick}
onPointerDown={this.onPointerDown}
@@ -2022,46 +1986,71 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
onDragOver={e => e.preventDefault()}
onContextMenu={this.onContextMenu}
style={{
- pointerEvents:
- this.props.Document.type === DocumentType.MARKER
- ? 'none' // bcz: ugh.. this is here to prevent markers, which render as freeform views, from grabbing events -- need a better approach.
- : SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement())
- ? 'all'
- : (this.props.pointerEvents?.() as any),
+ pointerEvents: SnappingManager.GetIsDragging() && this.childDocs.includes(DragManager.docsBeingDragged.lastElement()) ? 'all' : (this.props.pointerEvents?.() as any),
+ textAlign: this.isAnnotationOverlay ? 'initial' : undefined,
transform: `scale(${this.nativeDimScaling || 1})`,
width: `${100 / (this.nativeDimScaling || 1)}%`,
- height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.nativeDimScaling || 1)}%`, // : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ height: this.props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`,
}}>
- {this._firstRender ? this.placeholder : this.marqueeView}
- {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
-
- {/* // uncomment to show snap lines */}
- <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
- <svg style={{ width: '100%', height: '100%' }}>
- {this._hLines?.map(l => (
- <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
- ))}
- {this._vLines?.map(l => (
- <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
- ))}
- </svg>
- </div>
+ {this._lightboxDoc ? (
+ <div style={{ padding: 15, width: '100%', height: '100%' }}>
+ <DocumentView
+ {...this.props}
+ Document={this._lightboxDoc}
+ DataDoc={undefined}
+ PanelWidth={this.lightboxPanelWidth}
+ PanelHeight={this.lightboxPanelHeight}
+ NativeWidth={returnZero}
+ NativeHeight={returnZero}
+ onClick={this.onChildClickHandler}
+ onKey={this.onKeyDown}
+ onDoubleClick={this.onChildDoubleClickHandler}
+ onBrowseClick={this.onBrowseClickHandler}
+ docFilters={this.childDocFilters}
+ docRangeFilters={this.childDocRangeFilters}
+ searchFilterDocs={this.searchFilterDocs}
+ isDocumentActive={this.props.childDocumentsActive?.() ? this.props.isDocumentActive : this.isContentActive}
+ isContentActive={this.props.childContentsActive ?? emptyFunction}
+ addDocTab={this.addDocTab}
+ ScreenToLocalTransform={this.lightboxScreenToLocal}
+ fitContentsToBox={undefined}
+ focus={this.focus}
+ />
+ </div>
+ ) : (
+ <>
+ {this._firstRender ? this.placeholder : this.marqueeView}
+ {this.props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
+
+ {/* // uncomment to show snap lines */}
+ <div className="snapLines" style={{ position: 'absolute', top: 0, left: 0, width: '100%', height: '100%', pointerEvents: 'none' }}>
+ <svg style={{ width: '100%', height: '100%' }}>
+ {this._hLines?.map(l => (
+ <line x1="0" y1={l} x2="1000" y2={l} stroke="black" />
+ ))}
+ {this._vLines?.map(l => (
+ <line y1="0" x1={l} y2="1000" x2={l} stroke="black" />
+ ))}
+ </svg>
+ </div>
- {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
- <div
- className="collectionFreeForm-groupDropper"
- ref={this.createGroupEventsTarget}
- style={{
- width: this.ChildDrag ? '10000' : '100%',
- height: this.ChildDrag ? '10000' : '100%',
- left: this.ChildDrag ? '-5000' : 0,
- top: this.ChildDrag ? '-5000' : 0,
- position: 'absolute',
- background: '#0009930',
- pointerEvents: 'all',
- }}
- />
- ) : null}
+ {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ? (
+ <div
+ className="collectionFreeForm-groupDropper"
+ ref={this.createGroupEventsTarget}
+ style={{
+ width: this.ChildDrag ? '10000' : '100%',
+ height: this.ChildDrag ? '10000' : '100%',
+ left: this.ChildDrag ? '-5000' : 0,
+ top: this.ChildDrag ? '-5000' : 0,
+ position: 'absolute',
+ background: '#0009930',
+ pointerEvents: 'all',
+ }}
+ />
+ ) : null}
+ </>
+ )}
</div>
);
}
@@ -2085,7 +2074,8 @@ interface CollectionFreeFormViewPannableContentsProps {
transform: () => string;
zoomScaling: () => number;
viewDefDivClick?: ScriptField;
- children: () => JSX.Element[];
+ children?: React.ReactNode | undefined;
+ //children: () => JSX.Element[];
transition?: string;
presPaths: () => JSX.Element | null;
presPinView?: boolean;
@@ -2179,7 +2169,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
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
//willChange: "transform"
}}>
- {this.props.children()}
+ {this.props.children}
{!this.props.brushView.width ? null : (
<div
className="collectionFreeFormView-brushView"
@@ -2207,6 +2197,7 @@ interface CollectionFreeFormViewBackgroundGridProps {
PanelWidth: () => number;
PanelHeight: () => number;
isAnnotationOverlay?: boolean;
+ nativeDimScaling: () => number;
zoomScaling: () => number;
layoutDoc: Doc;
cachedCenteringShiftX: number;
@@ -2224,10 +2215,10 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
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() + 2 * renderGridSpace;
- const h = this.props.PanelHeight() + 2 * renderGridSpace;
+ const w = this.props.PanelWidth() / this.props.nativeDimScaling() + 2 * renderGridSpace;
+ const h = this.props.PanelHeight() / this.props.nativeDimScaling() + 2 * renderGridSpace;
const strokeStyle = Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'rgba(255,255,255,0.5)' : 'rgba(0, 0,0,0.5)';
- return (
+ return !this.props.nativeDimScaling() ? null : (
<canvas
className="collectionFreeFormView-grid"
width={w}
@@ -2262,20 +2253,24 @@ class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFor
}
export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY: number) {
+ const browseTransitionTime = 500;
SelectionManager.DeselectAll();
- dv.props.focus(dv.props.Document, {
- willPanZoom: true,
- afterFocus: async didMove => {
- if (!didMove) {
- const selfFfview = dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
- const parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
- const ffview = selfFfview && selfFfview.rootDoc[selfFfview.props.scaleField || '_viewScale'] !== 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
- await ffview?.zoomSmoothlyAboutPt(ffview.getTransform().transformPoint(clientX, clientY), 0.5);
- }
- return ViewAdjustment.doNothing;
- },
- });
- Doc.linkFollowHighlight(dv?.props.Document, false);
+ if (
+ dv.props.focus(dv.props.Document, {
+ willZoomCentered: true,
+ zoomScale: 0.8,
+ zoomTime: browseTransitionTime,
+ }) === undefined
+ ) {
+ const selfFfview = !dv.rootDoc._isGroup && dv.ComponentView instanceof CollectionFreeFormView ? dv.ComponentView : undefined;
+ let parFfview = dv.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView;
+ 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), 0.5, browseTransitionTime);
+ Doc.linkFollowHighlight(dv?.props.Document, false);
+ } else {
+ DocumentManager.Instance.showDocument(dv.rootDoc, { zoomScale: 0.8, willZoomCentered: true });
+ }
}
ScriptingGlobals.add(CollectionBrowseClick);
ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) {
@@ -2289,9 +2284,27 @@ ScriptingGlobals.add(function curKeyFrame(readOnly: boolean) {
if (readOnly) return selView[0].ComponentView?.getKeyFrameEditing?.() ? Colors.MEDIUM_BLUE : 'transparent';
runInAction(() => selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.()));
});
-ScriptingGlobals.add(function pinWithView(readOnly: boolean, pinDocContent: boolean) {
- !readOnly &&
- SelectionManager.Views().forEach(view =>
- TabDocView.PinDoc(view.rootDoc, { currentFrame: Cast(view.rootDoc.currentFrame, 'number', null), pinDocContent, pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()) })
- );
+ScriptingGlobals.add(function pinWithView(pinContent: boolean) {
+ SelectionManager.Views().forEach(view =>
+ view.props.pinToPres(view.rootDoc, {
+ currentFrame: Cast(view.rootDoc.currentFrame, 'number', null),
+ pinData: {
+ poslayoutview: pinContent,
+ dataview: pinContent,
+ },
+ pinViewport: MarqueeView.CurViewBounds(view.rootDoc, view.props.PanelWidth(), view.props.PanelHeight()),
+ })
+ );
+});
+ScriptingGlobals.add(function bringToFront() {
+ SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc));
+});
+ScriptingGlobals.add(function sendToBack(doc: Doc) {
+ SelectionManager.Views().forEach(view => view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.bringToFront(view.rootDoc, true));
+});
+ScriptingGlobals.add(function resetView() {
+ SelectionManager.Docs().forEach(doc => {
+ doc._freeform_panX = doc._freeform_panY = 0;
+ doc._freeform_scale = 1;
+ });
});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
index 9581563ce..c9168d40a 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -3,8 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@material-ui/core';
import { observer } from 'mobx-react';
import { unimplementedFunction } from '../../../../Utils';
-import { DocumentType } from '../../../documents/DocumentTypes';
-import { SelectionManager } from '../../../util/SelectionManager';
import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu';
@observer
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 0b7854926..641088675 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -19,7 +19,6 @@ import { undoBatch, UndoManager } from '../../../util/UndoManager';
import { ContextMenu } from '../../ContextMenu';
import { OpenWhere } from '../../nodes/DocumentView';
import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox';
-import { VideoBox } from '../../nodes/VideoBox';
import { pasteImageBitmap } from '../../nodes/WebBoxRenderer';
import { PreviewCursor } from '../../PreviewCursor';
import { SubCollectionViewProps } from '../CollectionSubView';
@@ -40,16 +39,7 @@ interface MarqueeViewProps {
nudge?: (x: number, y: number, nudgeTime?: number) => boolean;
ungroup?: () => void;
setPreviewCursor?: (func: (x: number, y: number, drag: boolean, hide: boolean) => void) => void;
- slowLoadDocuments: (
- files: File[] | string,
- options: DocumentOptions,
- generatedDocuments: Doc[],
- text: string,
- completed: ((doc: Doc[]) => void) | undefined,
- clientX: number,
- clientY: number,
- addDocument: (doc: Doc | Doc[]) => boolean
- ) => Promise<void>;
+ slowLoadDocuments: (files: File[] | string, options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => Promise<void>;
}
export interface MarqueeViewBounds {
@@ -62,8 +52,8 @@ export interface MarqueeViewBounds {
@observer
export class MarqueeView extends React.Component<SubCollectionViewProps & MarqueeViewProps> {
public static CurViewBounds(pinDoc: Doc, panelWidth: number, panelHeight: number) {
- const ps = NumCast(pinDoc._viewScale, 1);
- return { left: NumCast(pinDoc._panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
+ const ps = NumCast(pinDoc._freeform_scale, 1);
+ return { left: NumCast(pinDoc._freeform_panX) - panelWidth / 2 / ps, top: NumCast(pinDoc._freeform_panY) - panelHeight / 2 / ps, width: panelWidth / ps, height: panelHeight / ps };
}
private _commandExecuted = false;
@@ -112,7 +102,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._lassoPts = [];
};
- @undoBatch
@action
onKeyPress = (e: KeyboardEvent) => {
//make textbox and add it to this collection
@@ -120,8 +109,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const cm = ContextMenu.Instance;
const [x, y] = this.Transform.transformPoint(this._downX, this._downY);
if (e.key === '?') {
- cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), OpenWhere.addRight));
-
+ cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight));
cm.displayMenu(this._downX, this._downY, undefined, true);
e.stopPropagation();
} else if (e.key === 'u' && this.props.ungroup) {
@@ -167,7 +155,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
pasteImageBitmap((data: any, error: any) => {
error && console.log(error);
data &&
- VideoBox.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
+ Utils.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => {
this.props.Document['thumb-frozen'] = new ImageField(returnedfilename);
});
})
@@ -183,7 +171,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
e.stopPropagation();
} else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) {
FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : '';
- FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('live text batch');
+ FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note');
this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0));
e.stopPropagation();
}
@@ -206,7 +194,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
const rowProto = new Doc();
rowProto.title = rowProto.Id;
rowProto._width = 200;
- rowProto.isPrototype = true;
+ rowProto.isDataDoc = true;
for (let i = 1; i < ns.length - 1; i++) {
const values = ns[i].split('\t');
if (values.length === 1 && columns.length > 1) {
@@ -214,7 +202,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
continue;
}
const docDataProto = Doc.MakeDelegate(rowProto);
- docDataProto.isPrototype = true;
+ docDataProto.isDataDoc = true;
columns.forEach((col, i) => (docDataProto[columns[i]] = values.length > i ? (values[i].indexOf(Number(values[i]).toString()) !== -1 ? Number(values[i]) : values[i]) : undefined));
if (groupAttr) {
docDataProto._group = groupAttr;
@@ -238,6 +226,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onPointerDown = (e: React.PointerEvent): void => {
+ // if (this.props.pointerEvents?.() === 'none') return;
this._downX = this._lastX = e.clientX;
this._downY = this._lastY = e.clientY;
if (!(e.nativeEvent as any).marqueeHit) {
@@ -346,6 +335,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onClick = (e: React.MouseEvent): void => {
+ if (this.props.pointerEvents?.() === 'none') return;
if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
if (Doc.ActiveTool === InkTool.None) {
if (!(e.nativeEvent as any).marqueeHit) {
@@ -370,10 +360,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@undoBatch
@action
- delete = () => {
+ delete = (e?: React.PointerEvent<Element> | KeyboardEvent | undefined, hide?: boolean) => {
const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
- selected.forEach(doc => this.props.removeDocument?.(doc));
+ selected.forEach(doc => (hide ? (doc.hidden = true) : this.props.removeDocument?.(doc)));
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
@@ -387,21 +377,24 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
Doc.GetProto(doc).data = new List<Doc>(selected);
Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform';
!this.props.isAnnotationOverlay && Doc.AddFileOrphan(Doc.GetProto(doc));
- doc._panX = doc._panY = 0;
+ doc._freeform_panX = doc._freeform_panY = 0;
return doc;
})(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true));
- newCollection.system = undefined;
+ newCollection.isSystem = undefined;
newCollection._width = this.Bounds.width;
newCollection._height = this.Bounds.height;
newCollection._isGroup = makeGroup;
newCollection.forceActive = makeGroup;
+ newCollection.enableDragWhenActive = makeGroup;
newCollection.x = this.Bounds.left;
newCollection.y = this.Bounds.top;
- selected.forEach(d => (d.context = newCollection));
+ newCollection.layout_fitWidth = true;
+ selected.forEach(d => (d.embedContainer = newCollection));
this.hideMarquee();
return newCollection;
});
+ @undoBatch
@action
pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false);
@@ -422,25 +415,24 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
*/
@undoBatch
@action
- pinWithView = async () => {
- const doc = this.props.Document;
- TabDocView.PinDoc(doc, { pinViewport: this.Bounds });
+ pinWithView = () => {
+ TabDocView.PinDoc(this.props.Document, { pinViewport: this.Bounds });
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
};
@undoBatch
@action
- collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => {
- const selected = this.marqueeSelect(false);
+ collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => {
+ const selected = selection ?? this.marqueeSelect(false);
const activeFrame = selected.reduce((v, d) => v ?? Cast(d._activeFrame, 'number', null), undefined as number | undefined);
if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) {
this.props.removeDocument?.(selected);
}
const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group);
- newCollection._panX = this.Bounds.left + this.Bounds.width / 2;
- newCollection._panY = this.Bounds.top + this.Bounds.height / 2;
+ newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2;
+ newCollection._freeform_panY = this.Bounds.top + this.Bounds.height / 2;
newCollection._currentFrame = activeFrame;
this.props.addDocument?.(newCollection);
this.props.selectDocuments([newCollection]);
@@ -515,46 +507,46 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
};
@undoBatch
- @action
- summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => {
const selected = this.marqueeSelect(false).map(d => {
this.props.removeDocument?.(d);
d.x = NumCast(d.x) - this.Bounds.left;
d.y = NumCast(d.y) - this.Bounds.top;
return d;
});
- const summary = Docs.Create.TextDocument('', { backgroundColor: '#e2ad32', x: this.Bounds.left, y: this.Bounds.top, followLinkToggle: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: 'overview' });
+ const summary = Docs.Create.TextDocument('', {
+ backgroundColor: '#e2ad32',
+ x: this.Bounds.left,
+ y: this.Bounds.top,
+ followLinkToggle: true,
+ _width: 200,
+ _height: 200,
+ _layoutFitContentsToBox: true,
+ _layout_showSidebar: true,
+ title: 'overview',
+ });
const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' });
- DocUtils.MakeLink({ doc: summary }, { doc: portal }, 'summary of:summarized by', '');
+ DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' });
portal.hidden = true;
this.props.addDocument?.(portal);
this.props.addLiveTextDocument(summary);
MarqueeOptionsMenu.Instance.fadeOut(true);
- };
+ });
@action
- background = (e: KeyboardEvent | React.PointerEvent | undefined) => {
- const newCollection = this.getCollection([], undefined, undefined);
- this.props.addDocument?.(newCollection);
- MarqueeOptionsMenu.Instance.fadeOut(true);
- this.hideMarquee();
- setTimeout(() => this.props.selectDocuments([newCollection]));
- };
-
- @undoBatch
- marqueeCommand = action((e: KeyboardEvent) => {
+ marqueeCommand = (e: KeyboardEvent) => {
if (this._commandExecuted || (e as any).propagationIsStopped) {
return;
}
- if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd') {
+ if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd' || e.key === 'h') {
this._commandExecuted = true;
e.stopPropagation();
(e as any).propagationIsStopped = true;
- this.delete();
+ this.delete(e, e.key === 'h');
e.stopPropagation();
}
- if ('cbtsSpg'.indexOf(e.key) !== -1) {
+ if ('ctsSpg'.indexOf(e.key) !== -1) {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
@@ -562,7 +554,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (e.key === 'g') this.collection(e, true);
if (e.key === 'c' || e.key === 't') this.collection(e);
if (e.key === 's' || e.key === 'S') this.summary(e);
- if (e.key === 'b') this.background(e);
if (e.key === 'p') this.pileup(e);
this.cleanupInteractions(false);
}
@@ -572,7 +563,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
e.preventDefault();
this._lassoFreehand = !this._lassoFreehand;
}
- });
+ };
touchesLine(r1: { left: number; top: number; width: number; height: number }) {
for (const lassoPt of this._lassoPts) {
@@ -609,7 +600,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return false;
}
- marqueeSelect(selectBackgrounds: boolean = true) {
+ marqueeSelect(selectBackgrounds: boolean = false) {
const selection: Doc[] = [];
const selectFunc = (doc: Doc) => {
const layoutDoc = Doc.Layout(doc);