aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorNaafiyan Ahmed <naafiyan@gmail.com>2022-03-17 16:35:32 -0400
committerNaafiyan Ahmed <naafiyan@gmail.com>2022-03-17 16:35:32 -0400
commitcb5f0847b098d89a1390acf90579b3c7fbc5ac3e (patch)
tree0266608b61495b5903ff419fa26e04fb5fbeef0a /src/client/views/collections/collectionFreeForm
parentf1f341b53a1a49427cf7ef40afdcd95a9b0e4e9d (diff)
parent5c874c6829d9957696dfe61014173b6800c864df (diff)
Merge branch 'naafi-linking' of https://github.com/brown-dash/Dash-Web into naafi-linking
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx249
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx23
2 files changed, 163 insertions, 109 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index aeda71d01..e2ea81392 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -4,13 +4,12 @@ import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { DateField } from "../../../../fields/DateField";
import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc";
-import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas";
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 { createSchema, listSpec, makeInterface } from "../../../../fields/Schema";
+import { createSchema, listSpec } from "../../../../fields/Schema";
import { ScriptField } from "../../../../fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types";
import { TraceMobx } from "../../../../fields/util";
@@ -41,7 +40,6 @@ import { LightboxView } from "../../LightboxView";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView";
import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox";
-import { pageSchema } from "../../nodes/ImageBox";
import { PresBox } from "../../nodes/trails/PresBox";
import { StyleLayers, StyleProp } from "../../StyleProvider";
import { CollectionDockingView } from "../CollectionDockingView";
@@ -67,8 +65,6 @@ export const panZoomSchema = createSchema({
scrollHeight: "number" // this will be set when the collection is an annotation overlay for a PDF/Webpage
});
-type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>;
-const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema);
export type collectionFreeformViewProps = {
annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
@@ -81,7 +77,7 @@ export type collectionFreeformViewProps = {
};
@observer
-export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, Partial<collectionFreeformViewProps>>(PanZoomDocument) {
+export class CollectionFreeFormView extends CollectionSubView<Partial<collectionFreeformViewProps>>() {
public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive
private _lastNudge: any;
@@ -95,6 +91,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private _clusterDistance: number = 75;
private _hitCluster: number = -1;
private _disposers: { [name: string]: IReactionDisposer } = {};
+ private _renderCutoffData = observable.map<string, boolean>();
private _layoutPoolData = observable.map<string, PoolData>();
private _layoutSizeData = observable.map<string, { width?: number, height?: number }>();
private _cachedPool: Map<string, PoolData> = new Map();
@@ -109,6 +106,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@observable _viewTransition: 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 _pullCoords: number[] = [0, 0];
@observable _pullDirection: string = "";
@observable _showAnimTimeline = false;
@@ -163,7 +161,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.layoutDoc._panY = vals.bounds.cy;
this.layoutDoc._viewScale = vals.scale;
}
- freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined;
+ freeformData = (force?: boolean) => !this._firstRender && (this.fitToContent || force) ? this.fitToContentVals : undefined;
reverseNativeScaling = () => this.fitToContent ? true : false;
panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX);
panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY);
@@ -205,7 +203,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
if (this.Document._currentFrame !== undefined && !this.props.isAnnotationOverlay) {
- CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document._currentFrame, true);
+ CollectionFreeFormDocumentView.setupKeyframes(newBoxes, NumCast(this.Document._currentFrame), true);
}
}
return retVal;
@@ -235,12 +233,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
if (this.Document._currentFrame !== undefined) {
- CollectionFreeFormDocumentView.setupKeyframes([d], this.Document._currentFrame, false);
+ CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false);
const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
vals.x = x + (vals.x || 0) - dropPos[0];
vals.y = y + (vals.y || 0) - dropPos[1];
vals._scrollTop = this.Document.editScrollProgressivize ? vals._scrollTop : undefined;
- CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, vals);
+ CollectionFreeFormDocumentView.setValues(NumCast(this.Document._currentFrame), d, vals);
} else {
d.x = x + NumCast(d.x) - dropPos[0];
d.y = y + NumCast(d.y) - dropPos[1];
@@ -635,7 +633,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, 0, true);
+ this.setPan(NumCast(this.Document._panX) - dx, NumCast(this.Document._panY) - dy, 0, true);
this._lastX = e.clientX;
this._lastY = e.clientY;
}
@@ -967,10 +965,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
.filter(({ pos, size }) => pos && size).map(({ pos, size }) => ({ pos: pos!, size: size! }));
if (measuredDocs.length) {
const ranges = measuredDocs.reduce(({ xrange, yrange }, { pos, size }) => // computes range of content
- ({
- xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
- yrange: { min: Math.min(yrange.min, pos.y), max: Math.max(yrange.max, pos.y + (size.height || 0)) }
- })
+ ({
+ xrange: { min: Math.min(xrange.min, pos.x), max: Math.max(xrange.max, pos.x + (size.width || 0)) },
+ 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 }
@@ -1045,11 +1043,11 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
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: this.Document._panX, panY: this.Document._panY };
+ 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 = this.Document._panX;
- init.panY = this.Document._panY;
+ init.panX = NumCast(this.Document._panX);
+ init.panY = NumCast(this.Document._panY);
HistoryUtil.pushState(state);
}
}
@@ -1144,7 +1142,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
isContentActive = () => this.props.isSelected() || this.props.isContentActive();
- getChildDocView(entry: PoolData) {
+ getChildDocView(entry: PoolData, renderIndex: number) {
const childLayout = entry.pair.layout;
const childData = entry.pair.data;
const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine);
@@ -1153,6 +1151,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
Document={childLayout}
renderDepth={this.props.renderDepth + 1}
replica={entry.replica}
+ renderIndex={renderIndex}
+ renderCutoffProvider={this.renderCutoffProvider}
ContainingCollectionView={this.props.CollectionView}
ContainingCollectionDoc={this.props.Document}
CollectionFreeFormView={this}
@@ -1213,7 +1213,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const { z, color, zIndex } = params.pair.layout;
const { x, y, opacity } = this.Document._currentFrame === undefined ?
{ x: params.pair.layout.x, y: params.pair.layout.y, opacity: this.props.styleProvider?.(params.pair.layout, this.props, StyleProp.Opacity) } :
- CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document._currentFrame);
+ CollectionFreeFormDocumentView.getValues(params.pair.layout, NumCast(this.Document._currentFrame));
return {
x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"),
transition: StrCast(layoutDoc.dataTransition), opacity: this._keyframeEditing ? 1 : Cast(opacity, "number", null),
@@ -1256,6 +1256,12 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
}
+
+ renderCutoffProvider = computedFn(function renderCutoffProvider(this: any, doc: Doc) {
+ return !this._renderCutoffData.get(doc[Id] + "");
+ }.bind(this));
+
+
childPositionProviderUnmemoized = (doc: Doc, replica: string) => {
return this._layoutPoolData.get(doc[Id] + (replica || ""));
}
@@ -1300,6 +1306,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return { newPool, computedElementData: this.doFreeformLayout(newPool) };
}
+ @observable _numLoaded = 1;
get doLayoutComputation() {
const { newPool, computedElementData } = this.doInternalLayoutComputation;
const array = Array.from(newPool.entries());
@@ -1318,9 +1325,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this._cachedPool.clear();
Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1]));
const elements = computedElementData.slice();
- Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry =>
+ Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach((entry, i) =>
elements.push({
- ele: this.getChildDocView(entry[1]),
+ ele: this.getChildDocView(entry[1], i),
bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica)
}));
@@ -1369,35 +1376,38 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
componentDidMount() {
super.componentDidMount?.();
this.props.setContentView?.(this);
- this._disposers.layoutComputation = reaction(() => this.doLayoutComputation,
- (elements) => this._layoutElements = elements || [],
- { fireImmediately: true, name: "doLayout" });
-
- this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
-
- this._disposers.groupBounds = reaction(() => {
- if (this.props.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));
- }
- return undefined;
- },
- (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 pbounds = {
- x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
- r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1]
- };
- 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.x = pbounds.x;
- this.layoutDoc.y = pbounds.y;
+ setTimeout(action(() => {
+ this._firstRender = false;
+ this._disposers.layoutComputation = reaction(() => this.doLayoutComputation,
+ (elements) => this._layoutElements = elements || [],
+ { fireImmediately: true, name: "doLayout" });
+
+ this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
+
+ this._disposers.groupBounds = reaction(() => {
+ if (this.props.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));
}
- }, { fireImmediately: true });
+ return undefined;
+ },
+ (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 pbounds = {
+ x: (cbounds.x - p[0]) * this.zoomScaling() + c[0], y: (cbounds.y - p[1]) * this.zoomScaling() + c[1],
+ r: (cbounds.r - p[0]) * this.zoomScaling() + c[0], b: (cbounds.b - p[1]) * this.zoomScaling() + c[1]
+ };
+ 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.x = pbounds.x;
+ this.layoutDoc.y = pbounds.y;
+ }
+ }, { fireImmediately: true });
+ }));
}
componentWillUnmount() {
@@ -1449,8 +1459,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
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 = (this.Document._panX || 0) + (i % dim) * width - width * dim / 2;
- doc.y = (this.Document._panY || 0) + Math.floor(i / dim) * height - height * dim / 2;
+ 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;
});
}
@@ -1557,58 +1567,35 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
e.stopPropagation();
}
+ incrementalRender = action(() => {
+ if (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())) {
+ const 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);
+ }
+ }
+ this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1);
+ });
+
children = () => {
+ this.incrementalRender();
const children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : [];
- return [...children, ...this.views, <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />];
- }
-
- chooseGridSpace = (gridSpace: number): number => {
- if (!this.zoomScaling()) return 50;
- const divisions = this.props.PanelWidth() / this.zoomScaling() / gridSpace + 3;
- return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10);
- }
-
- @computed get backgroundGrid() {
- const gridSpace = this.chooseGridSpace(NumCast(this.layoutDoc["_backgroundGrid-spacing"], 50));
- const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.panX() % gridSpace - gridSpace) * this.zoomScaling();
- const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.panY() % gridSpace - gridSpace) * this.zoomScaling();
- const renderGridSpace = gridSpace * this.zoomScaling();
- const w = this.props.PanelWidth() + 2 * renderGridSpace;
- const h = this.props.PanelHeight() + 2 * renderGridSpace;
- const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
- return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
- ref={(el) => {
- const ctx = el?.getContext('2d');
- if (ctx) {
- const Cx = this.cachedCenteringShiftX % renderGridSpace;
- const Cy = this.cachedCenteringShiftY % renderGridSpace;
- ctx.lineWidth = Math.min(1, Math.max(0.5, this.zoomScaling()));
- ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
- ctx.clearRect(0, 0, w, h);
- if (ctx) {
- ctx.strokeStyle = strokeStyle;
- ctx.beginPath();
- for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
- ctx.moveTo(x, Cy - h);
- ctx.lineTo(x, Cy + h);
- }
- for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
- ctx.moveTo(Cx - w, y);
- ctx.lineTo(Cx + w, y);
- }
- ctx.stroke();
- }
- }
- }} />;
+ return [
+ ...children,
+ ...this.views,
+ <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+ ];
}
@computed get placeholder() {
- return <div className="collectionfreeformview-placeholder" style={{ background: this.Document.backgroundColor }}>
+ return <div className="collectionfreeformview-placeholder" style={{ background: StrCast(this.Document.backgroundColor) }}>
<span className="collectionfreeformview-placeholderSpan">{this.props.Document.title?.toString()}</span>
</div>;
}
@computed get marqueeView() {
+ TraceMobx();
return <MarqueeView
{...this.props}
ungroup={this.props.Document._isGroup ? this.promoteCollection : undefined}
@@ -1623,7 +1610,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
<div className="marqueeView-div" ref={this._marqueeRef} style={{ opacity: this.props.dontRenderDocuments ? 0 : undefined }}>
- {this.layoutDoc._backgroundGridShow ? this.backgroundGrid : (null)}
+ {this.layoutDoc._backgroundGridShow ?
+ <CollectionFreeFormBackgroundGrid
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={this.props.PanelHeight}
+ panX={this.panX}
+ panY={this.panY}
+ zoomScaling={this.zoomScaling}
+ layoutDoc={this.layoutDoc}
+ isAnnotationOverlay={this.isAnnotationOverlay}
+ cachedCenteringShiftX={this.cachedCenteringShiftX}
+ cachedCenteringShiftY={this.cachedCenteringShiftY}
+ /> : (null)}
<CollectionFreeFormViewPannableContents
isAnnotationOverlay={this.isAnnotationOverlay}
isAnnotationOverlayScrollable={this.props.isAnnotationOverlayScrollable}
@@ -1642,7 +1640,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
@computed get contentScaling() {
- if (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent) return 0;
+ if (this._firstRender || (this.props.isAnnotationOverlay && !this.props.annotationLayerHostsContent)) return 0;
const nw = this.nativeWidth;
const nh = this.nativeHeight;
const hscale = nh ? this.props.PanelHeight() / nh : 1;
@@ -1675,9 +1673,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.backgroundEvents ? "all" : this.props.pointerEvents as any,
transform: `scale(${this.contentScaling || 1})`,
width: `${100 / (this.contentScaling || 1)}%`,
- height: this.isAnnotationOverlay && this.Document.scrollHeight ? this.Document.scrollHeight : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
+ height: this.isAnnotationOverlay && this.Document.scrollHeight ? NumCast(this.Document.scrollHeight) : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight()
}}>
- {this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ?
+ {this._firstRender || (this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0) ?
this.placeholder : this.marqueeView}
{this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />}
@@ -1854,4 +1852,59 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
{this.zoomProgressivize}
</div>;
}
+}
+
+interface CollectionFreeFormViewBackgroundGridProps {
+ panX: () => number;
+ panY: () => number;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ isAnnotationOverlay?: boolean;
+ zoomScaling: () => number;
+ layoutDoc: Doc;
+ cachedCenteringShiftX: number;
+ cachedCenteringShiftY: number;
+}
+@observer
+class CollectionFreeFormBackgroundGrid extends React.Component<CollectionFreeFormViewBackgroundGridProps> {
+
+
+ chooseGridSpace = (gridSpace: number): number => {
+ if (!this.props.zoomScaling()) return 50;
+ const divisions = this.props.PanelWidth() / this.props.zoomScaling() / gridSpace + 3;
+ return divisions < 60 ? gridSpace : this.chooseGridSpace(gridSpace * 10);
+ }
+ render() {
+ const gridSpace = this.chooseGridSpace(NumCast(this.props.layoutDoc["_backgroundGrid-spacing"], 50));
+ const shiftX = (this.props.isAnnotationOverlay ? 0 : -this.props.panX() % gridSpace - gridSpace) * this.props.zoomScaling();
+ const shiftY = (this.props.isAnnotationOverlay ? 0 : -this.props.panY() % gridSpace - gridSpace) * this.props.zoomScaling();
+ const renderGridSpace = gridSpace * this.props.zoomScaling();
+ const w = this.props.PanelWidth() + 2 * renderGridSpace;
+ const h = this.props.PanelHeight() + 2 * renderGridSpace;
+ const strokeStyle = CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "rgba(255,255,255,0.5)" : "rgba(0, 0,0,0.5)";
+ return <canvas className="collectionFreeFormView-grid" width={w} height={h} style={{ transform: `translate(${shiftX}px, ${shiftY}px)` }}
+ ref={(el) => {
+ const ctx = el?.getContext('2d');
+ if (ctx) {
+ const Cx = this.props.cachedCenteringShiftX % renderGridSpace;
+ const Cy = this.props.cachedCenteringShiftY % renderGridSpace;
+ ctx.lineWidth = Math.min(1, Math.max(0.5, this.props.zoomScaling()));
+ ctx.setLineDash(gridSpace > 50 ? [3, 3] : [1, 5]);
+ ctx.clearRect(0, 0, w, h);
+ if (ctx) {
+ ctx.strokeStyle = strokeStyle;
+ ctx.beginPath();
+ for (let x = Cx - renderGridSpace; x <= w - Cx; x += renderGridSpace) {
+ ctx.moveTo(x, Cy - h);
+ ctx.lineTo(x, Cy + h);
+ }
+ for (let y = Cy - renderGridSpace; y <= h - Cy; y += renderGridSpace) {
+ ctx.moveTo(Cx - w, y);
+ ctx.lineTo(Cx + w, y);
+ }
+ ctx.stroke();
+ }
+ }
+ }} />;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 08da682bb..b10b0912f 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -30,6 +30,9 @@ import "./MarqueeView.scss";
import React = require("react");
import { StyleLayers } from "../../StyleProvider";
import { TreeView } from "../TreeView";
+import { VideoBox } from "../../nodes/VideoBox";
+import { ImageField, WebField } from "../../../../fields/URLField";
+import { pasteImageBitmap } from "../../nodes/WebBoxRenderer";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -134,17 +137,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
})();
e.stopPropagation();
} else if (e.key === "b" && e.ctrlKey) {
- // e.preventDefault();
- // navigator.clipboard.readText().then(text => {
- // const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
- // if (ns.length === 1 && text.startsWith("http")) {
- // this.props.addDocument(Docs.Create.ImageDocument(text, { _nativeWidth: 300, _width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer
- // } else {
- // this.pasteTable(ns, x, y);
- // }
- // });
- // e.stopPropagation();
-
+ document.body.focus(); // so that we can access the clipboard without an error
+ setTimeout(() =>
+ pasteImageBitmap((data: any, error: any) => {
+ error && console.log(error);
+ data && VideoBox.convertDataUri(data, this.props.Document[Id] + "-thumb-frozen").then(returnedfilename => {
+ this.props.Document["thumb-frozen"] = new ImageField(returnedfilename);
+ });
+ }));
+ } else if (e.key === "s" && e.ctrlKey) {
e.preventDefault();
const slide = Doc.copyDragFactory(Doc.UserDoc().emptySlide as Doc)!;
slide.x = x;