diff options
Diffstat (limited to 'src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts')
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts | 224 |
1 files changed, 224 insertions, 0 deletions
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; + }; +} |
