import { action, observable } from 'mobx'; import { CollectionFreeFormView } from '.'; import { intersectRect } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { StyleProp } from '../../StyleProp'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import './CollectionFreeFormView.scss'; 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 }); } handlePointerDown(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; } tryToDrag(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 => DocumentView.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; } initLayout() { 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.addDocument(c)); } @action addDocuments(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.addDocument(child)); } } @action addDocument = (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, props: Opt, property: string) => { 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.addDocument(doc)); } else { const palette = ['#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)']; // override palette cluster color with an explicitly set cluster doc color return this._clusterSets[cluster]?.reduce((b, s) => StrCast(s.backgroundColor, b), palette[cluster % palette.length]); } } } break; case StyleProp.FillColor: if (doc && this.Document._currentFrame !== undefined) { return CollectionFreeFormDocumentView.getStringValues(doc, NumCast(this.Document._currentFrame))?.fillColor; } break; default: } return this.viewStyleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 }; tryToSelect = (addToSel: boolean) => { if (addToSel && this._hitCluster !== -1) { !addToSel && DocumentView.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; }; }