diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/ClientUtils.ts | 61 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 1 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts | 224 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 537 | ||||
-rw-r--r-- | src/client/views/nodes/LabelBox.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/PDFBox.tsx | 5 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 1 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/ParagraphNodeSpec.ts | 1 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/RichTextRules.ts | 1 |
9 files changed, 419 insertions, 413 deletions
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index 8011a1928..dbbba896f 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -5,6 +5,7 @@ import * as rp from 'request-promise'; import { numberRange, decimalToHexString } from './Utils'; import { CollectionViewType, DocumentType } from './client/documents/DocumentTypes'; import { Colors } from './client/views/global/globalEnums'; +import { CreateImage } from './client/views/nodes/WebBoxRenderer'; export function DashColor(color: string) { try { @@ -658,3 +659,63 @@ export function dateRangeStrToDates(dateStr: string) { return [new Date(fromYear, fromMonth, fromDay), new Date(toYear, toMonth, toDay)]; } + +function replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { + if (oldDiv.childNodes && newDiv) { + for (let i = 0; i < oldDiv.childNodes.length; i++) { + replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); + } + } + if (oldDiv instanceof HTMLCanvasElement) { + if (oldDiv.className === 'collectionFreeFormView-grid') { + const newCan = newDiv as HTMLCanvasElement; + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(document.createElement('div')); + } else { + const canvas = oldDiv; + const img = document.createElement('img'); // create a Image Element + 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; + if (newCan) { + const parEle = newCan.parentElement as HTMLElement; + parEle.removeChild(newCan); + parEle.appendChild(img); + } + } + } +} + +export function UpdateIcon( + filename: string, + docViewContent: HTMLElement, + width: number, + height: number, + panelWidth: number, + panelHeight: number, + scrollTop: number, + realNativeHeight: number, + noSuffix: boolean, + replaceRootFilename: string | undefined, + cb: (iconFile: string, nativeWidth: number, nativeHeight: number) => any +) { + const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; + newDiv.style.width = width.toString(); + newDiv.style.height = height.toString(); + replaceCanvases(docViewContent, newDiv); + const htmlString = new XMLSerializer().serializeToString(newDiv); + const nativeWidth = width; + const nativeHeight = height; + return CreateImage(ClientUtils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) + .then(async (dataUrl: any) => { + const returnedFilename = await ClientUtils.convertDataUri(dataUrl, filename, noSuffix, replaceRootFilename); + cb(returnedFilename as string, nativeWidth, nativeHeight); + }) + .catch((error: any) => console.error('oops, something went wrong!', error)); +} diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ddb6f65b6..0e7f911c7 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -7,7 +7,6 @@ import * as JSZip from 'jszip'; import { action, reaction, runInAction } from 'mobx'; import { basename } from 'path'; import { ClientUtils, OmitKeys } from '../../ClientUtils'; -// eslint-disable-next-line import/extensions import * as JSZipUtils from '../../JSZipUtils'; import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts new file mode 100644 index 000000000..6fc295e27 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts @@ -0,0 +1,224 @@ +import { action, observable } from 'mobx'; +import { Doc, Opt } from '../../../../fields/Doc'; +import { NumCast, StrCast } from '../../../../fields/Types'; +import { intersectRect } from '../../../../Utils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; +import { FieldViewProps } from '../../nodes/FieldView'; +import { StyleProp } from '../../StyleProvider'; +import './CollectionFreeFormView.scss'; +import { CollectionFreeFormView } from '.'; + +export class CollectionFreeFormClusters { + private _view: CollectionFreeFormView; + private _clusterDistance: number = 75; + private _hitCluster: number = -1; + @observable _clusterSets: Doc[][] = []; + + constructor(view: CollectionFreeFormView) { + this._view = view; + } + get Document() { return this._view.Document; } // prettier-ignore + get DocumentView() { return this._view.DocumentView?.(); } // prettier-ignore + get childDocs() { return this._view.childDocs; } // prettier-ignore + get childLayoutPairs() { return this._view.childLayoutPairs; } // prettier-ignore + get screenToContentsXf() { return this._view.screenToFreeformContentsXf; } // prettier-ignore + get viewStyleProvider() { return this._view._props.styleProvider; } // prettier-ignore + get viewMoveDocument() { return this._view._props.moveDocument; } // prettier-ignore + get selectDocuments() { return this._view.selectDocuments; } // prettier-ignore + + static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { + const doc2Layout = Doc.Layout(doc2); + const doc1Layout = Doc.Layout(doc1); + const x2 = NumCast(doc2.x) - clusterDistance; + const y2 = NumCast(doc2.y) - clusterDistance; + const w2 = NumCast(doc2Layout._width) + clusterDistance; + const h2 = NumCast(doc2Layout._height) + clusterDistance; + const x = NumCast(doc1.x) - clusterDistance; + const y = NumCast(doc1.y) - clusterDistance; + const w = NumCast(doc1Layout._width) + clusterDistance; + const h = NumCast(doc1Layout._height) + clusterDistance; + return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 }); + } + pickCluster(probe: number[]) { + this._hitCluster = this.childLayoutPairs + .map(pair => pair.layout) + .reduce((cluster, cd) => { + const grouping = this.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 / 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; + }, -1); + return this._hitCluster; + } + + tryDragCluster(e: PointerEvent) { + const cluster = this._hitCluster; + if (cluster !== -1) { + const ptsParent = e; + if (ptsParent) { + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster); + const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView)!); + const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 }; + const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined); + de.moveDocument = this.viewMoveDocument; + de.offset = this.screenToContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); + DragManager.StartDocumentDrag( + clusterDocs.map(v => v.ContentDiv!), + de, + ptsParent.clientX, + ptsParent.clientY, + { hideSource: !de.dropAction } + ); + return true; + } + } + + return false; + } + + initClusters() { + if (this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length) { + return this.updateClusters(true); + } + return false; + } + @action + updateClusters(useClusters: boolean) { + this.Document._freeform_useClusters = useClusters; + this._clusterSets.length = 0; + this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c)); + } + + @action + updateClusterDocs(docs: Doc[]) { + const childLayouts = this.childLayoutPairs.map(pair => pair.layout); + if (this.Document._freeform_useClusters) { + const docFirst = docs[0]; + docs.forEach(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); + const preferredInd = NumCast(docFirst.layout_cluster); + docs.forEach(doc => { + doc.layout_cluster = -1; + }); + docs.map(doc => + this._clusterSets.map((set, i) => + set.forEach(member => { + if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormClusters.overlapping(doc, member, this._clusterDistance)) { + docFirst.layout_cluster = i; + } + }) + ) + ); + 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.forEach((set, i) => { + if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { + docFirst.layout_cluster = i; + } + }); + if (docFirst.layout_cluster === -1) { + docs.forEach(doc => { + doc.layout_cluster = this._clusterSets.length; + this._clusterSets.push([doc]); + }); + } else if (this._clusterSets.length) { + for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]); + docs.forEach(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.layout_cluster === i) && this.updateCluster(child)); + } + } + + @action + updateCluster = (doc: Doc) => { + const childLayouts = this.childLayoutPairs.map(pair => pair.layout); + if (this.Document._freeform_useClusters) { + this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); + const preferredInd = NumCast(doc.layout_cluster); + doc.layout_cluster = -1; + this._clusterSets.forEach((set, i) => + set.forEach(member => { + if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormClusters.overlapping(doc, member, this._clusterDistance)) { + doc.layout_cluster = i; + } + }) + ); + 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.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { + doc.layout_cluster = i; + } + }); + 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.layout_cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]); + this._clusterSets[doc.layout_cluster ?? 0].push(doc); + } + } + }; + + styleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => { + let styleProp = this.viewStyleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 + if (doc && this.childDocs?.includes(doc)) + switch (property.split(':')[0]) { + case StyleProp.BackgroundColor: + { + 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 { + // choose a cluster color from a palette + const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; + styleProp = colors[cluster % colors.length]; + const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); + // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document + set?.forEach(s => { + styleProp = StrCast(s.backgroundColor); + }); + } + } + } + break; + case StyleProp.FillColor: + if (doc && this.Document._currentFrame !== undefined) { + return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; + } + break; + default: + } + return styleProp; + }; + + trySelectCluster = (addToSel: boolean) => { + if (addToSel && this._hitCluster !== -1) { + !addToSel && SelectionManager.DeselectAll(); + const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); + this.selectDocuments(eles); + return true; + } + return false; + }; +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c1a889539..69cb52233 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -7,7 +7,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; -import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents } from '../../../../ClientUtils'; +import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, FieldType, Opt } from '../../../../fields/Doc'; import { DocData, Height, Width } from '../../../../fields/DocSymbols'; @@ -48,19 +48,23 @@ import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PresBox } from '../../nodes/trails/PresBox'; -// eslint-disable-next-line import/extensions -import { CreateImage } from '../../nodes/WebBoxRenderer'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; +import { CollectionFreeFormClusters } from './CollectionFreeFormClusters'; import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; +class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> { + render() { + return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore + } +} export interface collectionFreeformViewProps { NativeWidth?: () => number; NativeHeight?: () => number; @@ -79,18 +83,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection public get displayName() { return 'CollectionFreeFormView(' + (this.Document.title?.toString() ?? '') + ')'; } // this makes mobx trace() statements more descriptive + public unprocessedDocs: Doc[] = []; + public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>(); - @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, ''); - @computed get paintFunc() { - const field = this.dataDoc[this.fieldKey]; - const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as FieldType)).trim(); - return !paintFunc - ? '' - : paintFunc.includes('dashDiv') - ? `const dashDiv = document.querySelector('#${this._paintedId}'); - (async () => { ${paintFunc} })()` - : paintFunc; - } + _oldWheel: any; + _clusters = new CollectionFreeFormClusters(this); constructor(props: any) { super(props); makeObservable(this); @@ -102,41 +99,48 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection private _downX: number = 0; private _downY: number = 0; private _downTime = 0; - private _clusterDistance: number = 75; - private _hitCluster: number = -1; private _disposers: { [name: string]: IReactionDisposer } = {}; private _renderCutoffData = observable.map<string, boolean>(); private _batch: UndoManager.Batch | undefined = undefined; private _brushtimer: any; private _brushtimer1: any; + private _eraserLock = 0; + private _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. - public get isAnnotationOverlay() { - return this._props.isAnnotationOverlay; - } - 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 autoResetFieldKey() { - return (this._props.viewField ?? '') + '_freeform_autoReset'; - } + private get isAnnotationOverlay() { return this._props.isAnnotationOverlay; } // prettier-ignore + private get scaleFieldKey() { return (this._props.viewField ?? '') + '_freeform_scale'; } // prettier-ignore + private get panXFieldKey() { return (this._props.viewField ?? '') + '_freeform_panX'; } // prettier-ignore + private get panYFieldKey() { return (this._props.viewField ?? '') + '_freeform_panY'; } // prettier-ignore + private get autoResetFieldKey() { return (this._props.viewField ?? '') + '_freeform_autoReset'; } // prettier-ignore @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @observable _panZoomTransition: number = 0; // sets the pan/zoom transform ease time- used by nudge(), focus() etc to smoothly zoom/pan. set to 0 to use document's transition time or default of 0 @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[] = []; @observable _timelineRef = React.createRef<Timeline>(); @observable _marqueeViewRef = React.createRef<MarqueeView>(); @observable _brushedView: { width: number; height: number; panX: number; panY: number } | undefined = undefined; // highlighted region of freeform canvas used by presentations to indicate a region @observable GroupChildDrag: boolean = false; // child document view being dragged. needed to update drop areas of groups when a group item is dragged. + @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined; + @observable _lightboxDoc: Opt<Doc> = undefined; + @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, ''); + @observable _keyframeEditing = false; + @computed get layoutEngine() { + return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); + } + @computed get childPointerEvents() { + const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine); + return SnappingManager.IsResizing + ? 'none' + : this._props.childPointerEvents?.() ?? + (this._props.viewDefDivClick || // + (engine === computePassLayout.name && !this._props.isSelected()) || + this.isContentActive() === false + ? 'none' + : this._props.pointerEvents?.()); + } @computed get contentViews() { const viewsMask = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && ele.inkMask !== -1 && ele.inkMask !== undefined).map(ele => ele.ele); const renderableEles = this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z && (ele.inkMask === -1 || ele.inkMask === undefined)).map(ele => ele.ele); @@ -158,14 +162,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay; } @computed get contentBounds() { - const cb = Cast(this.dataDoc.contentBounds, listSpec('number')); - return cb - ? { x: cb[0], y: cb[1], r: cb[2], b: cb[3] } - : aggregateBounds( - this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!), - NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10), - NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10) - ); + return aggregateBounds( + this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!), + NumCast(this.layoutDoc._xPadding, this._props.xPadding ?? 10), + NumCast(this.layoutDoc._yPadding, this._props.yPadding ?? 10) + ); } @computed get nativeWidth() { return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); @@ -196,6 +197,27 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection .translate(-this.centeringShiftX, -this.centeringShiftY) .transform(this.panZoomXf); } + @computed get backgroundColor() { + return this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor); + } + @computed get nativeDimScaling() { + if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 1; + const nw = this.nativeWidth; + const nh = this.nativeHeight; + const hscale = nh ? this._props.PanelHeight() / nh : 1; + const wscale = nw ? this._props.PanelWidth() / nw : 1; + return wscale < hscale || (this._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale; + } + @computed get paintFunc() { + const field = this.dataDoc[this.fieldKey]; + const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as FieldType)).trim(); + return !paintFunc + ? '' + : paintFunc.includes('dashDiv') + ? `const dashDiv = document.querySelector('#${this._paintedId}'); + (async () => { ${paintFunc} })()` + : paintFunc; + } public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) { return DocumentView.SetViewTransition(docs, 'all', duration, timer, undefined, true); @@ -219,8 +241,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }); 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) { @@ -236,11 +256,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); } }; - @observable _keyframeEditing = false; @action setKeyFrameEditing = (set: boolean) => { this._keyframeEditing = set; }; getKeyFrameEditing = () => this._keyframeEditing; + onChildClickHandler = () => this._props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; @@ -276,7 +296,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection retVal = this._props.addDocument?.(newBox) || false; if (retVal) { this.bringToFront(newBox); - this.updateCluster(newBox); + this._clusters.updateCluster(newBox); } } else { retVal = this._props.addDocument?.(newBox) || false; @@ -309,6 +329,11 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return dispTime === -1 || curTime === -1 || (curTime - dispTime >= -1e-4 && curTime <= endTime); } + /** + * focuses on a specified point in the freeform coordinate space. (alternative to focusing on a Document) + * @param options + * @returns how long a transition it will be to focus on the point, or undefined the doc is a group or something else already moved + */ focusOnPoint = (options: FocusViewOptions) => { const { pointFocus, zoomTime, didMove } = options; if (!this.Document.isGroup && pointFocus && !didMove) { @@ -331,7 +356,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection * @returns */ groupFocus = (anchor: Doc, options: FocusViewOptions) => { - if (options.pointFocus) return this.focusOnPoint(options); + if (options.pointFocus) return undefined; options.docTransform = new Transform(NumCast(anchor.x) + NumCast(anchor._width)/2 - NumCast(this.layoutDoc[this.panXFieldKey]), NumCast(anchor.y) + NumCast(anchor._height)/2- NumCast(this.layoutDoc[this.panYFieldKey]), 1); // prettier-ignore const res = this._props.focus(this.Document, options); @@ -339,6 +364,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return res; }; + /** + * focuses the freeform view on the anchor subject to options. + * If a pointFocus is specified, then groupFocus is triggered instad + * Otherwise, this shifts the pan and zoom to the anchor target (as specified by options). + * NOTE: focusing on a group only has an effet if the options contextPath is empty. + * @param anchor + * @param options + * @returns + */ focus = (anchor: Doc, options: FocusViewOptions): any => { if (anchor.isGroup && !options.docTransform && options.contextPath?.length) { // don't focus on group if there's a context path because we're about to focus on a group item @@ -349,7 +383,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (options.pointFocus) return this.focusOnPoint(options); const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor); const anchorInChildViews = this.childLayoutPairs.map(pair => pair.layout).includes(anchor); - if (anchor.type !== DocumentType.CONFIG && !anchorInCollection && !anchorInChildViews) { + if (!anchorInCollection && !anchorInChildViews) { return undefined; } const xfToCollection = options?.docTransform ?? Transform.Identity(); @@ -420,7 +454,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection !d._keepZWhenDragged && (d.zIndex = zsorted.length + 1 + i); // bringToFront } - (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments); + (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this._clusters.updateClusterDocs(docDragData.droppedDocuments); return true; } @@ -469,189 +503,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onExternalDrop = (e: React.DragEvent) => (([x, y]) => super.onExternalDrop(e, { x, y }))(this.screenToFreeformContentsXf.transformPoint(e.pageX, e.pageY)); - static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { - const doc2Layout = Doc.Layout(doc2); - const doc1Layout = Doc.Layout(doc1); - const x2 = NumCast(doc2.x) - clusterDistance; - const y2 = NumCast(doc2.y) - clusterDistance; - const w2 = NumCast(doc2Layout._width) + clusterDistance; - const h2 = NumCast(doc2Layout._height) + clusterDistance; - const x = NumCast(doc1.x) - clusterDistance; - const y = NumCast(doc1.y) - clusterDistance; - const w = NumCast(doc1Layout._width) + clusterDistance; - const h = NumCast(doc1Layout._height) + clusterDistance; - return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 }); - } - pickCluster(probe: number[]) { - return this.childLayoutPairs - .map(pair => pair.layout) - .reduce((cluster, cd) => { - const grouping = this.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 / 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; - }, -1); - } - - tryDragCluster(e: PointerEvent, cluster: number) { - if (cluster !== -1) { - const ptsParent = e; - if (ptsParent) { - const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster); - const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView?.())!); - const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 }; - const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined); - de.moveDocument = this._props.moveDocument; - de.offset = this.screenToFreeformContentsXf.transformDirection(ptsParent.clientX - left, ptsParent.clientY - top); - DragManager.StartDocumentDrag( - clusterDocs.map(v => v.ContentDiv!), - de, - ptsParent.clientX, - ptsParent.clientY, - { hideSource: !de.dropAction } - ); - return true; - } - } - - return false; - } - - @action - updateClusters(useClusters: boolean) { - this.Document._freeform_useClusters = useClusters; - this._clusterSets.length = 0; - this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c)); - } - - @action - updateClusterDocs(docs: Doc[]) { - const childLayouts = this.childLayoutPairs.map(pair => pair.layout); - if (this.Document._freeform_useClusters) { - const docFirst = docs[0]; - docs.forEach(doc => this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1))); - const preferredInd = NumCast(docFirst.layout_cluster); - docs.forEach(doc => { - doc.layout_cluster = -1; - }); - docs.map(doc => - this._clusterSets.map((set, i) => - set.forEach(member => { - if (docFirst.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) { - docFirst.layout_cluster = i; - } - }) - ) - ); - 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.forEach((set, i) => { - if (docFirst.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { - docFirst.layout_cluster = i; - } - }); - if (docFirst.layout_cluster === -1) { - docs.forEach(doc => { - doc.layout_cluster = this._clusterSets.length; - this._clusterSets.push([doc]); - }); - } else if (this._clusterSets.length) { - for (let i = this._clusterSets.length; i <= NumCast(docFirst.layout_cluster); i++) !this._clusterSets[i] && this._clusterSets.push([]); - docs.forEach(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.layout_cluster === i) && this.updateCluster(child)); - } - } - - @action - updateCluster = (doc: Doc) => { - const childLayouts = this.childLayoutPairs.map(pair => pair.layout); - if (this.Document._freeform_useClusters) { - this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); - const preferredInd = NumCast(doc.layout_cluster); - doc.layout_cluster = -1; - this._clusterSets.forEach((set, i) => - set.forEach(member => { - if (doc.layout_cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && CollectionFreeFormView.overlapping(doc, member, this._clusterDistance)) { - doc.layout_cluster = i; - } - }) - ); - 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.layout_cluster === -1 && !set.filter(member => Doc.IndexOf(member, childLayouts) !== -1).length) { - doc.layout_cluster = i; - } - }); - 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.layout_cluster; i++) !this._clusterSets[i] && this._clusterSets.push([]); - this._clusterSets[doc.layout_cluster ?? 0].push(doc); - } - } - }; - - clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => { - let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 - if (doc && this.childDocList?.includes(doc)) - switch (property.split(':')[0]) { - case StyleProp.BackgroundColor: - { - 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 { - // choose a cluster color from a palette - const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; - styleProp = colors[cluster % colors.length]; - const set = this._clusterSets[cluster]?.filter(s => s.backgroundColor); - // override the cluster color with an explicitly set color on a non-background document. then override that with an explicitly set color on a background document - set?.forEach(s => { - styleProp = StrCast(s.backgroundColor); - }); - } - } - } - break; - case StyleProp.FillColor: - if (doc && this.Document._currentFrame !== undefined) { - return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; - } - break; - default: - } - return styleProp; - }; - - trySelectCluster = (addToSel: boolean) => { - if (addToSel && this._hitCluster !== -1) { - !addToSel && SelectionManager.DeselectAll(); - const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); - this.selectDocuments(eles); - return true; - } - return false; - }; - @action onPointerDown = (e: React.PointerEvent): void => { this._downX = this._lastX = e.pageX; @@ -672,8 +523,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection break; case InkTool.None: if (!(this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine))) { - this._hitCluster = this.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, this._hitCluster !== -1, false); + const hit = this._clusters.pickCluster(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); + setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, emptyFunction, hit !== -1, false); } break; default: @@ -682,25 +533,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; - public unprocessedDocs: Doc[] = []; - public static collectionsWithUnprocessedInk = new Set<CollectionFreeFormView>(); @undoBatch onGesture = (e: Event, ge: GestureUtils.GestureEvent) => { switch (ge.gesture) { - // case Gestures.Rectangle: - // { - // const strokes = this.getActiveDocuments() - // .filter(doc => doc.type === DocumentType.INK) - // .map(i => { - // const d = Cast(i.stroke, InkField); - // const x = NumCast(i.x) - Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); - // const y = NumCast(i.y) - Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); - // return !d ? [] : d.inkData.map(pd => ({ X: x + pd.X, Y: y + pd.Y })); - // }); - - // CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => {}); - // } - // break; case Gestures.Text: if (ge.text) { const B = this.screenToFreeformContentsXf.transformPoint(ge.points[0].X, ge.points[0].Y); @@ -756,7 +591,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } }; - @action scrollPan = (e: WheelEvent | { deltaX: number; deltaY: number }): void => { PresBox.Instance?.pauseAutoPres(); this.setPan(NumCast(this.Document[this.panXFieldKey]) - e.deltaX, NumCast(this.Document[this.panYFieldKey]) - e.deltaY, 0, true); @@ -775,7 +609,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 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, @@ -816,7 +649,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; onPointerMove = (e: PointerEvent) => { - if (this.tryDragCluster(e, this._hitCluster)) { + if (this._clusters.tryDragCluster(e)) { 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; } @@ -835,20 +668,18 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getEraserIntersections = (lastPoint: { X: number; Y: number }, currPoint: { X: number; Y: number }) => { const eraserMin = { X: Math.min(lastPoint.X, currPoint.X), Y: Math.min(lastPoint.Y, currPoint.Y) }; const eraserMax = { X: Math.max(lastPoint.X, currPoint.X), Y: Math.max(lastPoint.Y, currPoint.Y) }; - - return this.childDocs + // prettier-ignore + return this.childDocs .map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) .map(inkView => inkView!) .map(inkView => ({ inkViewBounds: inkView.getBounds, inkStroke: inkView.ComponentView as InkingStroke, inkView })) - .filter( - ({ inkViewBounds }) => + .filter(({ inkViewBounds }) => inkViewBounds && // bounding box of eraser segment and ink stroke overlap eraserMin.X <= inkViewBounds.right && eraserMin.Y <= inkViewBounds.bottom && eraserMax.X >= inkViewBounds.left && - eraserMax.Y >= inkViewBounds.top - ) + eraserMax.Y >= inkViewBounds.top) .reduce( (intersections, { inkStroke, inkView }) => { const { inkData } = inkStroke.inkScaledData(); @@ -1231,19 +1062,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } return undefined; }; - @computed get childPointerEvents() { - const engine = this._props.layoutEngine?.() || StrCast(this.Document._layoutEngine); - return SnappingManager.IsResizing - ? 'none' - : this._props.childPointerEvents?.() ?? - (this._props.viewDefDivClick || // - (engine === computePassLayout.name && !this._props.isSelected()) || - this.isContentActive() === false - ? 'none' - : this._props.pointerEvents?.()); - } - - @observable _childPointerEvents: 'none' | 'all' | 'visiblepainted' | undefined = undefined; childPointerEventsFunc = () => this._childPointerEvents; childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); getChildDocView(entry: PoolData) { @@ -1256,7 +1074,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection key={childLayout[Id] + (entry.replica || '')} Document={childLayout} containerViewPath={this.DocumentView?.().docViewPath} - styleProvider={this.clusterStyleProvider} + styleProvider={this._clusters.styleProvider} TemplateDataDocument={childData} dragStarting={this.dragStarting} dragEnding={this.dragEnding} @@ -1333,7 +1151,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } return this._props.addDocTab(docs, where); }); - @observable _lightboxDoc: Opt<Doc> = undefined; getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData { const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min); @@ -1351,16 +1168,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const rotation = Cast(_rotation,'number', !this.layoutDoc._rotation_jitter ? null : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) ); - const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider }; + const childProps = { ...this._props, fieldKey: '', styleProvider: this._clusters.styleProvider }; return { x: isNaN(NumCast(x)) ? 0 : NumCast(x), y: isNaN(NumCast(y)) ? 0 : NumCast(y), z: Cast(z, 'number'), autoDim, rotation, - color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color), - backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor), - opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity), + color: Cast(color, 'string') ? StrCast(color) : this._clusters.styleProvider(childDoc, childProps, StyleProp.Color), + backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this._clusters.styleProvider(childDoc, childProps, StyleProp.BackgroundColor), + opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._clusters.styleProvider?.(childDoc, childProps, StyleProp.Opacity), zIndex: Cast(zIndex, 'number'), width: _width, height: _height, @@ -1436,9 +1253,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return [] as ViewDefResult[]; } - @computed get layoutEngine() { - return this._props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); - } @computed get doInternalLayoutComputation() { TraceMobx(); const newPool = new Map<string, PoolData>(); @@ -1451,7 +1265,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } // prettier-ignore } - @action doLayoutComputation = (newPool: Map<string, PoolData>, computedElementData: ViewDefResult[]) => { const elements = computedElementData.slice(); Array.from(newPool.entries()) @@ -1464,7 +1277,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }) ); - this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length && this.updateClusters(true); + this._clusters.initClusters(); return elements; }; @@ -1489,7 +1302,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; childDocsFunc = () => this.childDocs; - @action closeInfo = () => { Doc.IsInfoUIDisabled = true }; // prettier-ignore + closeInfo = action(() => { Doc.IsInfoUIDisabled = true }); // prettier-ignore infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} LayoutDoc={this.layoutDoc} childDocs={this.childDocsFunc} close={this.closeInfo} />); componentDidMount() { @@ -1566,40 +1379,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } - static replaceCanvases(oldDiv: HTMLElement, newDiv: HTMLElement) { - if (oldDiv.childNodes && newDiv) { - for (let i = 0; i < oldDiv.childNodes.length; i++) { - this.replaceCanvases(oldDiv.childNodes[i] as HTMLElement, newDiv.childNodes[i] as HTMLElement); - } - } - if (oldDiv instanceof HTMLCanvasElement) { - if (oldDiv.className === 'collectionFreeFormView-grid') { - const newCan = newDiv as HTMLCanvasElement; - const parEle = newCan.parentElement as HTMLElement; - parEle.removeChild(newCan); - parEle.appendChild(document.createElement('div')); - } else { - const canvas = oldDiv; - const img = document.createElement('img'); // create a Image Element - 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; - if (newCan) { - const parEle = newCan.parentElement as HTMLElement; - parEle.removeChild(newCan); - parEle.appendChild(img); - } - } - } + componentWillUnmount() { + this.dataDoc[this.autoResetFieldKey] && this.resetView(); + Object.values(this._disposers).forEach(disposer => disposer?.()); } updateIcon = () => - CollectionFreeFormView.UpdateIcon( + UpdateIcon( this.layoutDoc[Id] + '-icon' + new Date().getTime(), this.DocumentView?.().ContentDiv!, NumCast(this.layoutDoc._width), @@ -1617,40 +1403,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } ); - public static UpdateIcon( - filename: string, - docViewContent: HTMLElement, - width: number, - height: number, - panelWidth: number, - panelHeight: number, - scrollTop: number, - realNativeHeight: number, - noSuffix: boolean, - replaceRootFilename: string | undefined, - cb: (iconFile: string, nativeWidth: number, nativeHeight: number) => any - ) { - const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = width.toString(); - newDiv.style.height = height.toString(); - this.replaceCanvases(docViewContent, newDiv); - const htmlString = new XMLSerializer().serializeToString(newDiv); - const nativeWidth = width; - const nativeHeight = height; - return CreateImage(ClientUtils.prepend(''), document.styleSheets, htmlString, nativeWidth, (nativeWidth * panelHeight) / panelWidth, (scrollTop * panelHeight) / realNativeHeight) - .then(async (dataUrl: any) => { - const returnedFilename = await ClientUtils.convertDataUri(dataUrl, filename, noSuffix, replaceRootFilename); - cb(returnedFilename as string, nativeWidth, nativeHeight); - }) - .catch((error: any) => console.error('oops, something went wrong!', error)); - } - - componentWillUnmount() { - this.dataDoc[this.autoResetFieldKey] && this.resetView(); - Object.values(this._disposers).forEach(disposer => disposer?.()); - } - - @action onCursorMove = () => { // super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); }; @@ -1734,7 +1486,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' }); !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null; - !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null; + !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this._clusters.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null; !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); const options = ContextMenu.Instance.findByDescription('Options...'); @@ -1827,21 +1579,32 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); - @computed get placeholder() { - return ( - <div className="collectionfreeformview-placeholder" style={{ background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) }}> - <span className="collectionfreeformview-placeholderSpan">{this.Document.annotationOn ? '' : this.Document.title?.toString()}</span> - </div> - ); - } - showPresPaths = () => SnappingManager.ShowPresPaths; brushedView = () => this._brushedView; - gridColor = () => - DashColor(lightOrDark(this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor))) - .fade(0.5) - .toString(); - @computed get backgroundGrid() { + gridColor = () => DashColor(lightOrDark(this.backgroundColor)).fade(0.5).toString(); // prettier-ignore + nativeDim = () => this.nativeDimScaling; + + brushView = action((viewport: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number = 2500) => { + this._brushtimer1 && clearTimeout(this._brushtimer1); + this._brushtimer && clearTimeout(this._brushtimer); + this._brushedView = undefined; + this._brushtimer1 = setTimeout( + action(() => { + this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 }; + this._brushtimer = setTimeout(action(() => { this._brushedView = undefined; }), holdTime); // prettier-ignore + }), + transTime + 1 + ); + }); + lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30); + lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30); + lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15); + onPassiveWheel = (e: WheelEvent) => { + const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight); + const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling; + this._props.isSelected() && !scrollable && e.preventDefault(); + }; + get backgroundGrid() { return ( <div> <CollectionFreeFormBackgroundGrid // bcz : UGHH don't know why, but if we don't wrap in a div, then PDF's don't render when taking snapshot of a dashboard and the background grid is on!!? @@ -1886,7 +1649,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge} addDocTab={this.addDocTab} slowLoadDocuments={this.slowLoadDocuments} - trySelectCluster={this.trySelectCluster} + trySelectCluster={this._clusters.trySelectCluster} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument} @@ -1902,39 +1665,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </MarqueeView> ); } - - @computed get nativeDimScaling() { - if (this._firstRender || (this._props.isAnnotationOverlay && !this._props.annotationLayerHostsContent)) return 1; - const nw = this.nativeWidth; - const nh = this.nativeHeight; - const hscale = nh ? this._props.PanelHeight() / nh : 1; - const wscale = nw ? this._props.PanelWidth() / nw : 1; - return wscale < hscale || (this._props.layout_fitWidth?.(this.Document) ?? this.layoutDoc.layout_fitWidth) ? wscale : hscale; - } - nativeDim = () => this.nativeDimScaling; - - @action - brushView = (viewport: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number = 2500) => { - this._brushtimer1 && clearTimeout(this._brushtimer1); - this._brushtimer && clearTimeout(this._brushtimer); - this._brushedView = undefined; - this._brushtimer1 = setTimeout( - action(() => { - this._brushedView = { ...viewport, panX: viewport.panX - viewport.width / 2, panY: viewport.panY - viewport.height / 2 }; - this._brushtimer = setTimeout(action(() => { this._brushedView = undefined; }), holdTime); // prettier-ignore - }), - transTime + 1 + get placeholder() { + return ( + <div className="collectionfreeformview-placeholder" style={{ background: this.backgroundColor }}> + <span className="collectionfreeformview-placeholderSpan">{this.Document.annotationOn ? '' : this.Document.title?.toString()}</span> + </div> ); - }; - lightboxPanelWidth = () => Math.max(0, this._props.PanelWidth() - 30); - lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30); - lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15); - onPassiveWheel = (e: WheelEvent) => { - const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_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 ( @@ -1992,10 +1729,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ) : ( <> {this._firstRender ? this.placeholder : this.marqueeView} - { - // eslint-disable-next-line no-use-before-define - this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} /> - } + {this._props.noOverlay ? null : <CollectionFreeFormOverlayView elements={this.elementFunc} />} {!this.GroupChildDrag ? null : <div className="collectionFreeForm-groupDropper" />} </> )} @@ -2003,13 +1737,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); } } - -@observer -class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> { - render() { - return this.props.elements().filter(ele => ele.bounds?.z).map(ele => ele.ele); // prettier-ignore - } -} // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 80ece7cc8..9df928581 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -13,7 +13,6 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { PinProps, ViewBoxBaseComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; import { FieldView, FieldViewProps } from './FieldView'; -// eslint-disable-next-line import/extensions import BigText from './LabelBigText'; import './LabelBox.scss'; import { PresBox } from './trails'; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 780ac3acb..4d5013437 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -6,7 +6,7 @@ import { observer } from 'mobx-react'; import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import * as React from 'react'; -import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; +import { ClientUtils, returnFalse, setupMoveUpEvents, UpdateIcon } from '../../../ClientUtils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -28,7 +28,6 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { PinProps, ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { Colors } from '../global/globalEnums'; -// eslint-disable-next-line import/extensions import { CreateImage } from './WebBoxRenderer'; import { PDFViewer } from '../pdf/PDFViewer'; import { SidebarAnnos } from '../SidebarAnnos'; @@ -184,7 +183,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps>() implem const docViewContent = this.DocumentView?.().ContentDiv!; const filename = this.layoutDoc[Id] + '-icon' + new Date().getTime(); this._pdfViewer?._mainCont.current && - CollectionFreeFormView.UpdateIcon( + UpdateIcon( filename, docViewContent, NumCast(this.layoutDoc._width), diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 52d9c3b31..d659d5e3c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -65,7 +65,6 @@ import { FootnoteView } from './FootnoteView'; import './FormattedTextBox.scss'; import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer'; -// eslint-disable-next-line import/extensions import { removeMarkWithAttrs } from './prosemirrorPatches'; import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; diff --git a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts index 6c88a0d29..8799964b3 100644 --- a/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts +++ b/src/client/views/nodes/formattedText/ParagraphNodeSpec.ts @@ -1,4 +1,3 @@ -/* eslint-disable import/extensions */ import { Node, DOMOutputSpec } from 'prosemirror-model'; import clamp from '../../../util/clamp'; import convertToCSSPTValue from '../../../util/convertToCSSPTValue'; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index c107a6724..72a2b706d 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -14,7 +14,6 @@ import { CollectionView } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; import { KeyValueBox } from '../KeyValueBox'; import { FormattedTextBox } from './FormattedTextBox'; -// eslint-disable-next-line import/extensions import { wrappingInputRule } from './prosemirrorPatches'; import { RichTextMenu } from './RichTextMenu'; import { schema } from './schema_rts'; |