diff options
Diffstat (limited to 'src')
23 files changed, 234 insertions, 187 deletions
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 8411cc6ee..77b111412 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -72,36 +72,38 @@ export const inpRules = { return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: size })); }), - // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document [[ <fieldKey> : <Doc>]] // [[:Doc]] => hyperlink [[fieldKey]] => show field [[fieldKey:Doc]] => show field of doc new InputRule( new RegExp(/\[\[([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\]\]$/), (state, match, start, end) => { - if (!match[2]) { - const docId = match[1]; - DocServer.GetRefField(docId).then(docx => { - const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500, _LODdisable: true, }, docId); - DocUtils.Publish(target, docId, returnFalse, returnFalse); + const fieldKey = match[1]; + const docid = match[2]?.substring(1); + if (!fieldKey) { + DocServer.GetRefField(docid).then(docx => { + const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true, }, docid); + DocUtils.Publish(target, docid, returnFalse, returnFalse); DocUtils.MakeLink({ doc: (schema as any).Document }, { doc: target }, "portal link", ""); }); - const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docId), location: "onRight", title: docId, targetId: docId }); + const link = state.schema.marks.link.create({ href: Utils.prepend("/doc/" + docid), location: "onRight", title: docid, targetId: docid }); return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); } - const fieldView = state.schema.nodes.dashField.create({ fieldKey: match[2]?.substring(1), docid: match[1] }); + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docid }); return state.tr.deleteRange(start, end).insert(start, fieldView); }), - // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // create an inline view of a document {{ <layoutKey> : <Doc> }} // {{:Doc}} => show default view of document {{<layout>}} => show layout for this doc {{<layout> : Doc}} => show layout for another doc new InputRule( new RegExp(/\{\{([a-zA-Z_ \-0-9]*)(:[a-zA-Z_ \-0-9]+)?\}\}$/), (state, match, start, end) => { - const docId = match[1]; - DocServer.GetRefField(docId).then(docx => { + const fieldKey = match[1]; + const docid = match[2]?.substring(1); + DocServer.GetRefField(docid).then(docx => { if (!(docx instanceof Doc && docx)) { - const docx = Docs.Create.FreeformDocument([], { title: docId, _width: 500, _height: 500, _LODdisable: true }, docId); - DocUtils.Publish(docx, docId, returnFalse, returnFalse); + const docx = Docs.Create.FreeformDocument([], { title: docid, _width: 500, _height: 500, _LODdisable: true }, docid); + DocUtils.Publish(docx, docid, returnFalse, returnFalse); } }); const node = (state.doc.resolve(start) as any).nodeAfter; - const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid: docId, float: "right", fieldKey: match[2]?.substring(1), alias: Utils.GenerateGuid() }); + const dashDoc = schema.nodes.dashDoc.create({ width: 75, height: 75, title: "dashDoc", docid, fieldKey, float: "right", alias: Utils.GenerateGuid() }); const sm = state.storedMarks || undefined; return node ? state.tr.replaceRangeWith(start, end, dashDoc).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; }), diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3957a1f26..b98e422cb 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -767,8 +767,8 @@ export class DashDocView { if (!(dashDoc instanceof Doc)) { alias && DocServer.GetRefField(docid).then(async dashDocBase => { if (dashDocBase instanceof Doc) { - const aliasedDoc = Doc.MakeDelegate(dashDocBase, docid + alias); - aliasedDoc.layoutKey = "layout_" + node.attrs.fieldKey; + const aliasedDoc = Doc.MakeAlias(dashDocBase, docid + alias); + aliasedDoc.layoutKey = "layout" + (node.attrs.fieldKey ? "_" + node.attrs.fieldKey : ""); self.doRender(aliasedDoc, removeDoc, node, view, getPos); } }); @@ -810,10 +810,10 @@ export class DashDocView { finalLayout._textTemplate = ComputedField.MakeFunction(`copyField(this.${finalKey})`, { this: Doc.name }); } } - this._reactionDisposer && this._reactionDisposer(); + this._reactionDisposer?.(); this._reactionDisposer = reaction(() => [finalLayout[WidthSym](), finalLayout[HeightSym]()], (dim) => { - this._dashSpan.style.width = this._outer.style.width = dim[0] + "px"; - this._dashSpan.style.height = this._outer.style.height = dim[1] + "px"; + this._dashSpan.style.width = this._outer.style.width = Math.max(20, dim[0]) + "px"; + this._dashSpan.style.height = this._outer.style.height = Math.max(20, dim[1]) + "px"; }, { fireImmediately: true }); ReactDOM.render(<DocumentView Document={finalLayout} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 0eeb1c83d..9cdd48089 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -254,7 +254,12 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { const img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : ""; if (img) { const split = img.split("src=\"")[1].split("\"")[0]; - const doc = Docs.Create.ImageDocument(split, { ...options, _width: 300 }); + let source = split; + if (split.startsWith("data:image") && split.includes("base64")) { + const [{ clientAccessPath }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] }); + source = Utils.prepend(clientAccessPath); + } + const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 }); ImageUtils.ExtractExif(doc); this.props.addDocument(doc); return; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index d5d62159b..bdd908807 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -133,7 +133,7 @@ export class CollectionView extends Touchable<FieldViewProps> { @action.bound addDocument(doc: Doc): boolean { - const targetDataDoc = Doc.GetProto(this.props.Document); + const targetDataDoc = Doc.GetProto(this.props.DataDoc || this.props.Document); Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); Doc.GetProto(doc).lastOpened = new DateField; @@ -144,7 +144,7 @@ export class CollectionView extends Touchable<FieldViewProps> { removeDocument(doc: Doc): boolean { const docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); docView && SelectionManager.DeselectDoc(docView); - const value = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []); + const value = Cast((this.props.DataDoc || this.props.Document)[this.props.fieldKey], listSpec(Doc), []); let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index f08c2506e..63bcc68e5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -60,32 +60,36 @@ function toLabel(target: FieldResult<Field>) { } export function computePivotLayout( - poolData: ObservableMap<string, PoolData>, + poolData: Map<string, PoolData>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: any) => ViewDefResult[] ) { + console.log("PIVOT " + pivotDoc[HeightSym]()); const fieldKey = "data"; - const pivotAxisWidth = NumCast(pivotDoc.pivotWidth, 1000); const pivotColumnGroups = new Map<FieldResult<Field>, Doc[]>(); const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3)); + let maxInColumn = 1; const pivotFieldKey = toLabel(pivotDoc.pivotField); for (const doc of childDocs) { const val = Field.toString(doc[pivotFieldKey] as Field); if (val) { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, []); pivotColumnGroups.get(val)!.push(doc); + maxInColumn = Math.max(maxInColumn, pivotColumnGroups.get(val)?.length || 0); } } - const minSize = Array.from(pivotColumnGroups.entries()).reduce((min, pair) => Math.min(min, pair[1].length), Infinity); - let numCols = NumCast(pivotDoc.pivotNumColumns, Math.ceil(Math.sqrt(minSize))); + const colWidth = panelDim[0] / pivotColumnGroups.size; + const colHeight = panelDim[1]; + const pivotAxisWidth = Math.sqrt(colWidth * colHeight / maxInColumn); + const numCols = Math.max(Math.round(colWidth / pivotAxisWidth), 1); + const docMap = new Map<Doc, ViewDefBounds>(); - const groupNames: PivotData[] = []; - numCols = Math.min(Math.max(1, panelDim[0] / pivotAxisWidth), numCols); + const groupNames: PivotData[] = [];; const expander = 1.05; const gap = .15; @@ -133,7 +137,7 @@ export function computePivotLayout( export function computeTimelineLayout( - poolData: ObservableMap<string, PoolData>, + poolData: Map<string, PoolData>, pivotDoc: Doc, childDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], @@ -204,7 +208,21 @@ export function computeTimelineLayout( x += scaling * (key - prevKey); const stack = findStack(x, stacking); prevKey = key; - !stack && Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth && groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize }); + !stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth) && groupNames.push({ type: "text", text: key.toString(), x: x, y: stack * 25, height: fontHeight, fontSize }); + layoutDocsAtTime(keyDocs, key); + }); + if (sortedKeys.length && curTime > sortedKeys[sortedKeys.length - 1]) { + x = (curTime - minTime) * scaling; + groupNames.push({ type: "text", text: curTime.toString(), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize }); + } + if (Math.ceil(maxTime - minTime) * scaling > x + 25) { + groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize }); + } + + const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1 } as any; + return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); + + function layoutDocsAtTime(keyDocs: Doc[], key: number) { keyDocs.forEach(doc => { const stack = findStack(x, stacking); const layoutDoc = Doc.Layout(doc); @@ -215,55 +233,38 @@ export function computeTimelineLayout( wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } docMap.set(doc, { - x: x, y: - Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2, + x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2, zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt }); stacking[stack] = x + pivotAxisWidth; }); - }); - if (sortedKeys.length && curTime > sortedKeys[sortedKeys.length - 1]) { - x = (curTime - minTime) * scaling; - groupNames.push({ type: "text", text: curTime.toString(), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize }); } - if (Math.ceil(maxTime - minTime) * scaling > x + 25) { - groupNames.push({ type: "text", text: Math.ceil(maxTime).toString(), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize }); - } - - const divider = { type: "div", color: "black", x: 0, y: 0, width: panelDim[0], height: 1 } as any; - return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); } function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>, - poolData: ObservableMap<string, PoolData>, viewDefsToJSX: (views: any) => ViewDefResult[], groupNames: PivotData[], minWidth: number, extras: PivotData[]) { + poolData: Map<string, PoolData>, viewDefsToJSX: (views: any) => ViewDefResult[], groupNames: PivotData[], minWidth: number, extras: PivotData[]) { const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, height: gn.height }) as PivotData); - const docEles = childPairs.filter(d => !d.layout.isMinimized).map(pair => - docMap.get(pair.layout) || { x: NumCast(pair.layout.x), y: NumCast(pair.layout.y), width: pair.layout[WidthSym](), height: pair.layout[HeightSym]() } as PivotData // new pos is computed pos, or pos written to the document's fields - ); + const docEles = childPairs.filter(d => !d.layout.isMinimized).map(pair => docMap.get(pair.layout) as PivotData); const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0); aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); const wscale = panelDim[0] / (aggBounds.r - aggBounds.x); let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale; if (Number.isNaN(scale)) scale = 1; - childPairs.map(pair => { - const fallbackPos = { - x: NumCast(pair.layout.x), - y: NumCast(pair.layout.y), - z: NumCast(pair.layout.z), - highlight: undefined, - zIndex: NumCast(pair.layout.zIndex), - width: NumCast(pair.layout._width), - height: NumCast(pair.layout._height) - }; - const newPosRaw = docMap.get(pair.layout) || fallbackPos; // new pos is computed pos, or pos written to the document's fields - const newPos = { - x: newPosRaw.x * scale, y: newPosRaw.y * scale, z: newPosRaw.z, zIndex: newPosRaw.zIndex, highlight: newPosRaw.highlight, - width: (newPosRaw.width || 0) * scale, height: newPosRaw.height! * scale - }; - const lastPos = poolData.get(pair.layout[Id]); // last computed pos - if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) { - runInAction(() => poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos })); + childPairs.filter(d => !d.layout.isMinimized).map(pair => { + const newPosRaw = docMap.get(pair.layout); + if (newPosRaw) { + const newPos = { + x: newPosRaw.x * scale, + y: newPosRaw.y * scale, + z: newPosRaw.z, + highlight: newPosRaw.highlight, + zIndex: newPosRaw.zIndex, + width: (newPosRaw.width || 0) * scale, + height: newPosRaw.height! * scale + }; + poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos }); } }); @@ -275,8 +276,7 @@ function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { y: gname.y * scale, color: gname.color, width: gname.width === undefined ? undefined : gname.width * scale, - height: Math.max(fontHeight, gname.height! * scale), - // height: gname.height === undefined ? undefined : gname.height * scale, + height: Math.max(fontHeight, (gname.height || 0) * scale), fontSize: gname.fontSize })))) }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f1a239050..6453cfe17 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,7 +1,7 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer } from "mobx"; +import { action, computed, observable, ObservableMap, reaction, runInAction, IReactionDisposer, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym, DocListCastAsync, Field } from "../../../../new_fields/Doc"; import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas"; @@ -72,7 +72,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private _clusterDistance: number = 75; private _hitCluster = false; private _layoutComputeReaction: IReactionDisposer | undefined; - private _layoutPoolData = new ObservableMap<string, any>(); + private _layoutPoolData = observable.map<string, any>(); public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @@ -789,47 +789,56 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return this._layoutPoolData.get(doc[Id]); }.bind(this)); - doTimelineLayout(poolData: ObservableMap<string, any>) { + doTimelineLayout(poolData: Map<string, any>) { return computeTimelineLayout(poolData, this.props.Document, this.childDocs, - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); + this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); } - doPivotLayout(poolData: ObservableMap<string, any>) { + doPivotLayout(poolData: Map<string, any>) { return computePivotLayout(poolData, this.props.Document, this.childDocs, - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)), [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); + this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); } - doFreeformLayout(poolData: ObservableMap<string, any>) { + _cachedPool: Map<string, any> = new Map(); + doFreeformLayout(poolData: Map<string, any>) { const layoutDocs = this.childLayoutPairs.map(pair => pair.layout); const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log); let state = initResult && initResult.success ? initResult.result.scriptState : undefined; const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { - const data = poolData.get(pair.layout[Id]); const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); state = pos.state === undefined ? state : pos.state; - if (!data || pos.x !== data.x || pos.y !== data.y || pos.z !== data.z || pos.zIndex !== data.zIndex || - pos.width !== data.width || pos.height !== data.height || pos.transition !== data.transition) { - runInAction(() => poolData.set(pair.layout[Id], pos)); - } + poolData.set(pair.layout[Id], pos); }); return { elements: elements }; } - get doLayoutComputation() { - let computedElementData: { elements: ViewDefResult[] }; + @computed get doInternalLayoutComputation() { + const newPool = new Map<string, any>(); switch (this.Document._freeformLayoutEngine) { - case "timeline": computedElementData = this.doTimelineLayout(this._layoutPoolData); break; - case "pivot": computedElementData = this.doPivotLayout(this._layoutPoolData); break; - default: computedElementData = this.doFreeformLayout(this._layoutPoolData); break; + case "timeline": return { newPool, computedElementData: this.doTimelineLayout(newPool) }; + case "pivot": return { newPool, computedElementData: this.doPivotLayout(newPool) }; } + return { newPool, computedElementData: this.doFreeformLayout(newPool) }; + } + get doLayoutComputation() { + const { newPool, computedElementData } = this.doInternalLayoutComputation; + Array.from(newPool.keys()).map(key => { + const lastPos = this._cachedPool.get(key); // last computed pos + const newPos = newPool.get(key); + if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) { + runInAction(() => this._layoutPoolData.set(key, { transition: "transform 1s", ...newPos })); + } + }); + this._cachedPool.clear(); + Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k))); this.childLayoutPairs.filter((pair, i) => this.isCurrent(pair.layout)).forEach(pair => computedElementData.elements.push({ ele: <CollectionFreeFormDocumentView key={pair.layout[Id]} {...this.getChildDocumentViewProps(pair.layout, pair.data)} dataProvider={this.childDataProvider} jitterRotation={NumCast(this.props.Document.jitterRotation)} - fitToBox={this.props.fitToBox || this.Document._freeformLayoutEngine === "pivot" || this.Document._freeformLayoutEngine === "timeline"} />, + fitToBox={this.props.fitToBox || this.Document._freeformLayoutEngine !== undefined} />, bounds: this.childDataProvider(pair.layout) })); @@ -838,12 +847,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { componentDidMount() { super.componentDidMount(); - this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation; }, - action((computation: { elements: ViewDefResult[] }) => computation && (this._layoutElements = computation.elements)), + this._layoutComputeReaction = reaction( + () => (this.doLayoutComputation), + (computation) => this._layoutElements = computation?.elements.slice() || [], { fireImmediately: true, name: "doLayout" }); } componentWillUnmount() { - this._layoutComputeReaction && this._layoutComputeReaction(); + this._layoutComputeReaction?.(); } @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } elementFunc = () => this._layoutElements; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 041eb69da..65862f34f 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -28,12 +28,12 @@ interface LayoutData { starSum: number; } -export const WidthUnit = { +export const DimUnit = { Pixel: "px", Ratio: "*" }; -const resolvedUnits = Object.values(WidthUnit); +const resolvedUnits = Object.values(DimUnit); const resizerWidth = 4; @observer @@ -45,12 +45,12 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu */ @computed private get ratioDefinedDocs() { - return this.childLayoutPairs.map(({ layout }) => layout).filter(({ widthUnit }) => StrCast(widthUnit) === WidthUnit.Ratio); + return this.childLayoutPairs.map(({ layout }) => layout).filter(({ dimUnit }) => StrCast(dimUnit) === DimUnit.Ratio); } /** - * This loops through all childLayoutPairs and extracts the values for widthUnit - * and widthMagnitude, ignoring any that are malformed. Additionally, it then + * This loops through all childLayoutPairs and extracts the values for dimUnit + * and dimMagnitude, ignoring any that are malformed. Additionally, it then * normalizes the ratio values so that one * value is always 1, with the remaining * values proportionate to that easily readable metric. * @returns the list of the resolved width specifiers (unit and magnitude pairs) @@ -60,11 +60,11 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu private get resolvedLayoutInformation(): LayoutData { let starSum = 0; const widthSpecifiers: WidthSpecifier[] = []; - this.childLayoutPairs.map(({ layout: { widthUnit, widthMagnitude } }) => { - const unit = StrCast(widthUnit); - const magnitude = NumCast(widthMagnitude); + this.childLayoutPairs.map(({ layout: { dimUnit, dimMagnitude } }) => { + const unit = StrCast(dimUnit); + const magnitude = NumCast(dimMagnitude); if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) { - (unit === WidthUnit.Ratio) && (starSum += magnitude); + (unit === DimUnit.Ratio) && (starSum += magnitude); widthSpecifiers.push({ magnitude, unit }); } /** @@ -82,9 +82,9 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu setTimeout(() => { const { ratioDefinedDocs } = this; if (this.childLayoutPairs.length) { - const minimum = Math.min(...ratioDefinedDocs.map(({ widthMagnitude }) => NumCast(widthMagnitude))); + const minimum = Math.min(...ratioDefinedDocs.map(({ dimMagnitude }) => NumCast(dimMagnitude))); if (minimum !== 0) { - ratioDefinedDocs.forEach(layout => layout.widthMagnitude = NumCast(layout.widthMagnitude) / minimum); + ratioDefinedDocs.forEach(layout => layout.dimMagnitude = NumCast(layout.dimMagnitude) / minimum); } } }); @@ -103,7 +103,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu @computed private get totalFixedAllocation(): number | undefined { return this.resolvedLayoutInformation?.widthSpecifiers.reduce( - (sum, { magnitude, unit }) => sum + (unit === WidthUnit.Pixel ? magnitude : 0), 0); + (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0); } /** @@ -160,8 +160,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu if (columnUnitLength === undefined) { return 0; // we're still waiting on promises to resolve } - let width = NumCast(layout.widthMagnitude); - if (StrCast(layout.widthUnit) === WidthUnit.Ratio) { + let width = NumCast(layout.dimMagnitude); + if (StrCast(layout.dimUnit) === DimUnit.Ratio) { width *= columnUnitLength; } return width; @@ -193,8 +193,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu drop = (e: Event, de: DragManager.DropEvent) => { if (super.drop(e, de)) { de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d.widthUnit = "*"; - d.widthMagnitude = 1; + d.dimUnit = "*"; + d.dimMagnitude = 1; })); } return false; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index e07985bb4..aa440b677 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -28,12 +28,12 @@ interface LayoutData { starSum: number; } -export const HeightUnit = { +export const DimUnit = { Pixel: "px", Ratio: "*" }; -const resolvedUnits = Object.values(HeightUnit); +const resolvedUnits = Object.values(DimUnit); const resizerHeight = 4; @observer @@ -45,12 +45,12 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) */ @computed private get ratioDefinedDocs() { - return this.childLayoutPairs.map(({ layout }) => layout).filter(({ widthUnit }) => StrCast(widthUnit) === HeightUnit.Ratio); + return this.childLayoutPairs.map(({ layout }) => layout).filter(({ dimUnit }) => StrCast(dimUnit) === DimUnit.Ratio); } /** - * This loops through all childLayoutPairs and extracts the values for widthUnit - * and widthMagnitude, ignoring any that are malformed. Additionally, it then + * This loops through all childLayoutPairs and extracts the values for dimUnit + * and dimUnit, ignoring any that are malformed. Additionally, it then * normalizes the ratio values so that one * value is always 1, with the remaining * values proportionate to that easily readable metric. * @returns the list of the resolved width specifiers (unit and magnitude pairs) @@ -60,11 +60,11 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) private get resolvedLayoutInformation(): LayoutData { let starSum = 0; const heightSpecifiers: HeightSpecifier[] = []; - this.childLayoutPairs.map(({ layout: { heightUnit, heightMagnitude } }) => { - const unit = StrCast(heightUnit); - const magnitude = NumCast(heightMagnitude); + this.childLayoutPairs.map(({ layout: { dimUnit, dimMagnitude } }) => { + const unit = StrCast(dimUnit); + const magnitude = NumCast(dimMagnitude); if (unit && magnitude && magnitude > 0 && resolvedUnits.includes(unit)) { - (unit === HeightUnit.Ratio) && (starSum += magnitude); + (unit === DimUnit.Ratio) && (starSum += magnitude); heightSpecifiers.push({ magnitude, unit }); } /** @@ -82,9 +82,9 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) setTimeout(() => { const { ratioDefinedDocs } = this; if (this.childLayoutPairs.length) { - const minimum = Math.min(...ratioDefinedDocs.map(({ heightMagnitude }) => NumCast(heightMagnitude))); + const minimum = Math.min(...ratioDefinedDocs.map(({ dimMagnitude }) => NumCast(dimMagnitude))); if (minimum !== 0) { - ratioDefinedDocs.forEach(layout => layout.heightMagnitude = NumCast(layout.heightMagnitude) / minimum); + ratioDefinedDocs.forEach(layout => layout.dimMagnitude = NumCast(layout.dimMagnitude) / minimum); } } }); @@ -103,7 +103,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) @computed private get totalFixedAllocation(): number | undefined { return this.resolvedLayoutInformation?.heightSpecifiers.reduce( - (sum, { magnitude, unit }) => sum + (unit === HeightUnit.Pixel ? magnitude : 0), 0); + (sum, { magnitude, unit }) => sum + (unit === DimUnit.Pixel ? magnitude : 0), 0); } /** @@ -160,8 +160,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) if (rowUnitLength === undefined) { return 0; // we're still waiting on promises to resolve } - let height = NumCast(layout.heightMagnitude); - if (StrCast(layout.heightUnit) === HeightUnit.Ratio) { + let height = NumCast(layout.dimMagnitude); + if (StrCast(layout.dimUnit) === DimUnit.Ratio) { height *= rowUnitLength; } return height; @@ -193,8 +193,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) drop = (e: Event, de: DragManager.DropEvent) => { if (super.drop(e, de)) { de.complete.docDragData?.droppedDocuments.forEach(action((d: Doc) => { - d.heightUnit = "*"; - d.heightMagnitude = 1; + d.dimUnit = "*"; + d.dimMagnitude = 1; })); } return false; diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index 11e210958..46c39d817 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import { observable, action } from "mobx"; import { Doc } from "../../../../new_fields/Doc"; import { NumCast, StrCast } from "../../../../new_fields/Types"; -import { WidthUnit } from "./CollectionMulticolumnView"; +import { DimUnit } from "./CollectionMulticolumnView"; interface ResizerProps { width: number; @@ -46,14 +46,14 @@ export default class ResizeBar extends React.Component<ResizerProps> { const unitLength = columnUnitLength(); if (unitLength) { if (toNarrow) { - const { widthUnit, widthMagnitude } = toNarrow; - const scale = widthUnit === WidthUnit.Ratio ? unitLength : 1; - toNarrow.widthMagnitude = NumCast(widthMagnitude) - Math.abs(movementX) / scale; + const { dimUnit, dimMagnitude } = toNarrow; + const scale = dimUnit === DimUnit.Ratio ? unitLength : 1; + toNarrow.dimMagnitude = NumCast(dimMagnitude) - Math.abs(movementX) / scale; } if (this.resizeMode === ResizeMode.Pinned && toWiden) { - const { widthUnit, widthMagnitude } = toWiden; - const scale = widthUnit === WidthUnit.Ratio ? unitLength : 1; - toWiden.widthMagnitude = NumCast(widthMagnitude) + Math.abs(movementX) / scale; + const { dimUnit, dimMagnitude } = toWiden; + const scale = dimUnit === DimUnit.Ratio ? unitLength : 1; + toWiden.dimMagnitude = NumCast(dimMagnitude) + Math.abs(movementX) / scale; } } } @@ -61,17 +61,17 @@ export default class ResizeBar extends React.Component<ResizerProps> { private get isActivated() { const { toLeft, toRight } = this.props; if (toLeft && toRight) { - if (StrCast(toLeft.widthUnit) === WidthUnit.Pixel && StrCast(toRight.widthUnit) === WidthUnit.Pixel) { + if (StrCast(toLeft.dimUnit) === DimUnit.Pixel && StrCast(toRight.dimUnit) === DimUnit.Pixel) { return false; } return true; } else if (toLeft) { - if (StrCast(toLeft.widthUnit) === WidthUnit.Pixel) { + if (StrCast(toLeft.dimUnit) === DimUnit.Pixel) { return false; } return true; } else if (toRight) { - if (StrCast(toRight.widthUnit) === WidthUnit.Pixel) { + if (StrCast(toRight.dimUnit) === DimUnit.Pixel) { return false; } return true; diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx index b394fed62..5b2054428 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnWidthLabel.tsx @@ -4,7 +4,7 @@ import { computed } from "mobx"; import { Doc } from "../../../../new_fields/Doc"; import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types"; import { EditableView } from "../../EditableView"; -import { WidthUnit } from "./CollectionMulticolumnView"; +import { DimUnit } from "./CollectionMulticolumnView"; interface WidthLabelProps { layout: Doc; @@ -18,8 +18,8 @@ export default class WidthLabel extends React.Component<WidthLabelProps> { @computed private get contents() { const { layout, decimals } = this.props; - const getUnit = () => StrCast(layout.widthUnit); - const getMagnitude = () => String(+NumCast(layout.widthMagnitude).toFixed(decimals ?? 3)); + const getUnit = () => StrCast(layout.dimUnit); + const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3)); return ( <div className={"label-wrapper"}> <EditableView @@ -27,7 +27,7 @@ export default class WidthLabel extends React.Component<WidthLabelProps> { SetValue={value => { const converted = Number(value); if (!isNaN(converted) && converted > 0) { - layout.widthMagnitude = converted; + layout.dimMagnitude = converted; return true; } return false; @@ -37,8 +37,8 @@ export default class WidthLabel extends React.Component<WidthLabelProps> { <EditableView GetValue={getUnit} SetValue={value => { - if (Object.values(WidthUnit).includes(value)) { - layout.widthUnit = value; + if (Object.values(DimUnit).includes(value)) { + layout.dimUnit = value; return true; } return false; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx index 56a2e868d..899577fd5 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx @@ -4,7 +4,7 @@ import { computed } from "mobx"; import { Doc } from "../../../../new_fields/Doc"; import { NumCast, StrCast, BoolCast } from "../../../../new_fields/Types"; import { EditableView } from "../../EditableView"; -import { HeightUnit } from "./CollectionMultirowView"; +import { DimUnit } from "./CollectionMultirowView"; interface HeightLabelProps { layout: Doc; @@ -18,8 +18,8 @@ export default class HeightLabel extends React.Component<HeightLabelProps> { @computed private get contents() { const { layout, decimals } = this.props; - const getUnit = () => StrCast(layout.heightUnit); - const getMagnitude = () => String(+NumCast(layout.heightMagnitude).toFixed(decimals ?? 3)); + const getUnit = () => StrCast(layout.dimUnit); + const getMagnitude = () => String(+NumCast(layout.dimMagnitude).toFixed(decimals ?? 3)); return ( <div className={"label-wrapper"}> <EditableView @@ -27,7 +27,7 @@ export default class HeightLabel extends React.Component<HeightLabelProps> { SetValue={value => { const converted = Number(value); if (!isNaN(converted) && converted > 0) { - layout.heightMagnitude = converted; + layout.dimMagnitude = converted; return true; } return false; @@ -37,8 +37,8 @@ export default class HeightLabel extends React.Component<HeightLabelProps> { <EditableView GetValue={getUnit} SetValue={value => { - if (Object.values(HeightUnit).includes(value)) { - layout.heightUnit = value; + if (Object.values(DimUnit).includes(value)) { + layout.dimUnit = value; return true; } return false; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index 20c6cd3df..4f58f3fa8 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -3,7 +3,7 @@ import { observer } from "mobx-react"; import { observable, action } from "mobx"; import { Doc } from "../../../../new_fields/Doc"; import { NumCast, StrCast } from "../../../../new_fields/Types"; -import { HeightUnit } from "./CollectionMultirowView"; +import { DimUnit } from "./CollectionMultirowView"; interface ResizerProps { height: number; @@ -46,14 +46,14 @@ export default class ResizeBar extends React.Component<ResizerProps> { const unitLength = columnUnitLength(); if (unitLength) { if (toNarrow) { - const { heightUnit, heightMagnitude } = toNarrow; - const scale = heightUnit === HeightUnit.Ratio ? unitLength : 1; - toNarrow.heightMagnitude = NumCast(heightMagnitude) - Math.abs(movementY) / scale; + const { dimUnit, dimMagnitude } = toNarrow; + const scale = dimUnit === DimUnit.Ratio ? unitLength : 1; + toNarrow.dimMagnitude = NumCast(dimMagnitude) - Math.abs(movementY) / scale; } if (this.resizeMode === ResizeMode.Pinned && toWiden) { - const { heightUnit, heightMagnitude } = toWiden; - const scale = heightUnit === HeightUnit.Ratio ? unitLength : 1; - toWiden.heightMagnitude = NumCast(heightMagnitude) + Math.abs(movementY) / scale; + const { dimUnit, dimMagnitude } = toWiden; + const scale = dimUnit === DimUnit.Ratio ? unitLength : 1; + toWiden.dimMagnitude = NumCast(dimMagnitude) + Math.abs(movementY) / scale; } } } @@ -61,17 +61,17 @@ export default class ResizeBar extends React.Component<ResizerProps> { private get isActivated() { const { toTop, toBottom } = this.props; if (toTop && toBottom) { - if (StrCast(toTop.heightUnit) === HeightUnit.Pixel && StrCast(toBottom.heightUnit) === HeightUnit.Pixel) { + if (StrCast(toTop.dimUnit) === DimUnit.Pixel && StrCast(toBottom.dimUnit) === DimUnit.Pixel) { return false; } return true; } else if (toTop) { - if (StrCast(toTop.heightUnit) === HeightUnit.Pixel) { + if (StrCast(toTop.dimUnit) === DimUnit.Pixel) { return false; } return true; } else if (toBottom) { - if (StrCast(toBottom.heightUnit) === HeightUnit.Pixel) { + if (StrCast(toBottom.dimUnit) === DimUnit.Pixel) { return false; } return true; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index ea7ed1d54..2a11267d4 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -2,7 +2,6 @@ import anime from "animejs"; import { computed, IReactionDisposer, observable, reaction, trace } from "mobx"; import { observer } from "mobx-react"; import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; -import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { Transform } from "../../util/Transform"; import { DocComponent } from "../DocComponent"; diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index 51c8e00da..bd1b6166f 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -45,13 +45,14 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo private get layoutDoc() { return this.props.Document && Doc.Layout(this.props.Document); } private get nativeWidth() { return NumCast(this.layoutDoc?._nativeWidth, this.props.PanelWidth()); } private get nativeHeight() { return NumCast(this.layoutDoc?._nativeHeight, this.props.PanelHeight()); } - private contentScaling = () => { + @computed get scaling() { const wscale = this.props.PanelWidth() / (this.nativeWidth || this.props.PanelWidth() || 1); if (wscale * this.nativeHeight > this.props.PanelHeight()) { return (this.props.PanelHeight() / (this.nativeHeight || this.props.PanelHeight() || 1)) || 1; } return wscale || 1; } + private contentScaling = () => this.scaling; @undoBatch @action @@ -67,8 +68,12 @@ export class ContentFittingDocumentView extends React.Component<ContentFittingDo } return true; } - private PanelWidth = () => this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); - private PanelHeight = () => this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); + private PanelWidth = () => this.panelWidth; + private PanelHeight = () => this.panelHeight;; + + @computed get panelWidth() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeWidth * this.contentScaling() : this.props.PanelWidth(); } + @computed get panelHeight() { return this.nativeHeight && (!this.props.Document || !this.props.Document._fitWidth) ? this.nativeHeight * this.contentScaling() : this.props.PanelHeight(); } + private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, -this.centeringYOffset).scale(1 / this.contentScaling()); private get centeringOffset() { return this.nativeWidth && (!this.props.Document || !this.props.Document._fitWidth) ? (this.props.PanelWidth() - this.nativeWidth * this.contentScaling()) / 2 : 0; } private get centeringYOffset() { return Math.abs(this.centeringOffset) < 0.001 ? (this.props.PanelHeight() - this.nativeHeight * this.contentScaling()) / 2 : 0; } diff --git a/src/client/views/nodes/RadialMenu.tsx b/src/client/views/nodes/RadialMenu.tsx index 74c5f53bd..9314a3899 100644 --- a/src/client/views/nodes/RadialMenu.tsx +++ b/src/client/views/nodes/RadialMenu.tsx @@ -70,8 +70,8 @@ export class RadialMenu extends React.Component { } this._shouldDisplay && (this._display = true); document.removeEventListener("pointermove", this.onPointerMove); - if (this._closest !== -1) { - this._items[this._closest]?.event(); + if (this._closest !== -1 && this._items?.length > this._closest) { + this._items[this._closest].event(); } } componentWillUnmount() { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 4f6f80775..4dcdc6581 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -421,8 +421,8 @@ export namespace Doc { doc.title = title; return doc; } - export function MakeAlias(doc: Doc) { - const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc) : Doc.MakeDelegate(doc); + export function MakeAlias(doc: Doc, id?: string) { + const alias = !GetT(doc, "isPrototype", "boolean", true) ? Doc.MakeCopy(doc, undefined, id) : Doc.MakeDelegate(doc, id); const layout = Doc.LayoutField(alias); if (layout instanceof Doc && layout !== alias && layout === Doc.Layout(alias)) { Doc.SetLayout(alias, Doc.MakeAlias(layout)); @@ -574,7 +574,7 @@ export namespace Doc { } return undefined; } - export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined = undefined) { + export function ApplyTemplateTo(templateDoc: Doc, target: Doc, targetKey: string, titleTarget: string | undefined) { if (!templateDoc) { target.layout = undefined; target._nativeWidth = undefined; @@ -584,14 +584,9 @@ export namespace Doc { return; } - if ((target[targetKey] as Doc)?.proto !== templateDoc) { - const layoutCustomLayout = Doc.MakeDelegate(templateDoc); - + if (!Doc.AreProtosEqual(target[targetKey] as Doc, templateDoc)) { titleTarget && (Doc.GetProto(target).title = titleTarget); - Doc.GetProto(target).type = DocumentType.TEMPLATE; - target.onClick = templateDoc.onClick instanceof ObjectField && templateDoc.onClick[Copy](); - - Doc.GetProto(target)[targetKey] = new PrefetchProxy(layoutCustomLayout); + Doc.GetProto(target)[targetKey] = new PrefetchProxy(templateDoc); } target.layoutKey = targetKey; return target; @@ -777,7 +772,7 @@ export namespace Doc { } export function matchFieldValue(doc: Doc, key: string, value: any): boolean { - const fieldVal = doc[key] ? doc[key] : doc[key + "_ext"]; + const fieldVal = doc[key]; if (Cast(fieldVal, listSpec("string"), []).length) { const vals = Cast(fieldVal, listSpec("string"), []); return vals.some(v => v === value); diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 2768f1213..3495a934d 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -136,7 +136,7 @@ export function getter(target: any, in_prop: string | symbol | number, receiver: let x = resolvedLayout[Id]; let layout = (resolvedLayout.layout as string).split("'")[1]; let expanded = getFieldImpl(target, layout + "-layout[" + x + "]", receiver); - return expanded?.[prop]; + return (expanded || resolvedLayout)?.[prop]; //return resolvedLayout[prop]; } } diff --git a/src/scraping/buxton/scraper.py b/src/scraping/buxton/scraper.py index f7a38112d..1441a8621 100644 --- a/src/scraping/buxton/scraper.py +++ b/src/scraping/buxton/scraper.py @@ -115,8 +115,8 @@ def write_collection(parse_results, display_fields, storage_key, viewType): target_collection.insert_one(view_doc) data_doc_guid = data_doc["_id"] - print(f"inserted view document ({view_doc_guid})") - print(f"inserted data document ({data_doc_guid})\n") + # print(f"inserted view document ({view_doc_guid})") + # print(f"inserted data document ({data_doc_guid})\n") return view_doc_guid @@ -188,8 +188,8 @@ def write_image(folder, name): "y": 10, "_width": min(800, native_width), "zIndex": 2, - "widthUnit": "*", - "widthMagnitude": 1 + "dimUnit": "*", + "dimMagnitude": 1 }, "__type": "Doc" } @@ -233,7 +233,7 @@ def parse_document(file_name: str): result = {} dir_path = image_dist + "/" + pure_name - print(dir_path) + # print(dir_path) mkdir_if_absent(dir_path) raw = str(docx2txt.process(source + "/" + file_name, dir_path)) @@ -252,7 +252,7 @@ def parse_document(file_name: str): medium = dir_path + "/" + image.replace(".", "_m.", 1) copyfile(resolved, original) copyfile(resolved, medium) - print(f"extracted {count} images...") + # print(f"extracted {count} images...") def sanitize(line): return re.sub("[\n\t]+", "", line).replace(u"\u00A0", " ").replace( u"\u2013", "-").replace(u"\u201c", '''"''').replace(u"\u201d", '''"''').strip() @@ -360,7 +360,7 @@ def parse_document(file_name: str): if len(notes) > 0: result["notes"] = listify(notes) - print("writing child schema...") + # print("writing child schema...") return { "schema": { @@ -392,7 +392,7 @@ def write_common_proto(): if os.path.exists(image_dist): - shutil.rmtree(image_dist) + shutil.rmtree(image_dist, True) while os.path.exists(image_dist): pass os.mkdir(image_dist) @@ -415,7 +415,7 @@ parent_guid = write_collection({ "__type": "Doc" }, "child_guids": schema_guids -}, ["title", "short_description", "original_price"], "data", 2) +}, ["title", "short_description", "original_price"], "data", 4) print("appending parent schema to main workspace...\n") target_collection.update_one( diff --git a/src/server/ApiManagers/DownloadManager.ts b/src/server/ApiManagers/DownloadManager.ts index 1bb84f374..fad5e6789 100644 --- a/src/server/ApiManagers/DownloadManager.ts +++ b/src/server/ApiManagers/DownloadManager.ts @@ -254,11 +254,13 @@ async function writeHierarchyRecursive(file: Archiver.Archiver, hierarchy: Hiera // and dropped in the browser and thus hosted remotely) so we upload it // to our server and point the zip file to it, so it can bundle up the bytes const information = await DashUploadUtils.UploadImage(result); - path = information.serverAccessPaths[SizeSuffix.Original]; + path = information instanceof Error ? "" : information.serverAccessPaths[SizeSuffix.Original]; } // write the file specified by the path to the directory in the // zip file given by the prefix. - file.file(path, { name: documentTitle, prefix }); + if (path) { + file.file(path, { name: documentTitle, prefix }); + } } else { // we've hit a collection, so we have to recurse await writeHierarchyRecursive(file, result, `${prefix}/${documentTitle}`); diff --git a/src/server/ApiManagers/GooglePhotosManager.ts b/src/server/ApiManagers/GooglePhotosManager.ts index 107542ce2..1727cc5a6 100644 --- a/src/server/ApiManagers/GooglePhotosManager.ts +++ b/src/server/ApiManagers/GooglePhotosManager.ts @@ -88,8 +88,13 @@ export default class GooglePhotosManager extends ApiManager { if (contents) { const completed: Opt<DashUploadUtils.ImageUploadInformation>[] = []; for (const item of contents.mediaItems) { - const { contentSize, ...attributes } = await DashUploadUtils.InspectImage(item.baseUrl); - const found: Opt<DashUploadUtils.ImageUploadInformation> = await Database.Auxiliary.QueryUploadHistory(contentSize!); + const results = await DashUploadUtils.InspectImage(item.baseUrl); + if (results instanceof Error) { + failed++; + continue; + } + const { contentSize, ...attributes } = results; + const found: Opt<DashUploadUtils.ImageUploadInformation> = await Database.Auxiliary.QueryUploadHistory(contentSize); if (!found) { const upload = await DashUploadUtils.UploadInspectedImage({ contentSize, ...attributes }, item.filename, prefix).catch(error => _error(res, downloadError, error)); if (upload) { diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index a92b613b7..4d09528f4 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -65,7 +65,8 @@ export default class UploadManager extends ApiManager { secureHandler: async ({ req, res }) => { const { sources } = req.body; if (Array.isArray(sources)) { - return res.send(await Promise.all(sources.map(url => DashUploadUtils.UploadImage(url)))); + const results = await Promise.all(sources.map(source => DashUploadUtils.UploadImage(source))); + return res.send(results); } res.send(); } diff --git a/src/server/ApiManagers/UtilManager.ts b/src/server/ApiManagers/UtilManager.ts index a0d0d0f4b..d7b085a30 100644 --- a/src/server/ApiManagers/UtilManager.ts +++ b/src/server/ApiManagers/UtilManager.ts @@ -47,7 +47,12 @@ export default class UtilManager extends ApiManager { const onResolved = (stdout: string) => { console.log(stdout); res.redirect("/"); }; const onRejected = (err: any) => { console.error(err.message); res.send(err); }; - const tryPython3 = () => command_line('python3 scraper.py', cwd).then(onResolved, onRejected); + const tryPython3 = (reason: any) => { + console.log("Initial scraper failed for the following reason:"); + console.log(red(reason.Error)); + console.log("Falling back to python3..."); + command_line('python3 scraper.py', cwd).then(onResolved, onRejected); + }; return command_line('python scraper.py', cwd).then(onResolved, tryPython3); }, diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index cb7104757..27c4bf854 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -1,4 +1,4 @@ -import { unlinkSync, createWriteStream, readFileSync, rename } from 'fs'; +import { unlinkSync, createWriteStream, readFileSync, rename, writeFile } from 'fs'; import { Utils } from '../Utils'; import * as path from 'path'; import * as sharp from 'sharp'; @@ -127,9 +127,12 @@ export namespace DashUploadUtils { * 3) the size of the image, in bytes (4432130) * 4) the content type of the image, i.e. image/(jpeg | png | ...) */ - export const UploadImage = async (source: string, filename?: string, format?: string, prefix: string = ""): Promise<ImageUploadInformation> => { + export const UploadImage = async (source: string, filename?: string, format?: string, prefix: string = ""): Promise<ImageUploadInformation | Error> => { const metadata = await InspectImage(source); - return UploadInspectedImage(metadata, filename, format, prefix); + if (metadata instanceof Error) { + return metadata; + } + return UploadInspectedImage(metadata, filename || metadata.filename, format, prefix); }; export interface InspectionResults { @@ -140,6 +143,7 @@ export namespace DashUploadUtils { contentType: string; nativeWidth: number; nativeHeight: number; + filename?: string; } export interface EnrichedExifData { @@ -164,7 +168,20 @@ export namespace DashUploadUtils { * * @param source is the path or url to the image in question */ - export const InspectImage = async (source: string): Promise<InspectionResults> => { + export const InspectImage = async (source: string): Promise<InspectionResults | Error> => { + let rawMatches: RegExpExecArray | null; + let filename: string | undefined; + if ((rawMatches = /^data:image\/([a-z]+);base64,(.*)/.exec(source)) !== null) { + const [ext, data] = rawMatches.slice(1, 3); + const resolved = filename = `upload_${Utils.GenerateGuid()}.${ext}`; + const error = await new Promise<Error | null>(resolve => { + writeFile(serverPathToFile(Directory.images, resolved), data, "base64", resolve); + }); + if (error !== null) { + return error; + } + source = `http://localhost:1050${clientPathToFile(Directory.images, resolved)}`; + } let resolvedUrl: string; const matches = isLocal().exec(source); if (matches === null) { @@ -187,6 +204,7 @@ export namespace DashUploadUtils { contentType: headers[type], nativeWidth, nativeHeight, + filename, ...results }; }; |
