From 810f86195188503b04d64f9d58ea4dfc3a639398 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 22 Mar 2022 11:32:47 -0400 Subject: fixed temporal media merge that had reverted a lot of things. --- src/client/documents/Documents.ts | 92 +++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 42 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index df573a377..9bcd23aa0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -23,7 +23,7 @@ import { DocumentManager } from "../util/DocumentManager"; import { dropActionType } from "../util/DragManager"; import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox"; import { LinkManager } from "../util/LinkManager"; -import { Scripting } from "../util/Scripting"; +import { ScriptingGlobals } from "../util/ScriptingGlobals"; import { undoBatch, UndoManager } from "../util/UndoManager"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; @@ -61,7 +61,6 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; -const path = require('path'); const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { @@ -180,7 +179,7 @@ export class DocumentOptions { layout?: string | Doc; // default layout string for a document contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width - childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view) + childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view) childLayoutString?: string; // template string for collection to use to render its children childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children @@ -235,7 +234,7 @@ export class DocumentOptions { docColorBtn?: string; userColorBtn?: string; canClick?: string; - script?: string; + script?: ScriptField; numBtnType?: string; numBtnMax?: number; numBtnMin?: number; @@ -361,31 +360,34 @@ export namespace Docs { const TemplateMap: TemplateMap = new Map([ [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, - options: { _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { + _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, + links: "@links(self)" + } }], [DocumentType.SEARCH, { layout: { view: SearchBox, dataField: defaultDataKey }, - options: { _width: 400, links: ComputedField.MakeFunction("links(self)") as any } + options: { _width: 400, links: "@links(self)" } }], [DocumentType.FILTER, { layout: { view: FilterBox, dataField: defaultDataKey }, - options: { _width: 400, links: ComputedField.MakeFunction("links(self)") as any } + options: { _width: 400, links: "@links(self)" } }], [DocumentType.COLOR, { layout: { view: ColorBox, dataField: defaultDataKey }, - options: { _nativeWidth: 220, _nativeHeight: 300, links: ComputedField.MakeFunction("links(self)") as any } + options: { _nativeWidth: 220, _nativeHeight: 300, links: "@links(self)" } }], [DocumentType.IMG, { layout: { view: ImageBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.WEB, { layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } }], [DocumentType.COL, { layout: { view: CollectionView, dataField: defaultDataKey }, - options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: ComputedField.MakeFunction("links(self)") as any } + options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: "@links(self)" } }], [DocumentType.KVP, { layout: { view: KeyValueBox, dataField: defaultDataKey }, @@ -393,19 +395,19 @@ export namespace Docs { }], [DocumentType.VID, { layout: { view: VideoBox, dataField: defaultDataKey }, - options: { _currentTimecode: 0, links: ComputedField.MakeFunction("links(self)") as any }, + options: { _currentTimecode: 0, links: "@links(self)" }, }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: "lightGray", links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 100, backgroundColor: "lightGray", links: "@links(self)" } }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: defaultDataKey }, - options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: ComputedField.MakeFunction("links(self)") as any } + options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } }], [DocumentType.MAP, { layout: { view: MapBox, dataField: defaultDataKey }, - options: { _height: 600, _width: 800, links: ComputedField.MakeFunction("links(self)") as any } + options: { _height: 600, _width: 800, links: "@links(self)" } }], [DocumentType.IMPORT, { layout: { view: DirectoryImportBox, dataField: defaultDataKey }, @@ -416,7 +418,7 @@ export namespace Docs { options: { childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", showCaption: "description", backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area - links: ComputedField.MakeFunction("links(self)") as any, + links: "@links(self)", _removeDropProperties: new List(["_layerTags", "isLinkButton"]), } }], @@ -432,61 +434,61 @@ export namespace Docs { }], [DocumentType.SCRIPTING, { layout: { view: ScriptingBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.YOUTUBE, { layout: { view: YoutubeBox, dataField: defaultDataKey } }], [DocumentType.LABEL, { layout: { view: LabelBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any, hideResizeHandles: true, hideDecorationTitle: true } + options: { links: "@links(self)", hideResizeHandles: true, hideDecorationTitle: true } }], [DocumentType.FUNCPLOT, { layout: { view: FunctionPlotBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.BUTTON, { layout: { view: LabelBox, dataField: "onClick" }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.SLIDER, { layout: { view: SliderBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.PRES, { layout: { view: PresBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.FONTICON, { layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: ComputedField.MakeFunction("links(self)") as any }, + options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: "@links(self)" }, }], [DocumentType.WEBCAM, { layout: { view: DashWebRTCVideo, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.PRESELEMENT, { layout: { view: PresElementBox, dataField: defaultDataKey } }], [DocumentType.MARKER, { layout: { view: CollectionView, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any, hideLinkButton: true } + options: { links: "@links(self)", hideLinkButton: true } }], [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method layout: { view: InkingStroke, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.SCREENSHOT, { layout: { view: ScreenshotBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], [DocumentType.COMPARISON, { layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: ComputedField.MakeFunction("links(self)") as any } + options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" } }], [DocumentType.GROUPDB, { data: new List(), @@ -495,7 +497,7 @@ export namespace Docs { }], [DocumentType.GROUP, { layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { links: ComputedField.MakeFunction("links(self)") as any } + options: { links: "@links(self)" } }], ]); @@ -592,6 +594,11 @@ export namespace Docs { system: true, _layoutKey: "layout", title, type, baseProto: true, x: 0, y: 0, _width: 300, ...(template.options || {}), layout: layout.view?.LayoutString(layout.dataField), data: template.data, layout_keyValue: KeyValueBox.LayoutString("") }; + Object.entries(options).map(pair => { + if (typeof pair[1] === "string" && pair[1].startsWith("@")) { + (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); + } + }); return Doc.assign(new Doc(prototypeId, true), options as any, undefined, true); } } @@ -655,7 +662,7 @@ export namespace Docs { export function ImageDocument(url: string, options: DocumentOptions = {}) { const imgField = new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: path.basename(url), ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options }); } export function PresDocument(initial: List = new List(), options: DocumentOptions = {}) { @@ -758,7 +765,7 @@ export namespace Docs { I.data = new InkField(points); I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; I["acl-Override"] = "None"; - I.links = ComputedField.MakeFunction("links(self)") as any; + I.links = "@links(self)"; I[Initializing] = false; return I; } @@ -1443,13 +1450,13 @@ export namespace DocUtils { if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; - proto.fileUpload = basename(pathname).replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); + proto.fileUpload = pathname.replace(/.*\//, "").replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); if (Upload.isImageInformation(result)) { const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); - proto["data-nativeOrientation"] = result.exifData?.data?.image?.Orientation; + proto["data-nativeOrientation"] = result.exifData?.data?.image?.Orientation ?? ((StrCast((result.exifData?.data as any)?.Orientation).includes("Rotate 90")) ? 5 : undefined); proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim; proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - if (Number(result.exifData?.data?.image?.Orientation) >= 5) { + if (NumCast(proto["data-nativeOrientation"]) >= 5) { proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim; proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); } @@ -1472,27 +1479,28 @@ export namespace DocUtils { export async function uploadYoutubeVideo(videoId: string, options: DocumentOptions) { const generatedDocuments: Doc[] = []; for (const { source: { name, type }, result } of await Networking.UploadYoutubeToServer(videoId)) { - processFileupload(generatedDocuments, name, type, result, options); + name && type && processFileupload(generatedDocuments, name, type, result, options); } return generatedDocuments; } export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { const generatedDocuments: Doc[] = []; - for (const { source: { name, type }, result } of await Networking.UploadFilesToServer(files)) { - processFileupload(generatedDocuments, name, type, result, options); + const upfiles = await Networking.UploadFilesToServer(files); + for (const { source: { name, type }, result } of upfiles) { + name && type && processFileupload(generatedDocuments, name, type, result, options); } return generatedDocuments; } } -Scripting.addGlobal("Docs", Docs); -Scripting.addGlobal(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); -Scripting.addGlobal(function generateLinkTitle(self: Doc) { +ScriptingGlobals.add("Docs", Docs); +ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); +ScriptingGlobals.add(function generateLinkTitle(self: Doc) { const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : ""; const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : ""; const relation = self.linkRelationship || "to"; return `${anchor1title} (${relation}) ${anchor2title}`; }); -Scripting.addGlobal(function openTabAlias(tab: Doc) { +ScriptingGlobals.add(function openTabAlias(tab: Doc) { CollectionDockingView.AddSplit(Doc.MakeAlias(tab), "right"); }); \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 3ca852f483e0868975678d3db97d72d637d8414c Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 3 Apr 2022 01:03:38 -0400 Subject: fixed ink handles to work in lightbox, so re-enabled open in lightbox for selected ink. fixed double-click to not lock out in some cases because pendingDoubleClick never gets unset. fixed text to not jitter scroll bar when typing enter at end of autosize texst box. --- src/client/documents/Documents.ts | 2 +- src/client/views/InkControlPtHandles.tsx | 1 - src/client/views/InkStrokeProperties.ts | 8 +++---- src/client/views/InkTangentHandles.tsx | 8 +++++-- src/client/views/InkingStroke.tsx | 36 +++++++++++++++++++------------- 5 files changed, 33 insertions(+), 22 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9bcd23aa0..fdfd7bd31 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -745,7 +745,7 @@ export namespace Docs { I.layout = InkingStroke.LayoutString("data"); I.color = color; I.hideDecorationTitle = true; // don't show title when selected - I.hideOpenButton = true; // don't show open full screen button when selected + // I.hideOpenButton = true; // don't show open full screen button when selected I.fillColor = fillColor; I.strokeWidth = strokeWidth; I.strokeBezier = strokeBezier; diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index a7f511ab4..d036a636a 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -21,7 +21,6 @@ export interface InkControlProps { inkCtrlPoints: InkData; screenCtrlPoints: InkData; screenSpaceLineWidth: number; - ScreenToLocalTransform: () => Transform; nearestScreenPt: () => PointData | undefined; } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 41cace1e3..e4486fc78 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -207,7 +207,7 @@ export class InkStrokeProperties { @undoBatch @action stretchInk = (inkStrokes: DocumentView[], scaling: number, scrpt: PointData, scrVec: PointData, scaleUniformly: boolean) => { - this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => { + this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData) => { const ptFromScreen = view.ComponentView?.ptFromScreen; const ptToScreen = view.ComponentView?.ptToScreen; return !ptToScreen || !ptFromScreen ? ink : @@ -227,7 +227,7 @@ export class InkStrokeProperties { @undoBatch @action moveControlPtHandle = (inkView: DocumentView, deltaX: number, deltaY: number, controlIndex: number, origInk?: InkData) => - this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => { + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { const order = controlIndex % 4; const closed = InkingStroke.IsClosed(ink); if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1) { @@ -435,13 +435,13 @@ export class InkStrokeProperties { @undoBatch @action moveTangentHandle = (inkView: DocumentView, deltaX: number, deltaY: number, handleIndex: number, oppositeHandleIndex: number, controlIndex: number) => - this.applyFunction(inkView, (view: DocumentView, ink: InkData, xScale: number, yScale: number) => { + this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { const doc = view.rootDoc; const closed = InkingStroke.IsClosed(ink); const oldHandlePoint = ink[handleIndex]; const oppositeHandlePoint = ink[oppositeHandleIndex]; const controlPoint = ink[controlIndex]; - const newHandlePoint = { X: ink[handleIndex].X - deltaX / xScale, Y: ink[handleIndex].Y - deltaY / yScale }; + const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY }; const inkCopy = ink.slice(); inkCopy[handleIndex] = newHandlePoint; const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index ab73e58a4..b15e4260d 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -29,8 +29,10 @@ export class InkTangentHandles extends React.Component { * @param handleNum The index of the currently selected handle point. */ onHandleDown = (e: React.PointerEvent, handleIndex: number): void => { - var controlUndo: UndoManager.Batch | undefined; + const ptFromScreen = this.props.inkView.ComponentView?.ptFromScreen; + if (!ptFromScreen) return; const screenScale = this.props.ScreenToLocalTransform().Scale; + var controlUndo: UndoManager.Batch | undefined; const order = handleIndex % 4; const oppositeHandleRawIndex = order === 1 ? handleIndex - 3 : handleIndex + 3; const oppositeHandleIndex = (oppositeHandleRawIndex < 0 ? this.props.screenCtrlPoints.length + oppositeHandleRawIndex : oppositeHandleRawIndex) % this.props.screenCtrlPoints.length; @@ -39,7 +41,9 @@ export class InkTangentHandles extends React.Component { (e: PointerEvent, down: number[], delta: number[]) => { if (!controlUndo) controlUndo = UndoManager.StartBatch("DocDecs move tangent"); if (e.altKey) this.onBreakTangent(controlIndex); - InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -delta[0] * screenScale, -delta[1] * screenScale, handleIndex, oppositeHandleIndex, controlIndex); + const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] }); + const inkMoveStart = ptFromScreen({ X: 0, Y: 0 }); + InkStrokeProperties.Instance.moveTangentHandle(this.props.inkView, -(inkMoveEnd.X - inkMoveStart.X), -(inkMoveEnd.Y - inkMoveStart.Y), handleIndex, oppositeHandleIndex, controlIndex); return false; }, () => { controlUndo?.end(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 40fe6aa68..23bdf8406 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -67,6 +67,10 @@ export class InkingStroke extends ViewBoxBaseComponent() { this._selDisposer?.(); } + // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke + // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc) + screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1) + /** * @returns the center of the ink stroke in the ink document's coordinate space (not screen space, and not the ink data coordinate space); * DocumentDecorations calls getBounds() on DocumentViews which call getCenter() if defined - in the case of ink it needs to be defined since @@ -119,7 +123,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { this._handledClick = false; const inkView = this.props.docViewPath().lastElement(); const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint( + const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint( (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); const { nearestSeg } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); @@ -160,7 +164,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { */ ptFromScreen = (scrPt: { X: number, Y: number }) => { const { inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const docPt = this.props.ScreenToLocalTransform().transformPoint(scrPt.X, scrPt.Y); + const docPt = this.screenToLocal().transformPoint(scrPt.X, scrPt.Y); const inkPt = { X: (docPt[0] - inkStrokeWidth / 2) / inkScaleX + inkStrokeWidth / 2 + inkLeft, Y: (docPt[1] - inkStrokeWidth / 2) / inkScaleY + inkStrokeWidth / 2 + inkTop, @@ -178,7 +182,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { X: (inkPt.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, Y: (inkPt.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2 }; - const scrPt = this.props.ScreenToLocalTransform().inverse().transformPoint(docPt.X, docPt.Y); + const scrPt = this.screenToLocal().inverse().transformPoint(docPt.X, docPt.Y); return { X: scrPt[0], Y: scrPt[1] }; } @@ -192,7 +196,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { snapPt = (scrPt: { X: number, Y: number }, excludeSegs?: number[]) => { const { inkData } = this.inkScaledData(); const { nearestPt, distance } = InkStrokeProperties.nearestPtToStroke(inkData, this.ptFromScreen(scrPt), excludeSegs ?? []); - return { nearestPt, distance: distance * this.props.ScreenToLocalTransform().inverse().Scale }; + return { nearestPt, distance: distance * this.screenToLocal().inverse().Scale }; } /** @@ -220,10 +224,14 @@ export class InkingStroke extends ViewBoxBaseComponent() { }; } + // + // this updates the highlight for the nearest point on the curve to the cursor. + // if the user double clicks, this highlighted point will be added as a control point in the curve. + // @action onPointerMove = (e: React.PointerEvent) => { const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint( + const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint( (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); const { distance, nearestT, nearestSeg, nearestPt } = InkStrokeProperties.nearestPtToStroke(screenPts, { X: e.clientX, Y: e.clientY }); @@ -246,10 +254,10 @@ export class InkingStroke extends ViewBoxBaseComponent() { componentUI = (boundsLeft: number, boundsTop: number) => { const inkDoc = this.props.Document; const { inkData, inkScaleX, inkScaleY, inkStrokeWidth, inkTop, inkLeft } = this.inkScaledData(); - const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.props.ScreenToLocalTransform().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke + const screenSpaceCenterlineStrokeWidth = Math.min(3, inkStrokeWidth * this.screenToLocal().inverse().Scale); // the width of the blue line widget that shows the centerline of the ink stroke - const screenInkWidth = this.props.ScreenToLocalTransform().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth); - const screenPts = inkData.map(point => this.props.ScreenToLocalTransform().inverse().transformPoint( + const screenInkWidth = this.screenToLocal().inverse().transformDirection(inkStrokeWidth, inkStrokeWidth); + const screenPts = inkData.map(point => this.screenToLocal().inverse().transformPoint( (point.X - inkLeft - inkStrokeWidth / 2) * inkScaleX + inkStrokeWidth / 2, (point.Y - inkTop - inkStrokeWidth / 2) * inkScaleY + inkStrokeWidth / 2)).map(p => ({ X: p[0], Y: p[1] })); const screenHdlPts = screenPts; @@ -264,9 +272,10 @@ export class InkingStroke extends ViewBoxBaseComponent() { ) : + startPt={screenPts[0]} + endPt={screenPts.lastElement()} + screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /> + ) :
{InteractionUtils.CreatePolyline(screenPts, 0, 0, Colors.MEDIUM_BLUE, screenInkWidth[0], screenSpaceCenterlineStrokeWidth, StrCast(inkDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(inkDoc.strokeBezier), @@ -277,14 +286,13 @@ export class InkingStroke extends ViewBoxBaseComponent() { inkCtrlPoints={inkData} screenCtrlPoints={screenHdlPts} nearestScreenPt={this.nearestScreenPt} - screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} - ScreenToLocalTransform={this.props.ScreenToLocalTransform} /> + screenSpaceLineWidth={screenSpaceCenterlineStrokeWidth} /> + ScreenToLocalTransform={this.screenToLocal} />
; } -- cgit v1.2.3-70-g09d2 From ab7948689e384af8779e581708df6fa4225a85a3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 4 Apr 2022 18:35:10 -0400 Subject: fixed autolink. fixed the pileup view to animate properly and activate without selection. added a 'dataTransition" prop to DocumentView to allow collections to animate changes in the size of documents. --- src/client/documents/Documents.ts | 18 ++++++++------- src/client/util/CurrentUserUtils.ts | 12 +++++----- .../views/collections/CollectionPileView.tsx | 27 ++++++++++++---------- src/client/views/collections/CollectionSubView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 19 +++++++++++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +++++++--- src/fields/Doc.ts | 1 - 9 files changed, 58 insertions(+), 36 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fdfd7bd31..bb60586eb 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -823,7 +823,7 @@ export namespace Docs { } export function PileDocument(documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: "visible", _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array, options: DocumentOptions, id?: string) { @@ -1344,7 +1344,7 @@ export namespace DocUtils { if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); } - export function pileup(docList: Doc[], x?: number, y?: number) { + export function pileup(docList: Doc[], x?: number, y?: number, create: boolean = true) { let w = 0, h = 0; runInAction(() => { docList.forEach(d => { @@ -1359,12 +1359,14 @@ export namespace DocUtils { d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection }); }); - const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, _overflow: "visible" }); - newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; - newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; - newCollection._width = newCollection._height = 110; - newCollection._jitterRotation = 10; - return newCollection; + if (create) { + const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, }); + newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; + newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; + newCollection._width = newCollection._height = 110; + newCollection._jitterRotation = 10; + return newCollection; + } } export function LeavePushpin(doc: Doc, annotationField: string) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f2094407d..af731ce9f 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -292,10 +292,10 @@ export class CurrentUserUtils { if (doc["template-icon-view"] === undefined) { const iconView = Docs.Create.LabelDocument({ title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); // Docs.Create.TextDocument("", { - // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)") + // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }) // }); // Doc.GetProto(iconView).icon = new RichTextField('{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[{"type":"dashField","attrs":{"fieldKey":"title","docid":""}}]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}', ""); iconView.isTemplateDoc = makeTemplate(iconView); @@ -304,7 +304,7 @@ export class CurrentUserUtils { if (doc["template-icon-view-rtf"] === undefined) { const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); @@ -312,20 +312,20 @@ export class CurrentUserUtils { if (doc["template-icon-view-button"] === undefined) { const iconBtnView = Docs.Create.FontIconDocument({ title: "icon_" + DocumentType.BUTTON, _nativeHeight: 30, _nativeWidth: 30, - _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + _width: 30, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconBtnView.isTemplateDoc = makeTemplate(iconBtnView, true, "icon_" + DocumentType.BUTTON); doc["template-icon-view-button"] = new PrefetchProxy(iconBtnView); } if (doc["template-icon-view-img"] === undefined) { const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true + title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); } if (doc["template-icon-view-col"] === undefined) { - const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(self)"), system: true }); + const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 0a336c544..0ca0a463e 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -35,6 +35,18 @@ export class CollectionPileView extends CollectionSubView() { layoutEngine = () => StrCast(this.Document._pileLayoutEngine); + @undoBatch + addPileDoc = (doc: Doc | Doc[]) => { + (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); + return this.props.addDocument?.(doc) || false; + } + + @undoBatch + removePileDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { + (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d))); + return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; + } + // returns the contents of the pileup in a CollectionFreeFormView @computed get contents() { const isStarburst = this.layoutEngine() === "starburst"; @@ -47,14 +59,8 @@ export class CollectionPileView extends CollectionSubView() { { - (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); - return this.props.addDocument?.(doc) || false; - })} - moveDocument={undoBatch((doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - (doc instanceof Doc ? [doc] : doc).map(undoBatch((d) => Doc.deiconifyView(d))); - return this.props.moveDocument?.(doc, targetCollection, addDoc) || false; - })} /> + addDocument={this.addPileDoc} + moveDocument={this.removePileDoc} /> ; } @@ -62,19 +68,16 @@ export class CollectionPileView extends CollectionSubView() { toggleStarburst = action(() => { if (this.layoutEngine() === 'starburst') { const defaultSize = 110; - this.layoutDoc._overflow = undefined; - this.childDocs.forEach(d => DocUtils.iconify(d)); this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize); this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize); - DocUtils.pileup(this.childDocs); + DocUtils.pileup(this.childDocs, undefined, undefined, false); this.layoutDoc._panX = 0; this.layoutDoc._panY = -10; this.props.Document._pileLayoutEngine = 'pass'; } else { const defaultSize = 25; - this.layoutDoc._overflow = 'visible'; !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500); !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); if (this.layoutEngine() === 'pass') { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 42e157396..d8f1287cd 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -453,7 +453,7 @@ export function CollectionSubView(moreProps?: X) { if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)); + addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..b2697cf08 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1152,6 +1152,7 @@ export class CollectionFreeFormView extends CollectionSubView this.props.removeDocument?.(d)); - const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2); + const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!; this.props.addDocument?.(newCollection); this.props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 979e86738..a9abf066e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -116,6 +116,7 @@ export interface DocumentViewSharedProps { PanelWidth: () => number; PanelHeight: () => number; docViewPath: () => DocumentView[]; + dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean); styleProvider: Opt; focus: DocFocusFunc; @@ -182,6 +183,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps { @observer export class DocumentViewInternal extends DocComponent() { public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered. + _animateScaleTime = 300; // milliseconds; @observable _animateScalingTo = 0; @observable _mediaState = 0; @observable _pendingDoubleClick = false; @@ -1078,9 +1080,8 @@ export class DocumentViewInternal extends DocComponent {this.innards} @@ -1247,8 +1248,8 @@ export class DocumentView extends React.Component { setTimeout(action(() => { this.setCustomView(custom, view); this.docView && (this.docView._animateScalingTo = 1); // expand it - setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), 400); - }), 400); + setTimeout(action(() => this.docView && (this.docView._animateScalingTo = 0)), this.docView!._animateScaleTime - 10); + }), this.docView!._animateScaleTime - 10); }); startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this.docView?.startDragging(x, y, dropAction, hideSource); @@ -1293,6 +1294,7 @@ export class DocumentView extends React.Component { {!this.props.Document || !this.props.PanelWidth() ? (null) : (
{ } } +export function deiconifyViewFunc(documentView: DocumentView) { + documentView.iconify(); + //StrCast(doc.layoutKey).split("_")[1] === "icon" && setNativeView(doc); +} +ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { + documentView.iconify(); +} +); + ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); else dv.switchViews(true, detailLayoutKeySuffix); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 75f0978a8..1f7e557d9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -359,18 +359,23 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); - oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink)).forEach(LinkManager.Instance.deleteLink); + oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); } } updateTitle = () => { + const title = StrCast(this.dataDoc.title) if (!this.props.dontRegisterView && // (this.props.Document.isTemplateForField === "text" || !this.props.Document.isTemplateForField) && // only update the title if the data document's data field is changing - StrCast(this.dataDoc.title).startsWith("-") && this._editorView && !this.dataDoc["title-custom"] && + (title.startsWith("-") || title.startsWith("@")) && this._editorView && !this.dataDoc["title-custom"] && (Doc.LayoutFieldKey(this.rootDoc) === this.fieldKey || this.fieldKey === "text")) { let node = this._editorView.state.doc; while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; - this.dataDoc.title = "-" + str.substr(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); + const prefix = str.startsWith("@") ? "" : "-"; + this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); + if (str.startsWith("@") && str.length > 1) { + Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc); + } } } @@ -909,6 +914,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } if (this._editorView && selected) { RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); + this.autoLink(); } }), { fireImmediately: true }); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 1edab16be..ebdbae344 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1414,7 +1414,6 @@ ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); -ScriptingGlobals.add(function deiconifyView(doc: any) { Doc.deiconifyView(doc); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); }); ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a document id in a script"); return "invalid"; }); -- cgit v1.2.3-70-g09d2 From b3d6eaa3a0b126712eae25c1b91925d030a2d900 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 7 Apr 2022 18:06:40 -0400 Subject: added RecordingView --- package-lock.json | 13 + package.json | 2 + src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 16 +- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/FieldView.tsx | 1 + .../views/nodes/RecordingBox/ProgressBar.scss | 26 ++ .../views/nodes/RecordingBox/ProgressBar.tsx | 46 +++ .../views/nodes/RecordingBox/RecordingBox.tsx | 24 ++ .../views/nodes/RecordingBox/RecordingView.scss | 207 +++++++++++++ .../views/nodes/RecordingBox/RecordingView.tsx | 326 +++++++++++++++++++++ src/client/views/nodes/RecordingBox/index.ts | 2 + src/fields/URLField.ts | 1 + 14 files changed, 667 insertions(+), 5 deletions(-) create mode 100644 src/client/views/nodes/RecordingBox/ProgressBar.scss create mode 100644 src/client/views/nodes/RecordingBox/ProgressBar.tsx create mode 100644 src/client/views/nodes/RecordingBox/RecordingBox.tsx create mode 100644 src/client/views/nodes/RecordingBox/RecordingView.scss create mode 100644 src/client/views/nodes/RecordingBox/RecordingView.tsx create mode 100644 src/client/views/nodes/RecordingBox/index.ts (limited to 'src/client/documents') diff --git a/package-lock.json b/package-lock.json index b1431e55b..d84e8a572 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2001,6 +2001,14 @@ "@types/react": "*" } }, + "@types/react-icons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@types/react-icons/-/react-icons-3.0.0.tgz", + "integrity": "sha512-Vefs6LkLqF61vfV7AiAqls+vpR94q67gunhMueDznG+msAkrYgRxl7gYjNem/kZ+as2l2mNChmF1jRZzzQQtMg==", + "requires": { + "react-icons": "*" + } + }, "@types/react-measure": { "version": "2.0.8", "resolved": "https://registry.npmjs.org/@types/react-measure/-/react-measure-2.0.8.tgz", @@ -15764,6 +15772,11 @@ "react-resizable": "^1.9.0" } }, + "react-icons": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/react-icons/-/react-icons-4.3.1.tgz", + "integrity": "sha512-cB10MXLTs3gVuXimblAdI71jrJx8njrJZmNMEMC+sQu5B/BIOmlsAjskdqpn81y8UBVEGuHODd7/ci5DvoSzTQ==" + }, "react-image-lightbox-with-rotate": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/react-image-lightbox-with-rotate/-/react-image-lightbox-with-rotate-5.1.1.tgz", diff --git a/package.json b/package.json index 2c3e641dd..35aa2ef92 100644 --- a/package.json +++ b/package.json @@ -132,6 +132,7 @@ "@types/dom-speech-recognition": "0.0.1", "@types/formidable": "1.0.31", "@types/google-maps": "^3.2.3", + "@types/react-icons": "^3.0.0", "@types/react-reconciler": "^0.26.4", "@types/reveal": "^3.3.33", "@types/supercluster": "^7.1.0", @@ -253,6 +254,7 @@ "react-datepicker": "^3.8.0", "react-dom": "^16.14.0", "react-grid-layout": "^0.18.3", + "react-icons": "^4.3.1", "react-image-lightbox-with-rotate": "^5.1.1", "react-jsx-parser": "^1.29.0", "react-loading": "^2.0.3", diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 161dff6e0..ca942a38a 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -9,6 +9,7 @@ export enum DocumentType { KVP = "kvp", VID = "video", AUDIO = "audio", + REC = "recording", PDF = "pdf", INK = "inks", SCREENSHOT = "screenshot", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bb60586eb..e50172af5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -11,7 +11,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { AudioField, ImageField, MapField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; +import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; import { OmitKeys, Utils } from "../../Utils"; @@ -61,6 +61,7 @@ import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; +import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { @@ -401,6 +402,10 @@ export namespace Docs { layout: { view: AudioBox, dataField: defaultDataKey }, options: { _height: 100, backgroundColor: "lightGray", links: "@links(self)" } }], + [DocumentType.REC, { + layout: { view: VideoBox, dataField: defaultDataKey }, + options: { _height: 100, backgroundColor: "pink", links: "@links(self)" } + }], [DocumentType.PDF, { layout: { view: PDFBox, dataField: defaultDataKey }, options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } @@ -468,7 +473,7 @@ export namespace Docs { options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: "@links(self)" }, }], [DocumentType.WEBCAM, { - layout: { view: DashWebRTCVideo, dataField: defaultDataKey }, + layout: { view: RecordingBox, dataField: defaultDataKey }, options: { links: "@links(self)" } }], [DocumentType.PRESELEMENT, { @@ -699,6 +704,10 @@ export namespace Docs { { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }); } + export function RecordingDocument(url: string, options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.REC), "", options); + } + export function SearchDocument(options: DocumentOptions = {}) { return InstanceFromProto(Prototypes.get(DocumentType.SEARCH), new List([]), options); } @@ -1160,6 +1169,9 @@ export namespace DocUtils { } else if (field instanceof AudioField) { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; + } else if (field instanceof RecordingField) { + created = Docs.Create.RecordingDocument((field).url.href, resolved); + layout = RecordingBox.LayoutString; } else if (field instanceof InkField) { created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); layout = InkingStroke.LayoutString; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index af731ce9f..edb362a37 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -424,8 +424,8 @@ export class CurrentUserUtils { doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, title: "empty screenshot", _width: 400, _height: 200, system: true, cloneFieldFilter: new List(["system"]) }); } if (doc.emptyWall === undefined) { - doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List(["system"]) }); - (doc.emptyWall as Doc).videoWall = true; + doc.emptyWall = Docs.Create.WebCamDocument("", { _width: 400, _height: 200, title: "recording", system: true, cloneFieldFilter: new List(["system"]) }); + (doc.emptyWall as Doc).recording = true; } if (doc.emptyAudio === undefined) { doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List(["system"]) }); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 005133eb0..da9b448ec 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -41,6 +41,7 @@ import { WebBox } from "./WebBox"; import React = require("react"); import XRegExp = require("xregexp"); import { MapBox } from "./MapBox/MapBox"; +import { RecordingBox } from "./RecordingBox"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -225,7 +226,7 @@ export class DocumentContentsView extends React.Component void +} + +export function ProgressBar(props: ProgressBarProps) { + + const handleClick = (e: React.MouseEvent) => { + let progressbar = document.getElementById('progressbar')! + let bounds = progressbar!.getBoundingClientRect(); + let x = e.clientX - bounds.left; + let percent = x / progressbar.clientWidth * 100 + + for (let i = 0; i < props.marks.length; i++) { + let start = i == 0 ? 0 : props.marks[i-1]; + if (percent > start && percent < props.marks[i]) { + props.playSegment(i) + // console.log(i) + // console.log(percent) + // console.log(props.marks[i]) + break + } + } + } + + return( +
+
+ {props.marks.map((mark) => { + return
+ })} +
+ ) +} \ No newline at end of file diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx new file mode 100644 index 000000000..6d444d324 --- /dev/null +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -0,0 +1,24 @@ +import { observer } from "mobx-react"; +import * as React from "react"; +import { ViewBoxBaseComponent } from "../../DocComponent"; +import { FieldView } from "../FieldView"; +import { RecordingView } from './RecordingView'; + + +@observer +export class RecordingBox extends ViewBoxBaseComponent(){ + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(RecordingBox, fieldKey); } + + private _ref: React.RefObject = React.createRef(); + + constructor(props: any) { + super(props); + } + + render() { + return
+ +
; + } +} diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss new file mode 100644 index 000000000..2eaf5468d --- /dev/null +++ b/src/client/views/nodes/RecordingBox/RecordingView.scss @@ -0,0 +1,207 @@ +video { + flex: 100%; + width: 100%; + min-height: 400px; + height: auto; + display: block; + background-color: black; +} + +button { margin: 0 .5rem } + +.recording-container { + height: 100%; + width: 100%; + display: flex; +} + +.video-wrapper { + max-width: 600px; + max-width: 700px; + position: relative; + display: flex; + justify-content: center; + overflow: hidden; + border-radius: 10px; +} + +.video-wrapper:hover .controls { + bottom: 30px; + transform: translateY(0%); + opacity: 100%; +} + +.controls { + display: flex; + align-items: center; + justify-content: space-evenly; + position: absolute; + padding: 14px; + width: 100%; + max-width: 500px; + flex-wrap: wrap; + background: rgba(255, 255, 255, 0.25); + box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1); + backdrop-filter: blur(4px); + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.18); + // transform: translateY(150%); + transition: all 0.3s ease-in-out; + // opacity: 0%; + bottom: 30px; + // bottom: -150px; +} + +.actions button { + background: none; + border: none; + outline: none; + cursor: pointer; +} + +.actions button i { + background-color: none; + color: white; + font-size: 30px; +} + +// input[type="range"] { +// -webkit-appearance: none !important; +// background: rgba(255, 255, 255, 0.2); +// border-radius: 20px; +// height: 4px; +// width: 350px; +// } + +// input[type="range"]::-webkit-slider-thumb { +// -webkit-appearance: none !important; +// cursor: pointer; +// height: 6px; +// } + +// input[type="range"]::-moz-range-progress { +// background: white; +// } + +.velocity { + appearance: none; + background: none; + color: white; + outline: none; + border: none; + text-align: center; + font-size: 16px; +} + +.mute-btn { + background: none; + border: none; + outline: none; + cursor: pointer; +} + +.mute-btn i { + background-color: none; + color: white; + font-size: 20px; +} + +.recording-sign { + height: 20px; + width: auto; + display: flex; + flex-direction: row; + position: absolute; + top: 10px; + right: 15px; + align-items: center; + justify-content: center; + + .timer { + font-size: 15px; + color: white; + margin: 0; + } + + .dot { + height: 15px; + width: 15px; + margin: 5px; + background-color: red; + border-radius: 50%; + display: inline-block; + } +} + +.controls-inner-container { + display: flex; + flex-direction: row; + justify-content: center; + width: 100%; + +} + +.record-button-wrapper { + width: 35px; + height: 35px; + font-size: 0; + background-color: grey; + border: 0px; + border-radius: 35px; + margin: 10px; + + .record-button { + position: relative; + background-color: red; + border: 0px; + border-radius: 50%; + height: 28px; + width: 28px; + top: 50%; + left: 50%; + margin: -14px 0px 0px -14px; + + &:hover { + width: 30px; + height: 30px; + margin: -15px 0px 0px -15px; + } + } + + .stop-button{ + position: relative; + background-color: red; + border: 0px; + border-radius: 10%; + height: 18px; + width: 18px; + top: 50%; + left: 50%; + margin: -9px 0px 0px -9px; + + // &:hover { + // width: 40px; + // height: 40px + // } + } + +} + +.video-edit-wrapper { + height: 100%; + display: flex; + flex-direction: row; + align-items: center; + position: absolute; + top: 0; + bottom: 0; + right: 50% - 15; + + .video-edit-buttons { + margin: 0 5px; + } + +} + + + diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx new file mode 100644 index 000000000..15f8c8626 --- /dev/null +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -0,0 +1,326 @@ +import * as React from 'react'; +import "./RecordingView.scss"; +import { ReactElement, useCallback, useEffect, useRef, useState } from "react"; +import { ProgressBar } from "./ProgressBar" +import { MdBackspace } from 'react-icons/md'; +import { FaCheckCircle } from 'react-icons/fa'; +import { IconContext } from "react-icons"; + + +enum RecordingStatus { + Recording, + Stopped, + Paused +} + +interface VideoSegment { + chunks: any[], + endTime: number +} + +const MAXTIME = 1000; + +export function RecordingView() { + + const [recording, setRecording] = useState(false); + const recordingTimerRef = useRef(0); + const [recordingTimer, setRecordingTimer] = useState(0); // unit is 0.01 second + const [playing, setPlaying] = useState(false); + const [progress, setProgress] = useState(0); + const [speed, setSpeed] = useState(1); + const [muted, setMuted] = useState(false); + + const [videos, setVideos] = useState([]); + // const [videos, setVideos] = useState([]); + const [currentVid, setCurrentVid] = useState(0); + const recorder = useRef(null); + const videoElementRef = useRef(null); + + const [finished, setFinished] = useState(false) + + + + const DEFAULT_MEDIA_CONSTRAINTS = { + video: { + width: 1280, + height: 720, + }, + audio: { + echoCancellation: true, + noiseSuppression: true, + sampleRate: 44100 + } + } + + useEffect(() => { + + if (finished) { + let allVideoChunks : any = [] + console.log(videos) + videos.forEach((vid) => { + console.log(vid.chunks) + allVideoChunks = allVideoChunks.concat(vid.chunks) + }) + + console.log(allVideoChunks) + + const blob = new Blob(allVideoChunks, { + type: 'video/webm' + }) + const blobUrl = URL.createObjectURL(blob) + + videoElementRef.current!.srcObject = null + videoElementRef.current!.src = blobUrl + videoElementRef.current!.muted = false + } + + + }, [finished]) + + useEffect(() => { + // check if the browser supports media devices on first load + if (!navigator.mediaDevices) { + console.log('This browser does not support getUserMedia.') + } + console.log('This device has the correct media devices.') + }, []) + + useEffect(() => { + // get access to the video element on every render + // videoElement = document.getElementById('video') as HTMLVideoElement; + videoElementRef.current = document.getElementById('video') as HTMLVideoElement; + }) + + // useEffect(() => { + // if (playing) { + // videoElement!.srcObject = null + // // videoElement!.src = videos[currentVid].url + // videoElement!.muted = false + // videoElement!.play() + // } else { + // videoElement!.pause(); + // } + // }, [playing, videoElement]); + + useEffect(() => { + let interval: any = null; + if (recording) { + interval = setInterval(() => { + setRecordingTimer(unit => unit + 1); + }, 10); + } else if (!recording && recordingTimer !== 0) { + clearInterval(interval); + } + return () => clearInterval(interval); + }, [recording]) + + useEffect(() => { + setVideoProgressHelper(recordingTimer) + recordingTimerRef.current = recordingTimer; + }, [recordingTimer]) + + const setVideoProgressHelper = (progress: number) => { + const newProgress = (progress / MAXTIME) * 100; + setProgress(newProgress) + } + const startShowingStream = async (mediaConstraints = DEFAULT_MEDIA_CONSTRAINTS) => { + const stream = await navigator.mediaDevices.getUserMedia(mediaConstraints) + + videoElementRef.current!.src = "" + videoElementRef.current!.srcObject = stream + videoElementRef.current!.muted = true + + return stream + } + + const record = async () => { + const stream = await startShowingStream(); + recorder.current = new MediaRecorder(stream) + + // temporary chunks of video + let chunks: any = [] + recorder.current.ondataavailable = (event: any) => { + // store the video chunks as it is recording + console.log("data available") + if (event.data.size > 0) { + chunks.push(event.data) + } + } + + recorder.current.onstart = (event: any) => { + console.log("on start") + setRecording(true); + } + + recorder.current.onstop = () => { + // if we have a last portion + if (chunks.length > 1) { + // append the current portion to the video pieces + setVideos(videos => [...videos, {chunks: chunks, endTime: recordingTimerRef.current}]) + } + + // reset the temporary chunks + chunks = [] + setRecording(false); + setFinished(true); + } + + // recording paused + recorder.current.onpause = (event: any) => { + // append the current portion to the video pieces + console.log(chunks) + setVideos(videos => [...videos, {chunks: chunks, endTime: recordingTimerRef.current}]) + + // reset the temporary chunks + chunks = [] + setRecording(false); + } + + recorder.current.onresume = async (event: any) => { + console.log(event) + await startShowingStream(); + setRecording(true); + } + + recorder.current.start(200) + } + + + const stop = () => { + if (recorder.current) { + if (recorder.current.state !== "inactive") { + recorder.current.stop(); + // recorder.current.stream.getTracks().forEach((track: any) => track.stop()) + } + } + } + + const pause = () => { + if (recorder.current) { + if (recorder.current.state === "recording") { + recorder.current.pause(); + } + } + } + + const startOrResume = () => { + console.log('[RecordingView.tsx] startOrResume') + if (!recorder.current || recorder.current.state === "inactive") { + record(); + } else if (recorder.current.state === "paused") { + recorder.current.resume(); + } + } + + const playSegment = (idx: number) => { + console.log(idx) + let currentChunks = videos[idx].chunks + console.log(currentChunks) + + const blob = new Blob(currentChunks, { + type: 'video/webm' + }) + const blobUrl = URL.createObjectURL(blob) + console.log(blobUrl) + + videoElementRef.current!.srcObject = null + videoElementRef.current!.src = blobUrl + videoElementRef.current!.muted = false + } + + const clearPrevious = () => { + const numVideos = videos.length + setRecordingTimer(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) + setVideoProgressHelper(numVideos == 1 ? 0 : videos[numVideos - 2].endTime) + setVideos(videos.filter((_, idx) => idx !== numVideos - 1)); + } + + // const handleVideoProgress = (event: any) => { + // const manualChange = Number(event.target.value); + // videoElement!.currentTime = (videoElement!.duration / 100) * manualChange; + // setProgress(manualChange) + // }; + + // const handleVideoSpeed = (event: any) => { + // const newSpeed = Number(event.target.value); + // videoElement!.playbackRate = speed; + // setSpeed(newSpeed) + // }; + + const handleOnTimeUpdate = () => { + if (playing) { + setVideoProgressHelper(videoElementRef.current!.currentTime) + } + }; + + const millisecondToMinuteSecond = (milliseconds: number) => { + const toTwoDigit = (digit: number) => { + return String(digit).length == 1 ? "0" + digit : digit + } + const minutes = Math.floor(( milliseconds % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((milliseconds % (1000 * 60)) / 1000); + return toTwoDigit(minutes) + " : " + toTwoDigit(seconds); + } + + + + + useEffect(() => { + console.log(videos.map((elt) => elt.endTime / MAXTIME * 100)) + console.log(videos) + }, [videos]) + + return ( +
+
+
+
) +} \ No newline at end of file diff --git a/src/client/views/nodes/RecordingBox/index.ts b/src/client/views/nodes/RecordingBox/index.ts new file mode 100644 index 000000000..ff21eaed6 --- /dev/null +++ b/src/client/views/nodes/RecordingBox/index.ts @@ -0,0 +1,2 @@ +export * from './RecordingView' +export * from './RecordingBox' \ No newline at end of file diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 1d4bbaed0..3e7542e46 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -53,6 +53,7 @@ export abstract class URLField extends ObjectField { export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short.ogg"; @scriptingGlobal @Deserializable("audio") export class AudioField extends URLField { } +@scriptingGlobal @Deserializable("recording") export class RecordingField extends URLField { } @scriptingGlobal @Deserializable("image") export class ImageField extends URLField { } @scriptingGlobal @Deserializable("video") export class VideoField extends URLField { } @scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } -- cgit v1.2.3-70-g09d2 From 6c7101d4f69dd79a83a48d04356748213c38a435 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 11 Apr 2022 13:31:33 -0400 Subject: making layout icons work better. --- src/client/documents/Documents.ts | 4 ++- src/client/util/CurrentUserUtils.ts | 15 +++++--- src/client/util/DragManager.ts | 6 ++-- src/client/views/DocumentDecorations.tsx | 20 ++++++++--- src/client/views/InkingStroke.tsx | 6 +++- src/client/views/StyleProvider.tsx | 7 ++-- .../views/collections/CollectionCarouselView.tsx | 4 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 40 ++++++++++++++++++++++ .../collectionFreeForm/MarqueeView.scss | 1 + src/client/views/nodes/DocumentView.tsx | 7 ++-- src/client/views/nodes/ImageBox.tsx | 6 ++-- src/client/views/nodes/LabelBigText.js | 33 +++++++++++++----- src/client/views/nodes/LabelBox.tsx | 5 +-- src/client/views/nodes/WebBox.tsx | 4 +-- src/fields/Doc.ts | 2 +- 15 files changed, 123 insertions(+), 37 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index bb60586eb..652f8e7e0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -139,6 +139,8 @@ export class DocumentOptions { _itemIndex?: number; // which item index the carousel viewer is showing _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts _singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document) + _minFontSize?: number; // minimum font size for labelBoxes + _maxFontSize?: number; // maximum font size for labelBoxes _columnWidth?: number; _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden _fontSize?: string; @@ -441,7 +443,7 @@ export namespace Docs { }], [DocumentType.LABEL, { layout: { view: LabelBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } + options: { links: "@links(self)", _singleLine: true } }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index af731ce9f..bc4dbcb2e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -9,7 +9,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; +import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; @@ -292,7 +292,7 @@ export class CurrentUserUtils { if (doc["template-icon-view"] === undefined) { const iconView = Docs.Create.LabelDocument({ title: "icon", textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("title"), _backgroundColor: "dimgray", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); // Docs.Create.TextDocument("", { // title: "icon", _width: 150, _height: 30, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }) @@ -304,7 +304,8 @@ export class CurrentUserUtils { if (doc["template-icon-view-rtf"] === undefined) { const iconRtfView = Docs.Create.LabelDocument({ title: "icon_" + DocumentType.RTF, textTransform: "unset", letterSpacing: "unset", layout: LabelBox.LayoutString("text"), - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + _singleLine: false, _minFontSize: 18, _maxFontSize: 24, + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconRtfView.isTemplateDoc = makeTemplate(iconRtfView, true, "icon_" + DocumentType.RTF); doc["template-icon-view-rtf"] = new PrefetchProxy(iconRtfView); @@ -319,14 +320,18 @@ export class CurrentUserUtils { } if (doc["template-icon-view-img"] === undefined) { const iconImageView = Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/face.gif", { - title: "data", _width: 50, isTemplateDoc: true, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true + title: "data", _width: 50, isTemplateDoc: true, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconImageView.isTemplateDoc = makeTemplate(iconImageView, true, "icon_" + DocumentType.IMG); doc["template-icon-view-img"] = new PrefetchProxy(iconImageView); } if (doc["template-icon-view-col"] === undefined) { - const iconColView = Docs.Create.TreeDocument([], { title: "data", _width: 180, _height: 80, onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); + const iconColView = Docs.Create.ImageDocument("", { title: "icon", _width: 180 / 4, _height: 135 / 4, onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true }); iconColView.isTemplateDoc = makeTemplate(iconColView, true, "icon_" + DocumentType.COL); + const proto = iconColView.proto as Doc; + proto["icon-nativeWidth"] = 180 / 4; + proto["icon-nativeHeight"] = 135 / 4; + proto.icon = new ImageField("http://www.cs.brown.edu/~bcz/noImage.png"); doc["template-icon-view-col"] = new PrefetchProxy(iconColView); } if (doc["template-icons"] === undefined) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 411fc6d11..d6d04db9a 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -380,8 +380,8 @@ export namespace DragManager { } } const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / ele.offsetWidth; - const scaleY = ele.offsetHeight ? rect.height / ele.offsetHeight : scaleX; + const scaleX = rect.width / (ele.offsetWidth || rect.width); + const scaleY = ele.offsetHeight ? rect.height / (ele.offsetHeight || rect.height) : scaleX; elesCont.left = Math.min(rect.left, elesCont.left); elesCont.top = Math.min(rect.top, elesCont.top); @@ -424,7 +424,7 @@ export namespace DragManager { const hideDragShowOriginalElements = (hide: boolean) => { dragLabel.style.display = hide ? "" : "none"; - !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); + //!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); eles.forEach(ele => ele.hidden = hide); }; options?.hideSource && hideDragShowOriginalElements(true); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index d7ead713a..1487d5bb0 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -144,10 +144,17 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return true; } + _iconifyTimeout: NodeJS.Timeout | undefined; onCloseClick = () => { - const selected = SelectionManager.Views().slice(); - SelectionManager.DeselectAll(); - selected.map(dv => dv.props.removeDocument?.(dv.props.Document)); + const views = SelectionManager.Views().slice().filter(v => v); + const icons = this._iconifyTimeout ? views : views.filter(view => view.rootDoc.layoutKey === "layout_icon"); + const others = this._iconifyTimeout ? [] : views.filter(view => view.rootDoc.layoutKey !== "layout_icon"); + icons.forEach(iconView => iconView.props.removeDocument?.(iconView.props.Document)); + others.forEach(dv => dv.iconify()); + others.length && (this._iconifyTimeout = setTimeout(() => { + views.forEach(view => SelectionManager.DeselectView(view)); + this._iconifyTimeout = undefined; + }, 1000)); } onMaximizeDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, () => { @@ -171,7 +178,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P } else if (e.altKey) { // open same document in new tab CollectionDockingView.ToggleSplit(selectedDocs[0].props.Document, "right"); } else { - LightboxView.SetLightboxDoc(selectedDocs[0].props.Document, undefined, selectedDocs.slice(1).map(view => view.props.Document)); + var openDoc = selectedDocs[0].props.Document; + if (openDoc.layoutKey === "layout_icon") { + openDoc = DocListCast(openDoc.aliases).find(alias => !alias.context) ?? Doc.MakeAlias(openDoc); + Doc.deiconifyView(openDoc); + } + LightboxView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1).map(view => view.props.Document)); } } SelectionManager.DeselectAll(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 06671961d..5e589fdea 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -70,7 +70,11 @@ export class InkingStroke extends ViewBoxBaseComponent() { // transform is the inherited screentolocal xf plus any scaling that was done to make the stroke // fit within its panel (e.g., for content fitting views like Lightbox or multicolumn, etc) - screenToLocal = () => this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1); + screenToLocal = () => { + console.log("Scaling = " + this.props.scaling?.()) + console.log("Stolocal = " + this.props.ScreenToLocalTransform().Scale) + return this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1); + } getAnchor = () => { console.log(document.activeElement); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 649ee8394..739670ecf 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -117,7 +117,10 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + "backgroundColor"], StrCast(doc?._backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : "")); + let docColor: Opt = + StrCast(doc?.[fieldKey + "backgroundColor"], + StrCast(doc?._backgroundColor, + StrCast(props?.Document.backgroundColor, isCaption ? "rgba(0,0,0,0.4)" : ""))); switch (doc?.type) { case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break; case DocumentType.PRES: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); break; @@ -127,7 +130,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt
- { + if (this.clicks) { + console.log("UPDATE ICON"); + } + this.clicks++; + this.props.docViewPath().lastElement().ContentDiv!.style.width = (this.layoutDoc[WidthSym]()).toString(); + this.props.docViewPath().lastElement().ContentDiv!.style.height = (this.layoutDoc[HeightSym]()).toString(); + var htmlString = this._mainCont && new XMLSerializer().serializeToString(this.props.docViewPath().lastElement().ContentDiv!); + this.props.docViewPath().lastElement().ContentDiv!.style.width = ""; + this.props.docViewPath().lastElement().ContentDiv!.style.height = ""; + const nativeWidth = this.layoutDoc[WidthSym](); + const nativeHeight = this.layoutDoc[HeightSym](); + + CreateImage( + "", + document.styleSheets, + htmlString?.replace(/"marqueeView"/g, '"marqueeView marqueeView2"'), + nativeWidth, + nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), + NumCast(this.layoutDoc._scrollTop) + ).then + ((data_url: any) => { + VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true).then( + returnedfilename => setTimeout(action(() => { + + this.dataDoc.icon = new ImageField(returnedfilename); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }), 500)); + }) + .catch(function (error: any) { + console.error('oops, something went wrong!', error); + }); + } + componentWillUnmount() { Object.values(this._disposers).forEach(disposer => disposer?.()); this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any); @@ -1476,6 +1515,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 62510ce9d..41e4d6b6a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -10,6 +10,7 @@ user-select: none; } + .marqueeView:focus-within { overflow: hidden; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 103c3624d..185eafa4a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -80,6 +80,7 @@ export type DocAfterFocusFunc = (notFocused: boolean) => Promise export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { + updateIcon?: () => void; // updates the icon representation of the document getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, smooth: boolean) => Opt; // returns the duration of the focus setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document @@ -1118,6 +1119,7 @@ export class DocumentViewInternal extends DocComponent !SnappingManager.GetIsDragging() && Doc.BrushDoc(this.props.Document)} onPointerLeave={e => !hasDescendantTarget(e.nativeEvent.x, e.nativeEvent.y, this.ContentDiv) && Doc.UnBrushDoc(this.props.Document)} style={{ + display: "inline", borderRadius: this.borderRounding, pointerEvents: this.pointerEvents, outline: highlighting && !this.borderRounding ? `${highlightColor} ${highlightStyle} ${highlightIndex}px` : "solid 0px", @@ -1233,6 +1235,7 @@ export class DocumentView extends React.Component { } public iconify() { + this.ComponentView?.updateIcon?.(); const layoutKey = Cast(this.Document.layoutKey, "string", null); if (layoutKey !== "layout_icon") { this.switchViews(true, "icon"); @@ -1332,8 +1335,8 @@ export function deiconifyViewFunc(documentView: DocumentView) { } ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { documentView.iconify(); -} -); + documentView.select(false); +}); ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { if (dv.Document.layoutKey === "layout_" + detailLayoutKeySuffix) dv.switchViews(false, "layout"); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 17f95c1cc..fb9e86487 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -73,7 +73,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent this._curSuffix = forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o", + ({ forceFull, scrSize, selected }) => this._curSuffix = this.fieldKey === "icon" ? "_m" : forceFull ? "_o" : scrSize < 100 ? "_s" : scrSize < 400 ? "_m" : scrSize < 800 || !selected ? "_l" : "_o", { fireImmediately: true, delay: 1000 }); this._disposers.path = reaction(() => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), ({ nativeSize, width }) => { @@ -245,8 +245,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent options.maximumFontSize) { + if (fontSize < options.minimumFontSize) { + parentStyle.display = "flex"; + parentStyle.alignItems = "center"; + style.whiteSpace = "pre-wrap"; + style.textAlign = "center"; + style.visibility = ""; + style.fontSize = "18px"; + style.lineHeight = "20px"; + style.top = ""; + style.left = ""; + style.margin = ""; + return element; + } + if (options.maximumFontSize && fontSize > options.maximumFontSize) { fontSize = options.maximumFontSize; lineHeight = fontSize / options.fontSizeFactor; } diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 0015f0b71..c689d9f40 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -4,7 +4,7 @@ import * as React from 'react'; import { Doc, DocListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; -import { Cast, StrCast } from '../../../fields/Types'; +import { Cast, StrCast, NumCast } from '../../../fields/Types'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; @@ -100,7 +100,8 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro BigText(r, { rotateText: null, fontSizeFactor: 1, - maximumFontSize: null, + minimumFontSize: NumCast(this.layoutDoc._minFontSize), + maximumFontSize: NumCast(this.layoutDoc._maxFontSize), limitingDimension: "both", horizontalAlign: "center", verticalAlign: "center", diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index c12059943..0fd193977 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -130,8 +130,8 @@ export class WebBox extends ViewBoxAnnotatableComponent Date: Tue, 12 Apr 2022 18:09:03 -0400 Subject: ink to text for groupings --- package-lock.json | 79 ++++++++- package.json | 1 + src/client/documents/Documents.ts | 5 +- src/client/views/GestureOverlay.tsx | 16 +- src/client/views/InkTranscription.scss | 5 + src/client/views/InkTranscription.tsx | 188 +++++++++++++++++++++ src/client/views/MainView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 19 ++- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 11 -- .../collections/collectionFreeForm/MarqueeView.tsx | 16 +- src/client/views/nodes/EquationBox.scss | 36 ++++ src/client/views/nodes/EquationBox.tsx | 91 +++++++++- src/fields/InkField.ts | 3 +- src/pen-gestures/GestureUtils.ts | 3 +- src/typings/index.d.ts | 1 + 15 files changed, 444 insertions(+), 34 deletions(-) create mode 100644 src/client/views/InkTranscription.scss create mode 100644 src/client/views/InkTranscription.tsx (limited to 'src/client/documents') diff --git a/package-lock.json b/package-lock.json index b1431e55b..1f8b1b8f6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4579,6 +4579,16 @@ "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, + "clipboard": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", + "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", + "requires": { + "good-listener": "^1.2.2", + "select": "^1.1.2", + "tiny-emitter": "^2.0.0" + } + }, "cliss": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/cliss/-/cliss-0.0.2.tgz", @@ -5307,6 +5317,11 @@ } } }, + "crypto-js": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-3.3.0.tgz", + "integrity": "sha512-DIT51nX0dCfKltpRiXV+/TVZq+Qq2NgF4644+K7Ttnla7zEzqc+kjJyiB96BHNyUTBxyjzRcZYpUdZa+QAqi6Q==" + }, "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", @@ -5885,6 +5900,11 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, + "delegate": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", + "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" + }, "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", @@ -7846,6 +7866,14 @@ "jquery": "*" } }, + "good-listener": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", + "requires": { + "delegate": "^3.1.2" + } + }, "google-auth-library": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-4.2.6.tgz", @@ -8462,6 +8490,29 @@ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, + "iink-js": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/iink-js/-/iink-js-1.5.4.tgz", + "integrity": "sha512-WcjMmyXbSo4GY56AVVTKRyxlguh2KAGFyoH24+Zmm87ZWII12or2uLiovAyOK4IE+VVz7m0U5RMuv8i/RV/3Nw==", + "requires": { + "@babel/runtime": "^7.9.2", + "clipboard": "^1.7.1", + "crypto-js": "^3.3.0", + "d3-selection": "^1.4.1", + "json-css": "^1.5.6", + "lodash.merge": "^4.6.2", + "loglevel": "^1.6.8", + "perfect-scrollbar": "^1.5.0", + "uuid-js": "^0.7.5" + }, + "dependencies": { + "d3-selection": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/d3-selection/-/d3-selection-1.4.2.tgz", + "integrity": "sha512-SJ0BqYihzOjDnnlfyeHT0e30k0K1+5sR3d5fNueCNeuhZTnGw4M4o8mqJchSwgKMXCNFo+e2VTChiSJ0vYtXkg==" + } + } + }, "image-data-uri": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/image-data-uri/-/image-data-uri-2.0.1.tgz", @@ -9524,6 +9575,11 @@ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, + "json-css": { + "version": "1.5.6", + "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz", + "integrity": "sha1-65ZPg0ouTqobwvaY/12wB6JsfAA=" + }, "json-parse-better-errors": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", @@ -9996,8 +10052,7 @@ "loglevel": { "version": "1.6.8", "resolved": "https://registry.npmjs.org/loglevel/-/loglevel-1.6.8.tgz", - "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==", - "dev": true + "integrity": "sha512-bsU7+gc9AJ2SqpzxwU3+1fedl8zAntbtC5XYlt3s2j1hJcn2PsXSmgN8TaLG/J1/2mod4+cE/3vNL70/c1RNCA==" }, "loglevelnext": { "version": "1.0.5", @@ -14793,6 +14848,11 @@ "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, + "perfect-scrollbar": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/perfect-scrollbar/-/perfect-scrollbar-1.5.5.tgz", + "integrity": "sha512-dzalfutyP3e/FOpdlhVryN4AJ5XDVauVWxybSkLZmakFE2sS3y3pc4JnSprw8tGmHvkaG5Edr5T7LBTZ+WWU2g==" + }, "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", @@ -16791,6 +16851,11 @@ "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz", "integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio=" }, + "select": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" + }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", @@ -18208,6 +18273,11 @@ "resolved": "https://registry.npmjs.org/timed-out/-/timed-out-4.0.1.tgz", "integrity": "sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=" }, + "tiny-emitter": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/tiny-emitter/-/tiny-emitter-2.1.0.tgz", + "integrity": "sha512-NB6Dk1A9xgQPMoGqC5CVXn123gWyte215ONT5Pp5a0yt4nlEoO1ZWeCwpncaekPHXO60i47ihFnZPiRPjRMq4Q==" + }, "tiny-inflate": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", @@ -19185,6 +19255,11 @@ "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==" }, + "uuid-js": { + "version": "0.7.5", + "resolved": "https://registry.npmjs.org/uuid-js/-/uuid-js-0.7.5.tgz", + "integrity": "sha1-bIhtAqU9LUDc8l2RoXC0p7JblNA=" + }, "valid-url": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/valid-url/-/valid-url-1.0.9.tgz", diff --git a/package.json b/package.json index 2c3e641dd..c8455c8a3 100644 --- a/package.json +++ b/package.json @@ -196,6 +196,7 @@ "https": "^1.0.0", "https-browserify": "^1.0.0", "i": "^0.3.7", + "iink-js": "^1.5.4", "image-data-uri": "^2.0.1", "image-size": "^0.7.5", "image-size-stream": "^1.1.0", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9bcd23aa0..884cc781d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -4,7 +4,7 @@ import { DateField } from "../../fields/DateField"; import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { HtmlField } from "../../fields/HtmlField"; -import { InkField } from "../../fields/InkField"; +import { InkField, PointData } from "../../fields/InkField"; import { List } from "../../fields/List"; import { ProxyField } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; @@ -738,7 +738,7 @@ export namespace Docs { return linkDoc; } - export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: { X: number, Y: number }[], options: DocumentOptions = {}) { + export function InkDocument(color: string, tool: string, strokeWidth: number, strokeBezier: string, fillColor: string, arrowStart: string, arrowEnd: string, dash: string, points: PointData[], options: DocumentOptions = {}) { const I = new Doc(); I[Initializing] = true; I.type = DocumentType.INK; @@ -1161,6 +1161,7 @@ export namespace DocUtils { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; } else if (field instanceof InkField) { + console.log("Documents " + field.inkData) created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 50dca0a99..16a9cfaeb 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -41,6 +41,7 @@ export class GestureOverlay extends Touchable { @observable private _menuY: number = -300; @observable private _pointerY?: number; @observable private _points: { X: number, Y: number }[] = []; + @observable private _timeStamps: number[] = []; @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; @@ -59,6 +60,7 @@ export class GestureOverlay extends Touchable { private pointerIdentifier?: number; private _hands: Map = new Map(); private _holdTimer: NodeJS.Timeout | undefined; + private _strokeStartTime: number = 0; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -418,6 +420,7 @@ export class GestureOverlay extends Touchable { this._strokes = []; this._points = []; + this._timeStamps = []; this._possibilities = []; document.removeEventListener("touchend", this.handleHandUp); } @@ -493,6 +496,8 @@ export class GestureOverlay extends Touchable { onPointerDown = (e: React.PointerEvent) => { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { this._points.push({ X: e.clientX, Y: e.clientY }); + this._timeStamps.push(0); + this._strokeStartTime = new Date().getTime(); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } @@ -501,6 +506,7 @@ export class GestureOverlay extends Touchable { @action onPointerMove = (e: PointerEvent) => { this._points.push({ X: e.clientX, Y: e.clientY }); + this._timeStamps.push(new Date().getTime() - this._strokeStartTime); if (this._points.length > 1) { const B = this.svgBounds; @@ -547,6 +553,7 @@ export class GestureOverlay extends Touchable { const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); //push first points to so interactionUtil knows pointer is up this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); + this._timeStamps.push(this._timeStamps[0]); const initialPoint = this._points[0.]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height); @@ -558,6 +565,7 @@ export class GestureOverlay extends Touchable { case ToolglassTools.InkToText: this._strokes.push(new Array(...this._points)); this._points = []; + this._timeStamps = []; CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => { const wordResults = results.filter((r: any) => r.category === "line"); const possibilities: string[] = []; @@ -581,6 +589,7 @@ export class GestureOverlay extends Touchable { case ToolglassTools.IgnoreGesture: this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; + this._timeStamps = []; break; } } @@ -589,6 +598,7 @@ export class GestureOverlay extends Touchable { this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; + this._timeStamps = []; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { this.InkShape = ""; Doc.UserDoc().activeInkTool = InkTool.None; @@ -635,9 +645,11 @@ export class GestureOverlay extends Touchable { this.dispatchGesture(GestureUtils.Gestures.Stroke); } this._points = []; + this._timeStamps = []; } } else { this._points = []; + this._timeStamps = []; } CollectionFreeFormViewChrome.Instance?.primCreated(); } @@ -686,6 +698,7 @@ export class GestureOverlay extends Touchable { } } this._points = []; + this._timeStamps = []; switch (shape) { //must push an extra point in the end so InteractionUtils knows pointer is up. //must be (points[0].X,points[0]-1) @@ -808,7 +821,8 @@ export class GestureOverlay extends Touchable { points: stroke ?? this._points, gesture: gesture as any, bounds: this.getBounds(stroke ?? this._points), - text: data + text: data, + times: this._timeStamps } } ) diff --git a/src/client/views/InkTranscription.scss b/src/client/views/InkTranscription.scss new file mode 100644 index 000000000..bbb0a1afa --- /dev/null +++ b/src/client/views/InkTranscription.scss @@ -0,0 +1,5 @@ +.ink-transcription { + .error-msg { + display: none !important; + } +} \ No newline at end of file diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx new file mode 100644 index 000000000..950f622dd --- /dev/null +++ b/src/client/views/InkTranscription.tsx @@ -0,0 +1,188 @@ +import { timeStamp } from 'console'; +import * as iink from 'iink-js'; +import { action, observable } from 'mobx'; +import * as React from 'react'; +import { Doc } from '../../fields/Doc'; +import { InkData, InkField } from "../../fields/InkField"; +import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { Docs } from "../documents/Documents"; +import './InkTranscription.scss'; + +export class InkTranscription extends React.Component { + static Instance: InkTranscription; + + @observable _mathRegister: any; + @observable _mathRef: any; + @observable _textRegister: any; + @observable _textRef: any; + private addDocument?: (doc: Doc | Doc[]) => boolean; + private bounds: { x: number, y: number, width: number, height: number } = { x: 0, y: 0, width: 0, height: 0 }; + + constructor(props: Readonly<{}>) { + super(props); + + InkTranscription.Instance = this; + } + + componentWillUnmount() { + this._mathRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); + this._textRef.removeEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + + this._mathRef.removeEventListener('changed', (e: any) => this.callExport(this._mathRef)); + this._textRef.removeEventListener('changed', (e: any) => this.callExport(this._textRef)); + } + + @action + setMathRef = (r: any) => { + if (!this._mathRegister) { + this._mathRegister = r ? iink.register(r, { + recognitionParams: { + type: 'MATH', + protocol: 'WEBSOCKET', + server: { + host: 'cloud.myscript.com', + applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', + hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', + websocket: { + pingEnabled: false, + autoReconnect: true + } + }, + iink: { + math: { + mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'] + }, + export: { + jiix: { + strokes: true + } + } + } + }, + triggers: { + exportContent: 'DEMAND' + } + }) : null; + } + + r.addEventListener('exported', (e: any) => this.exportInk(e, this._mathRef)); + r.addEventListener('changed', (e: any) => this.callExport(this._mathRef)); + + return this._mathRef = r; + } + + @action + setTextRef = (r: any) => { + if (!this._textRegister) { + this._textRegister = r ? iink.register(r, { + recognitionParams: { + type: 'TEXT', + protocol: 'WEBSOCKET', + server: { + host: 'cloud.myscript.com', + applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', + hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', + websocket: { + pingEnabled: false, + autoReconnect: true + } + }, + iink: { + text: { + mimeTypes: ['text/plain'] + }, + export: { + jiix: { + strokes: true + } + } + } + }, + triggers: { + exportContent: 'DEMAND' + } + }) : null; + } + + r.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + r.addEventListener('changed', (e: any) => this.callExport(this._textRef)); + + return this._textRef = r; + } + + transcribeInk = (inkdocs: Doc[], math: boolean, bounds: { x: number, y: number, width: number, height: number }, addDocument?: (doc: Doc | Doc[]) => boolean) => { + const strokes: InkData[] = []; + inkdocs.filter(i => Cast(i.data, InkField)).forEach(i => { + // TODO: interpolate missing times stamps + const d = Cast(i.data, InkField, null); + const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); + const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); + strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top, time: pd.time }))); + }); + + this.addDocument = addDocument; + this.bounds = bounds; + + const pointerData = { "events": strokes.map(stroke => this.inkJSON(stroke)) }; + // console.log(JSON.stringify(pointerData)); + const processGestures = false; + + if (math) { + this._mathRef.editor.pointerEvents(pointerData, processGestures); + } + else { + this._textRef.editor.pointerEvents(pointerData, processGestures); + } + } + + callExport(ref: any) { + if (ref.editor.canExport) { + ref.editor.export_(); + } + } + + inkJSON = (stroke: InkData) => { + return { + "pointerType": "PEN", + "pointerId": 1, + "x": stroke.map(point => point.X), + "y": stroke.map(point => point.Y), + "t": stroke.map(point => point.time), + "p": new Array(stroke.length).fill(1.0) + }; + } + + exportInk = (e: any, ref: any) => { + const exports = e.detail.exports; + if (exports) { + if (exports['application/x-latex']) { + const latex = exports['application/x-latex']; + console.log(latex); + + this.addDocument?.(Docs.Create.EquationDocument({ title: latex, x: this.bounds.width, y: 0, _width: this.bounds.width, _height: this.bounds.height })); + } + else if (exports['text/plain']) { + const text = exports['text/plain']; + console.log(text); + this.addDocument?.(Docs.Create.TextDocument(text, { title: text, x: this.bounds.width, y: 0, _width: this.bounds.width, _height: this.bounds.height })); + } + + ref.editor.clear(); + } + } + + render() { + return ( +
+
+
+
+
+
+ ) + } +} \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 8c0795881..84c1037dd 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -44,6 +44,7 @@ import { GestureOverlay } from './GestureOverlay'; import { DASHBOARD_SELECTOR_HEIGHT, LEFT_MENU_WIDTH } from './global/globalCssVariables.scss'; import { Colors } from './global/globalEnums'; import { KeyManager } from './GlobalKeyHandler'; +import { InkTranscription } from './InkTranscription'; import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; import "./MainView.scss"; @@ -184,7 +185,7 @@ export class MainView extends React.Component { fa.faArrowAltCircleDown, fa.faArrowAltCircleUp, fa.faArrowAltCircleLeft, fa.faArrowAltCircleRight, fa.faStopCircle, fa.faCheckCircle, fa.faGripVertical, fa.faSortUp, fa.faSortDown, fa.faTable, fa.faTh, fa.faThList, fa.faProjectDiagram, fa.faSignature, fa.faColumns, fa.faChevronCircleUp, fa.faUpload, fa.faBorderAll, fa.faBraille, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, fa.faSmile, fa.faIndent, fa.faOutdent, fa.faChartBar, fa.faBan, fa.faPhoneSlash, fa.faGripLines, - fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt]); + fa.faSave, fa.faBookmark, fa.faList, fa.faListOl, fa.faFolderPlus, fa.faLightbulb, fa.faBookOpen, fa.faMapMarkerAlt, fa.faSquareRootAlt]); this.initAuthenticationRouters(); } @@ -636,6 +637,7 @@ export class MainView extends React.Component { + {this.snapLines}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index e2ea81392..7d60387de 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -50,6 +50,7 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); +import { InkTranscription } from "../../InkTranscription"; export const panZoomSchema = createSchema({ _panX: "number", @@ -492,7 +493,9 @@ export class CollectionFreeFormView extends CollectionSubView { return { X: pt.X, Y: pt.Y, time: times[i] } }); + const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), strokes, { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() }); this.addDocument(inkDoc); e.stopPropagation(); @@ -1477,6 +1480,11 @@ export class CollectionFreeFormView extends CollectionSubView this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); this.props.ContainingCollectionView && appearanceItems.push({ description: "Ungroup collection", event: this.promoteCollection, icon: "table" }); + + this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to text", event: () => this.transcribeStrokes(false), icon: "font" }); + + this.props.Document._isGroup && this.childDocs.filter(s => s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" }); + !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); @@ -1536,6 +1544,15 @@ export class CollectionFreeFormView extends CollectionSubView { + if (this.props.Document._isGroup) { + const inkdocs = this.childDocs.filter(s => s.type === DocumentType.INK); + InkTranscription.Instance.transcribeInk(inkdocs, math, { x: NumCast(this.layoutDoc.x) ?? 0, y: NumCast(this.layoutDoc.y) ?? 0, width: NumCast(this.layoutDoc._width) ?? 0, height: NumCast(this.layoutDoc._height) ?? 0 }, this.addDocument); + } + } + @action setupDragLines = (snapToDraggedDoc: boolean = false) => { const activeDocs = this.getActiveDocuments(); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 1f59f9732..8a8b528f6 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -14,7 +14,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu { public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => void = unimplementedFunction; public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; - public inkToText: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; @@ -64,16 +63,6 @@ export class MarqueeOptionsMenu extends AntimodeMenu { , ]; - if (false && !SelectionManager.Views().some(v => v.props.Document.type !== DocumentType.INK)) { - buttons.push( - Change to Text
} placement="bottom"> - - ); - } return this.getElement(buttons); } } \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b10b0912f..a828cd981 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,14 +1,15 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc"; +import { AclAdmin, AclAugment, AclEdit, DataSym, Doc, Opt } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { ImageField } from "../../../../fields/URLField"; import { GetEffectiveAcl } from "../../../../fields/util"; -import { Utils, intersectRect, returnFalse } from "../../../../Utils"; +import { intersectRect, returnFalse, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; @@ -21,18 +22,17 @@ import { ContextMenu } from "../../ContextMenu"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { PresMovement } from "../../nodes/trails/PresEnums"; +import { VideoBox } from "../../nodes/VideoBox"; +import { pasteImageBitmap } from "../../nodes/WebBoxRenderer"; import { PreviewCursor } from "../../PreviewCursor"; +import { StyleLayers } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; import { SubCollectionViewProps } from "../CollectionSubView"; import { CollectionView } from "../CollectionView"; +import { TreeView } from "../TreeView"; import { MarqueeOptionsMenu } from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); -import { StyleLayers } from "../../StyleProvider"; -import { TreeView } from "../TreeView"; -import { VideoBox } from "../../nodes/VideoBox"; -import { ImageField, WebField } from "../../../../fields/URLField"; -import { pasteImageBitmap } from "../../nodes/WebBoxRenderer"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -265,7 +265,7 @@ export class MarqueeView extends React.Component() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(EquationBox, fieldKey); } public static SelectOnLoad: string = ""; + _ref: React.RefObject = React.createRef(); + @observable _inkOpen = false; + @observable _inkEditor: any; + @observable _inkRef: any; + componentDidMount() { if (EquationBox.SelectOnLoad === this.rootDoc[Id] && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath()))) { this.props.select(false); @@ -39,7 +45,11 @@ export class EquationBox extends ViewBoxBaseComponent() { } }, { fireImmediately: true }); } - plot: any; + + componentWillUnmount() { + this._inkRef.removeEventListener('exported', this.exportInk); + } + @action keyPressed = (e: KeyboardEvent) => { const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace("px", "")); @@ -65,6 +75,7 @@ export class EquationBox extends ViewBoxBaseComponent() { } if (e.key === "Backspace" && !this.dataDoc.text) this.props.removeDocument?.(this.rootDoc); } + onChange = (str: string) => { this.dataDoc.text = str; const style = this._ref.current && getComputedStyle(this._ref.current.element.current); @@ -75,6 +86,58 @@ export class EquationBox extends ViewBoxBaseComponent() { this.layoutDoc._height = Math.max(25, _height); } } + + @action + toggleInk = (e: React.PointerEvent) => { + e.stopPropagation(); + + this._inkOpen = !this._inkOpen; + + if (!this._inkEditor) { + this._inkEditor = this._inkRef ? iink.register(this._inkRef, { + recognitionParams: { + type: 'MATH', + protocol: 'WEBSOCKET', + server: { + host: 'cloud.myscript.com', + applicationKey: '7277ec34-0c2e-4ee1-9757-ccb657e3f89f', + hmacKey: 'f5cb18f2-1f95-4ddb-96ac-3f7c888dffc1', + }, + iink: { + math: { + mimeTypes: ['application/x-latex', 'application/vnd.myscript.jiix'] + }, + export: { + jiix: { + strokes: true + } + } + } + } + }) : null; + + this._inkRef.addEventListener('exported', this.exportInk) + } + } + + convertInk = (e: React.MouseEvent) => { + this._inkRef.editor.convert(); + } + + clearInk = (e: React.MouseEvent) => { + this._inkRef.editor.clear(); + this.onChange(""); + } + + exportInk = (e: any) => { + const exports = e.detail.exports; + if (exports && exports['application/x-latex']) { + this.onChange(exports['application/x-latex']); + console.log(JSON.parse(exports['application/vnd.myscript.jiix']).expressions[0].items[0]); + } + } + + render() { TraceMobx(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); @@ -82,18 +145,34 @@ export class EquationBox extends ViewBoxBaseComponent() { onPointerDown={e => !e.ctrlKey && e.stopPropagation()} style={{ transform: `scale(${scale})`, - width: `${100 / scale}%`, + width: `calc(${100 / scale}% + 25px)`, height: `${100 / scale}%`, pointerEvents: !this.props.isSelected() ? "none" : undefined, }} - onKeyDown={e => e.stopPropagation()} - > + onKeyDown={e => e.stopPropagation()}> + + +
+ +
+ +
this._inkRef = r)} + id="editor" onPointerDown={(e) => e.stopPropagation()} + touch-action="none" + style={{ display: this._inkOpen && this.props.isContentActive() ? "block" : "none" }}> + + +
+
); } } \ No newline at end of file diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 31024e805..48271d3d3 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -19,6 +19,7 @@ export enum InkTool { export interface PointData { X: number; Y: number; + time?: number; } export type Segment = Array; @@ -83,7 +84,7 @@ export class InkField extends ObjectField { } [ToScriptString]() { - return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}} `) + "])"; + return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}, time: ${i.time || 0}} `) + "])"; } [ToString]() { return "InkField"; diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index 65f2bf80c..cf59cb3c6 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -8,7 +8,8 @@ export namespace GestureUtils { readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, - readonly text?: any + readonly text?: any, + readonly times?: number[] ) { } } diff --git a/src/typings/index.d.ts b/src/typings/index.d.ts index 068ac2159..bc7727426 100644 --- a/src/typings/index.d.ts +++ b/src/typings/index.d.ts @@ -8,6 +8,7 @@ declare module 'webrtc-adapter'; declare module 'bezier-curve'; declare module 'fit-curve'; declare module 'react-audio-waveform'; +declare module 'iink-js'; declare module 'reveal'; declare module 'react-reveal'; -- cgit v1.2.3-70-g09d2 From 594f585b74bf6ecec3795a4b780da619c66f341d Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Apr 2022 10:57:08 -0400 Subject: added images to treeView bullets for docs with icons. fixed following pushpin links to prototypes to toggle delegates when shown. gave groups a default padding. fixed creating icons to not create unnecessary template docs. fixed toggling isPushpin. made summaries simple links instead of templates. --- src/client/documents/Documents.ts | 38 ++++++++++++---------- src/client/util/DocumentManager.ts | 5 +-- src/client/views/DocumentDecorations.tsx | 5 ++- src/client/views/StyleProvider.tsx | 13 ++++++-- src/client/views/collections/TreeView.scss | 2 +- src/client/views/collections/TreeView.tsx | 5 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 9 ++--- src/client/views/nodes/DocumentView.tsx | 8 +++-- src/client/views/nodes/LinkDocPreview.tsx | 9 ++++- 9 files changed, 59 insertions(+), 35 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 652f8e7e0..465cdf3ec 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -223,6 +223,7 @@ export class DocumentOptions { dockingConfig?: string; annotationOn?: Doc; isPushpin?: boolean; + isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document // BACKGROUND GRID @@ -807,7 +808,7 @@ export namespace Docs { } export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { - const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Freeform }, id); + const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id); documents.map(d => d.context = inst); return inst; } @@ -1317,22 +1318,25 @@ export namespace DocUtils { const _height = NumCast(doc._height); const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; - let fieldTemplate: Opt; - if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { - fieldTemplate = Docs.Create.TextDocument("", options); - } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); - } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); - } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); - } - const docTemplate = docLayoutTemplate || creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); - - fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); - docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + if (docLayoutTemplate) { + Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); + } else { + let fieldTemplate: Opt; + if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { + fieldTemplate = Docs.Create.TextDocument("", options); + } else if (doc.data instanceof PdfField) { + fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); + } else if (doc.data instanceof VideoField) { + fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof AudioField) { + fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); + } else if (doc.data instanceof ImageField) { + fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + } + const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); + fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); + docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); + } } export function makeCustomView(doc: Doc, custom: boolean, layout: string) { Doc.setNativeView(doc); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 1d603e18f..fb50ecb4a 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -176,12 +176,13 @@ export class DocumentManager { }); // if the target is hidden, un-hide it here. } const focusAndFinish = (didFocus: boolean) => { + const finalTargetDoc = docView?.Document ?? targetDoc; if (originatingDoc?.isPushpin) { if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal - targetDoc.hidden = !targetDoc.hidden; + finalTargetDoc.hidden = !finalTargetDoc.hidden; } } else { - targetDoc.hidden && (targetDoc.hidden = undefined); + finalTargetDoc.hidden && (finalTargetDoc.hidden = undefined); !noSelect && docView?.select(false); } finished?.(); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 062ca1181..a8d91e0a2 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -453,6 +453,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return (collectionAcl === AclAdmin || collectionAcl === AclEdit || GetEffectiveAcl(docView.rootDoc) === AclAdmin); }); } + @computed get hasIcons() { + return SelectionManager.Views().some(docView => docView.rootDoc.layoutKey === "layout_icon"); + } render() { const bounds = this.Bounds; @@ -516,7 +519,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> - {!canDelete ?
: topBtn("close", "times", undefined, this.onCloseClick, "Close")} + {!canDelete ?
: topBtn("close", this.hasIcons ? "times" : "window-maximize", undefined, this.onCloseClick, "Close")} {titleArea} {!canOpen ? (null) : topBtn("open", "external-link-alt", this.onMaximizeDown, undefined, "Open in Tab (ctrl: as alias, shift: in new collection)")} {hideResizers ? (null) : diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 9803f9782..dba290b46 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -3,10 +3,11 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; import { action, runInAction } from 'mobx'; +import { extname } from 'path'; import { Doc, Opt, StrListCast } from "../../fields/Doc"; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; -import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, ImageCast, NumCast, StrCast } from "../../fields/Types"; import { DashColor, lightOrDark } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; @@ -88,7 +89,15 @@ export function DefaultStyleProvider(doc: Opt, props: Opt props?.styleProvider?.(doc, props, StyleProp.ShowTitle); 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); switch (property.split(":")[0]) { - case StyleProp.TreeViewIcon: return Doc.toIcon(doc, isOpen); + case StyleProp.TreeViewIcon: + const icon = Cast(doc?.layout_icon, Doc, null) ? Doc.expandTemplateLayout(Cast(doc?.layout_icon, Doc, null), doc) : undefined; + if (icon && ImageCast(doc?.icon, ImageCast(doc?.data))) { + const img = ImageCast(doc?.icon, ImageCast(doc?.data)); + const ext = extname(img.url.href); + const url = doc?.icon ? img.url.href : img.url.href.replace(ext, "_s" + ext); + return ; + } + return Doc.toIcon(doc, isOpen); case StyleProp.DocContents: return undefined; case StyleProp.WidgetColor: return isAnnotated ? Colors.LIGHT_BLUE : darkScheme() ? "lightgrey" : "dimgrey"; case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null)); diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 2e33d3564..7c75810d3 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -47,7 +47,7 @@ width: $TREE_BULLET_WIDTH; color: $medium-gray; margin-top: 3px; - transform: scale(1.3, 1.3); + // transform: scale(1.3, 1.3); // bcz: why was this here? It makes displaying images in the treeView for documents that have icons harder. border: #80808030 1px solid; border-radius: 4px; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index ff5c4bab1..c2d0983da 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -31,6 +31,7 @@ import { CollectionTreeView } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); +import { IconProp } from '@fortawesome/fontawesome-svg-core'; export interface TreeViewProps { treeView: CollectionTreeView; @@ -471,7 +472,7 @@ export class TreeView extends React.Component { onClick={this.bulletClick} style={this.props.treeView.outlineMode ? { opacity: this.titleStyleProvider?.(this.doc, this.props.treeView.props, StyleProp.Opacity) } : { color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), - opacity: checked === "unchecked" ? undefined : 0.4 + opacity: checked === "unchecked" || typeof iconType !== "string" ? undefined : 0.4 }}> {this.props.treeView.outlineMode ? !(this.doc.text as RichTextField)?.Text ? (null) : @@ -484,7 +485,7 @@ export class TreeView extends React.Component { checked === "unchecked" ? "square" : !this.treeViewOpen ? "caret-right" : "caret-down"} />
- {this.onCheckedClick ? (null) : } + {this.onCheckedClick ? (null) : typeof iconType === "string" ? : iconType}
}
; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b8f687489..5405ab500 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -519,13 +519,8 @@ export class MarqueeView extends React.Component(selected); - Doc.GetProto(summary).layout_portal = CollectionView.LayoutString(Doc.LayoutFieldKey(summary) + "-annotations"); - summary._backgroundColor = "#e2ad32"; - portal.layoutKey = "layout_portal"; - portal.title = "document collection"; + const summary = Docs.Create.TextDocument("", { _backgroundColor: "#e2ad32", x: this.Bounds.left, y: this.Bounds.top, _width: 200, _height: 200, _fitToBox: true, _showSidebar: true, title: "overview" }); + const portal = Docs.Create.FreeformDocument(selected, { isGroup: true, backgroundColor: "transparent" }); DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summarizing", ""); this.props.addLiveTextDocument(summary); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 195f6c9e5..8bf9348c7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -594,8 +594,12 @@ export class DocumentViewInternal extends DocComponent, zoom: boolean, setPushpin: boolean): void => { this.Document.ignoreClick = false; - this.Document._isLinkButton = !this.Document._isLinkButton; - setPushpin && (this.Document.isPushpin = this.Document._isLinkButton); + if (setPushpin) { + this.Document.isPushpin = !this.Document.isPushpin; + this.Document._isLinkButton = this.Document.isPushpin || this.Document._isLinkButton; + } else { + this.Document._isLinkButton = !this.Document._isLinkButton; + } if (this.Document._isLinkButton && !this.onClickHandler) { this.Document.followLinkZoom = zoom; this.Document.followLinkLocation = location; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index d5f3f3b4e..eca2e3cfd 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -15,6 +15,7 @@ import { DocumentView, DocumentViewSharedProps } from "./DocumentView"; import './LinkDocPreview.scss'; import React = require("react"); import { DocumentType } from '../../documents/DocumentTypes'; +import { DragManager } from '../../util/DragManager'; interface LinkDocPreviewProps { linkDoc?: Doc; @@ -137,7 +138,13 @@ export class LinkDocPreview extends React.Component { @computed get previewHeader() { return !this._linkDoc || !this._targetDoc || !this._linkSrc ? (null) :
-
+
{ + DragManager.StartDocumentDrag([this._infoRef.current!], + new DragManager.DocumentDragData([this._targetDoc!]), e.pageX, e.pageY); + e.stopPropagation(); + LinkDocPreview.Clear(); + }}> {StrCast(this._targetDoc.title).length > 16 ? StrCast(this._targetDoc.title).substr(0, 16) + "..." : this._targetDoc.title}

{StrCast(this._linkDoc.description)}

-- cgit v1.2.3-70-g09d2 From 85fca7ecd26236407e266d6aec051d1f5fb5feb9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Apr 2022 09:30:37 -0400 Subject: gave summary documents pushpin behaviors. fixed following link to doc that is iconified to work. fixed pushin visibility toggling for docs that are iconified. --- src/client/documents/Documents.ts | 1 + src/client/views/StyleProvider.tsx | 2 +- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 6 +++--- src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 6 ++++-- src/client/views/nodes/DocumentView.tsx | 5 ++++- 5 files changed, 13 insertions(+), 7 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 465cdf3ec..69ea21541 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -117,6 +117,7 @@ export class DocumentOptions { _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed _freeformLOD?: boolean; // whether to use LOD to render a freeform document + _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index dba290b46..ba5aaf36a 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -117,7 +117,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { + const layoutdoc = Doc.Layout(doc); const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); - const pt2 = xf.transformPoint(NumCast(doc.x) + doc[WidthSym](), NumCast(doc.y) + doc[HeightSym]()); + const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]()); const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] }; const cx = NumCast(this.layoutDoc._panX); const cy = NumCast(this.layoutDoc._panY); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 5405ab500..2174aaf10 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -519,10 +519,12 @@ export class MarqueeView extends React.Component); } @computed get ContentScale() { return this.props.ContentScaling?.() || 1; } @computed get thumb() { return ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumb))?.url.href.replace(".png", "_m.png"); } - @computed get hidden() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Hidden); } + @computed get hidden() { return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Hidden); } @computed get opacity() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Opacity); } @computed get boxShadow() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow); } @computed get borderRounding() { return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); } @@ -1081,6 +1081,9 @@ export class DocumentViewInternal extends DocComponent Date: Fri, 22 Apr 2022 21:39:08 -0400 Subject: changes to allow PresBox to render using a Tree view --- src/client/documents/Documents.ts | 7 +-- src/client/views/collections/TabDocView.tsx | 9 ++++ src/client/views/collections/TreeView.tsx | 54 ++++++++++++----------- src/client/views/nodes/DocumentView.tsx | 3 +- src/client/views/nodes/trails/PresBox.tsx | 13 +++--- src/client/views/nodes/trails/PresElementBox.scss | 2 +- src/client/views/nodes/trails/PresElementBox.tsx | 4 +- 7 files changed, 54 insertions(+), 38 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 69ea21541..49e53a214 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -284,6 +284,7 @@ export class DocumentOptions { treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. + treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically // Action Button buttonMenu?: boolean; // whether a action button should be displayed @@ -365,7 +366,7 @@ export namespace Docs { [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, options: { - _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, + _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, links: "@links(self)" } }], @@ -460,8 +461,8 @@ export namespace Docs { options: { links: "@links(self)" } }], [DocumentType.SLIDER, { - layout: { view: SliderBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } + layout: { view: SliderBox, dataField: defaultDataKey, }, + options: { links: "@links(self)", treeViewGrowsHorizontally: true } }], [DocumentType.PRES, { layout: { view: PresBox, dataField: defaultDataKey }, diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index bd3e810c9..136486f47 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -33,6 +33,7 @@ import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormV import { CollectionView, CollectionViewType } from './CollectionView'; import "./TabDocView.scss"; import React = require("react"); +import { List } from '../../../fields/List'; const _global = (window /* browser */ || global /* node */) as any; interface TabDocViewProps { @@ -209,9 +210,17 @@ export class TabDocView extends React.Component { const pinDoc = Doc.MakeAlias(doc); pinDoc.presentationTargetDoc = doc; pinDoc.title = doc.title + " - Slide"; + pinDoc.data = new List(); // the children of the alias' layout are the presentation slide children. the alias' data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data pinDoc.presMovement = PresMovement.Zoom; pinDoc.groupWithUp = false; pinDoc.context = curPres; + // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time + pinDoc.treeViewRenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area + pinDoc.treeViewHeaderWidth = "100%"; // forces the header to grow to be the same size as its largest sibling. + pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field + pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view + pinDoc.treeViewGrowsHorizontally = true;// the document can expand horizontally when displayed as a tree view header + pinDoc.treeViewHideHeader = true; // this will force the document to render itself as the tree view header const presArray: Doc[] = PresBox.Instance?.sortArray(); const size: number = PresBox.Instance?._selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index c2d0983da..a35304787 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -112,7 +112,7 @@ export class TreeView extends React.Component { @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); } @computed get dataDoc() { return this.doc[DataSym]; } @computed get layoutDoc() { return Doc.Layout(this.doc); } - @computed get fieldKey() { return Doc.LayoutFieldKey(this.doc); } + @computed get fieldKey() { return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); } @computed get childDocs() { return this.childDocList(this.fieldKey); } @computed get childLinks() { return this.childDocList("links"); } @computed get childAliases() { return this.childDocList("aliases"); } @@ -156,18 +156,7 @@ export class TreeView extends React.Component { docView.select(false); } } - @action - openLevel = (docView: DocumentView) => { - if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) { - this.treeViewOpen = !this.treeViewOpen; - } else { - // choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias - // this.props.addDocTab(CurrentUserUtils.ActiveDashboard.isShared ? Doc.MakeAlias(this.props.document) : this.props.document, "add:right"); - const bestAlias = DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); - this.props.addDocTab(bestAlias ?? Doc.MakeAlias(this.props.document), "add:right"); - } - } constructor(props: any) { super(props); if (!TreeView._openLevelScript) { @@ -379,8 +368,14 @@ export class TreeView extends React.Component { return rows; } - rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1); - rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; + rtfWidth = () => { + const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + return Math.min(layout[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1); + } + rtfHeight = () => { + const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; + } rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth()); expandPanelHeight = () => { if (this.layoutDoc._fitWidth) return this.docHeight(); @@ -580,13 +575,20 @@ export class TreeView extends React.Component { } onKeyDown = (e: React.KeyboardEvent) => { if (this.doc.treeViewHideHeader || this.props.treeView.outlineMode) { - e.stopPropagation(); - e.preventDefault(); switch (e.key) { - case "Tab": setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); + case "Tab": + e.stopPropagation(); + e.preventDefault(); + setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab"); - case "Backspace": return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc); - case "Enter": return UndoManager.RunInBatch(this.makeTextCollection, "bullet"); + case "Backspace": + e.stopPropagation(); + e.preventDefault(); + return !(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc); + case "Enter": + e.stopPropagation(); + e.preventDefault(); + return UndoManager.RunInBatch(this.makeTextCollection, "bullet"); } } } @@ -691,7 +693,7 @@ export class TreeView extends React.Component { renderBulletHeader = (contents: JSX.Element, editing: boolean) => { return <>
{ renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { - const layout = StrCast(Doc.LayoutField(this.layoutDoc)); - const isExpandable = layout.includes(FormattedTextBox.name) || layout.includes(SliderBox.name); + const isExpandable = this.doc._treeViewGrowsHorizontally; const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth; const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight; return this._dref = r)} @@ -717,6 +718,7 @@ export class TreeView extends React.Component { NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined} + LayoutTemplate={this.props.treeView.props.childLayoutTemplate} isContentActive={isActive} isDocumentActive={isActive} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} @@ -759,10 +761,10 @@ export class TreeView extends React.Component { } // renders the document in the header field instead of a text proxy. - @computed get renderDocumentAsHeader() { + renderDocumentAsHeader = (asText: boolean) => { return <> {this.renderBullet} - {this.renderEmbeddedDocument(true, this.props.isContentActive)} + {this.renderEmbeddedDocument(asText, this.props.isContentActive)} ; } @@ -794,9 +796,9 @@ export class TreeView extends React.Component { //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document onKeyDown={this.onKeyDown}>
  • - {hideTitle && this.doc.type !== DocumentType.RTF ? + {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader" this.renderEmbeddedDocument(false, returnFalse) : - this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader : this.renderTitleAsHeader, this._editTitle)} + this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
  • ; } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1eaff3c1c..bcf00e88d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1308,6 +1308,7 @@ export class DocumentView extends React.Component { TraceMobx(); const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined); const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined); + const isPresTreeElement: boolean = this.props.treeViewDoc?.type === DocumentType.PRES; const isButton: boolean = this.props.Document.type === DocumentType.FONTICON || this.props.Document._viewType === CollectionViewType.Linear; return (
    {!this.props.Document || !this.props.PanelWidth() ? (null) : ( @@ -1316,7 +1317,7 @@ export class DocumentView extends React.Component { transition: this.props.dataTransition, position: this.props.Document.isInkMask ? "absolute" : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, - width: isButton ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, + width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, height: (!this.props.ignoreAutoHeight && this.layoutDoc.autoHeight && this.layoutDoc.type === DocumentType.RTF) || isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), }}> diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 2e312ee51..c9c74eca4 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -122,7 +122,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", type: DocumentType.PRESELEMENT, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" + title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" })); // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent @@ -308,7 +308,7 @@ export class PresBox extends ViewBoxBaseComponent() { } if (!group) this._selectedArray.clear(); this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array - if (this.layoutDoc._viewType === "stacking" && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list + if ([CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.layoutDoc._viewType as any) && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list this.onHideDocument(); //Handles hide after/before } }); @@ -620,9 +620,9 @@ export class PresBox extends ViewBoxBaseComponent() { //@ts-ignore const viewType = e.target.selectedOptions[0].value as CollectionViewType; // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here - viewType === CollectionViewType.Stacking && (this.rootDoc._pivotField = undefined); + [CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined); this.rootDoc._viewType = viewType; - if (viewType === CollectionViewType.Stacking) this.layoutDoc._gridGap = 0; + if ([CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType)) this.layoutDoc._gridGap = 0; }); /** @@ -696,7 +696,7 @@ export class PresBox extends ViewBoxBaseComponent() { }); return true; } - childLayoutTemplate = () => this.rootDoc._viewType !== CollectionViewType.Stacking ? undefined : this.presElement; + childLayoutTemplate = () => ![CollectionViewType.Stacking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any) ? undefined : this.presElement; removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; @@ -2262,6 +2262,7 @@ export class PresBox extends ViewBoxBaseComponent() { onChange={this.viewChanged} value={mode}> + }
    @@ -2459,7 +2460,7 @@ export class PresBox extends ViewBoxBaseComponent() { } ScriptingGlobals.add(function lookupPresBoxField(container: Doc, field: string, data: Doc) { if (field === 'indexInPres') return DocListCast(container[StrCast(container.presentationFieldKey)]).indexOf(data); - if (field === 'presCollapsedHeight') return container._viewType === CollectionViewType.Stacking ? 35 : 31; + if (field === 'presCollapsedHeight') return [CollectionViewType.Tree || CollectionViewType.Stacking].includes(container._viewType as any) ? 35 : 31; if (field === 'presStatus') return container.presStatus; if (field === '_itemIndex') return container._itemIndex; if (field === 'presBox') return container; diff --git a/src/client/views/nodes/trails/PresElementBox.scss b/src/client/views/nodes/trails/PresElementBox.scss index 1ad4b820e..a178be910 100644 --- a/src/client/views/nodes/trails/PresElementBox.scss +++ b/src/client/views/nodes/trails/PresElementBox.scss @@ -42,7 +42,7 @@ $slide-active: #5B9FDD; background-color: #d5dce2; border-radius: 5px; height: calc(100% - 7px); - width: calc(100% - 15px); + width: 100%; display: grid; grid-template-rows: 16px 10px auto; grid-template-columns: max-content max-content max-content max-content auto; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index a4ec559f5..5c16d743a 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -23,6 +23,7 @@ import { PresBox } from "./PresBox"; import "./PresElementBox.scss"; import { PresMovement } from "./PresEnums"; import React = require("react"); +import { CollectionViewType } from "../../collections/CollectionView"; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. @@ -87,6 +88,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { isContentActive={this.props.isContentActive} addDocTab={returnFalse} pinToPres={returnFalse} + fitContentsToDoc={returnTrue} PanelWidth={this.embedWidth} PanelHeight={this.embedHeight} ScreenToLocalTransform={Transform.Identity} @@ -321,7 +323,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined }}> -
    +
    Date: Mon, 25 Apr 2022 20:20:48 -0400 Subject: changed freeformview's with engines to not allow children to be edited using setHeight prop since engine layouts are temporary and shouldn't edit the doc. fixed tags to work with pivot view. cleaned up pile views to be less hacky.. --- src/client/documents/Documents.ts | 27 +++++++++++++--------- src/client/views/MainView.tsx | 1 + src/client/views/SidebarAnnos.tsx | 2 +- .../views/collections/CollectionPileView.tsx | 4 ++-- .../views/collections/CollectionStackingView.tsx | 6 ++--- src/client/views/collections/CollectionSubView.tsx | 2 +- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 1 + .../CollectionFreeFormLayoutEngines.tsx | 26 +++++++++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +++- .../views/nodes/CollectionFreeFormDocumentView.tsx | 5 +--- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 17 +++++++------- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/LinkDocPreview.tsx | 1 + src/client/views/nodes/WebBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 2 +- src/fields/Doc.ts | 2 +- 19 files changed, 62 insertions(+), 48 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 49e53a214..a19530c92 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -14,7 +14,7 @@ import { Cast, NumCast, StrCast } from "../../fields/Types"; import { AudioField, ImageField, MapField, PdfField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; -import { OmitKeys, Utils } from "../../Utils"; +import { OmitKeys, Utils, aggregateBounds } from "../../Utils"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { DocServer } from "../DocServer"; import { Networking } from "../Network"; @@ -1352,26 +1352,31 @@ export namespace DocUtils { if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); } - export function pileup(docList: Doc[], x?: number, y?: number, create: boolean = true) { + export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) { let w = 0, h = 0; runInAction(() => { docList.forEach(d => { DocUtils.iconify(d); - w = Math.max(d[WidthSym](), w); - h = Math.max(d[HeightSym](), h); + w = Math.max(NumCast(d._width), w); + h = Math.max(NumCast(d._height), h); }); - h = Math.max(h, w * 4 / 3); // converting to an icon does not update the height right away. so this is a fallback hack to try to do something reasonable docList.forEach((d, i) => { - d.x = Math.cos(Math.PI * 2 * i / docList.length) * 10 - w / 2; - d.y = Math.sin(Math.PI * 2 * i / docList.length) * 10 - h / 2; + d.x = Math.cos(Math.PI * 2 * i / docList.length) * size; + d.y = Math.sin(Math.PI * 2 * i / docList.length) * size; + d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + }); + const aggBounds = aggregateBounds(docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })), 0, 0); + docList.forEach((d, i) => { + d.x = NumCast(d.x) - ((aggBounds.r + aggBounds.x) / 2); + d.y = NumCast(d.y) - ((aggBounds.b + aggBounds.y) / 2); d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection }); }); if (create) { - const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - 55, y: (y || 0) - 55, _width: 110, _height: 100, }); - newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - 55; - newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - 55; - newCollection._width = newCollection._height = 110; + const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, }); + newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size; + newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size; + newCollection._width = newCollection._height = size * 2; newCollection._jitterRotation = 10; return newCollection; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index a67cb3014..68b4710ed 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -297,6 +297,7 @@ export class MainView extends React.Component { searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} + suppressSetHeight={true} renderDepth={-1} />; } diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index fae385660..43a02d029 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -88,7 +88,7 @@ export class SidebarAnnos extends React.Component { removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey); docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])]; showTitle = () => "title"; - setHeightCallback = (height: number) => this.props.setHeight(height + this.filtersHeight()); + setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight()); render() { const renderTag = (tag: string) => { const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 8832b0f4a..4489601db 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -74,13 +74,13 @@ export class CollectionPileView extends CollectionSubView() { this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize); this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize); - DocUtils.pileup(this.childDocs, undefined, undefined, false); + DocUtils.pileup(this.childDocs, undefined, undefined, NumCast(this.layoutDoc._width) / 2, false); this.layoutDoc._panX = 0; this.layoutDoc._panY = -10; this.props.Document._pileLayoutEngine = 'pass'; } else { const defaultSize = 25; - !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500); + !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 250); !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); if (this.layoutEngine() === 'pass') { this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 8634ea139..509005b45 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -141,7 +141,7 @@ export class CollectionStackingView extends CollectionSubView this.layoutDoc._columnHeaders = new List() ); this._autoHeightDisposer = reaction(() => this.layoutDoc._autoHeight, - autoHeight => autoHeight && this.props.setHeight(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), + autoHeight => autoHeight && this.props.setHeight?.(Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), this.headerMargin + (this.isStackingView ? Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))) : this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0))))); @@ -408,7 +408,7 @@ export class CollectionStackingView extends CollectionSubView Number(getComputedStyle(r).height.replace("px", ""))))); if (!LightboxView.IsLightboxDocView(this.props.docViewPath())) { - this.props.setHeight(height); + this.props.setHeight?.(height); } } })); @@ -458,7 +458,7 @@ export class CollectionStackingView extends CollectionSubView { if (this.layoutDoc._autoHeight && ref && this.refList.length && !SnappingManager.GetIsDragging()) { const height = this.refList.reduce((p, r) => p + Number(getComputedStyle(r).height.replace("px", "")), 0); - this.props.setHeight(this.headerMargin + height); + this.props.setHeight?.(this.headerMargin + height); } })); this.observer.observe(ref); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5bdd0c0ac..30e0adf24 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -453,7 +453,7 @@ export function CollectionSubView(moreProps?: X) { if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); + addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!,); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 76da96174..41970eb96 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -88,7 +88,7 @@ export class CollectionTreeView extends CollectionSubView p + Number(getComputedStyle(r).height.replace("px", "")), this.marginBot()); this.layoutDoc._autoHeightMargins = bodyHeight; - this.props.setHeight(bodyHeight + titleHeight); + this.props.setHeight?.(bodyHeight + titleHeight); } } unobserveHeight = (ref: any) => { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 6213850d8..b9710b3f5 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -30,6 +30,7 @@ import { CollectionTreeView } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); +import { KeyValueBox } from '../nodes/KeyValueBox'; export interface TreeViewProps { treeView: CollectionTreeView; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 598960af7..16c7df311 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -111,17 +111,17 @@ export function computerStarburstLayout( viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any ) { - const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; + const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel const docMap = new Map(); - const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])]; - const docSize = mustFit ? panelDim[0] * .33 : 75; // assume a icon sized at 75 - const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize]; + const docSize = mustFit ? panelDim[0] * .33 : 75; // assume an icon sized at 75 + const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize]; + const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize]; childPairs.forEach(({ layout, data }, i) => { - const docSize = layout.layoutKey === "layout_icon" ? mustFit ? panelDim[0] * .33 : 75 : 400; // assume a icon sized at 10750 + const docSize = layout.layoutKey === "layout_icon" ? (mustFit ? panelDim[0] * .33 : 75) : 400; // assume a icon sized at 75 const deg = i / childPairs.length * Math.PI * 2; docMap.set(layout[Id], { - x: Math.cos(deg) * (burstRadius[0] / 3) - docSize / 2, - y: Math.sin(deg) * (burstRadius[1] / 3) - docSize * layout[HeightSym]() / layout[WidthSym]() / 2, + x: Math.cos(deg) * burstRadius[0] - docSize / 2, + y: Math.sin(deg) * burstRadius[1] - docSize * layout[HeightSym]() / layout[WidthSym]() / 2, width: docSize,//layout[WidthSym](), height: docSize * layout[HeightSym]() / layout[WidthSym](), zIndex: NumCast(layout.zIndex), @@ -129,7 +129,7 @@ export function computerStarburstLayout( replica: "" }); }); - const divider = { type: "div", color: "transparent", x: -burstRadius[0] / 3, y: 0, width: 15, height: 15, payload: undefined }; + const divider = { type: "div", color: "transparent", x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined }; return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } @@ -267,7 +267,13 @@ export function computePivotLayout( }); const dividers = sortedPivotKeys.map((key, i) => - ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters })); + ({ + type: "div", color: "lightGray", + x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, + y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, + height: maxColHeight, + payload: pivotColumnGroups.get(key)!.filters + })); groupNames.push(...dividers); return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []); } @@ -404,7 +410,7 @@ function normalizeResults( const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); const docEles = Array.from(docMap.entries()).map(ele => ele[1]); const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0); - aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); + aggBounds.r = aggBounds.x + 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; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index eaef1b49c..a14405a0b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1158,6 +1158,7 @@ export class CollectionFreeFormView extends CollectionSubView(); - switch (this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine)) { + switch (this.layoutEngine) { case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; case "pivot": return { newPool, computedElementData: this.doEngineLayout(newPool, computePivotLayout) }; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 3e35039e3..6097425e1 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -17,9 +17,6 @@ import { StyleProp } from "../StyleProvider"; import "./CollectionFreeFormDocumentView.scss"; import { DocumentView, DocumentViewProps } from "./DocumentView"; import React = require("react"); -import { DocumentType } from "../../documents/DocumentTypes"; -import { collectionSchema } from "../../../fields/documentSchemas"; -import { CollectionViewType } from "../collections/CollectionView"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; @@ -174,7 +171,7 @@ export class CollectionFreeFormDocumentView extends DocComponent boolean, select: (ctrl: boolean) => void, scaling?: () => number, - setHeight: (height: number) => void, + setHeight?: (height: number) => void, layoutKey: string, }> { @computed get layout(): string { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 504a3afb6..20eabe6a9 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -114,6 +114,7 @@ export interface DocumentViewSharedProps { fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; + suppressSetHeight?: boolean; thumbShown?: () => boolean; isHovering?: () => boolean; setContentView?: (view: DocComponentView) => any; @@ -842,11 +843,7 @@ export class DocumentViewInternal extends DocComponent this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; - setHeight = (height: number) => { - if (this.props.renderDepth !== -1) { - this.layoutDoc._height = height; - } - } + setHeight = (height: number) => this.layoutDoc._height = height; setContentView = action((view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view); isContentActive = (outsideReaction?: boolean) => { return this.props.isContentActive() === false ? false : ( @@ -890,7 +887,7 @@ export class DocumentViewInternal extends DocComponent { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); } - @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); } + @computed get shouldNotScale() { + return (this.fitWidth && !this.nativeWidth) || + this.props.ContainingCollectionView?.collectionViewType === CollectionViewType.Time || + this.props.treeViewDoc || [CollectionViewType.Docking].includes(this.Document._viewType as any); + } @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : (this.nativeWidth || NumCast(this.layoutDoc.width)); } @computed get effectiveNativeHeight() { return this.shouldNotScale ? 0 : (this.nativeHeight || NumCast(this.layoutDoc.height)); } @computed get nativeScaling() { @@ -1324,7 +1325,7 @@ export class DocumentView extends React.Component { position: this.props.Document.isInkMask ? "absolute" : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, - height: (!this.props.ignoreAutoHeight && this.layoutDoc.autoHeight && this.layoutDoc.type === DocumentType.RTF) || isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : + height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), }}> boolean; isSelected: (outsideReaction?: boolean) => boolean; scaling?: () => number; - setHeight: (height: number) => void; + setHeight?: (height: number) => void; // properties intended to be used from within layout strings (otherwise use the function equivalents that work more efficiently with React) pointerEvents?: string; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index eca2e3cfd..55f6d6059 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -195,6 +195,7 @@ export class LinkDocPreview extends React.Component { ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} renderDepth={-1} + suppressSetHeight={true} PanelWidth={this.width} PanelHeight={this.height} focus={DocUtils.DefaultFocus} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 0fd193977..eeb7b925b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -164,7 +164,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { if (autoHeight) { this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]); - this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); + this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); } }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index dc1c67bea..710270be4 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1524,7 +1524,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (children) { const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + Number(getComputedStyle(child).height.replace("px", "")), margins); const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); - if (scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation + if (this.props.setHeight && scrollHeight && this.props.renderDepth && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation const setScrollHeight = () => this.rootDoc[this.fieldKey + "-scrollHeight"] = scrollHeight; if (this.rootDoc === this.layoutDoc.doc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 9aaa6e90f..92a69c02b 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -123,7 +123,7 @@ export class PDFViewer extends React.Component { autoHeight => { if (autoHeight) { this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]); - this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); + this.props.setHeight?.(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); } }); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 15df673f3..63af8401c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1108,7 +1108,7 @@ export namespace Doc { if (typeof value === "string") { value = value.replace(`,${Utils.noRecursionHack}`, ""); } - const fieldVal = doc[key]; + const fieldVal = key === "#" ? (StrCast(doc.tags).includes(":#" + value + ":") ? StrCast(doc.tags) : undefined) : doc[key]; if (Cast(fieldVal, listSpec("string"), []).length) { const vals = Cast(fieldVal, listSpec("string"), []); const docs = vals.some(v => (v as any) instanceof Doc); -- cgit v1.2.3-70-g09d2 From 267d4d7c3f2a85f0df292dea9667293eee3358c5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Apr 2022 13:46:33 -0400 Subject: fixed links to ink being computed correctly on the inkStroke's links field. fixed interacting with iconified ink. fixed cycling through links on linkDocPreivew to not deselect --- src/client/documents/Documents.ts | 2 +- src/client/views/StyleProvider.tsx | 6 ++++-- src/client/views/nodes/LinkDocPreview.tsx | 2 +- 3 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a19530c92..0bb873667 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -770,7 +770,7 @@ export namespace Docs { I.data = new InkField(points); I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; I["acl-Override"] = "None"; - I.links = "@links(self)"; + I.links = ComputedField.MakeFunction("links(self)"); I[Initializing] = false; return I; } diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a88c745b8..a530ff90a 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -23,6 +23,7 @@ import { FieldViewProps } from './nodes/FieldView'; import { SliderBox } from './nodes/SliderBox'; import "./StyleProvider.scss"; import React = require("react"); +import { InkingStroke } from './InkingStroke'; export enum StyleLayers { Background = "background" @@ -195,9 +196,10 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { setupMoveUpEvents(this, e, returnFalse, emptyFunction, action(() => { this._linkDoc = undefined; this._hrefInd = (this._hrefInd + 1) % (this.props.hrefs?.length || 1); - })); + }), true); } followLink = (e: React.PointerEvent) => { -- cgit v1.2.3-70-g09d2 From 59a22f3a007b201e68ac2f3e1d62e0ca5e66488a Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Apr 2022 12:13:44 -0400 Subject: fixed feedback dragging link button. fixed warnings. added some assymetric linkRelationships. --- src/client/documents/Documents.ts | 3 ++- src/client/util/DocumentManager.ts | 2 +- src/client/util/LinkManager.ts | 2 +- src/client/views/SidebarAnnos.tsx | 2 +- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../views/collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/linking/LinkMenu.tsx | 4 +--- src/client/views/nodes/DocumentLinksButton.scss | 2 ++ src/client/views/nodes/DocumentLinksButton.tsx | 10 +++++----- src/client/views/nodes/DocumentView.tsx | 4 ++-- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 4 ++-- src/client/views/nodes/formattedText/RichTextRules.ts | 2 +- src/client/views/search/SearchBox.tsx | 2 +- src/server/ApiManagers/UploadManager.ts | 4 ++-- 15 files changed, 24 insertions(+), 23 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0bb873667..087d32a60 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1094,10 +1094,11 @@ export namespace DocUtils { export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) { broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1); return DocUtils.ActiveRecordings.map(audio => - DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording link", "recording timeline")); + DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline")); } export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { + if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? "Commentary:Comments On" : "link"; const sv = DocumentManager.Instance.getDocumentView(source.doc); if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return; if (target.doc === Doc.UserDoc()) return undefined; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 412d5a169..b6c28d2fe 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -167,7 +167,7 @@ export class DocumentManager { ): Promise => { originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; - var docView = getFirstDocView(targetDoc, originatingDoc); + const docView = getFirstDocView(targetDoc, originatingDoc); const annotatedDoc = Cast(targetDoc.annotationOn, Doc, null); const resolvedTarget = targetDoc.type === DocumentType.MARKER ? annotatedDoc ?? targetDoc : targetDoc; // if target is a marker, then focus toggling should apply to the document it's on since the marker itself doesn't have a hidden field var wasHidden = resolvedTarget.hidden; diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 7ba7a1d4c..9445533dc 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -152,7 +152,7 @@ export class LinkManager { this.relatedLinker(anchor).forEach(link => { if (link.linkRelationship && link.linkRelationship !== "-ungrouped-") { const relation = StrCast(link.linkRelationship); - const anchorRelation = relation.indexOf(":") ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation; + const anchorRelation = relation.indexOf(":") !== -1 ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation; const group = anchorGroups.get(anchorRelation); anchorGroups.set(anchorRelation, group ? [...group, link] : [link]); } else { diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 43a02d029..04c0565ea 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -61,7 +61,7 @@ export class SidebarAnnos extends React.Component { FormattedTextBox.SelectOnLoad = target[Id]; FormattedTextBox.DontSelectInitialText = true; this.allMetadata.map(tag => target[tag] = tag); - DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline markup"); + DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline comment:comment on"); this.addDocument(target); this._stackRef.current?.focusDocument(target); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f0b3d70a0..3f72052ae 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -278,7 +278,7 @@ export class CollectionFreeFormView extends CollectionSubView
    : -
    +
    {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? //if the origin node is not this node -
    DocumentLinksButton.StartLink && DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)}> @@ -256,7 +255,8 @@ export class DocumentLinksButton extends React.Component +
    : diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9d738180b..8570f6fff 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -663,7 +663,7 @@ export class DocumentViewInternal extends DocComponent d.anchor1 === this.props.Document); if (!portalLink) { const portal = Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _fitWidth: true, title: StrCast(this.props.Document.title) + " [Portal]" }); - DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to"); + DocUtils.MakeLink({ doc: this.props.Document }, { doc: portal }, "portal to:portal from"); } this.Document.followLinkLocation = "inPlace"; this.Document.followLinkZoom = true; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index aadad5ffa..5982d4d66 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -152,7 +152,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { this.Document._curPage = Math.max(1, (NumCast(this.Document._curPage) || 1) - 1); return true; - }; + } public forwardPage = () => { this.Document._curPage = Math.min(NumCast(this.dataDoc[this.props.fieldKey + "-numPages"]), (NumCast(this.Document._curPage) || 1) + 1); return true; - }; + } public gotoPage = (p: number) => this.Document._curPage = p; @undoBatch diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 00c03875b..427e05edb 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -275,7 +275,7 @@ export class RichTextRules { this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); } const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid.replace(/^:/, ""), _width: 500, _height: 500, }, docid); - DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to", undefined); + DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to:portal from", undefined); const fstate = this.TextBox.EditorView?.state; if (fstate && selection) { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 5fe2a5ab1..9257cb75e 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -112,7 +112,7 @@ export class SearchBox extends ViewBoxBaseComponent() { if (this.props.linkFrom) { const linkFrom = this.props.linkFrom(); if (linkFrom) { - DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }, "Link"); + DocUtils.MakeLink({ doc: linkFrom }, { doc: linkTo }); } } }); diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index 5e0cd67cc..83a2bc79b 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -14,7 +14,7 @@ import { normalize } from "path"; import RouteSubscriber from "../RouteSubscriber"; const imageDataUri = require('image-data-uri'); import { SolrManager } from "./SearchManager"; -let fs = require('fs'); +const fs = require('fs'); export enum Directory { parsed_files = "parsed_files", @@ -265,7 +265,7 @@ export default class UploadManager extends ApiManager { } if (deleteFiles) { const path = serverPathToFile(Directory.images, ""); - let regex = new RegExp(`${deleteFiles}.*`); + const regex = new RegExp(`${deleteFiles}.*`); fs.readdirSync(path).filter((f: any) => regex.test(f)).map((f: any) => fs.unlinkSync(path + f)); } return imageDataUri.outputFile(uri, serverPathToFile(Directory.images, InjectSize(filename, origSuffix))).then((savedName: string) => { -- cgit v1.2.3-70-g09d2 From ce94cdd070035ef6374eeb471ecf6e4542b4ba20 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Apr 2022 13:53:09 -0400 Subject: fixed rendering of slides in filesystem view to not render the UI, just the doc name --- src/client/documents/Documents.ts | 1 + src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 087d32a60..741868a99 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -282,6 +282,7 @@ export class DocumentOptions { strokeWidth?: number; freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view + treeViewHideHeaderIfTemplate?: boolean; // whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox) treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 98d6049f0..8e45ec3b3 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -221,7 +221,7 @@ export class TabDocView extends React.Component { pinDoc.treeViewFieldKey = "data"; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field pinDoc.treeViewExpandedView = "data";// in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view pinDoc.treeViewGrowsHorizontally = true;// the document expands horizontally when displayed as a tree view header - pinDoc.treeViewHideHeader = true; // this will force the document to render itself as the tree view header + pinDoc.treeViewHideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header const presArray: Doc[] = PresBox.Instance?.sortArray(); const size: number = PresBox.Instance?._selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 3a49297f8..647476784 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -596,7 +596,7 @@ export class TreeView extends React.Component { return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView } onKeyDown = (e: React.KeyboardEvent) => { - if (this.doc.treeViewHideHeader || this.props.treeView.outlineMode) { + if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) { switch (e.key) { case "Tab": e.stopPropagation(); @@ -810,7 +810,7 @@ export class TreeView extends React.Component { render() { TraceMobx(); - const hideTitle = this.doc.treeViewHideHeader || this.props.treeView.outlineMode; + const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode; return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles
    Date: Thu, 28 Apr 2022 16:31:57 -0400 Subject: fixed timestamps and added some scaling --- src/client/documents/Documents.ts | 1 - src/client/views/GestureOverlay.tsx | 22 +++---------- src/client/views/InkTranscription.tsx | 38 +++++++++++++--------- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 ++++----- src/client/views/nodes/button/FontIconBox.tsx | 10 +++--- src/fields/InkField.ts | 3 +- src/pen-gestures/GestureUtils.ts | 3 +- 7 files changed, 41 insertions(+), 51 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 884cc781d..aee9ef49d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1161,7 +1161,6 @@ export namespace DocUtils { created = Docs.Create.AudioDocument((field).url.href, resolved); layout = AudioBox.LayoutString; } else if (field instanceof InkField) { - console.log("Documents " + field.inkData) created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 5f0311d38..fc0a214a6 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -42,7 +42,6 @@ export class GestureOverlay extends Touchable { @observable private _menuY: number = -300; @observable private _pointerY?: number; @observable private _points: { X: number, Y: number }[] = []; - @observable private _timeStamps: number[] = []; @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; @@ -61,7 +60,6 @@ export class GestureOverlay extends Touchable { private pointerIdentifier?: number; private _hands: Map = new Map(); private _holdTimer: NodeJS.Timeout | undefined; - private _strokeStartTime: number = 0; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @@ -299,7 +297,7 @@ export class GestureOverlay extends Touchable { pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v); } else { - console.log("not hand"); + // console.log("not hand"); } this.pointerIdentifier = pointer?.identifier; @@ -425,7 +423,6 @@ export class GestureOverlay extends Touchable { this._strokes = []; this._points = []; - this._timeStamps = []; this._possibilities = []; document.removeEventListener("touchend", this.handleHandUp); } @@ -509,8 +506,6 @@ export class GestureOverlay extends Touchable { CurrentUserUtils.SelectedTool = InkTool.Write; } this._points.push({ X: e.clientX, Y: e.clientY }); - this._timeStamps.push(0); - this._strokeStartTime = new Date().getTime(); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } @@ -519,7 +514,6 @@ export class GestureOverlay extends Touchable { @action onPointerMove = (e: PointerEvent) => { this._points.push({ X: e.clientX, Y: e.clientY }); - this._timeStamps.push(new Date().getTime() - this._strokeStartTime); if (this._points.length > 1) { const B = this.svgBounds; @@ -566,7 +560,6 @@ export class GestureOverlay extends Touchable { const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); //push first points to so interactionUtil knows pointer is up this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); - this._timeStamps.push(this._timeStamps[0]); const initialPoint = this._points[0.]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height); @@ -578,7 +571,6 @@ export class GestureOverlay extends Touchable { case ToolglassTools.InkToText: this._strokes.push(new Array(...this._points)); this._points = []; - this._timeStamps = []; CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => { const wordResults = results.filter((r: any) => r.category === "line"); const possibilities: string[] = []; @@ -602,7 +594,6 @@ export class GestureOverlay extends Touchable { case ToolglassTools.IgnoreGesture: this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - this._timeStamps = []; break; } } @@ -611,7 +602,6 @@ export class GestureOverlay extends Touchable { this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - this._timeStamps = []; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { this.InkShape = ""; Doc.UserDoc().activeInkTool = InkTool.None; @@ -638,7 +628,7 @@ export class GestureOverlay extends Touchable { if (!actionPerformed) { const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); newPoints.pop(); - console.log("getting to bezier math"); + // console.log("getting to bezier math"); const controlPoints: { X: number, Y: number }[] = []; const bezierCurves = fitCurve(newPoints, 10); @@ -649,21 +639,19 @@ export class GestureOverlay extends Touchable { controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); - } + } const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; - this._points = controlPoints; + this._points = controlPoints; this.dispatchGesture(GestureUtils.Gestures.Stroke); // TODO: nda - check inks to group here checkInksToGroup(); } this._points = []; - this._timeStamps = []; } } else { this._points = []; - this._timeStamps = []; } CollectionFreeFormViewChrome.Instance?.primCreated(); } @@ -712,7 +700,6 @@ export class GestureOverlay extends Touchable { } } this._points = []; - this._timeStamps = []; switch (shape) { //must push an extra point in the end so InteractionUtils knows pointer is up. //must be (points[0].X,points[0]-1) @@ -836,7 +823,6 @@ export class GestureOverlay extends Touchable { gesture: gesture as any, bounds: this.getBounds(stroke ?? this._points), text: data, - times: this._timeStamps } } ) diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index ea5d5a1ec..a96cda743 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -1,12 +1,13 @@ -import { timeStamp } from 'console'; import * as iink from 'iink-js'; import { action, observable } from 'mobx'; import * as React from 'react'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast } from '../../fields/Doc'; import { InkData, InkField } from "../../fields/InkField"; -import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { Cast, NumCast } from '../../fields/Types'; import { DocumentType } from "../documents/DocumentTypes"; import './InkTranscription.scss'; +import { Utils } from '../../Utils'; +import { timesSeries } from 'async'; export class InkTranscription extends React.Component { static Instance: InkTranscription; @@ -16,6 +17,7 @@ export class InkTranscription extends React.Component { @observable _textRegister: any; @observable _textRef: any; private currGroup?: Doc; + private containingLayout?: Doc; constructor(props: Readonly<{}>) { super(props); @@ -98,24 +100,26 @@ export class InkTranscription extends React.Component { return this._textRef = r; } - transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => { + transcribeInk = (groupDoc: Doc | undefined, containingLayout: Doc, inkDocs: Doc[], math: boolean) => { if (!groupDoc) return; const validInks = inkDocs.filter(s => s.type === DocumentType.INK); const strokes: InkData[] = []; + const times: number[] = []; validInks.filter(i => Cast(i.data, InkField)).forEach(i => { - // TODO: interpolate missing times stamps const d = Cast(i.data, InkField, null); const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); - strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top, time: pd.time }))); + strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top }))); + times.push(NumCast(i.creationDate, 1)); }); this.currGroup = groupDoc; + this.containingLayout = containingLayout; - const pointerData = { "events": strokes.map(stroke => this.inkJSON(stroke)) }; - // console.log(JSON.stringify(pointerData)); + const pointerData = { "events": strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) }; + console.log(JSON.stringify(pointerData)); const processGestures = false; if (math) { @@ -126,13 +130,17 @@ export class InkTranscription extends React.Component { } } - inkJSON = (stroke: InkData) => { + inkJSON = (stroke: InkData, time: number) => { + const scale = NumCast(this.containingLayout?._viewScale, 1); + const panX = NumCast(this.containingLayout?._panX); + const panY = NumCast(this.containingLayout?._panY); + return { "pointerType": "PEN", "pointerId": 1, - "x": stroke.map(point => point.X), - "y": stroke.map(point => point.Y), - "t": stroke.map(point => point.time), + "x": stroke.map(point => (point.X - panX) * scale), + "y": stroke.map(point => (point.Y - panY) * scale), + "t": new Array(stroke.length).fill(time), "p": new Array(stroke.length).fill(1.0) }; } @@ -142,7 +150,7 @@ export class InkTranscription extends React.Component { if (exports) { if (exports['application/x-latex']) { const latex = exports['application/x-latex']; - console.log(latex); + // console.log(latex); if (this.currGroup) { this.currGroup.text = latex; @@ -153,11 +161,11 @@ export class InkTranscription extends React.Component { } else if (exports['text/plain']) { const text = exports['text/plain']; - console.log(text); + // console.log(text); if (this.currGroup) { this.currGroup.text = text; - this.currGroup.title = text; + this.currGroup.title = text.split("\n")[0]; } ref.editor.clear(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 023953658..3c5cdb444 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -517,9 +517,7 @@ export class CollectionFreeFormView extends CollectionSubView { return { X: pt.X, Y: pt.Y, time: times[i] } }); - const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), strokes, + const inkDoc = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), points, { title: "ink stroke", x: B.x - ActiveInkWidth() / 2, y: B.y - ActiveInkWidth() / 2, _width: B.width + ActiveInkWidth(), _height: B.height + ActiveInkWidth() }); if (CurrentUserUtils.SelectedTool === InkTool.Write) { this.unprocessedDocs.push(inkDoc); @@ -1578,13 +1576,14 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.props.Document._isGroup && this.props.Document.text) { - const text = StrCast(this.props.Document.text); + if (!math) { + const text = StrCast(this.props.Document.text); - const lines = text.split("\n"); - const width = 30 + 7 * Math.max(...lines.map(line => line.length)); - const height = 30 + 15 * lines.length; + const lines = text.split("\n"); + const height = 30 + 15 * lines.length; - this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: text, x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: width, _height: height })); + this.props.ContainingCollectionView?.addDocument(Docs.Create.TextDocument(text, { title: lines[0], x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width) + 20, y: NumCast(this.layoutDoc.y), _width: 200, _height: height })); + } } } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index fc358f106..aca393c1a 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -262,7 +262,7 @@ export class FontIconBox extends DocComponent() { "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]; } } catch (e) { - console.log(e); + // console.log(e); } // Get items to place into the list @@ -699,13 +699,13 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) { export function checkInksToGroup() { - console.log("getting here to inks group"); + // console.log("getting here to inks group"); if (CurrentUserUtils.SelectedTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => { - console.log(inkDoc.x, inkDoc.y); + // console.log(inkDoc.x, inkDoc.y); }); }); } @@ -763,7 +763,7 @@ export function createInkGroup(inksToGroup?: Doc[]) { // TODO: nda - will probably need to go through and only remove the unprocessed selected docs ffView.unprocessedDocs = []; - InkTranscription.Instance.transcribeInk(newCollection, selected, false); + InkTranscription.Instance.transcribeInk(newCollection, ffView.layoutDoc, selected, false); }); } CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); @@ -845,7 +845,7 @@ ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boole if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { Doc.UserDoc().activeInkTool = InkTool.None; } else if (tool == "write") { - console.log("write mode selected - create groupDoc here!", tool) + // console.log("write mode selected - create groupDoc here!", tool) Doc.UserDoc().activeInkTool = tool; GestureOverlay.Instance.InkShape = ""; } else { diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 6d24c6cbc..31fa6dfac 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -20,7 +20,6 @@ export enum InkTool { export interface PointData { X: number; Y: number; - time?: number; } export type Segment = Array; @@ -85,7 +84,7 @@ export class InkField extends ObjectField { } [ToScriptString]() { - return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}, time: ${i.time || 0}} `) + "])"; + return "new InkField([" + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}`) + "])"; } [ToString]() { return "InkField"; diff --git a/src/pen-gestures/GestureUtils.ts b/src/pen-gestures/GestureUtils.ts index cf59cb3c6..65f2bf80c 100644 --- a/src/pen-gestures/GestureUtils.ts +++ b/src/pen-gestures/GestureUtils.ts @@ -8,8 +8,7 @@ export namespace GestureUtils { readonly gesture: Gestures, readonly points: PointData[], readonly bounds: Rect, - readonly text?: any, - readonly times?: number[] + readonly text?: any ) { } } -- cgit v1.2.3-70-g09d2 From 6c9999efe775c75c217ed19c493dc175c7a556bc Mon Sep 17 00:00:00 2001 From: mehekj Date: Thu, 28 Apr 2022 17:05:19 -0400 Subject: updated timestamps to creationdate --- src/client/documents/Documents.ts | 1 + src/client/views/InkTranscription.tsx | 11 ++++++++--- 2 files changed, 9 insertions(+), 3 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index aee9ef49d..6936e4628 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -763,6 +763,7 @@ export namespace Docs { I.author = Doc.CurrentUserEmail; I.rotation = 0; I.data = new InkField(points); + I.creationDate = new DateField; I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; I["acl-Override"] = "None"; I.links = "@links(self)"; diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index a96cda743..df3a1acb3 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -3,7 +3,7 @@ import { action, observable } from 'mobx'; import * as React from 'react'; import { Doc, DocListCast } from '../../fields/Doc'; import { InkData, InkField } from "../../fields/InkField"; -import { Cast, NumCast } from '../../fields/Types'; +import { Cast, DateCast, NumCast } from '../../fields/Types'; import { DocumentType } from "../documents/DocumentTypes"; import './InkTranscription.scss'; import { Utils } from '../../Utils'; @@ -112,14 +112,15 @@ export class InkTranscription extends React.Component { const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top }))); - times.push(NumCast(i.creationDate, 1)); + times.push(DateCast(i.creationDate).getDate().getTime()); }); this.currGroup = groupDoc; this.containingLayout = containingLayout; const pointerData = { "events": strokes.map((stroke, i) => this.inkJSON(stroke, times[i])) }; - console.log(JSON.stringify(pointerData)); + // console.log(JSON.stringify(pointerData)); + // console.log(pointerData); const processGestures = false; if (math) { @@ -163,6 +164,10 @@ export class InkTranscription extends React.Component { const text = exports['text/plain']; // console.log(text); + if (exports['application/vnd.myscript.jiix']) { + console.log(JSON.parse(exports['application/vnd.myscript.jiix'])); + } + if (this.currGroup) { this.currGroup.text = text; this.currGroup.title = text.split("\n")[0]; -- cgit v1.2.3-70-g09d2 From 32a7dfc6eae2def8d1b792edcb550316948f6ab0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Apr 2022 16:22:46 -0400 Subject: more cleanup --- src/client/documents/Documents.ts | 2 + src/client/util/CurrentUserUtils.ts | 80 +++++++++++++------------------------ 2 files changed, 30 insertions(+), 52 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 741868a99..dd3e13cfd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -156,6 +156,8 @@ export class DocumentOptions { _timelineLabel?: boolean; // whether the document exists on a timeline "_carousel-caption-xMargin"?: number; "_carousel-caption-yMargin"?: number; + "icon-nativeWidth"?: number; + "icon-nativeHeight"?: number; x?: number; y?: number; z?: number; // whether document is in overlay (1) or not (0 or undefined) diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7948aa70b..57313cecc 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -290,65 +290,41 @@ export class CurrentUserUtils { static setupDefaultIconTemplates(doc: Doc) { const templateIconsDoc = Cast(doc["template-icons"], Doc, null); - const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc, finalize: (icon: Doc) => Doc = (doc: Doc) => doc) => { - const iconFieldName = type ? "icon_" + type : "icon"; - if (templateIconsDoc?.[iconFieldName] === undefined) { - const template = finalize(MakeTemplate(iconTemplate(), true, iconFieldName, templateField)); + const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => { + const iconFieldName = "icon" + (type ? "_" + type : ""); + if (!templateIconsDoc?.[iconFieldName]) { + const template = MakeTemplate(iconTemplate(), true, iconFieldName, templateField); if (templateIconsDoc) { templateIconsDoc[iconFieldName] = new PrefetchProxy(template); Doc.AddDocToList(templateIconsDoc, "data", template); + } else { + return template; } - return template; } - return templateIconsDoc[iconFieldName] as Doc; - }; - const finalizeIconTemplate = (icon: Doc) => { - const iconProto = Doc.GetProto(icon); - iconProto["icon-nativeWidth"] = iconProto.width; - iconProto["icon-nativeHeight"] = iconProto.height; - return icon; }; - const iconList = [ - makeIconTemplate(undefined, "title", () => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _backgroundColor: "dimgray", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.AUDIO, "title", () => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _backgroundColor: "lightgreen", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.RTF, "text", () => Docs.Create.LabelDocument({ - _showTitle: "creationDate", textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", - _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.PDF, "title", () => Docs.Create.LabelDocument({ - textTransform: "unset", letterSpacing: "unset", _backgroundColor: "pink", _singleLine: false, _minFontSize: 10, _maxFontSize: 24, borderRounding: "5px", - _width: 75, _height: 125, _xPadding: 10, _yPadding: 10, - onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ - _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, - onDoubleClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.IMG, "data", () => Docs.Create.ImageDocument("", { - _width: 150, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - })), - makeIconTemplate(DocumentType.COL, "icon", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { - _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - }), finalizeIconTemplate), - makeIconTemplate(DocumentType.VID, "icon", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { - _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - }), finalizeIconTemplate), - // makeIconTemplate(DocumentType.PDF, "data", () => Docs.Create.ImageDocument("http://www.cs.brown.edu/~bcz/noImage.png", { - // _width: 360 / 4, _height: 270 / 4, _showTitle: "title", onClick: ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }), system: true - // }), setIconProtoFields) - ]; + const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); + const labelBox = (extra: object) => Docs.Create.LabelDocument({ + textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", + _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, system: true, onClick: deiconifyScript(), ...extra, + }); + const imageBox = (url: string, extra: object) => Docs.Create.ImageDocument(url, { + "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, + _showTitle: "title", system: true, onClick: deiconifyScript(), ...extra + }); + const newIconsList = [ + makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), + makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), + makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), + makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), + makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), + makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })), + // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {}), finalizeImageTemplate) + ].filter(d => d).map(d => d!); if (!templateIconsDoc) { - doc["template-icons"] = Docs.Create.TreeDocument(iconList, { title: "icon templates", _height: 75, system: true }); - iconList.map(ntype => (doc["template-icons"] as Doc)[StrCast(ntype.title)] = new PrefetchProxy(ntype)); + doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true }); + newIconsList.map(d => (doc["template-icons"] as Doc)[StrCast(d.title)] = new PrefetchProxy(d)); } } -- cgit v1.2.3-70-g09d2 From b6448779f56b3bcad108e8a2c8e75ac405524a3e Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 11 May 2022 17:08:17 -0400 Subject: fixed crop of pdfs when zoomed. removed some unused pdf code. removed unused LOD code. --- src/client/documents/Documents.ts | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 3 +- src/client/views/nodes/PDFBox.tsx | 7 +- src/client/views/pdf/PDFViewer.scss | 50 ++++--- src/client/views/pdf/PDFViewer.tsx | 158 ++++++++++----------- src/fields/documentSchemas.ts | 1 - 6 files changed, 113 insertions(+), 107 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index dd3e13cfd..d9c2e0d8b 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -116,7 +116,6 @@ export class DocumentOptions { _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed - _freeformLOD?: boolean; // whether to use LOD to render a freeform document _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5b3a7db41..193750d8a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1558,7 +1558,6 @@ export class CollectionFreeFormView extends CollectionSubView Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); if (!Doc.UserDoc().noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); - optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" }); } !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); const mores = ContextMenu.Instance.findByDescription("More..."); @@ -1739,7 +1738,7 @@ export class CollectionFreeFormView extends CollectionSubView - {this._firstRender || (this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0) ? + {this._firstRender ? this.placeholder : this.marqueeView} {this.props.noOverlay ? (null) : } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 98c17ed23..e6e723ef1 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -65,9 +65,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent; const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; const curPage = NumCast(this.Document._curPage) || 1; - return !this.props.isContentActive() ? (null) : + return !this.props.isContentActive() || this._pdfViewer?.isAnnotating ? (null) :
    [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true} onPointerDown={e => e.stopPropagation()} style={{ display: this.props.isContentActive() ? "flex" : "none" }}>
    e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 390aed1e0..822af6a68 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -1,5 +1,3 @@ - - .pdfViewer-content { height: 100%; width: 100%; @@ -8,33 +6,42 @@ top: 0; left: 0; } -.pdfViewerDash, .pdfViewerDash-interactive { + +.pdfViewerDash, +.pdfViewerDash-interactive { position: absolute; width: 100%; height: 100%; top: 0; - left:0; + left: 0; position: absolute; overflow-y: auto; overflow-x: hidden; transform-origin: top left; - + // .canvasWrapper { // transform: scale(0.75); // transform-origin: top left; // } .textLayer { opacity: unset; - mix-blend-mode: multiply;// bcz: makes text fuzzy! + mix-blend-mode: multiply; // bcz: makes text fuzzy! + span { padding-right: 5px; padding-bottom: 4px; } } - .textLayer ::selection { background: #ACCEF7; } // should match the backgroundColor in createAnnotation() + + .textLayer ::selection { + background: #ACCEF7; + } + + // should match the backgroundColor in createAnnotation() .textLayer .highlight { background-color: yellow; } + .textLayer .highlight.selected { background-color: orange; } @@ -43,32 +50,32 @@ position: relative; border: unset; } + .pdfViewerDash-text-selected { - // position: relative; // bcz: this breaks auto-scrolling using the inline search box + // position: relative; // bcz: this breaks auto-scrolling using the inline search box z-index: -1; - .textLayer{ - pointer-events: all; - user-select: text; + + .textLayer { + pointer-events: all; + user-select: text; } } + .pdfViewerDash-text { transform-origin: top left; + .textLayer { will-change: transform; } } - .pdfViewerDash-overlay, .pdfViewerDash-overlay-inking { + .pdfViewerDash-overlay { transform-origin: left top; position: absolute; top: 0px; left: 0px; display: inline-block; - width:100%; - pointer-events: all; - } - .pdfViewerDash-overlay { - pointer-events: none; + width: 100%; } .pdfViewerDash-overlayAnno { @@ -81,7 +88,7 @@ border-radius: 5px; display: block; } - + .pdfViewerDash-annotationLayer { position: absolute; transform-origin: left top; @@ -90,12 +97,13 @@ pointer-events: none; mix-blend-mode: multiply; // bcz: makes text fuzzy! } + .pdfViewerDash-waiting { width: 70%; height: 70%; - margin : 15%; + margin: 15%; transition: 0.4s opacity ease; - opacity: 0.7; + opacity: 0.7; position: absolute; z-index: 10; } @@ -103,4 +111,4 @@ .pdfViewerDash-interactive { pointer-events: all; -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 3c052d10b..38890410c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,18 +1,16 @@ -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; +import { Doc, DocListCast, Field, HeightSym, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; -import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, smoothScroll, Utils } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { CompiledScript, CompileScript } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; import { SharingManager } from "../../util/SharingManager"; import { SnappingManager } from "../../util/SnappingManager"; @@ -59,11 +57,9 @@ export class PDFViewer extends React.Component { static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _savedAnnotations = new ObservableMap(); - @observable private _script: CompiledScript = CompileScript("return true") as CompiledScript; @observable private _marqueeing: number[] | undefined; @observable private _textSelecting = true; @observable private _showWaiting = true; - @observable private _showCover = false; @observable private _zoomed = 1; @observable private _overlayAnnoInfo: Opt; @observable private Index: number = -1; @@ -79,14 +75,13 @@ export class PDFViewer extends React.Component { private _selectionText: string = ""; private _downX: number = 0; private _downY: number = 0; - private _coverPath: any; private _lastSearch = false; private _viewerIsSetup = false; private _ignoreScroll = false; private _initialScroll: Opt; private _forcedScroll = true; - + @observable isAnnotating = false; // key where data is stored @computed get allAnnotations() { return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined); @@ -108,13 +103,11 @@ export class PDFViewer extends React.Component { if (pathComponents.length) { params.subtree = `${pathComponents.join("/")}/`; } - this._coverPath = href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" }; } else { const params: any = { coreFilename: relative.split("/")[relative.split("/").length - 1], pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))), }; - this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" }; } runInAction(() => this._showWaiting = true); this.props.startupLive && this.setupPdfJsViewer(); @@ -207,17 +200,6 @@ export class PDFViewer extends React.Component { this.props.setPdfViewer(this); await this.initialLoad(); - this._disposers.filterScript = reaction( - () => ScriptCast(this.props.Document.filterScript), - action(scriptField => { - const oldScript = this._script.originalScript; - this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; - if (this._script.originalScript !== oldScript) { - this.Index = -1; - } - }), - { fireImmediately: true }); - this.createPdfViewer(); } @@ -232,7 +214,7 @@ export class PDFViewer extends React.Component { () => Math.abs(NumCast(this.props.Document._scrollTop)), (pos) => { if (!this._ignoreScroll) { - (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); + this._showWaiting && this.setupPdfJsViewer(); const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); @@ -377,6 +359,7 @@ export class PDFViewer extends React.Component { this.props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; + this.isAnnotating = true; if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) { this._textSelecting = false; document.addEventListener("pointermove", this.onSelectMove); // need this to prevent document from being dragged if stopPropagation doesn't get called @@ -393,6 +376,7 @@ export class PDFViewer extends React.Component { @action finishMarquee = (x?: number, y?: number) => { + this.isAnnotating = false; this._marqueeing = undefined; this._textSelecting = true; document.removeEventListener("pointermove", this.onSelectMove); @@ -402,6 +386,7 @@ export class PDFViewer extends React.Component { @action onSelectEnd = (e: PointerEvent): void => { + this.isAnnotating = false; clearStyleSheetRules(PDFViewer._annotationStyle); this.props.select(false); document.removeEventListener("pointermove", this.onSelectMove); @@ -459,20 +444,6 @@ export class PDFViewer extends React.Component { setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => this._setPreviewCursor = func; - getCoverImage = () => { - if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) { - setTimeout((() => { - this.props.Document._height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width; - Doc.SetNativeWidth(this.props.Document, (Doc.NativeWidth(this.props.Document) || 0) * this._coverPath.height / this._coverPath.width); - }).bind(this), 0); - } - const nativeWidth = Doc.NativeWidth(this.props.Document); - const nativeHeight = Doc.NativeHeight(this.props.Document); - const resolved = Utils.prepend(this._coverPath.path); - return this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)} - style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />; - } - @action onZoomWheel = (e: React.WheelEvent) => { if (this.props.isContentActive(true)) { @@ -495,7 +466,7 @@ export class PDFViewer extends React.Component { } @computed get overlayInfo() { - return !this._overlayAnnoInfo || this._overlayAnnoInfo.author === Doc.CurrentUserEmail ? (null) : + return !this._overlayAnnoInfo ? (null) :
    users.user.email === this._overlayAnnoInfo!.author)?.userColor }}> {this._overlayAnnoInfo.author + " " + Field.toString(this._overlayAnnoInfo.creationDate as Field)} @@ -517,41 +488,76 @@ export class PDFViewer extends React.Component { } return this.props.styleProvider?.(doc, props, property); } + renderAnnotations = (docFilters?: () => string[]) => + ; + + @computed get overlayLayerClickableAnnotations() { + return SnappingManager.GetIsDragging() ? (null) : this.renderAnnotations(); + } + @computed get overlayLayerOpaqueAnnotations() { + trace(); + return + } + @computed get overlayLayerTransparentAnnotations() { + trace(); + return + } @computed get overlayLayer() { - const renderAnnotations = (docFilters?: () => string[]) => - ; - return
    -
    - {renderAnnotations(this.transparentFilter)} + return
    +
    + {this.overlayLayerTransparentAnnotations}
    -
    anno.mixBlendMode) ? "hard-light" : undefined, - transform: `scale(${this._zoomed})` - }}> - {renderAnnotations(this.opaqueFilter)} - {SnappingManager.GetIsDragging() ? (null) : renderAnnotations()} +
    anno.mixBlendMode) ? "hard-light" : undefined, }}> + {this.overlayLayerOpaqueAnnotations} + {this.overlayLayerClickableAnnotations}
    ; } @@ -559,12 +565,6 @@ export class PDFViewer extends React.Component { return
    ; } @computed get contentScaling() { return this.props.ContentScaling?.() || 1; } - @computed get standinViews() { - return <> - {this._showCover ? this.getCoverImage() : (null)} - {this._showWaiting ? : (null)} - ; - } contentZoom = () => this._zoomed; savedAnnotations = () => this._savedAnnotations; render() { @@ -581,7 +581,7 @@ export class PDFViewer extends React.Component { {this.annotationLayer} {this.overlayLayer} {this.overlayInfo} - {this.standinViews} + {this._showWaiting ? : (null)} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : { savedAnnotations={this.savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} - anchorMenuCrop={this.crop} + anchorMenuCrop={this._textSelecting ? undefined : this.crop} />}
    ; diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 4d5ae1018..e532becb5 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -44,7 +44,6 @@ export const documentSchema = createSchema({ _showCaption: "string", // whether editable caption text is overlayed at the bottom of the document _showTitle: "string", // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover _showAudio: "boolean", // whether to show the audio record icon on documents - _freeformLOD: "boolean", // whether to enable LOD switching for CollectionFreeFormViews _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis _columnsFill: "boolean", // whether documents in a stacking view column should be sized to fill the column _columnsSort: "string", // how a document should be sorted "ascending", "descending", undefined (none) -- cgit v1.2.3-70-g09d2 From 57c1b8235b26a1b6a315922b4dc2926e1e597674 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 13 May 2022 18:17:10 -0400 Subject: removed layerProvider functionality. --- src/client/documents/Documents.ts | 3 +- src/client/views/DocComponent.tsx | 4 -- src/client/views/GestureOverlay.tsx | 1 - src/client/views/InkingStroke.tsx | 2 +- src/client/views/LightboxView.tsx | 1 - src/client/views/MainView.tsx | 6 --- src/client/views/OverlayView.tsx | 1 - src/client/views/Palette.tsx | 1 - src/client/views/PropertiesView.tsx | 2 - src/client/views/StyleProvider.tsx | 52 +++------------------- src/client/views/TemplateMenu.tsx | 1 - src/client/views/collections/CollectionMenu.tsx | 1 - .../collections/CollectionStackedTimeline.tsx | 2 +- .../views/collections/CollectionStackingView.tsx | 2 - .../views/collections/CollectionTreeView.tsx | 1 - src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 19 +------- src/client/views/collections/TreeView.tsx | 2 - .../collectionFreeForm/CollectionFreeFormView.tsx | 24 +++++----- .../collections/collectionFreeForm/MarqueeView.tsx | 10 ++--- .../collectionLinear/CollectionLinearView.tsx | 1 - .../CollectionMulticolumnView.tsx | 1 - .../CollectionMultirowView.tsx | 1 - .../collectionSchema/CollectionSchemaView.tsx | 1 - .../collections/collectionSchema/SchemaTable.tsx | 1 - src/client/views/linking/LinkPopup.tsx | 3 +- src/client/views/nodes/AudioBox.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 - src/client/views/nodes/DocumentView.tsx | 5 +-- src/client/views/nodes/FilterBox.tsx | 1 - src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/KeyValuePair.tsx | 1 - src/client/views/nodes/LinkAnchorBox.tsx | 1 - src/client/views/nodes/LinkDocPreview.tsx | 1 - src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 6 +-- .../views/nodes/formattedText/DashDocView.tsx | 1 - .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 2 +- src/client/views/nodes/trails/PresElementBox.tsx | 1 - src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/search/SearchBox.tsx | 2 +- src/fields/Doc.ts | 2 +- src/mobile/AudioUpload.tsx | 1 - src/mobile/MobileInterface.tsx | 1 - src/server/server_Initialization.ts | 2 +- 46 files changed, 41 insertions(+), 143 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d9c2e0d8b..797a129c4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -122,7 +122,6 @@ export class DocumentOptions { _scrollTop?: number; // scroll location for pdfs _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged _chromeHidden?: boolean; // whether the editing chrome for a document is hidden - _layerTags?: List; // layer tags a document has (used for tab filtering "layers" in document tab) _searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view) _forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active) _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere @@ -426,7 +425,7 @@ export namespace Docs { childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", showCaption: "description", backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area links: "@links(self)", - _removeDropProperties: new List(["_layerTags", "isLinkButton"]), + _removeDropProperties: new List(["isLinkButton"]), } }], [DocumentType.LINKDB, { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 79aaf2158..9c176a4fd 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -44,7 +44,6 @@ interface ViewBoxBaseProps { ContainingCollectionDoc: Opt; DocumentView?: () => DocumentView; fieldKey: string; - layerProvider?: (doc: Doc, assign?: boolean) => boolean; isSelected: (outsideReaction?: boolean) => boolean; isContentActive: () => boolean | undefined; renderDepth: number; @@ -85,7 +84,6 @@ export interface ViewBoxAnnotatableProps { DataDoc?: Doc; fieldKey: string; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) - layerProvider?: (doc: Doc, assign?: boolean) => boolean; isContentActive: () => boolean | undefined; select: (isCtrlPressed: boolean) => void; whenChildContentsActiveChanged: (isActive: boolean) => void; @@ -209,13 +207,11 @@ export function ViewBoxAnnotatableComponent

    () if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); doc.context = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - this.props.layerProvider?.(doc, true); Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); }); } else { added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document - this.props.layerProvider?.(doc, true); //DocUtils.LeavePushpin(doc); doc._stayInCollection = undefined; doc.context = this.props.Document; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 50dca0a99..482b62479 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -881,7 +881,6 @@ export class GestureOverlay extends Touchable { isContentActive={returnFalse} renderDepth={0} styleProvider={returnEmptyString} - layerProvider={undefined} docViewPath={returnEmptyDoclist} focus={DocUtils.DefaultFocus} whenChildContentsActiveChanged={emptyFunction} diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 3d9c048e5..d0f01c363 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -332,7 +332,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { inkStrokeWidth, inkStrokeWidth + (highlightIndex && closed && fillColor && (new Color(fillColor)).alpha() < 1 ? 6 : 15), StrCast(this.layoutDoc.strokeLineJoin), StrCast(this.layoutDoc.strokeLineCap), StrCast(this.layoutDoc.strokeBezier), !closed ? "none" : fillColor === "transparent" || suppressFill ? "none" : fillColor, startMarker, endMarker, - markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents?.() ?? (this.props.layerProvider?.(this.props.Document) === false ? "none" : "visiblepainted"), 0.0, + markerScale, undefined, inkScaleX, inkScaleY, "", this.props.pointerEvents?.() ?? (this.rootDoc._lockedPosition ? "none" : "visiblepainted"), 0.0, false, downHdlr); const fsize = +(StrCast(this.props.Document.fontSize, "12px").replace("px", "")); // bootsrap 3 style sheet sets line height to be 20px for default 14 point font size. diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 59ed0dc92..9882693ee 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -246,7 +246,6 @@ export class LightboxView extends React.Component { docFilters={this.docFilters} removeDocument={undefined} styleProvider={DefaultStyleProvider} - layerProvider={returnTrue} ScreenToLocalTransform={this.lightboxScreenToLocal} PanelWidth={this.lightboxWidth} PanelHeight={this.lightboxHeight} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index db51c54f8..2e1d10955 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -281,7 +281,6 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} - layerProvider={undefined} styleProvider={undefined} rootSelected={returnTrue} isContentActive={returnTrue} @@ -361,7 +360,6 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} - layerProvider={undefined} styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === Doc.UserDoc().myFilesystem ? DashboardStyleProvider : DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} @@ -402,7 +400,6 @@ export class MainView extends React.Component { docViewPath={returnEmptyDoclist} focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} - layerProvider={undefined} isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} @@ -516,7 +513,6 @@ export class MainView extends React.Component { dropAction={"alias"} setHeight={returnFalse} styleProvider={DefaultStyleProvider} - layerProvider={undefined} rootSelected={returnTrue} bringToFront={emptyFunction} select={emptyFunction} @@ -591,7 +587,6 @@ export class MainView extends React.Component { ContainingCollectionDoc={undefined} Document={DocumentLinksButton.invisibleWebDoc} dropAction={"move"} - layerProvider={undefined} styleProvider={undefined} isSelected={returnFalse} select={returnFalse} @@ -673,7 +668,6 @@ export class MainView extends React.Component { rootSelected={returnFalse} renderDepth={0} setHeight={returnFalse} - layerProvider={undefined} styleProvider={undefined} addDocTab={returnFalse} pinToPres={returnFalse} diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 0f51cf9b2..ebad2981d 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -195,7 +195,6 @@ export class OverlayView extends React.Component { whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} - layerProvider={undefined} docViewPath={returnEmptyDoclist} addDocTab={returnFalse} pinToPres={emptyFunction} diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index 529697f71..954529bc9 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -54,7 +54,6 @@ export default class Palette extends React.Component { focus={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={returnEmptyString} - layerProvider={undefined} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 21c688421..bba2ac211 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -303,7 +303,6 @@ export class PropertiesView extends React.Component { fitContentsToDoc={returnTrue} rootSelected={returnFalse} styleProvider={DefaultStyleProvider} - layerProvider={undefined} docViewPath={returnEmptyDoclist} freezeDimensions={true} dontCenter={"y"} @@ -1010,7 +1009,6 @@ export class PropertiesView extends React.Component { createNewFilterDoc={this.createNewFilterDoc} updateFilterDoc={this.updateFilterDoc} docViewPath={returnEmptyDoclist} - layerProvider={undefined} dontCenter="y" />

    diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5056dedaf..334415b38 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -26,10 +26,6 @@ import React = require("react"); import { InkingStroke } from './InkingStroke'; import { TreeSort } from './collections/TreeView'; -export enum StyleLayers { - Background = "background" -} - export enum StyleProp { TreeViewIcon = "treeViewIcon", TreeViewSortings = "treeViewSortings",// options for how to sort tree view items @@ -56,14 +52,9 @@ export enum StyleProp { function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; } -function toggleBackground(doc: Doc) { +function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => runInAction(() => { - const layers = StrListCast(doc._layerTags); - if (!layers.includes(StyleLayers.Background)) { - if (!layers.length) doc._layerTags = new List([StyleLayers.Background]); - else layers.push(StyleLayers.Background); - } - else layers.splice(layers.indexOf(StyleLayers.Background), 1); + doc._lockedPosition = !doc._lockedPosition; }), "toggleBackground"); } @@ -86,7 +77,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc && !Doc.IsSystem(doc) && Doc.UserDoc().renderStyle === "comic"; - const isBackground = () => StrListCast(doc?._layerTags).includes(StyleLayers.Background); + const isBackground = () => doc && BoolCast(doc._lockedPosition); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); const showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle); @@ -177,7 +168,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt 0 && ((doc.type === DocumentType.COL && doc._viewType !== CollectionViewType.Pile) || [DocumentType.RTF, DocumentType.IMG, DocumentType.INK].includes(doc.type as DocumentType)) ? -
    toggleBackground(doc)}> +
    toggleLockedPosition(doc)}>
    : (null); @@ -251,30 +240,3 @@ export function DashboardStyleProvider(doc: Opt, props: Opt { - if (doc.z) return true; - if (assign) { - const activeLayer = StrCast(thisDoc?.activeLayer); - if (activeLayer) { - const layers = Cast(doc._layerTags, listSpec("string"), []); - if (layers.length && !layers.includes(activeLayer)) layers.push(activeLayer); - else if (!layers.length) doc._layerTags = new List([activeLayer]); - if (activeLayer === "red" || activeLayer === "green" || activeLayer === "blue") doc._backgroundColor = activeLayer; - } - return true; - } else { - if (Doc.AreProtosEqual(doc, thisDoc)) return true; - const layers = StrListCast(doc._layerTags); - if (!layers.length && !thisDoc?.activeLayer) return true; - if (layers.includes(StrCast(thisDoc?.activeLayer))) return true; - return false; - } - }; -} \ No newline at end of file diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index b3a24e031..636f7042f 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -131,7 +131,6 @@ export class TemplateMenu extends React.Component { ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} styleProvider={DefaultStyleProvider} - layerProvider={undefined} setHeight={returnFalse} docViewPath={returnEmptyDoclist} docFilters={returnEmptyFilter} diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 364a2440e..b3fbd4ca4 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -108,7 +108,6 @@ export class CollectionMenu extends AntimodeMenu{ dropAction={"alias"} setHeight={returnFalse} styleProvider={DefaultStyleProvider} - layerProvider={undefined} rootSelected={returnTrue} bringToFront={emptyFunction} select={emptyFunction} diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index de307416f..683b6d51d 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -392,7 +392,7 @@ export class CollectionStackedTimeline extends CollectionSubView + style={{ pointerEvents: this.rootDoc._lockedPosition ? "none" : undefined }}> {this.showIsTagged()} {this.renderSubView(this.collectionViewType, props)}
    ); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 8dfc7edc7..73e5a9178 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -26,7 +26,7 @@ import { Colors, Shadows } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; -import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider'; +import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionDockingViewMenu } from './CollectionDockingViewMenu'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; @@ -94,20 +94,6 @@ export class TabDocView extends React.Component { if (tab.element[0].children[1].children.length === 1) { - const toggle = document.createElement("div"); - toggle.style.width = "10px"; - toggle.style.height = "calc(100% - 2px)"; - toggle.style.left = "-2px"; - toggle.style.bottom = "1px"; - toggle.style.borderTopRightRadius = "7px"; - toggle.style.position = "relative"; - toggle.style.display = "inline-block"; - toggle.style.background = "transparent"; - toggle.onclick = (e: MouseEvent) => { - if (tab.contentItem === tab.header.parent.getActiveContentItem()) { - tab.DashDoc.activeLayer = tab.DashDoc.activeLayer ? undefined : StyleLayers.Background; - } - }; iconWrap.className = "lm_iconWrap"; iconWrap.id = "lm_iconWrap"; closeWrap.className = "lm_iconWrap"; @@ -357,7 +343,6 @@ export class TabDocView extends React.Component { disableMinimap = () => !this._document || (this._document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._document)) || this._document?._viewType !== CollectionViewType.Freeform); hideMinimap = () => this.disableMinimap() || BoolCast(this._document?.hideMinimap); - @computed get layerProvider() { return this._document && DefaultLayerProvider(this._document); } @computed get docView() { return !this._activated || !this._document || this._document._viewType === CollectionViewType.Docking ? (null) : <> this._view = r)} @@ -369,7 +354,6 @@ export class TabDocView extends React.Component { isContentActive={returnTrue} PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} - layerProvider={this.layerProvider} styleProvider={DefaultStyleProvider} docFilters={CollectionDockingView.Instance.childDocFilters} docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} @@ -520,7 +504,6 @@ export class TabMinimapView extends React.Component { whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={TabMinimapView.miniStyleProvider} - layerProvider={undefined} addDocTab={this.props.addDocTab} pinToPres={TabDocView.PinDoc} docFilters={CollectionDockingView.Instance.childDocFilters} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 164021358..d608f800c 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -663,7 +663,6 @@ export class TreeView extends React.Component { hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} - layerProvider={returnTrue} docViewPath={returnEmptyDoclist} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} @@ -760,7 +759,6 @@ export class TreeView extends React.Component { renderDepth={this.props.renderDepth + 1} treeViewDoc={this.props.treeView?.props.Document} rootSelected={returnTrue} - layerProvider={returnTrue} docViewPath={this.props.treeView.props.docViewPath} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4f79d19ad..322944d92 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -44,7 +44,7 @@ import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { VideoBox } from "../../nodes/VideoBox"; import { CreateImage } from "../../nodes/WebBoxRenderer"; -import { StyleLayers, StyleProp } from "../../StyleProvider"; +import { StyleProp } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; import { CollectionViewType } from "../CollectionView"; @@ -122,8 +122,6 @@ export class CollectionFreeFormView extends CollectionSubView ele.bounds && !ele.bounds.z).map(ele => ele.ele); } - @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } - @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && this.props.isContentActive(); } @computed get fitToContentVals() { return { bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 }, @@ -222,8 +220,8 @@ export class CollectionFreeFormView extends CollectionSubView 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?.filter(s => !StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); - set?.filter(s => StrListCast(s._layerTags).includes(StyleLayers.Background)).map(s => styleProp = StrCast(s.backgroundColor)); + set?.map(s => styleProp = StrCast(s.backgroundColor)); } } //else if (doc && NumCast(doc.group, -1) !== -1) styleProp = "gray"; return styleProp; @@ -1017,7 +1014,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (sendToBack || StrListCast(doc._layerTags).includes(StyleLayers.Background)) { + if (sendToBack) { doc.zIndex = 0; } else if (doc.isInkMask) { doc.zIndex = 5000; @@ -1155,7 +1152,7 @@ export class CollectionFreeFormView extends CollectionSubView { const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); - const pointerEvents = this.props.isContentActive() === false ? "none" : this.backgroundActive || this.props.childPointerEvents ? "all" : + const pointerEvents = this.props.isContentActive() === false ? "none" : this.props.childPointerEvents ? "all" : (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? "none" : this.props.pointerEvents?.(); return pointerEvents; } @@ -1195,7 +1192,6 @@ export class CollectionFreeFormView extends CollectionSubView intersectRect(docDims(doc), rect); const otherBounds = { left: this.panX(), top: this.panY(), width: Math.abs(size[0]), height: Math.abs(size[1]) }; - let snappableDocs = activeDocs.filter(doc => !StrListCast(doc._layerTags).includes(StyleLayers.Background) && doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to + let snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect))); // if not, see if there are background docs to snap to !snappableDocs.length && (snappableDocs = activeDocs.filter(doc => doc.z !== undefined && isDocInView(doc, otherBounds))); // if not, then why not snap to floating docs @@ -1739,7 +1735,7 @@ export class CollectionFreeFormView extends CollectionSubView
    } - {this.props.Document._isGroup && SnappingManager.GetIsDragging() && (this.ChildDrag || this.props.layerProvider?.(this.props.Document) === false) ? + {this.props.Document._isGroup && SnappingManager.GetIsDragging() && this.ChildDrag ?
    , options: DocumentOptions, id?: string) => Doc>, layers: string[], makeGroup: Opt) => { + getCollection = action((selected: Doc[], creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt) => { const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { Doc.GetProto(doc).data = new List(selected); Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; @@ -357,7 +356,6 @@ export class MarqueeView extends React.Component(layers); newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; newCollection._isGroup = makeGroup; @@ -437,7 +435,7 @@ export class MarqueeView extends React.Component { - const newCollection = this.getCollection([], undefined, [StyleLayers.Background], undefined); + const newCollection = this.getCollection([], undefined, undefined); this.props.addDocument?.(newCollection); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); @@ -617,7 +615,7 @@ export class MarqueeView extends React.Component this.props.layerProvider?.(doc) !== false && !doc.z).map(selectFunc); + this.props.activeDocuments().filter(doc => !doc.z && !doc._lockedPosition).map(selectFunc); if (!selection.length && selectBackgrounds) this.props.activeDocuments().filter(doc => doc.z === undefined).map(selectFunc); if (!selection.length) this.props.activeDocuments().filter(doc => doc.z !== undefined).map(selectFunc); return selection; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index c615bfb8e..bec582dcd 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -155,7 +155,6 @@ export class CollectionLinearView extends CollectionSubView() { renderDepth={this.props.renderDepth + 1} focus={emptyFunction} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 6929a1cd8..92f9b2f49 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -219,7 +219,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 7e2b83230..4c4054b09 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -218,7 +218,6 @@ export class CollectionMultirowView extends CollectionSubView() { Document={layout} DataDoc={layout.resolvedDataDoc as Doc} styleProvider={this.props.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index a93762ea4..b731479a5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -417,7 +417,6 @@ export class CollectionSchemaView extends CollectionSubView() { docRangeFilters={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} styleProvider={DefaultStyleProvider} - layerProvider={undefined} docViewPath={returnEmptyDoclist} ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} diff --git a/src/client/views/collections/collectionSchema/SchemaTable.tsx b/src/client/views/collections/collectionSchema/SchemaTable.tsx index 605481ddf..bea5b3be6 100644 --- a/src/client/views/collections/collectionSchema/SchemaTable.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTable.tsx @@ -573,7 +573,6 @@ export class SchemaTable extends React.Component { Document={this._showDoc} DataDoc={this._showDataDoc} styleProvider={DefaultStyleProvider} - layerProvider={undefined} docViewPath={returnEmptyDoclist} freezeDimensions={true} focus={DocUtils.DefaultFocus} diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index c8be9069c..4b33ef8ae 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -67,7 +67,7 @@ export class LinkPopup extends React.Component { */} - { pinToPres={emptyFunction} rootSelected={returnTrue} styleProvider={DefaultStyleProvider} - layerProvider={undefined} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} PanelWidth={this.getPWidth} diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index e28e5b453..d97cb6f84 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -654,7 +654,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent {!this.path ? this.recordingControls : this.playbackControls}
    ; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index edadd59c0..5a0ab9110 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -21,7 +21,6 @@ import React = require("react"); export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { dataProvider?: (doc: Doc, replica: string) => { x: number, y: number, zIndex?: number, opacity?: number, highlight?: boolean, z: number, transition?: string } | undefined; sizeProvider?: (doc: Doc, replica: string) => { width: number, height: number } | undefined; - layerProvider: ((doc: Doc, assign?: boolean) => boolean) | undefined; renderCutoffProvider: (doc: Doc) => boolean; zIndex?: number; highlight?: boolean; @@ -159,7 +158,6 @@ export class CollectionFreeFormDocumentView extends DocComponent number; docViewPath: () => DocumentView[]; dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt - layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean); styleProvider: Opt; focus: DocFocusFunc; fitWidth?: (doc: Doc) => boolean; @@ -474,7 +473,7 @@ export class DocumentViewInternal extends DocComponent() { treeViewHideHeaderFields={false} dontRegisterView={true} styleProvider={this.FilterStyleProvider} - layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} scriptContext={this.props.scriptContext} moveDocument={returnFalse} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index e7c43e5cd..3b04aa807 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -379,7 +379,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { docRangeFilters: returnEmptyFilter, searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, - layerProvider: undefined, docViewPath: returnEmptyDoclist, ContainingCollectionView: undefined, ContainingCollectionDoc: undefined, diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 437d29f39..7fd289a97 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -89,7 +89,6 @@ export class LinkAnchorBox extends ViewBoxBaseComponent() { openLinkTargetOnRight = (e: React.MouseEvent) => { const alias = Doc.MakeAlias(Cast(this.layoutDoc[this.fieldKey], Doc, null)); alias._isLinkButton = undefined; - alias._layerTags = undefined; alias.layoutKey = "layout"; this.props.addDocTab(alias, "add:right"); } diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 375434933..ba515fb89 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -179,7 +179,6 @@ export class LinkDocPreview extends React.Component { moveDocument={returnFalse} rootSelected={returnFalse} styleProvider={this.props.docProps?.styleProvider} - layerProvider={this.props.docProps?.layerProvider} docViewPath={returnEmptyDoclist} ScreenToLocalTransform={Transform.Identity} isDocumentActive={returnFalse} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 3b4c94562..c350e3139 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -809,7 +809,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { e.stopPropagation(); e.preventDefault(); }}> diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 20e01be05..445df8ddd 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -74,7 +74,7 @@ export class WebBox extends ViewBoxAnnotatableComponent a.textInlineAnnotations); } @computed get webField() { return Cast(this.dataDoc[this.props.fieldKey], WebField)?.url; } - @computed get webThumb() { return this.props.thumbShown?.() && ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop ? this.layoutDoc.thumb : undefined))?.url } + @computed get webThumb() { return this.props.thumbShown?.() && ImageCast(this.layoutDoc["thumb-frozen"], ImageCast(this.layoutDoc.thumbScrollTop === this.layoutDoc._scrollTop ? this.layoutDoc.thumb : undefined))?.url; } constructor(props: any) { super(props); @@ -134,7 +134,7 @@ export class WebBox extends ViewBoxAnnotatableComponent setTimeout(action(() => { this.lockout = false; this.layoutDoc.thumb = new ImageField(returnedfilename); - this.layoutDoc.thumbScrollTop = scrollTop + this.layoutDoc.thumbScrollTop = scrollTop; }), 500)); }) .catch(function (error: any) { @@ -717,7 +717,7 @@ export class WebBox extends ViewBoxAnnotatableComponent !this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; annotationPointerEvents = () => this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"; render() { - const pointerEvents = this.props.layerProvider?.(this.layoutDoc) === false ? "none" : this.props.pointerEvents?.() as any; + const pointerEvents = this.layoutDoc._lockedPosition ? "none" : this.props.pointerEvents?.() as any; const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const scale = previewScale * (this.props.scaling?.() || 1); const renderAnnotations = (docFilters?: () => string[]) => diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 364be461f..1d8e3a2cf 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -182,7 +182,6 @@ export class DashDocViewInternal extends React.Component { removeDocument={this.removeDoc} isDocumentActive={returnFalse} isContentActive={this._textBox.props.isContentActive} - layerProvider={this._textBox.props.layerProvider} styleProvider={this._textBox.props.styleProvider} docViewPath={this._textBox.props.docViewPath} ScreenToLocalTransform={this.getDocTransform} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 29117794e..f2a222cee 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1630,7 +1630,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const active = this.props.isContentActive(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); + const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 820d7ef96..64f5a296f 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -700,7 +700,7 @@ export class PresBox extends ViewBoxBaseComponent() { removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && this.props.layerProvider?.(this.layoutDoc) !== false) && + isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && !this.layoutDoc._lockedPosition) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) /** diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index f4dc9b615..ef918d991 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -80,7 +80,6 @@ export class PresElementBox extends ViewBoxBaseComponent() { Document={this.targetDoc} DataDoc={this.targetDoc[DataSym] !== this.targetDoc && this.targetDoc[DataSym]} styleProvider={this.styleProvider} - layerProvider={this.props.layerProvider} docViewPath={returnEmptyDoclist} rootSelected={returnTrue} addDocument={returnFalse} diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index fb2e33e2a..5bdce273d 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -26,7 +26,7 @@ export {DocListCast(this.props.anno.textInlineAnnotations).map(a => )} -
    +
    ; } } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 2905e96d9..152b7bbcb 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -127,7 +127,7 @@ export class SearchBox extends ViewBoxBaseComponent() { static foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) { let newarray: Doc[] = []; var depth = 0; - let visited: Doc[] = []; + const visited: Doc[] = []; while (docs.length > 0) { newarray = []; docs.filter(d => d && !visited.includes(d)).forEach(d => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 9de8d0831..194b3ba27 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1026,7 +1026,7 @@ export namespace Doc { if (status === DocBrushStatus.unbrushed) { const lastBrushed = Array.from(brushManager.BrushedDoc.keys()).lastElement(); if (lastBrushed) { - for (var link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) { + for (const link of LinkManager.Instance.getAllDirectLinks(lastBrushed)) { const a1 = Cast(link.anchor1, Doc, null); const a2 = Cast(link.anchor2, Doc, null); if (Doc.AreProtosEqual(a1, doc) || Doc.AreProtosEqual(a2, doc) || diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx index 88221732e..418464f0e 100644 --- a/src/mobile/AudioUpload.tsx +++ b/src/mobile/AudioUpload.tsx @@ -96,7 +96,6 @@ export class AudioUpload extends React.Component { isDocumentActive={returnTrue} isContentActive={emptyFunction} focus={emptyFunction} - layerProvider={undefined} styleProvider={() => "rgba(0,0,0,0)"} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index cfcc48608..78ec706d7 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -214,7 +214,6 @@ export class MobileInterface extends React.Component { isContentActive={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={this.whitebackground} - layerProvider={undefined} docViewPath={returnEmptyDoclist} whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/server/server_Initialization.ts b/src/server/server_Initialization.ts index bdea57cb2..fd000a83c 100644 --- a/src/server/server_Initialization.ts +++ b/src/server/server_Initialization.ts @@ -195,7 +195,7 @@ function proxyServe(req: any, requrl: string, response: any) { console.log("EROR?: ", e); } } else req.pipe(request(requrl)).pipe(response); - } + }; retrieveHTTPBody = () => { req.headers.cookie = ""; req.pipe(request(requrl)) -- cgit v1.2.3-70-g09d2 From e6b882080471d915a80400e7e9a97ff78a232147 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 18 May 2022 14:05:39 -0400 Subject: fixed linking bug so that both start and end link can be regions. added zoom to target checkbox for link editing. extracted out a new field pointerEvents so that lockPosition is not automatically conflratd with no pointer events. updated freeformview focus to work with zooming on region selections. --- src/client/documents/Documents.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/PropertiesView.tsx | 37 +++++++++++++++++++--- src/client/views/StyleProvider.tsx | 5 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 24 +++++++------- src/client/views/linking/LinkEditor.scss | 18 +++++++++++ src/client/views/linking/LinkEditor.tsx | 30 +++++++++++++++--- src/client/views/nodes/DocumentView.tsx | 4 +-- src/client/views/nodes/ImageBox.tsx | 6 ++-- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 4 ++- src/client/views/pdf/PDFViewer.tsx | 29 +++++++++-------- 12 files changed, 120 insertions(+), 44 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 797a129c4..2dd8de089 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -173,6 +173,7 @@ export class DocumentOptions { label?: string; hidden?: boolean; _hidden?: boolean; + pointerEvents?: string; // pointer events that the documentview should have mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing" autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. @@ -482,7 +483,7 @@ export namespace Docs { }], [DocumentType.MARKER, { layout: { view: CollectionView, dataField: defaultDataKey }, - options: { links: "@links(self)", hideLinkButton: true } + options: { links: "@links(self)", hideLinkButton: true, pointerEvents: "none" } }], [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method layout: { view: InkingStroke, dataField: defaultDataKey }, diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 9b95f1525..ffa168f6b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -192,7 +192,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
    {"Set onClick to follow primary link"}
    }>
    this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, false, false)))}> + onClick={undoBatch(e => this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}>
    ; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index bba2ac211..bcfd2dd56 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1227,6 +1227,13 @@ export class PropertiesView extends React.Component { setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow))); } + toggleZoomToTarget1 = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom))); + } + toggleZoomToTarget2 = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom))); + } + @computed get editRelationship() { return {

    Information

    -
    +

    Link Relationship

    {this.editRelationship}
    -
    +

    Description

    {this.editDescription}

    Behavior

    -
    +

    Follow

    -
    +

    Auto-move anchor

    -
    +

    Display arrow

    +
    +

    Zoom to target

    + +
    +
    +

    Zoom to source

    + +
    ; } diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 334415b38..93da2fa19 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -55,6 +55,7 @@ function darkScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme === function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => runInAction(() => { doc._lockedPosition = !doc._lockedPosition; + doc._pointerEvents = doc._lockedPosition ? "none" : undefined; }), "toggleBackground"); } @@ -194,10 +195,10 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { const layoutdoc = Doc.Layout(doc); - const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); - const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); const pt2 = xf.transformPoint(NumCast(doc.x) + layoutdoc[WidthSym](), NumCast(doc.y) + layoutdoc[HeightSym]()); - const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1] }; - const cx = NumCast(this.layoutDoc._panX); - const cy = NumCast(this.layoutDoc._panY); - const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 }; + const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] }; if (scale) { const maxZoom = 5; // sets the limit for how far we will zoom. this is useful for preventing small text boxes from filling the screen. So probably needs to be more sophisticated to consider more about the target and context + const newScale = Math.min(maxZoom, 1 / (this.contentScaling || 1) * scale * Math.min(this.props.PanelWidth() / Math.abs(bounds.width), this.props.PanelHeight() / Math.abs(bounds.height))); return { - panX: (bounds.left + bounds.right) / 2, - panY: (bounds.top + bounds.bot) / 2, - scale: Math.min(maxZoom, scale * Math.min(this.props.PanelWidth() / Math.abs(pt2[0] - pt[0]), this.props.PanelHeight() / Math.abs(pt2[1] - pt[1]))) + panX: this.props.isAnnotationOverlay ? bounds.left - (Doc.NativeWidth(this.layoutDoc) / newScale - bounds.width) / 2 : (bounds.left + bounds.right) / 2, + panY: this.props.isAnnotationOverlay ? bounds.top - (Doc.NativeHeight(this.layoutDoc) / newScale - bounds.height) / 2 : (bounds.top + bounds.bot) / 2, + scale: newScale }; } + const pw = this.props.PanelWidth() / NumCast(this.layoutDoc._viewScale, 1); + const ph = this.props.PanelHeight() / NumCast(this.layoutDoc._viewScale, 1); + const cx = NumCast(this.layoutDoc._panX); + const cy = NumCast(this.layoutDoc._panY); + const screen = { left: cx - pw / 2, right: cx + pw / 2, top: cy - ph / 2, bot: cy + ph / 2 }; if ((screen.right - screen.left) < (bounds.right - bounds.left) || (screen.bot - screen.top) < (bounds.bot - bounds.top)) { return { @@ -1990,6 +1991,7 @@ class CollectionFreeFormBackgroundGrid extends React.Component { if (!didMove) { diff --git a/src/client/views/linking/LinkEditor.scss b/src/client/views/linking/LinkEditor.scss index abd413f57..1d6496d3c 100644 --- a/src/client/views/linking/LinkEditor.scss +++ b/src/client/views/linking/LinkEditor.scss @@ -60,6 +60,24 @@ } } +.linkEditor-zoomFollow { + padding-left: 26px; + padding-right: 6.5px; + padding-bottom: 3.5px; + display: flex; + + .linkEditor-zoomFollow-label { + text-decoration-color: black; + color: black; + line-height: 1.7; + } + + .linkEditor-zoomFollow-input { + display: block; + width: 20px; + } +} + .linkEditor-description { padding-left: 26px; padding-right: 6.5px; diff --git a/src/client/views/linking/LinkEditor.tsx b/src/client/views/linking/LinkEditor.tsx index db331bb75..c3e0aff11 100644 --- a/src/client/views/linking/LinkEditor.tsx +++ b/src/client/views/linking/LinkEditor.tsx @@ -9,7 +9,6 @@ import { undoBatch } from "../../util/UndoManager"; import './LinkEditor.scss'; import { LinkRelationshipSearch } from "./LinkRelationshipSearch"; import React = require("react"); -import { ToString } from "../../../fields/FieldSymbols"; interface LinkEditorProps { @@ -23,6 +22,7 @@ export class LinkEditor extends React.Component { @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); + @observable zoomFollow1 = StrCast(this.props.sourceDoc.followLinkZoom); @observable openDropdown: boolean = false; @observable showInfo: boolean = false; @computed get infoIcon() { if (this.showInfo) { return "chevron-up"; } return "chevron-down"; } @@ -143,9 +143,9 @@ export class LinkEditor extends React.Component { @action handleDescriptionChange = (e: React.ChangeEvent) => { this.description = e.target.value; } @action - handleRelationshipChange = (e: React.ChangeEvent) => { - this.relationship = e.target.value; - } + handleRelationshipChange = (e: React.ChangeEvent) => { this.relationship = e.target.value; } + @action + handleZoomFollowChange = (e: React.ChangeEvent) => { this.props.sourceDoc.followLinkZoom = e.target.checked; } @action handleRelationshipSearchChange = (result: string) => { this.setRelationshipValue(result); @@ -183,6 +183,27 @@ export class LinkEditor extends React.Component {
    ; } + @computed + get editZoomFollow() { + //NOTE: confusingly, the classnames for the following relationship JSX elements are the same as the for the description elements for shared CSS + return
    +
    Zoom To Link Target:
    +
    +
    + +
    +
    +
    ; + } @computed get editDescription() { @@ -303,6 +324,7 @@ export class LinkEditor extends React.Component { {this.editDescription} {this.editRelationship} + {this.editZoomFollow} {this.followingDropdown}
    diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index b6a2fae1a..49c2761b2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -605,7 +605,7 @@ export class DocumentViewInternal extends DocComponent, zoom: boolean, setPushpin: boolean): void => { + toggleFollowLink = (location: Opt, zoom?: boolean, setPushpin?: boolean): void => { this.Document.ignoreClick = false; if (setPushpin) { this.Document.isPushpin = !this.Document.isPushpin; @@ -614,7 +614,7 @@ export class DocumentViewInternal extends DocComponent) => Opt = () => undefined; @observable _curSuffix = ""; @observable _uploadIcon = uploadIcons.idle; @@ -62,7 +63,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - const anchor = AnchorMenu.Instance?.GetAnchor(this._savedAnnotations); + const anchor = this._getAnchor?.(this._savedAnnotations); anchor && this.addDocument(anchor); return anchor ?? this.rootDoc; } @@ -366,6 +367,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { + this._getAnchor = AnchorMenu.Instance?.GetAnchor; this._marqueeing = undefined; this.props.select(false); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 35b5e78a8..cbe7a5cc6 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -198,7 +198,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent { const anchor = - AnchorMenu.Instance?.GetAnchor() ?? + this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations()) ?? Docs.Create.TextanchorDocument({ title: StrCast(this.rootDoc.title + "@" + NumCast(this.layoutDoc._scrollTop)?.toFixed(0)), y: NumCast(this.layoutDoc._scrollTop), diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 445df8ddd..2112c1d44 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -52,6 +52,7 @@ export class WebBox extends ViewBoxAnnotatableComponent = React.createRef(); private _keyInput = React.createRef(); private _initialScroll: Opt = NumCast(this.layoutDoc.thumbScrollTop, NumCast(this.layoutDoc.scrollTop)); + private _getAnchor: (savedAnnotations?: ObservableMap) => Opt = () => undefined; private _sidebarRef = React.createRef(); private _searchRef = React.createRef(); private _searchString = ""; @@ -256,7 +257,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { const anchor = - AnchorMenu.Instance?.GetAnchor(this._savedAnnotations) ?? + this._getAnchor(this._savedAnnotations) ?? Docs.Create.WebanchorDocument(this._url, { title: StrCast(this.rootDoc.title + " " + this.layoutDoc._scrollTop), y: NumCast(this.layoutDoc._scrollTop), @@ -555,6 +556,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { + this._getAnchor = AnchorMenu.Instance?.GetAnchor; this._marqueeing = undefined; this._isAnnotating = false; this._iframeClick = undefined; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index b0a5fc93b..305b1fe68 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -6,7 +6,6 @@ import { Doc, DocListCast, Field, HeightSym, Opt } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, OmitKeys, returnFalse, smoothScroll, Utils } from "../../../Utils"; import { DocUtils } from "../../documents/Documents"; @@ -59,7 +58,6 @@ export class PDFViewer extends React.Component { @observable private _marqueeing: number[] | undefined; @observable private _textSelecting = true; @observable private _showWaiting = true; - @observable private _zoomed = 1; @observable private _overlayAnnoInfo: Opt; @observable private Index: number = -1; @@ -70,6 +68,7 @@ export class PDFViewer extends React.Component { private _annotationLayer: React.RefObject = React.createRef(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _viewer: React.RefObject = React.createRef(); + public _getAnchor: (savedAnnotations?: ObservableMap) => Opt = () => undefined; _mainCont: React.RefObject = React.createRef(); private _selectionText: string = ""; private _downX: number = 0; @@ -184,7 +183,7 @@ export class PDFViewer extends React.Component { pagesinit = () => { if (this._pdfViewer._setDocumentViewerElement?.offsetParent) { - runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1); + runInAction(() => this._pdfViewer.currentScaleValue = this.props.layoutDoc._viewScale = 1); this.gotoPage(NumCast(this.props.Document._curPage, 1)); } document.removeEventListener("pagesinit", this.pagesinit); @@ -355,6 +354,7 @@ export class PDFViewer extends React.Component { @action finishMarquee = (x?: number, y?: number) => { + this._getAnchor = AnchorMenu.Instance?.GetAnchor; this.isAnnotating = false; this._marqueeing = undefined; this._textSelecting = true; @@ -387,13 +387,14 @@ export class PDFViewer extends React.Component { const rect = clientRects.item(i); if (rect && rect.width !== this._mainCont.current.clientWidth && rect.width) { const scaleX = this._mainCont.current.offsetWidth / boundingRect.width; + const pdfScale = NumCast(this.props.layoutDoc._viewScale, 1); const annoBox = document.createElement("div"); annoBox.className = "marqueeAnnotator-annotationBox"; // transforms the positions from screen onto the pdf div - annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / this._zoomed + this._mainCont.current.scrollTop).toString(); - annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / this._zoomed).toString(); - annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / this._zoomed).toString(); - annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / this._zoomed).toString(); + annoBox.style.top = ((rect.top - boundingRect.top) * scaleX / pdfScale + this._mainCont.current.scrollTop).toString(); + annoBox.style.left = ((rect.left - boundingRect.left) * scaleX / pdfScale).toString(); + annoBox.style.width = (rect.width * this._mainCont.current.offsetWidth / boundingRect.width / pdfScale).toString(); + annoBox.style.height = (rect.height * this._mainCont.current.offsetHeight / boundingRect.height / pdfScale).toString(); this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, this.getPageFromScroll(rect.top)); } } @@ -430,14 +431,14 @@ export class PDFViewer extends React.Component { if (e.ctrlKey) { const curScale = Number(this._pdfViewer.currentScaleValue); this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale - curScale * e.deltaY / 1000)); - this._zoomed = Number(this._pdfViewer.currentScaleValue); + this.props.layoutDoc._viewScale = Number(this._pdfViewer.currentScaleValue); } } } pointerEvents = () => this.props.isContentActive() && this.props.pointerEvents?.() !== "none" && !MarqueeOptionsMenu.Instance.isShown() ? "all" : SnappingManager.GetIsDragging() ? undefined : "none"; @computed get annotationLayer() { - return
    + return
    {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => )}
    ; @@ -453,7 +454,7 @@ export class PDFViewer extends React.Component { } showInfo = action((anno: Opt) => this._overlayAnnoInfo = anno); - overlayTransform = () => this.scrollXf().scale(1 / this._zoomed); + overlayTransform = () => this.scrollXf().scale(1 / NumCast(this.props.layoutDoc._viewScale, 1)); panelWidth = () => this.props.PanelWidth() / (this.props.scaling?.() || 1); // (this.Document.scrollHeight || Doc.NativeHeight(this.Document) || 0); panelHeight = () => this.props.PanelHeight() / (this.props.scaling?.() || 1); // () => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : Doc.NativeWidth(this.Document); basicFilter = () => [...this.props.docFilters(), Utils.PropUnsetFilter("textInlineAnnotations")]; @@ -498,7 +499,7 @@ export class PDFViewer extends React.Component { style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none", mixBlendMode: "multiply", - transform: `scale(${this._zoomed})` + transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }}> {this.overlayTransparentAnnotations}
    @@ -506,7 +507,7 @@ export class PDFViewer extends React.Component { style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : "none", mixBlendMode: this.allAnnotations.some(anno => anno.mixBlendMode) ? "hard-light" : undefined, - transform: `scale(${this._zoomed})` + transform: `scale(${NumCast(this.props.layoutDoc._viewScale, 1)})` }}> {this.overlayOpaqueAnnotations} {this.overlayClickableAnnotations} @@ -517,7 +518,7 @@ export class PDFViewer extends React.Component { return
    ; } @computed get contentScaling() { return this.props.ContentScaling?.() || 1; } - contentZoom = () => this._zoomed; + contentZoom = () => NumCast(this.props.layoutDoc._viewScale, 1); savedAnnotations = () => this._savedAnnotations; render() { TraceMobx(); @@ -525,7 +526,7 @@ export class PDFViewer extends React.Component {
    600) ? Doc.NativeHeight(this.props.Document) : `${100 / this.contentScaling}%`, transform: `scale(${this.contentScaling})` }} > -- cgit v1.2.3-70-g09d2 From 6df80c76b4046ef1ac9e98e52f766dfaf540ead2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 20 May 2022 12:51:51 -0400 Subject: removed enumerables field stuff which was hacky and never used. cleaned up some of currentUserUtils initialiazation. fixed treeView sorting for 'none" which fixes problem with slide template. --- src/client/documents/Documents.ts | 24 -- src/client/util/CurrentUserUtils.ts | 242 +++++---------------- .../views/collections/CollectionStackingView.tsx | 1 - src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/ComparisonBox.tsx | 2 +- .../views/nodes/formattedText/DashFieldView.tsx | 14 -- 6 files changed, 57 insertions(+), 228 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2dd8de089..4be9674f7 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1406,30 +1406,6 @@ export namespace DocUtils { return undefined; } - export async function addFieldEnumerations(doc: Opt, enumeratedFieldKey: string, enumerations: { title: string, _backgroundColor?: string, color?: string }[]) { - let optionsCollection = await DocServer.GetRefField(enumeratedFieldKey); - if (!(optionsCollection instanceof Doc)) { - optionsCollection = Docs.Create.StackingDocument([], { title: `${enumeratedFieldKey} field set`, system: true }, enumeratedFieldKey); - Doc.AddDocToList((Doc.UserDoc().fieldTypes as Doc), "data", optionsCollection as Doc); - } - const options = optionsCollection as Doc; - const targetDoc = doc && Doc.GetProto(Cast(doc.rootDocument, Doc, null) || doc); - const docFind = `options.data?.find(doc => doc.title === (this.rootDocument||this)["${enumeratedFieldKey}"])?`; - targetDoc && (targetDoc.backgroundColor = ComputedField.MakeFunction(docFind + `._backgroundColor || "white"`, undefined, { options })); - targetDoc && (targetDoc.color = ComputedField.MakeFunction(docFind + `.color || "black"`, undefined, { options })); - targetDoc && (targetDoc.borderRounding = ComputedField.MakeFunction(docFind + `.borderRounding`, undefined, { options })); - enumerations.map(enumeration => { - const found = DocListCast(options.data).find(d => d.title === enumeration.title); - if (found) { - found._backgroundColor = enumeration._backgroundColor || found._backgroundColor; - found._color = enumeration.color || found._color; - } else { - Doc.AddDocToList(options, "data", Docs.Create.TextDocument(enumeration.title, { ...enumeration, system: true })); - } - }); - return optionsCollection; - } - // /** // * // * @param dms Degree Minute Second format exif gps data diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8fed6e940..e0580d3c4 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -75,201 +75,68 @@ export class CurrentUserUtils { @observable public static searchPanelWidth: number = 0; // sets up the default User Templates - slideView, headerView - static setupUserTemplateButtons(doc: Doc) { - // Prototype for mobile button (not sure if 'Advanced Item Prototypes' is ideal location) - if (doc["template-mobile-button"] === undefined) { - const queryTemplate = this.mobileButton({ - title: "NEW MOBILE BUTTON", - onClick: undefined, - }, - [this.createToolButton({ - ignoreClick: true, - icon: "mobile", - btnType: ButtonType.ToolButton, - backgroundColor: "transparent" - }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); - doc["template-mobile-button"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, title: "mobile button", icon: "mobile", btnType: ButtonType.ToolButton, - }); - } - - if (doc["template-button-slides"] === undefined) { - const slideTemplate = Docs.Create.MultirowDocument( - [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) - ], - { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } - ); - slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); - doc["template-button-slides"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, title: "presentation slide", icon: "address-card", - btnType: ButtonType.ToolButton - }); - } - - if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to. - const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _autoHeight: true, system: true }, "header")); // text needs to be a space to allow templateText to be created - linkTemplate.system = true; - Doc.GetProto(linkTemplate).layout = - "
    " + - " " + - " " + - "
    "; - (linkTemplate.proto as Doc).isTemplateDoc = makeTemplate(linkTemplate.proto as Doc, true, "linkView"); - - const rtf2 = { - doc: { - type: "doc", content: [ - { - type: "paragraph", - content: [{ - type: "dashField", - attrs: { - fieldKey: "src", - hideKey: false - } - }] - }, - { type: "paragraph" }, - { - type: "paragraph", - content: [{ - type: "dashField", - attrs: { - fieldKey: "dst", - hideKey: false - } - }] - }] + static setupExperimentalTemplateButtons(doc: Doc) { + if (doc["template-experimental-buttons"] === undefined) { + const requiredTypeNameFields = [ + { + type: "slide", icon: "address-card", template: Docs.Create.MultirowDocument( + [ + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) + ], + { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } + ) }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - linkTemplate.header = new RichTextField(JSON.stringify(rtf2), ""); - - doc["template-button-link"] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, title: "link view", icon: "window-maximize", system: true, - btnType: ButtonType.ToolButton + { + type: "mobile", icon: "mobile", template: this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, + [this.createToolButton({ ignoreClick: true, icon: "mobile", btnType: ButtonType.ToolButton, backgroundColor: "transparent" }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) + ] + ) + }, + ]; + const requiredTypes = requiredTypeNameFields.map(({ type, icon, template }) => { + if (doc[`template-button-${type}`] === undefined) { + template.isTemplateDoc = makeTemplate(template); + doc[`template-button-${type}`] = CurrentUserUtils.createToolButton({ + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), + dragFactory: new PrefetchProxy(template) as any as Doc, + title: type, + icon, + btnType: ButtonType.ToolButton, + }); + } + return doc[`template-button-${type}`] as Doc; }); - } - // if (doc["template-button-switch"] === undefined) { - // const { FreeformDocument, MulticolumnDocument, TextDocument } = Docs.Create; - - // const yes = FreeformDocument([], { title: "yes", _height: 35, _width: 50, _dimUnit: DimUnit.Pixel, _dimMagnitude: 40, system: true }); - // const name = TextDocument("name", { title: "name", _height: 35, _width: 70, _dimMagnitude: 1, system: true }); - // const no = FreeformDocument([], { title: "no", _height: 100, _width: 100, system: true }); - // const labelTemplate = { - // doc: { - // type: "doc", content: [{ - // type: "paragraph", - // content: [{ type: "dashField", attrs: { fieldKey: "PARAMS", hideKey: true } }] - // }] - // }, - // selection: { type: "text", anchor: 1, head: 1 }, - // storedMarks: [] - // }; - // Doc.GetProto(name).text = new RichTextField(JSON.stringify(labelTemplate), "PARAMS"); - // Doc.GetProto(yes).backgroundColor = ComputedField.MakeFunction("self[this.PARAMS] ? 'green':'red'"); - // // Doc.GetProto(no).backgroundColor = ComputedField.MakeFunction("!self[this.PARAMS] ? 'red':'white'"); - // // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = true"); - // Doc.GetProto(yes).onClick = ScriptField.MakeScript("self[this.PARAMS] = !self[this.PARAMS]"); - // // Doc.GetProto(no).onClick = ScriptField.MakeScript("self[this.PARAMS] = false"); - // const box = MulticolumnDocument([/*no, */ yes, name], { title: "value", _width: 120, _height: 35, system: true }); - // box.isTemplateDoc = makeTemplate(box, true, "switch"); - - // doc["template-button-switch"] = CurrentUserUtils.createToolButton({ - // onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - // dragFactory: new PrefetchProxy(box) as any as Doc, title: "data switch", icon: "toggle-on", system: true, - // btnType: ButtonType.ToolButton - // }); - // } - - const requiredTypes = [ - doc["template-button-slides"] as Doc, - doc["template-mobile-button"] as Doc, - doc["template-button-link"] as Doc, - //doc["template-button-switch"] as Doc] - ]; - if (doc["template-buttons"] === undefined) { - doc["template-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { - title: "Advanced Item Prototypes", _xMargin: 0, _showTitle: "title", _chromeHidden: true, + doc["template-experimental-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { + title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, _stayInCollection: true, _hideContextMenu: true, _forceActive: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true })); - } else { - const curButnTypes = Cast(doc["template-buttons"], Doc, null); - DocListCastAsync(curButnTypes.data).then(async curBtns => { - curBtns && await Promise.all(curBtns); - requiredTypes.map(btype => Doc.AddDocToList(curButnTypes, "data", btype)); - }); } - return doc["template-buttons"] as Doc; + return doc["template-experimental-buttons"] as Doc; } // setup the different note type skins static setupNoteTemplates(doc: Doc) { - if (doc["template-note-Note"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", isTemplateDoc: true, backgroundColor: "yellow", system: true, icon: "sticky-note", - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Note"); - doc["template-note-Note"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Idea"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", backgroundColor: "pink", system: true, icon: "lightbulb", - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Idea"); - doc["template-note-Idea"] = new PrefetchProxy(noteView); - } - if (doc["template-note-Topic"] === undefined) { - const noteView = Docs.Create.TextDocument("", { - title: "text", backgroundColor: "lightblue", system: true, icon: "book-open", - _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - }); - noteView.isTemplateDoc = makeTemplate(noteView, true, "Topic"); - doc["template-note-Topic"] = new PrefetchProxy(noteView); - } - // if (doc["template-note-Todo"] === undefined) { - // const noteView = Docs.Create.TextDocument("", { - // title: "text", backgroundColor: "orange", _autoHeight: false, _height: 100, _showCaption: "caption", - // layout: FormattedTextBox.LayoutString("Todo"), caption: RichTextField.DashField("taskStatus"), system: true, - // _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize), - // }); - // noteView.isTemplateDoc = makeTemplate(noteView, true, "Todo"); - // doc["template-note-Todo"] = new PrefetchProxy(noteView); - // } - // const taskStatusValues = [ - // { title: "todo", _backgroundColor: "blue", color: "white", system: true }, - // { title: "in progress", _backgroundColor: "yellow", color: "black", system: true }, - // { title: "completed", _backgroundColor: "green", color: "white", system: true } - // ]; - // if (doc.fieldTypes === undefined) { - // doc.fieldTypes = Docs.Create.TreeDocument([], { title: "field enumerations", system: true }); - // DocUtils.addFieldEnumerations(Doc.GetProto(doc["template-note-Todo"] as any as Doc), "taskStatus", taskStatusValues); - // } - if (doc["template-notes"] === undefined) { - doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument([doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc], // doc["template-note-Todo"] as any as Doc], - { title: "Note Layouts", _height: 75, system: true })); - } else { - const curNoteTypes = Cast(doc["template-notes"], Doc, null); - const requiredTypes = [doc["template-note-Note"] as any as Doc, doc["template-note-Idea"] as any as Doc, doc["template-note-Topic"] as any as Doc];//, doc["template-note-Todo"] as any as Doc]; - DocListCastAsync(curNoteTypes.data).then(async curNotes => { - curNotes && await Promise.all(curNotes); - requiredTypes.map(ntype => Doc.AddDocToList(curNoteTypes, "data", ntype)); - }); + const requiredTypeNameFields = [ + { type: "Note", backgroundColor: "yellow", icon: "sticky-note" }, + { type: "Idea", backgroundColor: "pink", icon: "lightbulb" }, + { type: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; + const requiredTypes = requiredTypeNameFields.map(({ type, backgroundColor, icon }) => + doc[`template-note-${type}`] as Doc ?? + (() => { + const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor, system: true, icon }); + noteView.isTemplateDoc = makeTemplate(noteView, true, type); + return doc[`template-note-${type}`] = new PrefetchProxy(noteView); + })()); + + doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument(requiredTypes, { title: "Note Layouts", _height: 75, system: true })); } return doc["template-notes"] as Doc; @@ -277,11 +144,12 @@ export class CurrentUserUtils { // creates Note templates, and initial "user" templates static setupDocTemplates(doc: Doc) { - const noteTemplates = CurrentUserUtils.setupNoteTemplates(doc); - const userTemplateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); - const clickTemplates = CurrentUserUtils.setupClickEditorTemplates(doc); if (doc.templateDocs === undefined) { - doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([noteTemplates, userTemplateBtns, clickTemplates], { + doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([ + CurrentUserUtils.setupNoteTemplates(doc), + CurrentUserUtils.setupExperimentalTemplateButtons(doc), + CurrentUserUtils.setupClickEditorTemplates(doc) + ], { title: "template layouts", _xMargin: 0, system: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) })); @@ -717,7 +585,7 @@ export class CurrentUserUtils { static async setupToolsBtnPanel(doc: Doc) { // setup a masonry view of all he creators const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); - const templateBtns = CurrentUserUtils.setupUserTemplateButtons(doc); + const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); doc["tabs-button-tools"] = undefined; @@ -1260,7 +1128,7 @@ export class CurrentUserUtils { doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; doc._raiseWhenDragged = true; - doc._showLabel = false; + doc._showLabel = true; doc._showMenuLabel = true; doc.textAlign = StrCast(doc.textAlign, "left"); doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4338dedcc..4630b3bf2 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -482,7 +482,6 @@ export class CollectionStackingView extends CollectionSubView { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion)); + const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); const rowWidth = () => panelWidth() - treeBulletWidth(); const treeViewRefs = new Map(); return docs.filter(child => child instanceof Doc).map((child, i) => { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 49cd39f43..5ea6d567a 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -76,7 +76,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { return
    e.stopPropagation()} // prevent triggering slider movement in registerSliding - onClick={e => this.clearDoc(e, `compareBox-${which}`)}> + onClick={e => this.clearDoc(e, which)}>
    ; }; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 34908e54b..1a8352b72 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -58,7 +58,6 @@ export class DashFieldViewInternal extends React.Component(); - @observable _showEnumerables: boolean = false; @observable _dashDoc: Doc | undefined; constructor(props: IDashFieldViewInternal) { @@ -110,7 +109,6 @@ export class DashFieldViewInternal extends React.Component this.fieldSpanKeyDown(e, r)); r?.addEventListener("blur", e => r && this.updateText(r.textContent!, false)); r?.addEventListener("pointerdown", action((e) => { - this._showEnumerables = true; e.stopPropagation(); })); }} > @@ -124,7 +122,6 @@ export class DashFieldViewInternal extends React.Component { if (e.key === "Enter") { // handle the enter key by "submitting" the current text to Dash's database. - e.ctrlKey && DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: span.textContent! }]); this.updateText(span.textContent!, true); e.preventDefault();// prevent default to avoid a newline from being generated and wiping out this field view } @@ -142,7 +139,6 @@ export class DashFieldViewInternal extends React.Component { - this._showEnumerables = false; if (nodeText) { const newText = nodeText.startsWith(":=") || nodeText.startsWith("=:=") ? ":=-computed-" : nodeText; @@ -154,7 +150,6 @@ export class DashFieldViewInternal extends React.Component (forceMatch ? StrCast(opt.title).startsWith(newText) : StrCast(opt.title) === newText) && (modText = StrCast(opt.title))); if (modText) { // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; - DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true); } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key else if (nodeText.startsWith(":=")) { @@ -178,13 +173,6 @@ export class DashFieldViewInternal extends React.Component { - e.stopPropagation(); - const collview = await DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, [{ title: this._fieldKey }]); - collview instanceof Doc && this.props.tbox.props.addDocTab(collview, "add:right"); - } - // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label @@ -220,8 +208,6 @@ export class DashFieldViewInternal extends React.Component} -
    ; } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 0cddc2350124f7ae870e362a4774de6f4d9a0545 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 May 2022 10:12:19 -0400 Subject: more code cleanup in currentuserutils. some fixes to dragFactory-count titles. --- src/Utils.ts | 1 + src/client/documents/Documents.ts | 9 +- src/client/util/CurrentUserUtils.ts | 156 ++++++--------------- src/client/util/DragManager.ts | 6 +- src/client/views/MainView.tsx | 3 +- src/client/views/collections/CollectionMenu.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 13 +- 7 files changed, 66 insertions(+), 124 deletions(-) (limited to 'src/client/documents') diff --git a/src/Utils.ts b/src/Utils.ts index c3c13a555..dbae0aa8c 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -8,6 +8,7 @@ import Color = require('color'); export namespace Utils { export let DRAG_THRESHOLD = 4; + export let SNAP_THRESHOLD = 10; export function readUploadedFileAsText(inputFile: File) { const temporaryFileReader = new FileReader(); diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 4be9674f7..6fa467368 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -156,6 +156,7 @@ export class DocumentOptions { "_carousel-caption-yMargin"?: number; "icon-nativeWidth"?: number; "icon-nativeHeight"?: number; + "dragFactory-count"?: number; // number of items created from a drag button (used for setting title with incrementing index) x?: number; y?: number; z?: number; // whether document is in overlay (1) or not (0 or undefined) @@ -672,8 +673,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options }); } - export function PresDocument(initial: List = new List(), options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.PRES), initial, options); + export function PresDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List(), options); } export function ScriptingDocument(script: Opt, options: DocumentOptions = {}, fieldKey?: string) { @@ -693,8 +694,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options); } - export function ScreenshotDocument(title: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", { ...options, title }); + export function ScreenshotDocument(options: DocumentOptions = {}) { + return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options); } export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b9789ab7f..85769d915 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -24,7 +24,7 @@ import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; -import { DragManager } from "./DragManager"; +import { DragManager, dropActionType } from "./DragManager"; import { makeTemplate, MakeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; @@ -55,7 +55,6 @@ interface Button { } export let resolvedPorts: { server: number, socket: number }; -const headerViewVersion = "0.1"; export class CurrentUserUtils { private static curr_id: string; //TODO tfs: these should be temporary... @@ -87,7 +86,7 @@ export class CurrentUserUtils { }, { type: "mobile", icon: "mobile", template: this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, - [this.createToolButton({ ignoreClick: true, icon: "mobile", btnType: ButtonType.ToolButton, backgroundColor: "transparent" }), + [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), this.mobileTextContainer({}, [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) ] @@ -102,7 +101,6 @@ export class CurrentUserUtils { dragFactory: new PrefetchProxy(template) as any as Doc, title: type, icon, - btnType: ButtonType.ToolButton, }); } return doc[`template-button-${type}`] as Doc; @@ -179,6 +177,9 @@ export class CurrentUserUtils { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", system: true, onClick: deiconifyScript(), ...extra }); + const fontBox = () => Docs.Create.FontIconDocument({ + _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() + }); if (!templateIconsDoc) { const newIconsList = [ makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), @@ -189,7 +190,7 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.BUTTON, "data", () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() })), + makeIconTemplate(DocumentType.BUTTON, "data", () => fontBox()), // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) ].filter(d => d).map(d => d!); @@ -201,30 +202,23 @@ export class CurrentUserUtils { title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc }[] { + const standardOps = () => ({ _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); if (doc.emptyPresentation === undefined) { - doc.emptyPresentation = Docs.Create.PresDocument(new List(), - { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyPresentation = Docs.Create.PresDocument({ ...standardOps(), title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); } if (doc.emptyCollection === undefined) { - doc.emptyCollection = Docs.Create.FreeformDocument([], - { _nativeWidth: undefined, _nativeHeight: undefined, _fitWidth: true, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyCollection = Docs.Create.FreeformDocument([], { ...standardOps(), title: "freeform", _width: 150, _height: 100 }); } if (doc.emptyPane === undefined) { - doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _backgroundGridShow: true, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyPane = Docs.Create.FreeformDocument([], { ...standardOps(), title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }); } if (doc.emptySlide === undefined) { - const textDoc = Docs.Create.TreeDocument([], { - title: "Slide", _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, - allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, - backgroundColor: "white", system: true, cloneFieldFilter: new List(["system"]) + doc.emptySlide = Docs.Create.TreeDocument([], { + ...standardOps(), title: ComputedField.MakeFunction('self.text?.Text') as any, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined, + allowOverlayDrop: true, treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" }); - Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); - doc.emptySlide = textDoc; } - if ((doc.emptyHeader as Doc)?.version !== headerViewVersion) { + if (doc.emptyHeader === undefined) { const json = { doc: { type: "doc", @@ -245,9 +239,8 @@ export class CurrentUserUtils { storedMarks: [] }; const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { - title: "text", version: headerViewVersion, _height: 70, _headerPointerEvents: "all", - _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, _fitWidth: true, - cloneFieldFilter: new List(["system"]) + ...standardOps(), title: "text", _height: 70, + _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, }, "header"); const headerBtnHgt = 10; headerTemplate[DataSym].layout = @@ -263,43 +256,34 @@ export class CurrentUserUtils { // "
    "; (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView"); doc.emptyHeader = headerTemplate; - ((doc.emptyHeader as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "comparison box", _width: 300, _height: 300, system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "Comparer", _width: 300, _height: 300 }); } if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { ...standardOps(), title: "script", _width: 200, _height: 250, }); } if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument("empty screenshot", { _fitWidth: true, title: "empty screenshot", _width: 400, _height: 200, system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyScreenshot = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "empty screenshot", _width: 400, _height: 200 }); } if (doc.emptyWall === undefined) { - doc.emptyWall = Docs.Create.ScreenshotDocument("", { _fitWidth: true, _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyWall = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "screen snapshot", _width: 400, _height: 200, }); (doc.emptyWall as Doc).videoWall = true; } if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, _height: 100, title: "audio recording", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { ...standardOps(), title: "audio recording", _width: 200, _height: 100, }); } if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { _width: 200, title: "text note", _autoHeight: true, system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyNote as Doc).proto as Doc)["dragFactory-count"] = 0; - } - if (doc.emptyImage === undefined) { - doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true }); + doc.emptyNote = Docs.Create.TextDocument("", { ...standardOps(), title: "text note", _width: 200, _autoHeight: true }); } if (doc.emptyButton === undefined) { - doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyButton = Docs.Create.ButtonDocument({ ...standardOps(), title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }); } if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, system: true, cloneFieldFilter: new List(["system"]) }); + doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { ...standardOps(), title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }); } if (doc.emptyMap === undefined) { - doc.emptyMap = Docs.Create.MapDocument([], { title: "map", _showSidebar: true, _width: 800, _height: 600, system: true, cloneFieldFilter: new List(["system"]) }); - ((doc.emptyMap as Doc).proto as Doc)["dragFactory-count"] = 0; + doc.emptyMap = Docs.Create.MapDocument([], { ...standardOps(), title: "map", _showSidebar: true, _width: 800, _height: 600, }); } if (doc.activeMobileMenu === undefined) { this.setupActiveMobileMenu(doc); @@ -309,7 +293,6 @@ export class CurrentUserUtils { { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc }, - { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc }, { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc }, { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc }, @@ -719,30 +702,21 @@ export class CurrentUserUtils { })) as any as Doc static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ - ...opts, btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _removeDropProperties: new List(["_dropAction", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true + btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, })) as any as Doc /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupDockedButtons(doc: Doc) { if (doc.dockedBtns === undefined) { - if (doc["dockedBtn-undo"] === undefined) { - doc["dockedBtn-undo"] = CurrentUserUtils.createToolButton({ - title: "undo", icon: "undo-alt", _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, system: true, - onClick: ScriptField.MakeScript("undo()"), btnType: ButtonType.ToolButton, _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to undo" - }); - } - if (doc["dockedBtn-redo"] === undefined) { - doc["dockedBtn-redo"] = CurrentUserUtils.createToolButton({ - title: "redo", icon: "redo-alt", _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, system: true, - onClick: ScriptField.MakeScript("redo()"), btnType: ButtonType.ToolButton, _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_hideContextMenu", "stayInCollection"]), toolTip: "Click to redo", - }); - } + const dockBtn = (opts: DocumentOptions) => CurrentUserUtils.createToolButton({ _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...opts }); + const btnDescs = [ + { title: "undo", opts: () => ({ icon: "undo-alt", onClick: ScriptField.MakeScript("undo()"), toolTip: "Click to undo" }) }, + { title: "redo", opts: () => ({ icon: "redo-alt", onClick: ScriptField.MakeScript("redo()"), toolTip: "Click to redo" }) } + ]; doc.dockedBtns = CurrentUserUtils.linearButtonList({ title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true - }, [doc["dockedBtn-undo"] as Doc, doc["dockedBtn-redo"] as Doc]); + }, btnDescs.map(desc => doc[`dockedBtn-${desc.title}`] as Doc ?? (doc[`dockedBtn-${desc.title}`] = dockBtn({ title: desc.title, ...desc.opts() })))); } } @@ -996,7 +970,7 @@ export class CurrentUserUtils { static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); - const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + await DocListCastAsync((doc.globalGroupDatabase as Doc).data); reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]), async () => { const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); @@ -1025,9 +999,6 @@ export class CurrentUserUtils { doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false); doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); doc.noviceMode = BoolCast(doc.noviceMode, true); - doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // - doc["constants-dragThreshold"] = NumCast(doc["constants-dragThreshold"], 4); // - Utils.DRAG_THRESHOLD = NumCast(doc["constants-dragThreshold"]); doc.savedFilters = new List(); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; @@ -1049,15 +1020,6 @@ export class CurrentUserUtils { doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - // uncomment this to setup a default note style that uses the custom header layout - // PromiseValue(doc.emptyHeader).then(factory => { - // if (Cast(doc.defaultTextLayout, Doc, null)?.version !== headerViewVersion) { - // const deleg = Doc.delegateDragFactory(factory as Doc); - // deleg.title = "header"; - // doc.defaultTextLayout = new PrefetchProxy(deleg); - // Doc.AddDocToList(Cast(doc["template-notes"], Doc, null), "data", deleg); - // } - // }); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { @@ -1264,26 +1226,16 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { view && SelectionManager.SelectView(view, false); } }); -ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, - "document containing all shared Docs"); -ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, - "is Dash in novice mode"); -ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, - "creates a snapshot copy of a dashboard"); -ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, - "creates a new dashboard when called"); -ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, - "creates a new presentation when called"); -ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, - "creates a new folder in myFiles when called"); -ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, - "returns all the links to the document or its annotations", "(doc: any)"); -ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, - "imports files from device directly into the import sidebar"); -ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { - SharingManager.Instance.open(undefined, dashboard); -}, - "opens sharing dialog for Dashboard"); +ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, "document containing all shared Docs"); +ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode"); +ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); +ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); +ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called"); +ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); +ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); +ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); +ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); +ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); if (dashboards && dashboards.length > 1) { @@ -1292,21 +1244,10 @@ ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { } }, "Remove Dashboard from Dashboards"); -ScriptingGlobals.add(async function addToDashboards(dashboard: Doc) { +ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); - - const allDocs = await DocListCastAsync(dashboard[DataSym]["data-all"]); - - // moves the data-all field from the datadoc to the layoutdoc, necessary for off screen docs tab to function properly - // dashboard["data-all"] = new List(allDocs); - // dashboardAlias["data-all"] = new List((allDocs || []).map(doc => Doc.MakeAlias(doc))); - - // const dockingConfig = JSON.parse(StrCast(dashboardAlias.dockingConfig)); - // dashboardAlias.dockingConfig = JSON.stringify(dockingConfig); - dashboardAlias.data = new List(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder))); DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias); - //new List(); DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any; Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias); @@ -1344,9 +1285,6 @@ ScriptingGlobals.add(function makeTopLevelFolder() { TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined }; return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder); }); -ScriptingGlobals.add(function toggleComicMode() { - Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; -}); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { if (readOnly) return; const sel = SelectionManager.Views()[0]; @@ -1359,8 +1297,7 @@ ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { CollectionFreeFormDocumentView.updateKeyframe(col.childDocs, currentFrame || 0); sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame)); -} -); +}); ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { if (readOnly) return; const sel = SelectionManager.Views()[0]; @@ -1372,5 +1309,4 @@ ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { } CollectionFreeFormDocumentView.gotoKeyframe(col.childDocs.slice()); sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); -} -); \ No newline at end of file +}); \ No newline at end of file diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 5a00e0c04..9f8c49081 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -7,7 +7,7 @@ import { listSpec } from "../../fields/Schema"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; -import { emptyFunction } from "../../Utils"; +import { emptyFunction, Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import * as globalCssVariables from "../views/global/globalCssVariables.scss"; import { DocumentView } from "../views/nodes/DocumentView"; @@ -277,7 +277,7 @@ export namespace DragManager { SnappingManager.setSnapLines(horizLines, vertLines); } export function snapDragAspect(dragPt: number[], snapAspect: number) { - let closest = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10); + let closest = Utils.SNAP_THRESHOLD; let near = dragPt; const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => { if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0 @@ -312,7 +312,7 @@ export namespace DragManager { } // snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) { - const snapThreshold = NumCast(Doc.UserDoc()["constants-snapThreshold"], 10); + const snapThreshold = Utils.SNAP_THRESHOLD; const snapVal = (pts: number[], drag: number, snapLines: number[]) => { if (snapLines.length) { const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 45dd84e31..6a2df3b51 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -244,8 +244,7 @@ export class MainView extends React.Component { title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true })); } - const pres = Docs.Create.PresDocument(new List(), - { title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); + const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); CollectionDockingView.AddSplit(pres, "right"); this.userDoc.activePresentation = pres; Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index b3fbd4ca4..c322d9e16 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -467,7 +467,7 @@ export class CollectionViewBaseChrome extends React.Component { - const doc = Docs.Create.ScreenshotDocument("screen recording", { _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); + const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); CollectionDockingView.AddSplit(doc, "right"); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d0c8c5826..164bcb9b0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -17,7 +17,7 @@ import { InkTool } from '../../../../fields/InkField'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; -import { Cast, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; +import { Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, OmitKeys, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; @@ -62,6 +62,7 @@ import { schema } from "./schema_rts"; import { SummaryView } from "./SummaryView"; import applyDevTools = require("prosemirror-dev-tools"); import React = require("react"); +import { ComputedField } from '../../../../fields/ScriptField'; const translateGoogleApi = require("translate-google-api"); export interface FormattedTextBoxProps { @@ -371,9 +372,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp while (node.firstChild && node.firstChild.type.name !== "text") node = node.firstChild; const str = node.textContent; const prefix = str.startsWith("@") ? "" : "-"; - this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); - if (str.startsWith("@") && str.length > 1) { - Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc); + + const cfield = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc.title)); + if (!(cfield instanceof ComputedField)) { + this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); + if (str.startsWith("@") && str.length > 1) { + Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc); + } } } } -- cgit v1.2.3-70-g09d2 From d50b851d8e92797d214fc35cf243bfb29e970b36 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 23 May 2022 21:58:26 -0400 Subject: simplified dashboards by getting rid of confusing on-screen/off-screen tabs. Now off screen tabs appear in the header bar which is not currently specific to a dashboard. --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 33 +-------------- src/client/views/MainView.tsx | 4 +- .../views/collections/CollectionDockingView.tsx | 48 +++++++++++++++++----- src/client/views/collections/TabDocView.tsx | 3 +- 5 files changed, 43 insertions(+), 49 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6fa467368..c3b6953b5 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -905,9 +905,7 @@ export namespace Docs { } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true, isFolder: true }); - const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true, isFolder: true }); - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 1e42b9073..9212d8ed9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1172,22 +1172,8 @@ export class CurrentUserUtils { freeformDoc.context = dashboardDoc; // switching the tabs from the datadoc to the regular doc - const dashboardTabs = dashboardDoc[DataSym].data; - dashboardDoc[DataSym].data = new List(); - dashboardDoc.data = dashboardTabs; - - // collating all docs on the dashboard to make a data-all field - const allDocs = new List(); - const allDocs2 = new List(); // Array.from, spread, splice all cause so stack or acl issues for some reason - DocListCast(dashboardTabs).forEach(doc => { - const tabDocs = DocListCast(doc.data); - allDocs.push(...tabDocs); - allDocs2.push(...tabDocs); - }); - dashboardDoc[DataSym]["data-all"] = allDocs; - dashboardDoc["data-all"] = allDocs2; - DocListCast(dashboardDoc.data).forEach(doc => doc.dashboard = dashboardDoc); - DocListCast(dashboardDoc.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any; + const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); + dashboardDoc.data = new List(dashboardTabs); userDoc.activePresentation = presentation; @@ -1258,21 +1244,6 @@ ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { }, "adds Dashboard to set of Dashboards"); -/** - * Dynamically computes which docs should be rendered in the off-screen tabs tree of a dashboard. - */ -ScriptingGlobals.add(function dynamicOffScreenDocs(dashboard: Doc) { - if (dashboard[DataSym] instanceof Doc) { - const allDocs = DocListCast(dashboard["data-all"]); - const onScreenTab = DocListCast(dashboard.data)[0]; - const onScreenDocs = DocListCast(onScreenTab.data); - return new List(allDocs.reduce((result: Doc[], doc) => { - !onScreenDocs.includes(doc) && !onScreenDocs.includes(doc.aliasOf as Doc) && (result.push(doc)); - return result; - }, [])); - } - return []; -}); ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) { let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined; if (selected && checkParent) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 69e394790..f3dc2cf85 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -92,7 +92,7 @@ export class MainView extends React.Component { @computed private get headerBarDoc() { return this.userDoc ? CurrentUserUtils.MyHeaderBarDoc : CurrentUserUtils.MyHeaderBarDoc; } @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } - headerBarDocWidth = () => this._dashUIWidth; + headerBarDocWidth = () => this.mainDocViewWidth(); headerBarDocHeight = () => CurrentUserUtils.headerBarHeight ?? 0; topMenuHeight = () => 35; topMenuWidth = returnZero; // value is ignored ... @@ -102,7 +102,7 @@ export class MainView extends React.Component { leftMenuFlyoutHeight = () => this._dashUIHeight; propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0)); propertiesHeight = () => this._dashUIHeight; - mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth(); + mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth(); mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); componentDidMount() { diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 30f60bba6..1d65256a6 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -81,6 +81,8 @@ export class CollectionDockingView extends CollectionSubView() { tabItemDropped = () => DragManager.CompleteWindowDrag?.(false); tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => { + const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc; + dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc])); DragManager.CompleteWindowDrag = (aborted: boolean) => { if (aborted) { proxy._dragListener.AbortDrag(); @@ -90,6 +92,8 @@ export class CollectionDockingView extends CollectionSubView() { this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout) } DragManager.CompleteWindowDrag = undefined; + } else { + DragManager.DocDragData?.droppedDocuments.forEach(doc => this.addToTabDocList(doc)); } finishDrag?.(aborted); }; @@ -100,12 +104,43 @@ export class CollectionDockingView extends CollectionSubView() { this.stateChanged(); } + addToTabDocList = (document: Doc) => { + const instance = CollectionDockingView.Instance; + if (instance) { + const docList = DocListCast(instance.props.Document[DataSym].data); + // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases + !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data", document); + // adds an alias of the doc to the data-all field of the layoutdocs of the aliases + DocListCast(instance.props.Document.aliases).forEach(alias => { + const aliasDocList = DocListCast(alias.data); + // if aliasDocList contains the alias, don't do anything + // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias + !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); + }); + } + } + removeFromTabDocList = (document: Doc) => { + const instance = CollectionDockingView.Instance; + if (instance) { + // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases + Doc.RemoveDocFromList(instance.props.Document[DataSym], "data", document); + // adds an alias of the doc to the data-all field of the layoutdocs of the aliases + DocListCast(instance.props.Document.aliases).forEach(alias => { + // if aliasDocList contains the alias, don't do anything + // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias + Doc.RemoveDocFromList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); + }); + document.type !== DocumentType.KVP && Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", document); + } + } + @undoBatch public static CloseSplit(document: Opt, panelName?: string): boolean { const tab = Array.from(CollectionDockingView.Instance.tabMap.keys()).find((tab) => panelName ? tab.contentItem.config.props.panelName === panelName : tab.DashDoc === document); if (tab) { const j = tab.header.parent.contentItems.indexOf(tab.contentItem); if (j !== -1) { + CollectionDockingView.Instance.removeFromTabDocList(tab.dashDoc as Doc); tab.header.parent.contentItems[j].remove(); return CollectionDockingView.Instance.layoutChanged(); } @@ -185,16 +220,7 @@ export class CollectionDockingView extends CollectionSubView() { const instance = CollectionDockingView.Instance; if (!instance) return false; else { - const docList = DocListCast(instance.props.Document[DataSym]["data-all"]); - // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases - !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data-all", document); - // adds an alias of the doc to the data-all field of the layoutdocs of the aliases - DocListCast(instance.props.Document[DataSym].aliases).forEach(alias => { - const aliasDocList = DocListCast(alias["data-all"]); - // if aliasDocList contains the alias, don't do anything - // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias - !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data-all", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); - }); + instance.addToTabDocList(document); } const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName); @@ -477,7 +503,7 @@ export class CollectionDockingView extends CollectionSubView() { //if (confirm('really close this?')) { if (!stack.parent.parent.isRoot || stack.parent.contentItems.length > 1) { stack.remove(); - stack.contentItems.forEach((contentItem: any) => Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", contentItem.tab.DashDoc, undefined, true, true)); + stack.contentItems.forEach((contentItem: any) => CollectionDockingView.Instance.removeFromTabDocList(contentItem.tab.DashDoc)); } else { alert('cant delete the last stack'); } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 0070d0b9d..88d14fdfe 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -180,8 +180,7 @@ export class TabDocView extends React.Component { tab.closeElement.off('click') //unbind the current click handler .click(function () { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", doc); - Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", doc, undefined, true, true); + CollectionDockingView.Instance?.removeFromTabDocList(doc); SelectionManager.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); }); -- cgit v1.2.3-70-g09d2 From a01cd55030f549b1c4c207d23731a00e689989c3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 25 May 2022 15:18:46 -0400 Subject: variety of small fixes for text / sharing. made typed text default to 35 height so that when shared it doesn't start big and jump to small. changed permissions to be private until an Acl is set - this prevents private docs from flicking on momentarily when shared since fields are not distributed atomically. added Shift/Alt/Ctrl Enter for freeform and stacking views to create neighboring docs. fixed first typed char of text to have a user_mark. made shared text doc header overlap to prevent scrolling. --- src/client/DocServer.ts | 60 ++++++++++------------ src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 2 +- src/client/views/MainView.tsx | 4 +- src/client/views/StyleProvider.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 22 ++++++++ .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 16 +++--- .../formattedText/ProsemirrorExampleTransfer.ts | 14 +++-- src/fields/util.ts | 7 ++- 10 files changed, 77 insertions(+), 57 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index e498a7cca..dbc4783d8 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -364,40 +364,36 @@ export namespace DocServer { const proms: Promise[] = []; runInAction(() => { for (const field of fields) { - if (field !== undefined && field !== null && !_cache[field.id]) { + const cached = _cache[field.id]; + if (!cached) { // deserialize - const cached = _cache[field.id]; - if (!cached) { - const prom = SerializationHelper.Deserialize(field).then(deserialized => { - fieldMap[field.id] = deserialized; - - //overwrite or delete any promises (that we inserted as flags - // to indicate that the field was in the process of being fetched). Now everything - // should be an actual value within or entirely absent from the cache. - if (deserialized !== undefined) { - _cache[field.id] = deserialized; - } else { - delete _cache[field.id]; - } - return deserialized; - }); - // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. - // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). - // The mapping in the .then call ensures that when other callers await these promises, they'll - // get the resolved field - _cache[field.id] = prom; - - // adds to a list of promises that will be awaited asynchronously - proms.push(prom); - } else if (cached instanceof Promise) { - proms.push(cached as any); - } - } else if (_cache[field.id] instanceof Promise) { - proms.push(_cache[field.id] as any); - (_cache[field.id] as any).then((f: any) => fieldMap[field.id] = f); + const prom = SerializationHelper.Deserialize(field).then(deserialized => { + fieldMap[field.id] = deserialized; + + //overwrite or delete any promises (that we inserted as flags + // to indicate that the field was in the process of being fetched). Now everything + // should be an actual value within or entirely absent from the cache. + if (deserialized !== undefined) { + _cache[field.id] = deserialized; + } else { + delete _cache[field.id]; + } + return deserialized; + }); + // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) + // we set the value at the field's id to a promise that will resolve to the field. + // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). + // The mapping in the .then call ensures that when other callers await these promises, they'll + // get the resolved field + _cache[field.id] = prom; + + // adds to a list of promises that will be awaited asynchronously + proms.push(prom); + } else if (cached instanceof Promise) { + proms.push(cached as any); + cached.then((f: any) => fieldMap[field.id] = f); } else if (field) { - proms.push(_cache[field.id] as any); + proms.push(cached as any); fieldMap[field.id] = DocServer.GetCachedRefField(field.id) || field; } } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c3b6953b5..ed37c48dc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -369,7 +369,7 @@ export namespace Docs { [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, options: { - _height: 150, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, + _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, links: "@links(self)" } }], diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e4dc175bd..436237d6c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1184,7 +1184,7 @@ export class CurrentUserUtils { public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { const tbox = Docs.Create.TextDocument("", { _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor, - _width: width || 200, _height: height || 100, x: x, y: y, _fitWidth: true, _autoHeight: true, title + _width: width || 200, _height: 35, x: x, y: y, _fitWidth: true, _autoHeight: true, title }); const template = Doc.UserDoc().defaultTextLayout; if (template instanceof Doc) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 183efc944..ff2857739 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -120,8 +120,8 @@ export class MainView extends React.Component { } this._sidebarContent.proto = undefined; if (!MainView.Live) { - DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "autoHeight", "showSidebar", "sidebarWidthPercent", "viewTransition", - "panX", "panY", "width", "height", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap", + DocServer.setPlaygroundFields(["dataTransition", "treeViewOpen", "showSidebar", "sidebarWidthPercent", "viewTransition", + "panX", "panY", "nativeWidth", "nativeHeight", "text-scrollHeight", "text-height", "hideMinimap", "viewScale", "scrollTop", "hidden", "curPage", "viewType", "chromeHidden", "nativeWidth"]); // can play with these fields on someone else's } DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index c44443264..553f84a67 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -70,6 +70,7 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab // export function DefaultStyleProvider(doc: Opt, props: Opt, property: string): any { + const remoteDocHeader = "author;creationDate;noMargin"; const docProps = testDocProps(props) ? props : undefined; const selected = property.includes(":selected"); const isCaption = property.includes(":caption"); @@ -111,7 +112,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + "color"], StrCast(doc?._color)); @@ -126,7 +127,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._viewType as any) || - doc?.type === DocumentType.RTF || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; + (doc?.type === DocumentType.RTF && !showTitle()?.includes("noMargin")) || doc?.type === DocumentType.LABEL) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { if (MainView.Instance.LastButton === doc) return Colors.LIGHT_GRAY; let docColor: Opt = diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 4630b3bf2..dddae4a34 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -28,6 +28,8 @@ import "./CollectionStackingView.scss"; import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; import { CollectionSubView } from "./CollectionSubView"; import { CollectionViewType } from "./CollectionView"; +import { FieldViewProps } from "../nodes/FieldView"; +import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; const _global = (window /* browser */ || global /* node */) as any; @@ -207,6 +209,25 @@ export class CollectionStackingView extends CollectionSubView { + const docView = fieldProps.DocumentView?.(); + if (docView && ["Enter"].includes(e.key) && e.ctrlKey) { + e.stopPropagation?.(); + const below = !e.altKey && e.key !== "Tab"; + const layoutKey = StrCast(docView.LayoutFieldKey); + const newDoc = Doc.MakeCopy(docView.rootDoc, true); + const dataField = docView.rootDoc[Doc.LayoutFieldKey(newDoc)]; + newDoc[DataSym][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; + if (layoutKey !== "layout" && docView.rootDoc[layoutKey] instanceof Doc) { + newDoc[layoutKey] = docView.rootDoc[layoutKey]; + } + Doc.GetProto(newDoc).text = undefined; + FormattedTextBox.SelectOnLoad = newDoc[Id]; + return this.addDocument?.(newDoc); + } + } isContentActive = () => this.props.isSelected() || this.props.isContentActive(); getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; @@ -225,6 +246,7 @@ export class CollectionStackingView extends CollectionSubView { const docView = fieldProps.DocumentView?.(); - if (docView && docView.rootDoc._singleLine && ["Tab", "Enter"].includes(e.key)) { + if (docView && (e.ctrlKey || e.shiftKey || e.altKey || docView.rootDoc._singleLine) && ["Tab", "Enter"].includes(e.key)) { e.stopPropagation?.(); const below = !e.altKey && e.key !== "Tab"; const layoutKey = StrCast(docView.LayoutFieldKey); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 822bc996b..d468822c0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -245,8 +245,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - const tsel = this._editorView.state.selection.$from; - //tsel.marks().filter(m => m.type === this._editorView!.state.schema.marks.user_mark).map(m => AudioBox.SetScrubTime(Math.max(0, m.attrs.modified * 1000))); const curText = state.doc.textBetween(0, state.doc.content.size, " \n"); const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.props.fieldKey], RichTextField) : undefined; // the actual text in the text box const curProto = Cast(Cast(this.dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype @@ -348,7 +346,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } autoLink = () => { - if (this._editorView) { + if (this._editorView?.state.doc.textContent) { const newAutoLinks = new Set(); const oldAutoLinks = DocListCast(this.props.Document.links).filter(link => link.linkRelationship === LinkManager.AutoKeywords); const f = this._editorView.state.selection.from; @@ -962,7 +960,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }, { fireImmediately: true } ); quickScroll = undefined; - setTimeout(this.tryUpdateScrollHeight, 10); + this.tryUpdateScrollHeight(); } pushToGoogleDoc = async () => { @@ -1193,19 +1191,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())); if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { + const selLoadChar = FormattedTextBox.SelectOnLoadChar; FormattedTextBox.SelectOnLoad = ""; this.props.select(false); - if (FormattedTextBox.SelectOnLoadChar && this._editorView) { + if (selLoadChar && this._editorView) { const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; const tr = this._editorView.state.tr.setStoredMarks(storedMarks).insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size).setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); - FormattedTextBox.SelectOnLoadChar = ""; - } else if (curText && !FormattedTextBox.DontSelectInitialText) { - selectAll(this._editorView!.state, this._editorView?.dispatch); + } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { + selectAll(this._editorView.state, this._editorView?.dispatch) this.startUndoTypingBatch(); + } else if (this._editorView) { + this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } FormattedTextBox.DontSelectInitialText = false; } diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 0dd0a8411..e979ae59e 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -80,8 +80,10 @@ export function buildKeymap>(schema: S, props: any, mapKey //Commands for lists bind("Ctrl-i", (state: EditorState, dispatch: (tx: Transaction) => void) => canEdit(state) && wrapInList(schema.nodes.ordered_list)(state as any, dispatch as any)); + bind("Ctrl-Tab", () => props.onKey?.(event, props) ? true : true); + bind("Alt-Tab", () => props.onKey?.(event, props) ? true : true); bind("Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (props.onKey?.({ key: "Tab" }, props)) return true; + if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const ref = state.selection; const range = ref.$from.blockRange(ref.$to); @@ -106,7 +108,7 @@ export function buildKeymap>(schema: S, props: any, mapKey }); bind("Shift-Tab", (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (props.onKey?.({ key: "Tab", shiftKey: true })) return true; // single line docs don't process tabs so that their containers can decide what to do. This should be a prop + if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); @@ -154,11 +156,9 @@ export function buildKeymap>(schema: S, props: any, mapKey return tx; }; - //Command to create a text document to the right of the selected textbox - bind("Alt-Enter", () => true); - //Command to create a text document to the bottom of the selected textbox - bind("Ctrl-Enter", () => true); + bind("Alt-Enter", () => props.onKey?.(event, props) ? true : true); + bind("Ctrl-Enter", () => props.onKey?.(event, props) ? true : true); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); bind("Backspace", (state: EditorState, dispatch: (tx: Transaction>) => void) => { @@ -244,8 +244,6 @@ export function buildKeymap>(schema: S, props: any, mapKey return false; }); - // mac && bind("Ctrl-Enter", cmd); - // bind("Mod-Enter", cmd); bind("Shift-Enter", cmd); return keys; diff --git a/src/fields/util.ts b/src/fields/util.ts index c708affe3..ef5ac79b8 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -175,8 +175,11 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { * Calculates the effective access right to a document for the current user. */ export function GetEffectiveAcl(target: any, user?: string): symbol { - return !target ? AclPrivate : - target[UpdatingFromServer] ? AclAdmin : getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) + if (!target) return AclPrivate; + if (target[UpdatingFromServer]) return AclAdmin; + // authored documents are private until an ACL is set. this also fixes notes that flicker on and off when a remote types to create a private note into a shared collection. + if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate; + return getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } function getPropAcl(target: any, prop: string | symbol | number) { -- cgit v1.2.3-70-g09d2 From 76d737a05093e4132f2ab56630c4003879747ef9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 26 May 2022 15:36:32 -0400 Subject: removed setting 'data' field of docking views on aliases. assigned acl's to delegates to match prototype. changed how documents are created to make sure acl's are set first so that, when sharing, remote clients will get the ACLs first and not render documents transiently when they don't have permissions. turned off inheriting ACL's from outer collection --- src/client/documents/Documents.ts | 15 +++++++++------ src/client/util/CurrentUserUtils.ts | 3 --- src/client/views/collections/CollectionDockingView.tsx | 17 ++--------------- src/client/views/global/globalCssVariables.scss | 2 +- src/fields/Doc.ts | 1 + src/fields/util.ts | 3 ++- 6 files changed, 15 insertions(+), 26 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ed37c48dc..8d29bcebe 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -639,13 +639,14 @@ export namespace Docs { const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_" const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); + dataProps["acl-Override"] = "None"; + dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + dataProps.system = viewProps.system; dataProps.isPrototype = true; dataProps.author = Doc.CurrentUserEmail; dataProps.creationDate = new DateField; dataProps[`${fieldKey}-lastModified`] = new DateField; - dataProps["acl-Override"] = "None"; - dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; dataProps[fieldKey] = data; @@ -654,10 +655,12 @@ export namespace Docs { dataProps[fieldKey + "-annotations"] = new List(); const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); - viewProps.author = Doc.CurrentUserEmail; - viewProps["acl-Override"] = "None"; - viewProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewProps, true, true); + const viewFirstProps: { [id: string]: any } = {}; + viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + viewFirstProps["acl-Override"] = "None"; + viewFirstProps.author = Doc.CurrentUserEmail; + const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); + Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) && diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 436237d6c..083530ce7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1236,9 +1236,6 @@ ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { "Remove Dashboard from Dashboards"); ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); - dashboardAlias.data = new List(DocListCast(dashboard.data).map(tabFolder => Doc.MakeAlias(tabFolder))); - DocListCast(dashboardAlias.data).forEach(doc => doc.dashboard = dashboardAlias); - DocListCast(dashboardAlias.data)[1].data = ComputedField.MakeFunction(`dynamicOffScreenDocs(self.dashboard)`) as any; Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias); }, diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 1d65256a6..c60434d95 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -109,27 +109,14 @@ export class CollectionDockingView extends CollectionSubView() { if (instance) { const docList = DocListCast(instance.props.Document[DataSym].data); // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases - !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document[DataSym], "data", document); - // adds an alias of the doc to the data-all field of the layoutdocs of the aliases - DocListCast(instance.props.Document.aliases).forEach(alias => { - const aliasDocList = DocListCast(alias.data); - // if aliasDocList contains the alias, don't do anything - // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias - !DocListCast(document.aliases).some(a => aliasDocList.includes(a)) && Doc.AddDocToList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); - }); + !docList.includes(document) && !docList.includes(document.aliasOf as Doc) && Doc.AddDocToList(instance.props.Document, "data", document); } } removeFromTabDocList = (document: Doc) => { const instance = CollectionDockingView.Instance; if (instance) { // adds the doc of the newly created tab to the data-all field if it doesn't already include that doc or one of its aliases - Doc.RemoveDocFromList(instance.props.Document[DataSym], "data", document); - // adds an alias of the doc to the data-all field of the layoutdocs of the aliases - DocListCast(instance.props.Document.aliases).forEach(alias => { - // if aliasDocList contains the alias, don't do anything - // otherwise add the original or an alias depending on whether the doc you're looking at is the current doc or a different alias - Doc.RemoveDocFromList(alias, "data", document);//alias !== instance.props.Document ? Doc.MakeAlias(document) : document); - }); + Doc.RemoveDocFromList(instance.props.Document, "data", document); document.type !== DocumentType.KVP && Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", document); } } diff --git a/src/client/views/global/globalCssVariables.scss b/src/client/views/global/globalCssVariables.scss index 520ac9357..a14634bdc 100644 --- a/src/client/views/global/globalCssVariables.scss +++ b/src/client/views/global/globalCssVariables.scss @@ -10,7 +10,7 @@ $black: #000000; $light-blue: #bdddf5; $light-blue-transparent: #bdddf590; $medium-blue: #4476f7; -$medium-blue-alt: #4476f73d; +$medium-blue-alt: #0047ff54; $pink: #e0217d; $yellow: #f5d747; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 194b3ba27..b0a45091e 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -821,6 +821,7 @@ export namespace Doc { delegate[Initializing] = true; delegate.proto = doc; delegate.author = Doc.CurrentUserEmail; + Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => delegate[key] = doc[key]); if (!Doc.IsSystem(doc)) Doc.AddDocToList(doc[DataSym], "aliases", delegate); title && (delegate.title = title); delegate[Initializing] = false; diff --git a/src/fields/util.ts b/src/fields/util.ts index ef5ac79b8..d1e565774 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -138,6 +138,7 @@ export function denormalizeEmail(email: string) { * Copies parent's acl fields to the child */ export function inheritParentAcls(parent: Doc, child: Doc) { + return; const dataDoc = parent[DataSym]; for (const key of Object.keys(dataDoc)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. @@ -177,7 +178,7 @@ const getEffectiveAclCache = computedFn(function (target: any, user?: string) { export function GetEffectiveAcl(target: any, user?: string): symbol { if (!target) return AclPrivate; if (target[UpdatingFromServer]) return AclAdmin; - // authored documents are private until an ACL is set. this also fixes notes that flicker on and off when a remote types to create a private note into a shared collection. + // authored documents are private until an ACL is set. if (!target[AclSym] && target.author && target.author !== Doc.CurrentUserEmail) return AclPrivate; return getEffectiveAclCache(target, user);// all changes received from the server must be processed as Admin. return this directly so that the acls aren't cached (UpdatingFromServer is not observable) } -- cgit v1.2.3-70-g09d2 From 9acba91baa0ee2ee43106d344392039a2cbd0e46 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 6 Jun 2022 10:16:13 -0400 Subject: fixed range filtering, filtering by string, --- src/client/documents/Documents.ts | 5 ++-- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/FilterBox.tsx | 33 ++++++++++++++++++++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 ++- 4 files changed, 37 insertions(+), 7 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ecacc9fd5..2ca9cdb71 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1048,11 +1048,10 @@ export namespace DocUtils { const key = docRangeFilters[i]; const min = Number(docRangeFilters[i + 1]); const max = Number(docRangeFilters[i + 2]); - const val = Cast(d[key], "number", null); - if (val < min || val > max) return false; + const val = typeof d[key] === "string" ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], "number", null); if (val === undefined) { //console.log("Should 'undefined' pass range filter or not?") - } + } else if (val < min || val > max) return false; } return true; }); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 9de44fb00..23b6a7f72 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -499,7 +499,7 @@ export class TreeView extends React.Component {
    ; } - return
      { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, returnFalse)}
    ; // "layout" + return
      { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}
    ; // "layout" } get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index a47e17dfc..28834a202 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -206,7 +206,7 @@ export class FilterBox extends ViewBoxBaseComponent() { facetValues.strings.map(val => { const num = val ? Number(val) : Number.NaN; if (Number.isNaN(num)) { - nonNumbers++; + num && nonNumbers++; } else { minVal = Math.min(num, minVal); maxVal = Math.max(num, maxVal); @@ -216,8 +216,9 @@ export class FilterBox extends ViewBoxBaseComponent() { if (facetHeader === "text" || facetValues.rtFields / allCollectionDocs.length > 0.1) { newFacet = Docs.Create.TextDocument("", { title: facetHeader, system: true, target: targetDoc, _width: 100, _height: 25, _stayInCollection: true, - treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true + treeViewExpandedView: "layout", _treeViewOpen: true, _forceActive: true, ignoreClick: true, }); + Doc.GetProto(newFacet).forceActive = true; // required for FormattedTextBox to be able to gain focus since it will never be 'selected' Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox newFacet._textBoxPaddingX = newFacet._textBoxPaddingY = 4; const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`; @@ -238,6 +239,7 @@ export class FilterBox extends ViewBoxBaseComponent() { Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal; const scriptText = `setDocRangeFilter(this?.target, "${facetHeader}", range)`; newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); + newFacet.data = ComputedField.MakeFunction(`readNumFacetData(self.target, self, "${FilterBox.targetDocChildKey}", "${facetHeader}")`); } else { newFacet = new Doc(); newFacet.system = true; @@ -401,6 +403,7 @@ export class FilterBox extends ViewBoxBaseComponent() { docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} + childDocumentsActive={returnTrue} ContainingCollectionDoc={this.props.ContainingCollectionDoc} ContainingCollectionView={this.props.ContainingCollectionView} PanelWidth={this.props.PanelWidth} @@ -479,6 +482,32 @@ ScriptingGlobals.add(function determineCheckedState(layoutDoc: Doc, facetHeader: } return undefined; }); +ScriptingGlobals.add(function readNumFacetData(layoutDoc: Doc, facetDoc: Doc, childKey: string, facetHeader: string) { + const allCollectionDocs = new Set(); + const activeTabs = DocListCast(layoutDoc[childKey]); + SearchBox.foreachRecursiveDoc(activeTabs, (depth: number, doc: Doc) => allCollectionDocs.add(doc)); + const set = new Set(); + if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key))); + else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field))); + const facetValues = Array.from(set).filter(v => v); + + let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; + facetValues.map(val => { + const num = val ? Number(val) : Number.NaN; + if (!Number.isNaN(num)) { + minVal = Math.min(num, minVal); + maxVal = Math.max(num, maxVal); + } + }); + const newFacetField = Doc.LayoutFieldKey(facetDoc); + const ranged = Doc.readDocRangeFilter(layoutDoc, facetHeader); + const extendedMinVal = minVal - Math.min(1, Math.floor(Math.abs(maxVal - minVal) * .1)); + const extendedMaxVal = Math.max(minVal + 1, maxVal + Math.min(1, Math.ceil(Math.abs(maxVal - minVal) * .05))); + facetDoc[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0]; + facetDoc[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1]; + Doc.GetProto(facetDoc)[newFacetField + "-minThumb"] = extendedMinVal; + Doc.GetProto(facetDoc)[newFacetField + "-maxThumb"] = extendedMaxVal; +}) ScriptingGlobals.add(function readFacetData(layoutDoc: Doc, childKey: string, facetHeader: string) { const allCollectionDocs = new Set(); const activeTabs = DocListCast(layoutDoc[childKey]); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index bf3c01d1f..7a500ac88 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -283,6 +283,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.dataDoc[this.props.fieldKey] = undefined; this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); this.dataDoc[this.props.fieldKey + "-noTemplate"] = undefined; // mark the data field as not being split from any template it might have + ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); unchanged = false; } this._applyingChange = ""; @@ -1248,6 +1249,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } onPointerDown = (e: React.PointerEvent): void => { + if (this.Document.forceActive) e.stopPropagation(); this.tryUpdateScrollHeight(); // if a doc a fitwidth doc is being viewed in different context (eg freeform & lightbox), then it will have conflicting heights. so when the doc is clicked on, we want to make sure it has the appropriate height for the selected view. if ((e.target as any).tagName === "AUDIOTAG") { e.preventDefault(); @@ -1641,7 +1643,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } render() { TraceMobx(); - const selected = this.props.isSelected(); + const selected = this.props.isSelected() || this.Document.forceActive; const active = this.props.isContentActive(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; -- cgit v1.2.3-70-g09d2 From c99825362f4a221af728f1dcee139c7403a7916f Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 6 Jun 2022 13:13:49 -0400 Subject: added auto links from recordings to pdfs when they scroll. opened audio links on right by default instead of in lightbox. focus on pdf/coponent anchor when following link from it (via the LinkMenu) --- src/client/documents/Documents.ts | 7 +++++-- src/client/views/InkTangentHandles.tsx | 3 +-- src/client/views/collections/CollectionStackedTimeline.tsx | 1 + src/client/views/linking/LinkMenuItem.tsx | 4 ++++ src/client/views/pdf/PDFViewer.tsx | 7 +++++++ 5 files changed, 18 insertions(+), 4 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2ca9cdb71..817478b2f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1097,8 +1097,11 @@ export namespace DocUtils { export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) { broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1); - return DocUtils.ActiveRecordings.map(audio => - DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline")); + return DocUtils.ActiveRecordings.map(audio => { + const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline"); + link && (link.followLinkLocation = "add:right"); + return link; + }); } export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { diff --git a/src/client/views/InkTangentHandles.tsx b/src/client/views/InkTangentHandles.tsx index b15e4260d..ae35bc980 100644 --- a/src/client/views/InkTangentHandles.tsx +++ b/src/client/views/InkTangentHandles.tsx @@ -13,7 +13,6 @@ import { Colors } from "./global/globalEnums"; import { InkingStroke } from "./InkingStroke"; import { InkStrokeProperties } from "./InkStrokeProperties"; import { DocumentView } from "./nodes/DocumentView"; - export interface InkHandlesProps { inkDoc: Doc; inkView: DocumentView; @@ -103,7 +102,7 @@ export class InkTangentHandles extends React.Component { r={screenSpaceLineWidth * 2} fill={Colors.MEDIUM_BLUE} strokeWidth={1} - stroke={Colors.MEDIUM_BLUE} + stroke={Colors.BLACK} onPointerDown={e => this.onHandleDown(e, pts.I)} pointerEvents="all" cursor="default" diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index ca0b9d3d3..ebdea9aaf 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -444,6 +444,7 @@ export class CollectionStackedTimeline extends CollectionSubView { this.props.itemHandler?.(this.props.linkDoc); } else { + const focusDoc = Cast(this.props.linkDoc.anchor1, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor1, Doc, null) : + Cast(this.props.linkDoc.anchor2, Doc, null)?.annotationOn === this.props.sourceDoc ? Cast(this.props.linkDoc.anchor12, Doc, null) : undefined; + + if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, true); LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false); } }); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index f706be6c6..0ded1bb3c 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -274,6 +274,8 @@ export class PDFViewer extends React.Component { } } + @observable private _scrollTimer: any; + onScroll = (e: React.UIEvent) => { if (this._mainCont.current && !this._forcedScroll) { this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation) @@ -281,6 +283,11 @@ export class PDFViewer extends React.Component { this.props.layoutDoc._scrollTop = this._mainCont.current.scrollTop; } this._ignoreScroll = false; + if (this._scrollTimer) clearTimeout(this._scrollTimer); // wait until a scrolling pause, then create an anchor to audio + this._scrollTimer = setTimeout(() => { + DocUtils.MakeLinkToActiveAudio(() => this.props.DocumentView?.().ComponentView?.getAnchor!()!, false); + this._scrollTimer = undefined; + }, 200); } } -- cgit v1.2.3-70-g09d2 From f8840672a7bcb3f337d8e50098f374a1b2d441ce Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 Jun 2022 01:17:24 -0400 Subject: added ability to make thumbnails of dashboards. started to cleanup dockingView/goldenlayout undo event handling. cleaned up tab doc list in docking view. made titles editable again in treeview. fixed overlay view to work with image docs etc by setting top/left in css --- src/client/documents/Documents.ts | 6 +-- src/client/goldenLayout.js | 2 +- src/client/util/CurrentUserUtils.ts | 26 ++++++++--- src/client/views/DocumentButtonBar.tsx | 53 ++++++++++------------ src/client/views/MainView.tsx | 12 +---- src/client/views/OverlayView.scss | 2 + src/client/views/OverlayView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 21 ++++++--- src/client/views/collections/TabDocView.tsx | 28 ++++-------- src/client/views/collections/TreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 45 ++++++++++-------- src/client/views/nodes/WebBoxRenderer.js | 4 +- src/client/views/topbar/TopBar.tsx | 6 +-- 13 files changed, 108 insertions(+), 103 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 817478b2f..acc1e341a 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -671,9 +671,9 @@ export namespace Docs { return viewDoc; } - export function ImageDocument(url: string, options: DocumentOptions = {}) { - const imgField = new ImageField(url); - return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(url), ...options }); + export function ImageDocument(url: string|ImageField, options: DocumentOptions = {}) { + const imgField = url instanceof ImageField ? url : new ImageField(url); + return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }); } export function PresDocument(options: DocumentOptions = {}) { diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 82b10608d..4735f216f 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -1431,7 +1431,7 @@ this.root.callDownwards('_$init'); if (config.maximisedItemId === '__glMaximised') { - this.root.getItemsById(config.maximisedItemId)[0].toggleMaximise(); + this.root.getItemsById(config.maximisedItemId)[0]?.toggleMaximise(); } }, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 51bfdbbd2..885679b64 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -8,7 +8,7 @@ import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; +import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; @@ -22,11 +22,10 @@ import { TreeView } from "../views/collections/TreeView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; -import { LabelBox } from "../views/nodes/LabelBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; -import { DragManager, dropActionType } from "./DragManager"; +import { DragManager } from "./DragManager"; import { makeTemplate, MakeTemplate } from "./DropConverter"; import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; @@ -1185,9 +1184,24 @@ export class CurrentUserUtils { } public static async snapshotDashboard(userDoc: Doc) { - const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); - CurrentUserUtils.openDashboard(userDoc, copy); + const docView = CollectionDockingView.Instance.props.DocumentView?.(); + const content = docView?.ContentDiv; + if (docView && content) { + const _width = Number(getComputedStyle(content).width.replace("px","")); + const _height = Number(getComputedStyle(content).height.replace("px","")); + CollectionFreeFormView.UpdateIcon( + docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + content, + _width, _height, + _width, _height, 0, + (iconFile, _nativeWidth, _nativeHeight) => { + const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); + const proto = Cast(img.proto, Doc, null)!; + proto["data-nativeWidth"] = _width; + proto["data-nativeHeight"] = _height; + docView.dataDoc.thumb = img; + }); + } } public static createNewDashboard = async (userDoc: Doc, id?: string) => { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index c0645f0ab..103734b9b 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -213,34 +213,31 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @undoBatch @action pinWithView = (targetDoc: Doc) => { - if (targetDoc) { - TabDocView.PinDoc(targetDoc); - setTimeout(() => { - const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; - const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); - if (scrollable) { - const scroll = targetDoc._scrollTop; - activeDoc.presPinView = true; - activeDoc.presPinViewScroll = scroll; - } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { - activeDoc.presPinView = true; - activeDoc.presStartTime = targetDoc._currentTimecode; - activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; - } else if (pannable) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeDoc.presPinView = true; - activeDoc.presPinViewX = x; - activeDoc.presPinViewY = y; - activeDoc.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeDoc.presPinClipWidth = width; - activeDoc.presPinView = true; - } - }); + const activeDoc = targetDoc && TabDocView.PinDoc(targetDoc); + if (activeDoc) { + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetDoc.type as any) || targetDoc._viewType === CollectionViewType.Stacking; + const pannable: boolean = ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG); + if (scrollable) { + const scroll = targetDoc._scrollTop; + activeDoc.presPinView = true; + activeDoc.presPinViewScroll = scroll; + } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { + activeDoc.presPinView = true; + activeDoc.presStartTime = targetDoc._currentTimecode; + activeDoc.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; + } else if (pannable) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeDoc.presPinClipWidth = width; + activeDoc.presPinView = true; + } } } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index cdf85c905..a30db66e4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -226,17 +226,7 @@ export class MainView extends React.Component { if (received && !this.userDoc) { reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true }); } else { - if (received && CurrentUserUtils._urlState.sharing) { - reaction(() => CollectionDockingView.Instance && CollectionDockingView.Instance.initialized, - initialized => initialized && received && DocServer.GetRefField(received).then(docField => { - if (docField instanceof Doc && docField._viewType !== CollectionViewType.Docking) { - CollectionDockingView.AddSplit(docField, "right"); - } - }), - ); - } - const activeDash = PromiseValue(this.userDoc.activeDashboard); - activeDash.then(dash => { + PromiseValue(this.userDoc.activeDashboard).then(dash => { if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash); else CurrentUserUtils.createNewDashboard(this.userDoc); }); diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 555f4298d..f22e68ff5 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -46,4 +46,6 @@ .overlayView-doc { z-index: 9002; //so that it appears above chroma position: absolute; + top: 0; + left: 0; } \ No newline at end of file diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index c3c103b50..f5e686473 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -200,7 +200,7 @@ export class OverlayView extends React.Component { ScreenToLocalTransform={this.docScreenToLocalXf(d)} renderDepth={1} isDocumentActive={returnTrue} - isContentActive={retu} + isContentActive={returnTrue} whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index de2106e4a..5af72b7d1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -26,6 +26,7 @@ import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require("react"); +import { SelectionManager } from '../../util/SelectionManager'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -50,9 +51,8 @@ export class CollectionDockingView extends CollectionSubView() { public _flush: UndoManager.Batch | undefined; private _ignoreStateChange = ""; public tabMap: Set = new Set(); - public get initialized() { return this._goldenLayout !== null; } public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; } - @observable private _goldenLayout: any = null; + private _goldenLayout: any = null; constructor(props: SubCollectionViewProps) { super(props); @@ -118,6 +118,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch public static OpenFullScreen(doc: Doc) { + SelectionManager.DeselectAll(); const instance = CollectionDockingView.Instance; if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") { return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); @@ -271,7 +272,7 @@ export class CollectionDockingView extends CollectionSubView() { return true; } - async setupGoldenLayout() { + setupGoldenLayout = async () => { const config = StrCast(this.props.Document.dockingConfig); if (config) { const matches = config.match(/\"documentId\":\"[a-z0-9-]+\"/g); @@ -291,7 +292,7 @@ export class CollectionDockingView extends CollectionSubView() { } this.tabMap.clear(); this._goldenLayout?.destroy(); - runInAction(() => this._goldenLayout = new GoldenLayout(JSON.parse(config))); + this._goldenLayout = new GoldenLayout(JSON.parse(config)); this._goldenLayout.on('tabCreated', this.tabCreated); this._goldenLayout.on('tabDestroyed', this.tabDestroyed); this._goldenLayout.on('stackCreated', this.stackCreated); @@ -322,7 +323,7 @@ export class CollectionDockingView extends CollectionSubView() { } this._ignoreStateChange = ""; }); - setTimeout(() => this.setupGoldenLayout(), 0); + setTimeout(this.setupGoldenLayout); //window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window } } @@ -413,19 +414,25 @@ export class CollectionDockingView extends CollectionSubView() { const docs = !docids ? [] : docids.map(id => DocServer.GetCachedRefField(id)).filter(f => f).map(f => f as Doc); const changesMade = this.props.Document.dockcingConfig !== json; if (changesMade && !this._flush) { - this.props.Document.dockingConfig = json; - this.props.Document.data = new List(docs); + UndoManager.RunInBatch(() => { + this.props.Document.dockingConfig = json; + this.props.Document.data = new List(docs); + }, "state changed"); } return changesMade; } tabDestroyed = (tab: any) => { + const dview = CollectionDockingView.Instance.props.Document; + const fieldKey = CollectionDockingView.Instance.props.fieldKey; + Doc.RemoveDocFromList(dview, fieldKey, tab.DashDoc); this.tabMap.delete(tab); tab._disposers && Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); tab.reactComponents?.forEach((ele: any) => ReactDOM.unmountComponentAtNode(ele)); this.stateChanged(); } tabCreated = (tab: any) => { + this.tabMap.add(tab); tab.contentItem.element[0]?.firstChild?.firstChild?.InitTab?.(tab); // have to explicitly initialize tabs that reuse contents from previous tabs (ie, when dragging a tab around a new tab is created for the old content) } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 0141724ea..75a8207dd 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -82,7 +82,6 @@ export class TabDocView extends React.Component { const iconWrap = document.createElement("div"); const closeWrap = document.createElement("div"); - titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; titleEle.onkeydown = (e: KeyboardEvent) => { @@ -185,14 +184,13 @@ export class TabDocView extends React.Component { UndoManager.RunInBatch(() => tab.contentItem.remove(), "delete tab"); }); } - CollectionDockingView.Instance.tabMap.add(tab); } /** * Adds a document to the presentation view **/ @action - public static async PinDoc(doc: Doc, pinProps?: PinProps) { + public static PinDoc(doc: Doc, pinProps?: PinProps) { //add this new doc to props.Document // all docs will be added to the ActivePresentation as stored on CurrentUserUtils @@ -246,19 +244,14 @@ export class TabDocView extends React.Component { pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - const dview = CollectionDockingView.Instance.props.Document; - const fieldKey = CollectionDockingView.Instance.props.fieldKey; - const tabdocs = await DocListCastAsync(dview[fieldKey]); - runInAction(() => { - if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) { - tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs - CollectionDockingView.AddSplit(curPres, "right"); - } - PresBox.Instance?._selectedArray.clear(); - pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - DocumentManager.Instance.jumpToDocument(doc, false, undefined, []); - batch.end(); - }); + if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { + CollectionDockingView.AddSplit(curPres, "right"); + setTimeout(() => DocumentManager.Instance.jumpToDocument(doc, false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things + } + PresBox.Instance?._selectedArray.clear(); + pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array + setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs + return pinDoc; } } @@ -282,7 +275,6 @@ export class TabDocView extends React.Component { componentWillUnmount() { this._tabReaction?.(); this._view && DocumentManager.Instance.RemoveView(this._view); - this.tab && CollectionDockingView.Instance.tabMap.delete(this.tab); this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged); } @@ -430,9 +422,7 @@ export class TabDocView extends React.Component { }} ref={ref => { if (this._mainCont = ref) { if (this._lastTab) { - console.log("DUP tab") this._view && DocumentManager.Instance.RemoveView(this._view); - CollectionDockingView.Instance.tabMap.delete(this._lastTab); } this._lastTab = this.tab; (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index befd2020c..09f05f69a 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -609,10 +609,8 @@ export class TreeView extends React.Component { return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); } - // TODO: currently doc focus works, but can't seem to edit title - // onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); onChildClick = () => { - return this.props.onChildClick?.() ?? (ScriptField.MakeFunction(`DocFocusOrOpen(self)`)! || this._editTitleScript?.()); + return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 77cf0fc84..5f927a2a9 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1489,7 +1489,7 @@ export class CollectionFreeFormView extends CollectionSubView { + 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); @@ -1508,32 +1508,39 @@ export class CollectionFreeFormView extends CollectionSubView { - const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; + updateIcon = () => CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + this.props.docViewPath().lastElement().ContentDiv!, + this.layoutDoc[WidthSym](), this.layoutDoc[HeightSym](), + this.props.PanelWidth(), this.props.PanelHeight(), 0, + (iconFile, nativeWidth, nativeHeight) => { + this.dataDoc.icon = new ImageField(iconFile); + this.dataDoc["icon-nativeWidth"] = nativeWidth; + this.dataDoc["icon-nativeHeight"] = nativeHeight; + }); + + public static UpdateIcon(filename:string, docViewContent:HTMLElement, width: number, height: number, + panelWidth:number, panelHeight: number, scrollTop:number, cb:(iconFile:string, nativeWidth:number, nativeHeight:number) => any) { const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = (this.layoutDoc[WidthSym]()).toString(); - newDiv.style.height = (this.layoutDoc[HeightSym]()).toString(); + newDiv.style.width = width.toString(); + newDiv.style.height = height.toString(); this.replaceCanvases(docViewContent, newDiv); - const htmlString = this._mainCont && new XMLSerializer().serializeToString(newDiv); - const nativeWidth = this.layoutDoc[WidthSym](); - const nativeHeight = this.layoutDoc[HeightSym](); - + const htmlString = new XMLSerializer().serializeToString(newDiv); + const nativeWidth = width; + const nativeHeight = height; CreateImage( - "", + Utils.prepend(""), document.styleSheets, htmlString, nativeWidth, - nativeWidth * this.props.PanelHeight() / this.props.PanelWidth(), - NumCast(this.layoutDoc._scrollTop) + nativeWidth * panelHeight / panelWidth, + scrollTop ).then ((data_url: any) => { - VideoBox.convertDataUri(data_url, this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), true, this.layoutDoc[Id] + "-icon").then( - returnedfilename => setTimeout(action(() => { - - this.dataDoc.icon = new ImageField(returnedfilename); - this.dataDoc["icon-nativeWidth"] = nativeWidth; - this.dataDoc["icon-nativeHeight"] = nativeHeight; - }), 500)); + VideoBox.convertDataUri(data_url, filename).then( + returnedfilename => setTimeout(() => { + cb(returnedfilename as string, nativeWidth, nativeHeight); + }, 500)); }) .catch(function (error: any) { console.error('oops, something went wrong!', error); diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index d9524dd6e..f3f1bcf5c 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -214,8 +214,8 @@ var ForeignHtmlRenderer = function (styleSheets) { .replace(/noscript/g, "div").replace(/
    <\/div>/g, "") // when scripting isn't available (ie, rendering web pages here),
    } placement="bottom"> @@ -57,12 +57,12 @@ export class TopBar extends React.Component { await CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); batch.end(); }}> - {"Snapshot"} + Snapshot
    Browsing mode for directly navigating to documents
    } placement="bottom">
    MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> - {"Explore"} + Explore
    -- cgit v1.2.3-70-g09d2 From 3213c24210d67af0a4b027863773ad383404a59c Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 9 Jun 2022 11:07:34 -0400 Subject: renamed fitToBox to fitContentsToBox. fixed pinning multiple documents at same time to not bring up presBox multiple times. --- src/client/documents/Documents.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 21 ++---------- src/client/views/LightboxView.tsx | 2 -- src/client/views/MainView.tsx | 2 +- src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/PropertiesView.tsx | 2 +- src/client/views/collections/CollectionMenu.tsx | 4 +-- .../views/collections/CollectionTimeView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 25 +++++++-------- src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 37 +++++++--------------- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../CollectionMulticolumnView.tsx | 2 +- .../CollectionMultirowView.tsx | 2 +- .../collectionSchema/CollectionSchemaView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +-- src/client/views/nodes/MapBox/MapBox.tsx | 6 ++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 ++-- src/client/views/nodes/trails/PresBox.tsx | 14 ++++---- src/client/views/nodes/trails/PresElementBox.tsx | 2 +- src/fields/documentSchemas.ts | 6 ++-- src/mobile/AudioUpload.tsx | 4 +-- 22 files changed, 60 insertions(+), 92 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index acc1e341a..144a3d9cf 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -113,7 +113,8 @@ export class DocumentOptions { _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", true); _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units", true); _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)", true); - _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents + _fitContentsToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents + _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed _isPushpin?: boolean; // whether document, when clicked, toggles display of its link target diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 41224b1c5..0bbe473d7 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -204,27 +204,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
    {SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}
    }>
    this.props.views().map(view => view && this.pinWithView(view.props.Document)))}> + onClick={(e => TabDocView.PinDoc(this.props.views().filter(v => v).map(dv => dv!.rootDoc), {pinDocView: true}))} + >
    ; } - @undoBatch - @action - pinWithView = (targetDoc: Doc) => targetDoc && TabDocView.PinDoc(targetDoc, {pinDocView: true}); - - @computed - get pinWithViewButton() { - const presPinWithViewIcon = ; - const targetDoc = this.view0?.props.Document; - return !targetDoc ? (null) :
    {"Pin with current view"}
    }> -
    this.pinWithView(targetDoc)}> - {presPinWithViewIcon} -
    -
    ; - } - @computed get shareButton() { const targetDoc = this.view0?.props.Document; @@ -349,9 +335,6 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
    {this.pinButton}
    - {/*
    - {this.pinWithViewButton} -
    */} {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
    {this.shareButton}
    } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index f67f37bfb..fcc4aea13 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -207,7 +207,6 @@ export class LightboxView extends React.Component { future = () => LightboxView._future; tourMap = () => LightboxView._tourMap; - fitToBox = () => LightboxView._docTarget === LightboxView.LightboxDoc; render() { let downx = 0, downy = 0; return !LightboxView.LightboxDoc ? (null) : @@ -241,7 +240,6 @@ export class LightboxView extends React.Component { DataDoc={undefined} LayoutTemplate={LightboxView.LightboxDocTemplate} addDocument={undefined} - // fitContentsToDoc={this.fitToBox} // bcz: why do we want this? when we initially open a colletion, we shrinkwrap it which allows for user navigation. if we later encounter a collection, it's not clear to me that we want to make it either shrinkwrap or fitContents... isDocumentActive={returnFalse} isContentActive={returnTrue} addDocTab={this.addDocTab} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 641178cd9..84a131a57 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -287,7 +287,7 @@ export class MainView extends React.Component { styleProvider={DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} isDocumentActive={returnTrue} // headerBar is always documentActive (ie, the docView gets pointer events) isContentActive={returnTrue} // headerBar is awlays contentActive which means its items are always documentActive ScreenToLocalTransform={this.headerBarScreenXf} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index f24ff09db..74055f057 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -71,7 +71,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { return this.propertyToggleBtn("Lock\xA0View", "_lockedTransform", on => `${on ? "Unlock" : "Lock"} panning of view`, on => "lock"); } @computed get fitContentButton() { - return this.propertyToggleBtn("View All", "_fitToBox", on => `${on ? "Don't" : "Do"} fit content to container visible area`, on => "eye"); + return this.propertyToggleBtn("View All", "_fitContentsToBox", on => `${on ? "Don't" : "Do"} fit content to container visible area`, on => "eye"); } @computed get fitWidthButton() { return this.propertyToggleBtn("Fit\xA0Width", "_fitWidth", on => `${on ? "Don't" : "Do"} fit content to width of container`, on => "arrows-alt-h"); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index bcfd2dd56..16eb95cf4 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -300,7 +300,7 @@ export class PropertiesView extends React.Component { Document={layoutDoc} DataDoc={this.dataDoc} renderDepth={1} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} rootSelected={returnFalse} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 01e77f342..39f6466d6 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -252,8 +252,8 @@ export class CollectionViewBaseChrome extends React.Component this.target._fitToBox = !this.target._fitToBox), + script: "self.target._fitContentsToBox = !self.target._fitContentsToBox;", + immediate: undoBatch((source: Doc[]) => this.target._fitContentsToBox = !this.target._fitContentsToBox), initialize: emptyFunction }; _fitContentCommand = { diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 4f6f45d2f..7573b938a 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -135,7 +135,7 @@ export class CollectionTimeView extends CollectionSubView() { onClick={this.contentsDown}> { * Adds a document to the presentation view **/ @action - public static PinDoc(doc: Doc, pinProps?: PinProps) { - //add this new doc to props.Document + public static PinDoc(docs: Doc|Doc[], pinProps?: PinProps) { + const docList = ((docs instanceof Doc) ? [docs]: docs); + const batch = UndoManager.StartBatch("pinning doc"); // all docs will be added to the ActivePresentation as stored on CurrentUserUtils const curPres = CurrentUserUtils.ActivePresentation; - if (curPres) { + curPres && docList.forEach(doc => { // Edge Case 1: Cannot pin document to itself if (doc === curPres) { alert("Cannot pin presentation document to itself"); return; } - const batch = UndoManager.StartBatch("pinning doc"); const pinDoc = Doc.MakeAlias(doc); pinDoc.presentationTargetDoc = doc; pinDoc.title = doc.title + " - Slide"; @@ -279,17 +279,16 @@ export class TabDocView extends React.Component { pinDoc.presMovement = PresMovement.None; } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { - const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); - if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); - CollectionDockingView.AddSplit(curPres, "right"); - setTimeout(() => DocumentManager.Instance.jumpToDocument(doc, false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things - } PresBox.Instance?._selectedArray.clear(); pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array - setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs - return pinDoc; + }); + if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { + const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); + if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); + CollectionDockingView.AddSplit(curPres, "right"); + setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things } + setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs } componentDidMount() { @@ -572,7 +571,7 @@ export class TabMinimapView extends React.Component { docFilters={CollectionDockingView.Instance.childDocFilters} docRangeFilters={CollectionDockingView.Instance.childDocRangeFilters} searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} />
    diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 09f05f69a..8824750a3 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -796,7 +796,7 @@ export class TreeView extends React.Component { isDocumentActive={isActive} styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} hideTitle={asText} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} hideDecorationTitle={this.props.treeView.outlineMode} hideResizeHandles={this.props.treeView.outlineMode} onClick={this.onChildClick} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index b70e6ff98..ec3cf1fe8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -9,7 +9,7 @@ import { InkData, InkField, InkTool, PointData, Segment } from "../../../../fiel import { List } from "../../../../fields/List"; import { ObjectField } from "../../../../fields/ObjectField"; import { RichTextField } from "../../../../fields/RichTextField"; -import { createSchema, listSpec } from "../../../../fields/Schema"; +import { listSpec } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { ImageField } from "../../../../fields/URLField"; @@ -41,6 +41,7 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveIn import { LightboxView } from "../../LightboxView"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView"; +import { FieldViewProps } from "../../nodes/FieldView"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PresBox } from "../../nodes/trails/PresBox"; import { VideoBox } from "../../nodes/VideoBox"; @@ -50,27 +51,13 @@ import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; import { TreeViewType } from "../CollectionTreeView"; import { CollectionViewType } from "../CollectionView"; +import { TabDocView } from "../TabDocView"; import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from "./CollectionFreeFormLayoutEngines"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { FieldView, FieldViewProps } from "../../nodes/FieldView"; -import { TabDocView } from "../TabDocView"; -export const panZoomSchema = createSchema({ - _panX: "number", - _panY: "number", - _currentTimecode: "number", - _timecodeToShow: "number", - _currentFrame: "number", - _useClusters: "boolean", - _viewTransition: "string", - _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _yPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _fitToBox: "boolean", - scrollHeight: "number" // this will be set when the collection is an annotation overlay for a PDF/Webpage -}); export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -138,20 +125,20 @@ export class CollectionFreeFormView extends CollectionSubView e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } - @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); } - @computed get nativeHeight() { return this.fitToContent ? 0 : Doc.NativeHeight(this.Document); } + @computed get nativeWidth() { return this.fitContentsToBox ? 0 : Doc.NativeWidth(this.Document); } + @computed get nativeHeight() { return this.fitContentsToBox ? 0 : Doc.NativeHeight(this.Document); } @computed get cachedCenteringShiftX(): number { - const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling; + const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / scaling; // shift so pan position is at center of window for non-overlay collections } @computed get cachedCenteringShiftY(): number { - const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling; + const scaling = this.fitContentsToBox || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelHeight() / 2 / scaling;// shift so pan position is at center of window for non-overlay collections } @computed get cachedGetLocalTransform(): Transform { @@ -176,8 +163,8 @@ export class CollectionFreeFormView extends CollectionSubView !this._firstRender && (this.fitToContent || force) ? this.fitToContentVals : undefined; - reverseNativeScaling = () => this.fitToContent ? true : false; + freeformData = (force?: boolean) => !this._firstRender && (this.fitContentsToBox || force) ? this.fitToContentVals : undefined; + reverseNativeScaling = () => this.fitContentsToBox ? true : false; panX = () => this.freeformData()?.bounds.cx ?? NumCast(this.Document._panX); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document._panY); zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)); @@ -1272,7 +1259,7 @@ export class CollectionFreeFormView extends CollectionSubView; } addDocTab = action((doc: Doc, where: string) => { @@ -1616,7 +1603,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); - appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); + appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); this.props.ContainingCollectionView && diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3fac3350a..6df2bb102 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -516,7 +516,7 @@ export class MarqueeView extends React.Component Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, smooth: boolean) => Opt; // returns the duration of the focus setViewSpec?: (anchor: Doc, preview: boolean) => void; // sets viewing information for a componentview, typically when following a link. 'preview' tells the view to use the values without writing to the document - reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. + reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected. isAnyChildContentActive?: () => boolean; // is any child content of the document active @@ -113,7 +113,7 @@ export interface DocumentViewSharedProps { Document: Doc; DataDoc?: Doc; contentBounds?: () => (undefined|{x:number, y:number, r:number, b:number}); - fitContentsToDoc?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document + fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitContentsToBox property on a Document ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; suppressSetHeight?: boolean; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 9f1c019fe..5f4c17ee6 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -259,7 +259,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (this._loadPending && this._map.getBounds()) { this._loadPending = false; - this.layoutDoc.fitToBox && this.fitBounds(this._map); + this.layoutDoc.fitContentsToBox && this.fitBounds(this._map); } this.dataDoc.mapLat = this._map.getCenter()?.lat(); this.dataDoc.mapLng = this._map.getCenter()?.lng(); @@ -284,7 +284,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { if (this._loadPending && this._map.getBounds()) { this._loadPending = false; - this.layoutDoc.fitToBox && this.fitBounds(this._map); + this.layoutDoc.fitContentsToBox && this.fitBounds(this._map); } this.dataDoc.mapZoom = this._map.getZoom(); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 7d0302b26..4e431e7bd 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -495,7 +495,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // embed document when dragg marked as embed } else if (de.embedKey) { const target = dragData.droppedDocuments[0]; - target._fitToBox = true; + target._fitContentsToBox = true; const node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), @@ -1557,7 +1557,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - fitToBox = () => this.props.Document._fitToBox; + fitContentsToBox = () => this.props.Document._fitContentsToBox; sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); sidebarAddDocument = (doc: Doc | Doc[], sidebarKey?: string) => { if (!this.layoutDoc._showSidebar) this.toggleSidebar(); @@ -1632,7 +1632,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp ScreenToLocalTransform={this.sidebarScreenToLocal} renderDepth={this.props.renderDepth + 1} setHeight={this.setSidebarHeight} - fitContentsToDoc={this.fitToBox} + fitContentsToBox={this.fitContentsToBox} noSidebar={true} fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />; }; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 6de04bd31..cebf9487a 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -35,8 +35,8 @@ export interface PinProps { setPosition?: boolean; hidePresBox?: boolean; pinWithView?: PinViewProps; - pinDocView?: boolean; - panelWidth?: number; + pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document + panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) panelHeight?: number } @@ -997,7 +997,7 @@ export class PresBox extends ViewBoxBaseComponent() { /** * Method called for viewing paths which adds a single line with * points at the center of each document added. - * Design choice: When this is called it sets _fitToBox as true so the + * Design choice: When this is called it sets _fitContentsToBox as true so the * user can have an overview of all of the documents in the collection. * (Design needed for when documents in presentation trail are in another * collection) @@ -1800,16 +1800,16 @@ export class PresBox extends ViewBoxBaseComponent() { doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y }); break; case 'title': - doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : "Title slide", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'header': - doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([header], { title: input ? input : "Section header", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'content': - doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : "Title and content", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'twoColumns': - doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitToBox: true, x: x, y: y }); + doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : "Title and two columns", _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; default: break; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 5eff47a86..ba0193e4b 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -85,7 +85,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { isContentActive={this.props.isContentActive} addDocTab={returnFalse} pinToPres={returnFalse} - fitContentsToDoc={returnTrue} + fitContentsToBox={returnTrue} PanelWidth={this.embedWidth} PanelHeight={this.embedHeight} ScreenToLocalTransform={Transform.Identity} diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index e532becb5..be39e0709 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -36,8 +36,8 @@ export const documentSchema = createSchema({ _nativeHeight: "number", // " _width: "number", // width of document in its container's coordinate system _height: "number", // " - _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitToBox is set - _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitToBox is set + _xPadding: "number", // pixels of padding on left/right of collectionfreeformview contents when fitContentsToBox is set + _yPadding: "number", // pixels of padding on top/bottom of collectionfreeformview contents when fitContentsToBox is set _xMargin: "number", // margin added on left/right of most documents to add separation from their container _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container _overflow: "string", // sets overflow behvavior for CollectionFreeForm views @@ -59,7 +59,7 @@ export const documentSchema = createSchema({ borderRounding: "string", // border radius rounding of document boxShadow: "string", // the amount of shadow around the perimeter of a document color: "string", // foreground color of document - fitToBox: "boolean", // whether freeform view contents should be zoomed/panned to fill the area of the document view + fitContentsToBox: "boolean",// whether freeform view contents should be zoomed/panned to fill the area of the document view box fontSize: "string", hidden: "boolean", // whether a document should not be displayed isInkMask: "boolean", // is the document a mask (ie, sits on top of other documents, has an unbounded width/height that is dark, and content uses 'hard-light' mix-blend-mode to let other documents pop through) diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx index 418464f0e..64baa351c 100644 --- a/src/mobile/AudioUpload.tsx +++ b/src/mobile/AudioUpload.tsx @@ -19,7 +19,7 @@ import React = require('react'); @observer export class AudioUpload extends React.Component { - @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc; + @observable public _audioCol: Doc = FieldValue(Cast(Docs.Create.FreeformDocument([Cast(Docs.Create.AudioDocument(nullAudio, { title: "mobile audio", _width: 500, _height: 100 }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc; /** * Handles the onclick functionality for the 'Restart' button @@ -36,7 +36,7 @@ export class AudioUpload extends React.Component { title: "mobile audio", _width: 500, _height: 100 - }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitToBox: true, boxShadow: "0 0" }), Doc)) as Doc; + }), Doc) as Doc], { title: "mobile audio", _width: 300, _height: 300, _fitContentsToBox: true, boxShadow: "0 0" }), Doc)) as Doc; } /** -- cgit v1.2.3-70-g09d2 From 78b3b4f194b94a230ff0b07e397b595700d6529f Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 10 Jun 2022 10:52:45 -0400 Subject: fixed some errors --- src/client/documents/Documents.ts | 4 ++++ src/client/views/nodes/trails/PresElementBox.tsx | 11 +++++------ 2 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 088593871..0bb6ce27f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -191,6 +191,10 @@ export class DocumentOptions { childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children hideLinkButton?: boolean; // whether the blue link counter button should be hidden + hideDecorationTitle?: boolean; + hideOpenButton?: boolean; + hideResizeHandles?: boolean; + hideDocumentButtonBar?: boolean; hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 9ad13eb84..50df00612 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, Opt } from "../../../../fields/Doc"; +import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils"; @@ -328,7 +328,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @undoBatch @action - hideRecording = (e: React.PointerEvent) => { + hideRecording = (e: React.MouseEvent) => { e.stopPropagation() this.removeAllRecordingInOverlay() } @@ -366,8 +366,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { hideDecorationTitle: true, hideOpenButton: true, // hideDeleteButton: true, - cloneFieldFilter: - new List(["system"]) + cloneFieldFilter: new List(["system"]) }); // attach the recording to the slide, and attach the slide to the recording @@ -375,8 +374,8 @@ export class PresElementBox extends ViewBoxBaseComponent() { activeItem.recording = recording // make recording box appear in the bottom right corner of the screen - recording.x = window.innerWidth - recording._width - 20; - recording.y = window.innerHeight - recording._height - 20; + recording.x = window.innerWidth - recording[WidthSym]() - 20; + recording.y = window.innerHeight - recording[HeightSym]() - 20; Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, recording); } } -- cgit v1.2.3-70-g09d2 From 18fb08801442e6d1d98eb3a7de20f654be56d6ac Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 13 Jun 2022 20:11:12 -0400 Subject: fixed adding annotations when in augment mode to rtfsocsvvvvvvvvdssssdc. cw22121q````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````````` --- src/client/documents/Documents.ts | 1 + src/client/views/DocComponent.tsx | 2 +- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | 2 +- 4 files changed, 5 insertions(+), 4 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0bb6ce27f..b290b2d58 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -663,6 +663,7 @@ export namespace Docs { // so that the list of annotations is already initialised, prevents issues in addonly. // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. dataProps[fieldKey + "-annotations"] = new List(); + dataProps[fieldKey + "-sidebar"] = new List(); const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); const viewFirstProps: { [id: string]: any } = {}; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 80835447d..8486b3d62 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -183,7 +183,7 @@ export function ViewBoxAnnotatableComponent

    () const targetDataDoc = this.props.Document[DataSym]; const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); const added = docs.filter(d => !docList.includes(d)); - const effectiveAcl = GetEffectiveAcl(this.dataDoc); + const effectiveAcl = GetEffectiveAcl(targetDataDoc); if (added.length) { if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 4e431e7bd..f29b879b3 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -255,7 +255,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const removeSelection = (json: string | undefined) => json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || effectiveAcl === AclSelfEdit) { + if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) { const accumTags = [] as string[]; state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => { if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith("#")) { @@ -1523,7 +1523,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp case "Tab": e.preventDefault(); break; default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; case " ": - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})) + [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})) .addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } this.startUndoTypingBatch(); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index fb49b0698..c66cb502e 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -44,7 +44,7 @@ export function buildKeymap>(schema: S, props: any, mapKey } const canEdit = (state: any) => { - switch (GetEffectiveAcl(props.Document)) { + switch (GetEffectiveAcl(props.DataDoc)) { case AclAugment: return false; case AclSelfEdit: for (var i = state.selection.from; i < state.selection.to; i++) { -- cgit v1.2.3-70-g09d2 From da6283875ec7f59af602e14ed19fcab93533f377 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 14 Jun 2022 13:00:27 -0400 Subject: made shared with me doc count decrement when a document is viewed (double-clicked, dragged out) --- src/client/documents/Documents.ts | 4 ++-- src/client/util/CurrentUserUtils.ts | 12 ++++++---- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/button/FontIconBadge.tsx | 30 ++++++++++++------------- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/fields/Doc.ts | 1 + 6 files changed, 28 insertions(+), 23 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b290b2d58..d2937b83f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -198,7 +198,6 @@ export class DocumentOptions { hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; - watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List; hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) @@ -251,6 +250,7 @@ export class DocumentOptions { numBtnMax?: number; numBtnMin?: number; switchToggle?: boolean; + badgeValue?: ScriptField; //LINEAR VIEW linearViewIsExpanded?: boolean; // is linear view expanded @@ -294,7 +294,7 @@ export class DocumentOptions { treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically - + treeViewChildDoubleClick?: ScriptField; // // Action Button buttonMenu?: boolean; // whether a action button should be displayed buttonMenuDoc?: Doc; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index aa7c2a39b..bb78fe4e7 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -23,6 +23,7 @@ import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; +import { DocumentView } from "../views/nodes/DocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; import { DragManager } from "./DragManager"; @@ -354,6 +355,7 @@ export class CurrentUserUtils { } static menuBtnDescriptions(doc: Doc) { + const badgeScript = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, @@ -361,7 +363,7 @@ export class CurrentUserUtils { { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, + { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue: badgeScript}, { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, ]; @@ -370,7 +372,7 @@ export class CurrentUserUtils { static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.menuStack === undefined) { await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) => + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => Docs.Create.FontIconDocument({ icon, btnType: ButtonType.MenuButton, @@ -387,7 +389,7 @@ export class CurrentUserUtils { _removeDropProperties: new List(["dropAction", "_stayInCollection"]), _width: 60, _height: 60, - watchedDocuments, + badgeValue, onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) }) ); @@ -918,11 +920,12 @@ export class CurrentUserUtils { // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document if (doc.mySharedDocs === undefined) { let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); + const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) if (!sharedDocs) { sharedDocs = Docs.Create.TreeDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - _chromeHidden: true, boxShadow: "0 0", + _chromeHidden: true, boxShadow: "0 0", treeViewChildDoubleClick: dblClkScript, dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" }, sharingDocumentId + "outer", sharingDocumentId); (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; @@ -1069,6 +1072,7 @@ export class CurrentUserUtils { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } + return doc; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8824750a3..59dc5671b 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -613,7 +613,7 @@ export class TreeView extends React.Component { return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } - onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null)); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx index cf86b5e07..3b5aac221 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -7,30 +7,30 @@ import { DragManager } from "../../../util/DragManager"; import "./FontIconBadge.scss"; interface FontIconBadgeProps { - collection: Doc | undefined; + value: string | undefined; } @observer export class FontIconBadge extends React.Component { _notifsRef = React.createRef(); - onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - (e: PointerEvent) => { - const dragData = new DragManager.DocumentDragData([this.props.collection!]); - DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); - return true; - }, - returnFalse, emptyFunction, false); - } + // onPointerDown = (e: React.PointerEvent) => { + // setupMoveUpEvents(this, e, + // (e: PointerEvent) => { + // const dragData = new DragManager.DocumentDragData([this.props.collection!]); + // DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); + // return true; + // }, + // returnFalse, emptyFunction, false); + // } render() { - if (!(this.props.collection instanceof Doc)) return (null); - const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read + if (this.props.value === undefined) return (null); return

    -
    0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} +
    + {this.props.value}
    ; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index a1b9023f3..97e6eddfe 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
    {this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
    ); break; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6abc27b23..7b72787d1 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1436,6 +1436,7 @@ ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.cop ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); -- cgit v1.2.3-70-g09d2 From 21eb25198c27d65e398d11b018a8dc792297e35a Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 Jun 2022 09:33:01 -0400 Subject: updated fontIconBadge to use 'viewed' list to decrement value when items are viewed. cleaned up how sharedDoc is setup in currentUserUtils to allow updates to not require rebuilding DB --- src/client/documents/Documents.ts | 10 ++- src/client/util/CurrentUserUtils.ts | 75 +++++++++++++--------- src/client/views/collections/TreeView.tsx | 2 +- src/client/views/nodes/button/FontIconBadge.tsx | 32 ++++----- src/client/views/nodes/button/FontIconBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/fields/Doc.ts | 1 + src/fields/Types.ts | 4 ++ 8 files changed, 77 insertions(+), 53 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b290b2d58..5f009573e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -98,6 +98,7 @@ export class DocumentOptions { allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); + userColor?: string; // color associated with a Dash user (seen in header fields of shared documents) color?: string; // foreground color data doc backgroundColor?: STRt = new StrInfo("background color for data doc"); _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true); @@ -190,6 +191,10 @@ export class DocumentOptions { childLayoutString?: string; // template string for collection to use to render its children childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children + childContextMenuFilters?: List; + childContextMenuScripts?: List; + childContextMenuLabels?: List; + childContextMenuIcons?: List; hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideDecorationTitle?: boolean; hideOpenButton?: boolean; @@ -198,7 +203,6 @@ export class DocumentOptions { hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template isTemplateDoc?: boolean; - watchedDocuments?: Doc; // list of documents an icon doc monitors in order to display a badge count targetScriptKey?: string; // where to write a template script (used by collections with click templates which need to target onClick, onDoubleClick, etc) templates?: List; hero?: ImageField; // primary image that best represents a compound document (e.g., for a buxton device document that has multiple images) @@ -250,7 +254,8 @@ export class DocumentOptions { numBtnType?: string; numBtnMax?: number; numBtnMin?: number; - switchToggle?: boolean; + switchToggle?: boolean; + badgeValue?: ScriptField; //LINEAR VIEW linearViewIsExpanded?: boolean; // is linear view expanded @@ -294,6 +299,7 @@ export class DocumentOptions { treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically + treeViewChildDoubleClick?: ScriptField; // // Action Button buttonMenu?: boolean; // whether a action button should be displayed diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 60bfc165b..b4e18a8bb 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -7,7 +7,7 @@ import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, DateCast, DocCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; @@ -354,6 +354,7 @@ export class CurrentUserUtils { } static menuBtnDescriptions(doc: Doc) { + const badgeValue = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, @@ -361,7 +362,7 @@ export class CurrentUserUtils { { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, + { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue}, { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, ]; @@ -369,8 +370,9 @@ export class CurrentUserUtils { static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { if (doc.menuStack === undefined) { - await this.setupSharingSidebar(doc, sharingDocumentId, linkDatabaseId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments, hidden }) => + await this.setupLinkDocs(doc, linkDatabaseId); + await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => Docs.Create.FontIconDocument({ icon, btnType: ButtonType.MenuButton, @@ -387,7 +389,7 @@ export class CurrentUserUtils { _removeDropProperties: new List(["dropAction", "_stayInCollection"]), _width: 60, _height: 60, - watchedDocuments, + badgeValue, onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) }) ); @@ -900,10 +902,7 @@ export class CurrentUserUtils { } // Sharing sidebar is where shared documents are contained - static async setupSharingSidebar(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.myPublishedDocs === undefined) { - doc.myPublishedDocs = new List(); - } + static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); if (!linkDocs) { @@ -915,28 +914,41 @@ export class CurrentUserUtils { } doc.myLinkDatabase = new PrefetchProxy(linkDocs); } - // TODO:glr NOTE: treeViewHideTitle & _showTitle may be confusing, treeViewHideTitle is for the editable title (just for tree view), _showTitle is to show the Document title for any document - if (doc.mySharedDocs === undefined) { - let sharedDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(sharingDocumentId + "outer"); - if (!sharedDocs) { - sharedDocs = Docs.Create.TreeDocument([], { - title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, - _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, - _chromeHidden: true, boxShadow: "0 0", - dontRegisterView: true, explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" - }, sharingDocumentId + "outer", sharingDocumentId); - (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Augment; - } - if (sharedDocs instanceof Doc) { - Doc.GetProto(sharedDocs).userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; - const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); - const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); - sharedDocs.childContextMenuFilters = new List([dashboardFilter!,]); - sharedDocs.childContextMenuScripts = new List([addToDashboards!,]); - sharedDocs.childContextMenuLabels = new List(["Add to Dashboards",]); - sharedDocs.childContextMenuIcons = new List(["user-plus",]); - } - doc.mySharedDocs = new PrefetchProxy(sharedDocs as Doc); + } + // A user's sharing document is where all documents that are shared to that user are placed. + // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field + // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents + static async setupSharedDocs(doc: Doc, sharingDocumentId: string) { + const addToDashboards = ScriptField.MakeScript(`addToDashboards(self)`); + const dashboardFilter = ScriptField.MakeFunction(`doc._viewType === '${CollectionViewType.Docking}'`, { doc: Doc.name }); + const dblClkScript = ScriptField.MakeScript("{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}", {scriptContext:"any", documentView:Doc.name}) + + const sharedDocOpts:DocumentOptions = { + title: "My Shared Docs", + userColor: "rgb(202, 202, 202)", + childContextMenuFilters: new List([dashboardFilter!,]), + childContextMenuScripts: new List([addToDashboards!,]), + childContextMenuLabels: new List(["Add to Dashboards",]), + childContextMenuIcons: new List(["user-plus",]), + treeViewChildDoubleClick: dblClkScript, + }; + const sharedRequiredDocOpts:DocumentOptions = { + "acl-Public": SharingPermissions.Augment, "_acl-Public": SharingPermissions.Augment, + childDropAction: "alias", system: true, contentPointerEvents: "all", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, + // NOTE: treeViewHideTitle & _showTitle is for a TreeView's editable title, _showTitle is for DocumentViews title bar + _showTitle: "title", treeViewHideTitle: true, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true, + explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" + }; + + const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer")); + if (!(sharedDocs instanceof Doc)) { + doc.mySharedDocs = new PrefetchProxy( + Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); + } else { + Object.entries(sharedRequiredDocOpts).forEach(pair => { + const targetDoc = pair[0].startsWith("_") ? sharedDocs as Doc : Doc.GetProto(sharedDocs as Doc); + targetDoc[pair[0]] = pair[1]; + }); } } @@ -1044,6 +1056,7 @@ export class CurrentUserUtils { doc.savedFilters = new List(); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; + doc.myPublishedDocs = doc.myPublishedDocs ?? new List(); doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 8824750a3..59dc5671b 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -613,7 +613,7 @@ export class TreeView extends React.Component { return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); } - onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null)); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx index cf86b5e07..df17d603f 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -6,31 +6,31 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils import { DragManager } from "../../../util/DragManager"; import "./FontIconBadge.scss"; -interface FontIconBadgeProps { - collection: Doc | undefined; +interface FontIconBadgeProps { + value: string | undefined; } @observer export class FontIconBadge extends React.Component { _notifsRef = React.createRef(); - onPointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, - (e: PointerEvent) => { - const dragData = new DragManager.DocumentDragData([this.props.collection!]); - DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); - return true; - }, - returnFalse, emptyFunction, false); - } + // onPointerDown = (e: React.PointerEvent) => { + // setupMoveUpEvents(this, e, + // (e: PointerEvent) => { + // const dragData = new DragManager.DocumentDragData([this.props.collection!]); + // DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); + // return true; + // }, + // returnFalse, emptyFunction, false); + // } render() { - if (!(this.props.collection instanceof Doc)) return (null); - const length = DocListCast(this.props.collection.data).filter(d => GetEffectiveAcl(d) !== AclPrivate).length; // Object.keys(d).length).length; // filter out any documents that we can't read + if (this.props.value === undefined) return (null); return
    -
    0 ? { "display": "initial" } : { "display": "none" }} - onPointerDown={this.onPointerDown} > - {length} +
    + {this.props.value}
    ; } diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index a1b9023f3..97e6eddfe 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
    {this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
    ); break; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f29b879b3..16a523b40 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -382,11 +382,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - // creates links between terms in a document and documents which have a matching Id + // creates links between terms in a document and published documents (myPublishedDocs) that have titles starting with an '@' hyperlinkTerm = (tr: any, target: Doc, newAutoLinks: Set) => { const editorView = this._editorView; if (editorView && (editorView as any).docView && !Doc.AreProtosEqual(target, this.rootDoc)) { - const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); + const autoLinkTerm = StrCast(target.title).replace(/^@/, ""); const flattened1 = this.findInNode(editorView, editorView.state.doc, autoLinkTerm); var alink: Doc | undefined; flattened1.forEach((flat, i) => { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 6abc27b23..7b72787d1 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1436,6 +1436,7 @@ ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.cop ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); +ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); ScriptingGlobals.add(function setInPlace(doc: any, field: any, value: any) { return Doc.SetInPlace(doc, field, value, false); }); ScriptingGlobals.add(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2); }); ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); diff --git a/src/fields/Types.ts b/src/fields/Types.ts index 7e2aa5681..bf40a0d7b 100644 --- a/src/fields/Types.ts +++ b/src/fields/Types.ts @@ -76,6 +76,10 @@ export function Cast(field: FieldResult, ctor: T, defaultVal return defaultVal === null ? undefined : defaultVal; } +export function DocCast(field: FieldResult, defaultVal?: Doc) { + return Cast(field, Doc, null) ?? defaultVal; +} + export function NumCast(field: FieldResult, defaultVal: number | null = 0) { return Cast(field, "number", defaultVal); } -- cgit v1.2.3-70-g09d2 From c0826fb904f5d2448635b5dae1fc4337fead939e Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 05:31:25 -0400 Subject: lots of changes to try to cleanup CurrentUserUtils so changes can be made without rebuilding the DB. --- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 1216 +++++++++----------- src/client/util/DocumentManager.ts | 3 +- src/client/util/Scripting.ts | 2 +- src/client/views/DocComponent.tsx | 2 +- src/client/views/GlobalKeyHandler.ts | 2 +- src/client/views/MainView.tsx | 36 +- src/client/views/OverlayView.tsx | 2 +- src/client/views/PropertiesView.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 2 +- src/client/views/collections/CollectionMenu.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collectionLinear/CollectionLinearView.tsx | 4 +- src/client/views/linking/LinkPopup.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/FilterBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 12 +- src/client/views/nodes/button/FontIconBox.tsx | 2 +- src/client/views/nodes/trails/PresBox.tsx | 8 +- src/fields/Doc.ts | 5 +- src/fields/util.ts | 3 + src/mobile/MobileInterface.tsx | 2 +- 22 files changed, 629 insertions(+), 711 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 5f009573e..927996af2 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -178,10 +178,15 @@ export class DocumentOptions { hidden?: boolean; _hidden?: boolean; pointerEvents?: string; // pointer events that the documentview should have - mediaState?: string; // status of media document: "pendingRecording", "recording", "paused", "playing" + mediaState?: string; // status of audio/video media document: "pendingRecording", "recording", "paused", "playing" + recording?: boolean; // whether WebCam is recording or not autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. toolTip?: string; // tooltip to display on hover + contextMenuFilters?: List; + contextMenuScripts?: List; + contextMenuLabels?: List; + contextMenuIcons?: List; dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links layout?: string | Doc; // default layout string for a document @@ -238,7 +243,7 @@ export class DocumentOptions { isPushpin?: boolean; isGroup?: boolean; // whether a collection should use a grouping UI behavior _removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document - + noteType?: string; // BACKGROUND GRID _backgroundGridShow?: boolean; @@ -286,6 +291,7 @@ export class DocumentOptions { clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop cloneFieldFilter?: List; // fields not to copy when the document is clonedclipboard?: Doc; + filterBoolean ?: string; useCors?: boolean; icon?: string; target?: Doc; // available for use in scripts as the primary target document diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b4e18a8bb..afb1442bf 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,16 +1,17 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, StrListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; +import { listSpec } from "../../fields/Schema"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { BoolCast, Cast, DateCast, DocCast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, PromiseValue, ScriptCast, StrCast } from "../../fields/Types"; import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; -import { Utils } from "../../Utils"; +import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; @@ -42,17 +43,17 @@ interface Button { toolTip?: string; icon?: string; btnType?: ButtonType; - click?: string; numBtnType?: NumButtonType; numBtnMin?: number; numBtnMax?: number; switchToggle?: boolean; - script?: string; width?: number; - list?: string[]; + btnList?: List; ignoreClick?: boolean; buttonText?: string; - hidden?: string; + scripts?: { script?: string; onClick?: string; } + funcs?: { [key:string]: string }; + subMenu?: Button[]; } export let resolvedPorts: { server: number, socket: number }; @@ -73,101 +74,134 @@ export class CurrentUserUtils { @observable public static headerBarHeight: number = 0; @observable public static searchPanelWidth: number = 0; - // sets up the default User Templates - slideView, headerView - static setupExperimentalTemplateButtons(doc: Doc) { - if (doc["template-experimental-buttons"] === undefined) { - const requiredTypeNameFields = [ - { - type: "slide", icon: "address-card", template: Docs.Create.MultirowDocument( - [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) - ], - { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } - ) - }, - { - type: "mobile", icon: "mobile", template: this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, - [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) - ] - ) - }, - ]; - const requiredTypes = requiredTypeNameFields.map(({ type, icon, template }) => { - if (doc[`template-button-${type}`] === undefined) { - template.isTemplateDoc = makeTemplate(template); - doc[`template-button-${type}`] = CurrentUserUtils.createToolButton({ - onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), - dragFactory: new PrefetchProxy(template) as any as Doc, - title: type, - icon, - }); + static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, functions?:{[key:string]: string}) { + scripts && Object.keys(scripts).map(key => { + if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { + doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); + } + }); + functions && Object.keys(functions).map(key => { + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + if (ScriptCast(cfield)?.script.originalScript !== functions[key] && functions[key]) { + doc[key] = ComputedField.MakeFunction(functions[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); + } + }); + return doc; + } + static AssignOpts(doc:Doc|undefined, reqdOpts:DocumentOptions, items?:Doc[]) { + if (doc) { + const compareValues = (val1:any, val2:any) => { + if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) { + return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v)); + } + return val1 === val2; + } + Object.entries(reqdOpts).forEach(pair => { + const targetDoc = pair[0].startsWith("_") ? doc : Doc.GetProto(doc as Doc); + if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/,"")) || + !compareValues(pair[1], targetDoc[pair[0]])) { + targetDoc[pair[0]] = pair[1]; } - return doc[`template-button-${type}`] as Doc; }); - - doc["template-experimental-buttons"] = new PrefetchProxy(Docs.Create.MasonryDocument(requiredTypes, { - title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - hidden: ComputedField.MakeFunction("IsNoviceMode()") as any, - _stayInCollection: true, _hideContextMenu: true, _forceActive: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true - })); + items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), "data", item)); } - return doc["template-experimental-buttons"] as Doc; + return doc; } - // setup the different note type skins - static setupNoteTemplates(doc: Doc) { - if (doc["template-notes"] === undefined) { - const requiredTypeNameFields = [ - { type: "Note", backgroundColor: "yellow", icon: "sticky-note" }, - { type: "Idea", backgroundColor: "pink", icon: "lightbulb" }, - { type: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; - const requiredTypes = requiredTypeNameFields.map(({ type, backgroundColor, icon }) => - doc[`template-note-${type}`] as Doc ?? - (() => { - const noteView = Docs.Create.TextDocument("", { title: "text", backgroundColor, system: true, icon }); - noteView.isTemplateDoc = makeTemplate(noteView, true, type); - return doc[`template-note-${type}`] = new PrefetchProxy(noteView); - })()); - - doc["template-notes"] = new PrefetchProxy(Docs.Create.TreeDocument(requiredTypes, { title: "Note Layouts", _height: 75, system: true })); - } + // initializes experimental advanced template views - slideView, headerView + static setupExperimentalTemplateButtons(doc: Doc, field="template-experimental-buttons") { + const tempDocs = DocCast(doc[field]); + const requiredTypeNameFields:{opts:DocumentOptions, template:() => Doc}[] = [ + { + opts:{type: "slide", icon: "address-card"}, template: () => Docs.Create.MultirowDocument( + [ + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) + ], + { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } + ) + }, + { + opts:{type: "mobile", icon: "mobile"}, template: () => this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, + [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) + ] + ) + }, + ]; + const requiredTypes = requiredTypeNameFields.map(({ opts, template }) => { + const docType = DocListCast(tempDocs?.data)?.find(doc => doc.title === opts.type); + const reqdOpts = { + dragFactory: template(), + title: opts.type, + icon: opts.icon + }; + const reqdScripts = {onDragStart: 'copyDragFactory(this.dragFactory)'}; + const makeTemp = (doc:Doc) => { + doc.isTemplateDoc = makeTemplate(doc); + return doc; + } + return this.AssignScripts(!docType ? makeTemp(CurrentUserUtils.createToolButton(reqdOpts)) : this.AssignOpts(docType, reqdOpts)!, reqdScripts)!; + }); + + const reqdOpts = { + title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, + _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, + _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, + }; + const reqdScripts = {dropConverter : "convertToButtons(dragData)"}; + const reqdFuncs = {hidden: "IsNoviceMode()"} + return this.AssignScripts(!tempDocs ? + (doc[field] = Docs.Create.MasonryDocument(requiredTypes, reqdOpts)) : + this.AssignOpts(tempDocs, reqdOpts, requiredTypes)!, + reqdScripts, reqdFuncs); + } + + /// Initializes templates that can be applied to notes + static setupNoteTemplates(doc: Doc, field="template-notes") { + const tempNotes = DocCast(doc[field]); + const reqdTempOpts:DocumentOptions[] = [ + { noteType: "Note", backgroundColor: "yellow", icon: "sticky-note"}, + { noteType: "Idea", backgroundColor: "pink", icon: "lightbulb" }, + { noteType: "Topic", backgroundColor: "lightblue", icon: "book-open" }]; + const reqdNoteList = reqdTempOpts.map(opts => { + const reqdOpts = {...opts, title: "text", system: true}; + const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined; + const makeTemp = (doc:Doc, noteType?:string) => { + doc.isTemplateDoc = makeTemplate(doc, true, noteType??"Note"); + return doc; + } + return this.AssignOpts(noteType, reqdOpts) ?? makeTemp(Docs.Create.TextDocument("",reqdOpts), opts.noteType); + }); - return doc["template-notes"] as Doc; + const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, system: true }; + return this.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); } - // creates Note templates, and initial "user" templates + /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc) { - if (doc.templateDocs === undefined) { - doc.templateDocs = new PrefetchProxy(Docs.Create.TreeDocument([ - CurrentUserUtils.setupNoteTemplates(doc), - CurrentUserUtils.setupExperimentalTemplateButtons(doc), - CurrentUserUtils.setupClickEditorTemplates(doc) - ], { - title: "template layouts", _xMargin: 0, system: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }) - })); - } + const templates = [ + CurrentUserUtils.setupNoteTemplates(doc), + CurrentUserUtils.setupClickEditorTemplates(doc) + ]; + const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; + const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; + return this.AssignScripts(this.AssignOpts(DocCast(doc.templateDocs), reqdOpts, templates) ?? (doc.templateDocs = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts); } // setup templates for different document types when they are iconified from Document Decorations - static setupDefaultIconTemplates(doc: Doc) { - const templateIconsDoc = Cast(doc["template-icons"], Doc, null); + static setupDefaultIconTemplates(doc: Doc, field="template-icons") { + const templateIconsDoc = DocCast(doc[field]); const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => { const iconFieldName = "icon" + (type ? "_" + type : ""); if (!templateIconsDoc?.[iconFieldName]) { const template = MakeTemplate(iconTemplate(), true, iconFieldName, templateField); if (templateIconsDoc) { - templateIconsDoc[iconFieldName] = new PrefetchProxy(template); - Doc.AddDocToList(templateIconsDoc, "data", template); - } else { - return template; + templateIconsDoc[iconFieldName] = template; } + return template; } }; const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); @@ -182,46 +216,57 @@ export class CurrentUserUtils { const fontBox = () => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, system: true, onClick: deiconifyScript() }); - if (!templateIconsDoc) { - const newIconsList = [ - makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), - makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), - makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), - makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), - makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), - makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), - makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), - makeIconTemplate(DocumentType.BUTTON, "data", fontBox), - //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now - makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })), - // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) - ].filter(d => d).map(d => d!); - - doc["template-icons"] = Docs.Create.TreeDocument(newIconsList, { title: "icon templates", _height: 75, system: true }); - } + const iconTemplates = [ + makeIconTemplate(undefined, "title", () => labelBox({ _backgroundColor: "dimgray" })), + makeIconTemplate(DocumentType.AUDIO, "title", () => labelBox({ _backgroundColor: "lightgreen" })), + makeIconTemplate(DocumentType.PDF, "title", () => labelBox({ _backgroundColor: "pink" })), + makeIconTemplate(DocumentType.WEB, "title", () => labelBox({ _backgroundColor: "brown" })), + makeIconTemplate(DocumentType.RTF, "text", () => labelBox({ _showTitle: "creationDate" })), + makeIconTemplate(DocumentType.IMG, "data", () => imageBox("", { _height: undefined, })), + makeIconTemplate(DocumentType.COL, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.VID, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})), + makeIconTemplate(DocumentType.BUTTON, "data", fontBox), + //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now + makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })), + // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) + ].filter(d => d).map(d => d!); + + const reqdOpts = { title: "icon templates", _height: 75, system: true }; + this.AssignOpts(templateIconsDoc, reqdOpts, iconTemplates) ?? (doc[field] = Docs.Create.TreeDocument(iconTemplates, reqdOpts)); } + /// initalizes the set of default versions of most document types static creatorBtnDescriptors(doc: Doc): { - title: string, toolTip: string, icon: string, drag?: string, ignoreClick?: boolean, - click?: string, backgroundColor?: string, dragFactory?: Doc, noviceMode?: boolean, clickFactory?: Doc + title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, + backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, }[] { const standardOps = () => ({ _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); - if (doc.emptyPresentation === undefined) { - doc.emptyPresentation = Docs.Create.PresDocument({ ...standardOps(), title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); - } - if (doc.emptyCollection === undefined) { - doc.emptyCollection = Docs.Create.FreeformDocument([], { ...standardOps(), title: "freeform", _width: 150, _height: 100 }); - } - if (doc.emptyPane === undefined) { - doc.emptyPane = Docs.Create.FreeformDocument([], { ...standardOps(), title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }); - } - if (doc.emptySlide === undefined) { - doc.emptySlide = Docs.Create.TreeDocument([], { - ...standardOps(), title: ComputedField.MakeFunction('self.text?.Text') as any, _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined, - allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" - }); - } + const emptyThings:{key:string, // the field name where the empty thing will be stored + opts:DocumentOptions, // the document options that are required for the empty thing + funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing + creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist + }[] = [ + {key: "emptyPresentation", creator: Docs.Create.PresDocument, opts: { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, + {key: "emptyCollection", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "freeform", _width: 150, _height: 100 }}, + {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts : { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, + {key: "emptySlide", creator: (opts) => Docs.Create.TreeDocument([], opts), funcs: {title: 'self.text?.Text'}, opts: { + _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, + "dragFactory-count": undefined, allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" + }}, + {key: "emptyComparison", creator: Docs.Create.ComparisonDocument, opts: { title: "Comparer", _width: 300, _height: 300 }}, + {key: "emptyScript", creator: (opts) => Docs.Create.ScriptingDocument(undefined, opts), opts: { title: "script", _width: 200, _height: 250, }}, + {key: "emptyScreenshot", creator: Docs.Create.ScreenshotDocument, opts: { title: "empty screenshot", _width: 400, _height: 200 }}, + {key: "emptyWebCam", creator: (opts) => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, + {key: "emptyAudio", creator: (opts) => Docs.Create.AudioDocument(nullAudio, opts), opts: { title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, }}, + {key: "emptyNote", creator: (opts) => Docs.Create.TextDocument("", opts), opts: { title: "text note", _width: 200, _autoHeight: true }}, + {key: "emptyButton", creator: Docs.Create.ButtonDocument, opts: { title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, + {key: "emptyWebpage", creator: (opts) => Docs.Create.WebDocument("http://www.bing.com/", opts), opts: { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }}, + {key: "emptyMap", creator: (opts) => Docs.Create.MapDocument([], opts), opts: { title: "map", _showSidebar: true, _width: 800, _height: 600, }} + ]; + + emptyThings.forEach(thing => + this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), thing.funcs)) + if (doc.emptyHeader === undefined) { const json = { doc: { @@ -242,196 +287,124 @@ export class CurrentUserUtils { selection: { type: "text", anchor: 1, head: 1 }, storedMarks: [] }; - const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { - ...standardOps(), title: "text", _height: 70, - _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, - }, "header"); const headerBtnHgt = 10; - headerTemplate[DataSym].layout = + const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { + ...standardOps(), title: "text", _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, + layout: "" + ` ` + " " + ` Metadata` + - ""; + "" + }, "header"); // "
    " + // " " + // " " + // "
    "; - (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView"); + Doc.GetProto(headerTemplate).isTemplateDoc = makeTemplate(Doc.GetProto(headerTemplate), true, "headerView"); doc.emptyHeader = headerTemplate; } - if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "Comparer", _width: 300, _height: 300 }); - } - if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { ...standardOps(), title: "script", _width: 200, _height: 250, }); - } - if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument({ ...standardOps(), title: "empty screenshot", _width: 400, _height: 200 }); - } - if (doc.emptyWall === undefined) { - doc.emptyWall = Docs.Create.WebCamDocument("", { _width: 400, _height: 200, title: "recording", system: true, cloneFieldFilter: new List(["system"]) }); - (doc.emptyWall as Doc).recording = true; - } - if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { ...standardOps(), title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, }); - } - if (doc.emptyNote === undefined) { - doc.emptyNote = Docs.Create.TextDocument("", { ...standardOps(), title: "text note", _width: 200, _autoHeight: true }); - } - if (doc.emptyButton === undefined) { - doc.emptyButton = Docs.Create.ButtonDocument({ ...standardOps(), title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }); - } - if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("http://www.bing.com/", { ...standardOps(), title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }); - } - if (doc.emptyMap === undefined) { - doc.emptyMap = Docs.Create.MapDocument([], { ...standardOps(), title: "map", _showSidebar: true, _width: 800, _height: 600, }); - } - if (doc.activeMobileMenu === undefined) { - this.setupActiveMobileMenu(doc); - } + return [ - { toolTip: "Tap to create a note in a new pane, drag for a note", title: "Note", icon: "sticky-note", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyNote as Doc, noviceMode: true, clickFactory: doc.emptyNote as Doc, }, - { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, - { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, - { toolTip: "Tap to create a progressive slide", title: "Slide", icon: "file", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySlide as Doc }, - { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, - { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc }, - { toolTip: "Tap to create a videoWall", title: "Wall", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWall as Doc }, - { toolTip: "Tap to create an audio recorder in a overlay pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openInOverlay(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, - { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc }, - // { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, - { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc }, - { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, - { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc }, - { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { toolTip: "Tap to create a map in the new pane, drag for a map", title: "Map", icon: "map-marker-alt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyMap as Doc, noviceMode: true } - ]; - + { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, clickFactory: doc.emptyPane as Doc, }, + { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: {onClick: 'openInOverlay(copyDragFactory(this.dragFactory))',onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a custom note", title: "Custom", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create a progressive slide",title: "Slide", icon: "file", dragFactory: doc.emptySlide as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreenshot as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs:{ hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a mobile view", title: "Phone", icon: "mobile", dragFactory: doc.activeMobileMenu as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: 'this.dragFactory'}, funcs: {hidden: 'IsNoviceMode()'} }, + { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, + // { toolTip: "Tap or drag to create a presentation", title: "Trails", icon: "pres-trail", dragFactory: doc.emptyPresentation as Doc,scripts: {onClick: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', onDragStart: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`}, funcs: {hidden: 'IsNoviceMode()'} }, + ]; } - // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools - static setupCreatorButtons(doc: Doc) { - let alreadyCreatedButtons: string[] = []; - const dragCreatorSet = Cast(doc.myItemCreators, Doc, null); - if (dragCreatorSet) { - alreadyCreatedButtons = DocListCast(dragCreatorSet.data).map(d => StrCast(d.title)); - } - const buttons = CurrentUserUtils.creatorBtnDescriptors(doc).filter(d => !alreadyCreatedButtons?.includes(d.title)); - const creatorBtns = buttons.map(({ title, toolTip, icon, ignoreClick, drag, click, backgroundColor, dragFactory, noviceMode, clickFactory }) => Docs.Create.FontIconDocument({ - _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, - icon, - title, - toolTip, - btnType: ButtonType.ToolButton, - ignoreClick, - _dropAction: "alias", - onDragStart: drag ? ScriptField.MakeFunction(drag) : undefined, - onClick: click ? ScriptField.MakeScript(click) : undefined, - backgroundColor: backgroundColor ? backgroundColor : Colors.DARK_GRAY, - color: Colors.WHITE, - _hideContextMenu: true, - _removeDropProperties: new List(["_stayInCollection"]), - _stayInCollection: true, - dragFactory, - clickFactory, - hidden: !noviceMode ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, - system: true, - })); + /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools + static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc { + const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { + const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; + const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["backgroundColor"]).omit, + _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias", + btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, + _removeDropProperties: new List(["_stayInCollection"]), + }; + return this.AssignScripts(this.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs); + }); - if (dragCreatorSet === undefined) { - doc.myItemCreators = new PrefetchProxy(Docs.Create.MasonryDocument(creatorBtns, { - title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, - _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), system: true - })); - } else { - creatorBtns.forEach(nb => Doc.AddDocToList(doc.myItemCreators as Doc, "data", nb)); - } - return doc.myItemCreators as Doc; + const reqdOpts = { + title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, + _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, + }; + const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; + return this.AssignScripts(this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); } - static menuBtnDescriptions(doc: Doc) { - const badgeValue = ScriptField.MakeFunction("((len) => len ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length)") + /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents + static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}}[] { + const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; return [ - { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, - { title: "Search", target: Cast(doc.mySearchPanel, Doc, null), icon: "search", click: 'selectMainMenu(self)' }, - { title: "Files", target: Cast(doc.myFilesystem, Doc, null), icon: "folder-open", click: 'selectMainMenu(self)' }, - { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, - { title: "Imports", target: Cast(doc.myImportDocs, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, - { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Shared with me", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', badgeValue}, - { title: "Trails", target: Cast(doc.myTrails, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, - { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)', hidden: "IsNoviceMode()" }, + { title: "Dashboards", target: CurrentUserUtils.MyDashboards, icon: "desktop", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Search", target: CurrentUserUtils.MySearcher, icon: "search", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Files", target: CurrentUserUtils.MyFilesystem, icon: "folder-open", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Tools", target: CurrentUserUtils.MyTools, icon: "wrench", scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, + { title: "Imports", target: CurrentUserUtils.MyImports, icon: "upload", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Recently Closed", target: CurrentUserUtils.MyRecentlyClosed, icon: "archive", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "Shared with me", target: CurrentUserUtils.MySharedDocs, icon: "users", scripts:{onClick: 'selectMainMenu(self)'}, funcs:{badgeValue:badgeValue}}, + { title: "Trails", target: CurrentUserUtils.MyTrails, icon: "pres-trail", scripts:{onClick: 'selectMainMenu(self)'} }, + { title: "User Doc", target: CurrentUserUtils.MyUserDocView, icon: "address-card",scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, ]; } - static async setupMenuPanel(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (doc.menuStack === undefined) { - await this.setupLinkDocs(doc, linkDatabaseId); - await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, badgeValue, hidden }) => - Docs.Create.FontIconDocument({ - icon, - btnType: ButtonType.MenuButton, - _stayInCollection: true, - _hideContextMenu: true, - _chromeHidden: true, - system: true, - dontUndo: true, - title, - target, - dontRegisterView: true, - hidden: hidden ? ComputedField.MakeFunction("IsNoviceMode()") as any : undefined, - _dropAction: "alias", - _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - _width: 60, - _height: 60, - badgeValue, - onClick: ScriptField.MakeScript(click, { scriptContext: "any" }) - }) - ); - - doc.searchBtn = menuBtns.find(btn => btn.title === "Search"); - - doc.menuStack = new PrefetchProxy(Docs.Create.StackingDocument(menuBtns, { - title: "menuItemPanel", - childDropAction: "alias", - _chromeHidden: true, - backgroundColor: Colors.DARK_GRAY, - boxShadow: "rgba(0,0,0,0)", - dontRegisterView: true, - dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - ignoreClick: true, - _gridGap: 0, - _yMargin: 0, - _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true - })); - } - return doc.menuStack as Doc; + /// setup the left sidebar container and panels that can be displayed within it + static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { + this.AssignOpts(DocCast(doc[field]), {}) ?? (doc[field] = ((doc:Doc) => {doc.system = true; return doc;})(new Doc())); + CurrentUserUtils.setupSearcher(doc, "mySearcher"); + CurrentUserUtils.setupToolsBtnPanel(doc, "myTools"); + CurrentUserUtils.setupImportSidebar(doc, "myImports"); + CurrentUserUtils.setupDashboards(doc, "myDashboards"); + CurrentUserUtils.setupTrails(doc, "myTrails"); + CurrentUserUtils.setupFilesystem(doc, "myFilesystem"); + CurrentUserUtils.setupRecentlyClosedDocs(doc, "myRecentlyClosed"); + CurrentUserUtils.setupUserDocView(doc, "myUserDocView"); } + /// Initializes the left sidebar menu buttons and the panels they open up + static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") { + this.setupLeftSidebarPanel(doc); // the tools/panels opened up by the menu buttons + const myLeftSidebarMenu = DocCast(doc[field]); + const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => { + const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; + const reqdBtnOpts:DocumentOptions = { + title, icon, target, btnType: ButtonType.MenuButton, system: true, dontUndo: true, dontRegisterView: true, + _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias", + _removeDropProperties: new List(["dropAction", "_stayInCollection"]), + }; + return this.AssignScripts(this.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs); + }); - // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu - static setupActiveMobileMenu(doc: Doc) { - if (doc.activeMobileMenu === undefined) { - doc.activeMobileMenu = this.setupMobileMenu(); - } - return doc.activeMobileMenu as Doc; + const reqdStackOpts:DocumentOptions ={ + title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, + _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true + }; + const reqdScripts = { dropConverter: "convertToButtons(dragData)" } + return this.AssignScripts(this.AssignOpts(myLeftSidebarMenu, reqdStackOpts, menuBtns) ?? (doc[field] = Docs.Create.StackingDocument(menuBtns, reqdStackOpts)), reqdScripts); } - // Sets up mobileMenu stacking document - static setupMobileMenu() { - const menu = new PrefetchProxy(Docs.Create.StackingDocument(this.setupMobileButtons(), { - _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true, - })); - return menu; + // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu + static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") { + const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,}; + this.AssignOpts(DocCast(doc[field]), reqdOpts, this.setupMobileButtons()) ?? (doc[field] = Docs.Create.StackingDocument(this.setupMobileButtons(), reqdOpts)); } - // SEts up mobile buttons for inside mobile menu + // Sets up mobile buttons for inside mobile menu static setupMobileButtons(doc?: Doc, buttons?: string[]) { + return []; const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " }, { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." }, @@ -533,375 +506,329 @@ export class CurrentUserUtils { }); } - static setupLibrary(userDoc: Doc) { - return CurrentUserUtils.setupDashboards(userDoc); - } - - // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. - // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static setupToolsBtnPanel(doc: Doc) { - // setup a masonry view of all he creators - const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc); - const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); - - doc.myCreators = doc.myCreators ?? new PrefetchProxy(Docs.Create.StackingDocument([creatorBtns, templateBtns], { - title: "all Creators", _yMargin: 0, _autoHeight: true, _xMargin: 0, _fitWidth: true, - _width: 500, _height: 300, ignoreClick: true, _lockedPosition: true, system: true, _chromeHidden: true, - })); - if (!DocListCast(doc.myCreators).includes(creatorBtns) || !DocListCast(doc.myCreators).includes(templateBtns)) Doc.GetProto(doc.myCreators as Doc).data = new List([creatorBtns, templateBtns]); - - doc.myColorPicker = doc.myColorPicker ?? new PrefetchProxy(Docs.Create.ColorDocument({ - title: "color picker", _width: 220, _dropAction: "alias", _hideContextMenu: true, _stayInCollection: true, _forceActive: true, _removeDropProperties: new List(["dropAction", "_stayInCollection", "_hideContextMenu", "forceActive"]), system: true - })); - - doc.myTools = doc.myTools ?? new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc], { - title: "My Tools", _showTitle: "title", _width: 500, _yMargin: 20, ignoreClick: true, _lockedPosition: true, _forceActive: true, - system: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, boxShadow: "0 0", - })) as any as Doc; - if (!DocListCast(doc.myTools).includes(doc.myCreators as Doc)) Doc.GetProto(doc.myTools as Doc).data = new List([doc.myCreators as Doc]); - } - - static async setupDashboards(doc: Doc) { - // setup dashboards library item - await doc.myDashboards; - if (doc.myDashboards === undefined) { - const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); - const newDashboardButton: Doc = Docs.Create.FontIconDocument({ onClick: newDashboard, _forceActive: true, toolTip: "Create new dashboard", _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); - doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Dashboards", _showTitle: "title", _height: 400, childHideLinkButton: true, freezeChildren: "remove|add", - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newDashboardButton, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, - explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." - })); - const toggleDarkTheme = ScriptField.MakeScript(`this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`); - const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); - const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`); - const shareDashboard = ScriptField.MakeScript(`shareDashboard(self)`); - const removeDashboard = ScriptField.MakeScript('removeDashboard(self)'); - const developerFilter = ScriptField.MakeFunction('!IsNoviceMode()'); - // (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, shareDashboard!, removeDashboard!]); - // (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Share Dashboard", "Remove Dashboard"]); - // (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "user-friends", "times"]); - (doc.myDashboards as any as Doc).childContextMenuScripts = new List([newDashboard!, toggleDarkTheme!, toggleComic!, snapshotDashboard!, shareDashboard!, removeDashboard!]); - (doc.myDashboards as any as Doc).childContextMenuLabels = new List(["Create New Dashboard", "Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]); - (doc.myDashboards as any as Doc).childContextMenuIcons = new List(["plus", "chalkboard", "tv", "camera", "users", "times"]); - (doc.myDashboards as any as Doc).childContextMenuFilters = new List([undefined as any, developerFilter, developerFilter, developerFilter, undefined as any, undefined as any]); - } - return doc.myDashboards as any as Doc; + /// Search option on the left side button panel + static setupSearcher(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { + dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", + _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, + }; + this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.SearchDocument(reqdOpts)); } - static setupPresentations(doc: Doc) { - if (doc.myTrails === undefined) { - const newTrail = ScriptField.MakeScript(`createNewPresentation()`); - const newTrailButton: Doc = Docs.Create.FontIconDocument({ onClick: newTrail, _forceActive: true, toolTip: "Create new trail", _stayInCollection: true, _hideContextMenu: true, title: "New trail", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New trail", icon: "plus", system: true }); - doc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Trails", _showTitle: "title", _height: 100, - treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, - explainer: "All of the trails that you have created will appear here." - })); - (doc.myTrails as any as Doc).contextMenuScripts = new List([newTrail!]); - (doc.myTrails as any as Doc).contextMenuLabels = new List(["Create New Trail"]); - (doc.myTrails as any as Doc).childContextMenuIcons = new List(["plus"]); - } - return doc.myTrails as any as Doc; + /// Initializes the panel of draggable tools that is opened from the left sidebar. + static setupToolsBtnPanel(doc: Doc, field:string) { + const myTools = DocCast(doc[field]); + const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined); + //const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); + const reqdToolOps:DocumentOptions = { + title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", + _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, + }; + this.AssignOpts(myTools, reqdToolOps, [creatorBtns, /*templateBtns*/]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, /*templateBtns*/], reqdToolOps)); } - static async setupFilesystem(doc: Doc) { - await doc.myFilesystem; - if (doc.myFilesystem === undefined) { - doc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); - // doc.myFileRoot = Docs.Create.TreeDocument([], { title: "file root", _stayInCollection: true, system: true, isFolder: true }); - const newFolder = ScriptField.MakeFunction(`makeTopLevelFolder()`, { scriptContext: "any" })!; - const newFolderButton: Doc = Docs.Create.FontIconDocument({ - onClick: newFolder, _forceActive: true, toolTip: "Create new folder", - _stayInCollection: true, _hideContextMenu: true, title: "New folder", btnType: ButtonType.ClickButton, _width: 30, _height: 30, - buttonText: "New folder", icon: "folder-plus", system: true - }); - doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileOrphans as Doc], { - title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100, - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true, - explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." - })); - (doc.myFilesystem as any as Doc).contextMenuScripts = new List([newFolder]); - (doc.myFilesystem as any as Doc).contextMenuLabels = new List(["Create new folder"]); - (doc.myFilesystem as any as Doc).childContextMenuIcons = new List(["plus"]); + /// initializes the left sidebar dashboard pane + static setupDashboards(doc: Doc, field:string) { + var myDashboards = DocCast(doc[field]); + + const newDashboard = `createNewDashboard(Doc.UserDoc())`; + const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, + title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true }; + const reqdBtnScript = {onClick: newDashboard,} + const newDashboardButton = this.AssignScripts(this.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); + + const reqdOpts:DocumentOptions = { + title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true, + targetDropAction: "same", treeViewType: TreeViewType.fileSystem, isFolder: true, system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true, + buttonMenu: true, buttonMenuDoc: newDashboardButton, childDropAction: "alias", + _showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true, + contextMenuLabels: new List(["Create New Dashboard"]), + contextMenuIcons: new List(["plus"]), + childContextMenuLabels: new List(["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard"]),// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters + childContextMenuIcons: new List(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters + explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." + }; + myDashboards = this.AssignOpts(myDashboards, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; + const contextMenuScripts = [newDashboard]; + const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters + const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts + if (Cast(myDashboards.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { + myDashboards.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); + } + if (Cast(myDashboards.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { + myDashboards.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); + } + if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) { + myDashboards.childContextMenuFilters = new List(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!)); } - return doc.myFilesystem as any as Doc; } - static setupRecentlyClosedDocs(doc: Doc) { - if (doc.myRecentlyClosedDocs === undefined) { - const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); - const clearDocsButton: Doc = Docs.Create.FontIconDocument({ onClick: clearAll, _forceActive: true, toolTip: "Empty recently closed", _stayInCollection: true, _hideContextMenu: true, title: "Empty", btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "Empty", icon: "trash", system: true }); - doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Recently Closed", _showTitle: "title", buttonMenu: true, buttonMenuDoc: clearDocsButton, childHideLinkButton: true, - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, - explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." - - })); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List([clearAll!]); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List(["Empty recently closed"]); - (doc.myRecentlyClosedDocs as any as Doc).contextMenuIcons = new List(["trash"]); - + /// initializes the left sidebar Trails pane + static setupTrails(doc: Doc, field:string) { + var myTrails = DocCast(doc[field]); + const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, + title: "New trail", toolTip: "Create new trail", btnType: ButtonType.ClickButton, buttonText: "New trail", icon: "plus", system: true }; + const reqdBtnScript = {onClick: `createNewPresentation()`}; + const newTrailButton = this.AssignScripts(this.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); + + const reqdOpts:DocumentOptions = { + title: "My Trails", _showTitle: "title", _height: 100, + treeViewHideTitle: true, _fitWidth: true, _gridGap: 5, _forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, ignoreClick: true, buttonMenu: true, buttonMenuDoc: newTrailButton, + contextMenuIcons: new List(["plus"]), + contextMenuLabels: new List(["Create New Trail"]), + _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, + explainer: "All of the trails that you have created will appear here." + }; + myTrails = this.AssignOpts(myTrails, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([],reqdOpts )); + const contextMenuScripts = [reqdBtnScript.onClick]; + if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { + myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } } - static setupFilterDocs(doc: Doc) { - // setup Filter item - if (doc.currentFilter === undefined) { - doc.currentFilter = Docs.Create.FilterDocument({ - title: "Unnamed Filter", _height: 150, - treeViewHideTitle: true, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, _autoHeight: true, _fitWidth: true - }); - const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); - (doc.currentFilter as Doc).contextMenuScripts = new List([clearAll!]); - (doc.currentFilter as Doc).contextMenuLabels = new List(["Clear All"]); - (doc.currentFilter as Doc).filterBoolean = "AND"; + /// initializes the left sidebar File system pane + static setupFilesystem(doc: Doc, field:string) { + var myFilesystem = DocCast(doc[field]); + const reqdOrphansOpts:DocumentOptions = { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }; + this.AssignOpts(DocCast(doc.myFileOrphans), reqdOrphansOpts) ?? (doc.myFileOrphans = Docs.Create.TreeDocument([], reqdOrphansOpts)); + + const newFolder = `makeTopLevelFolder()`; + const newFolderOpts: DocumentOptions = { + _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _width: 30, _height: 30, + title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", system: true + }; + const newFolderScript = { onClick: newFolder}; + const newFolderButton = this.AssignScripts(this.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); + + const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, + title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true, + isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, boxShadow: "0 0", childDontRegisterViews: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, childDropAction: "alias", + childContextMenuLabels: new List(["Create new folder"]), + childContextMenuIcons: new List(["plus"]), + explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." + }; + myFilesystem = this.AssignOpts(myFilesystem, reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([DocCast(doc.myFileOrphans)], reqdOpts)); + const childContextMenuScripts = [newFolder]; + if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { + myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } } - static setupUserDoc(doc: Doc) { - if (doc.myUserDoc === undefined) { - doc.treeViewOpen = true; - doc.treeViewExpandedView = "fields"; - doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { - treeViewHideTitle: true, _gridGap: 5, _forceActive: true, title: "My UserDoc", _showTitle: "title", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true - })) as any as Doc; + /// initializes the panel displaying docs that have been recently closed + static setupRecentlyClosedDocs(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, + title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", + contextMenuLabels: new List(["Empty recently closed"]), + contextMenuIcons:new List(["trash"]), + explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." + }; + const recentlyClosed = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + + const clearAll = (target:string) => `getProto(${target}).data = new List([])`; + const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, + title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", system: true, + toolTip: "Empty recently closed",}; + const clearBtnsScripts = {onClick: clearAll("self.target")} + const clearDocsButton = this.AssignScripts( + this.AssignOpts(DocCast(recentlyClosed?.clearDocsBtn), clearBtnsOpts) ?? (recentlyClosed.clearDocsBtn = Docs.Create.FontIconDocument(clearBtnsOpts)), + clearBtnsScripts); + + if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton; + + if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) { + recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("self"))!]) } } - static setupSidebarContainer(doc: Doc) { - if (doc.sidebar === undefined) { - const sidebarContainer = new Doc(); - sidebarContainer.system = true; - doc.sidebar = new PrefetchProxy(sidebarContainer); - } - return doc.sidebar as Doc; + /// creates a new, empty filter doc + static createFilterDoc() { + const clearAll = `getProto(self).data = new List([])`; + const reqdOpts:DocumentOptions = { + _lockedPosition: true, _autoHeight: true, _fitWidth: true, _height: 150, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, + title: "Unnamed Filter", filterBoolean: "AND", boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, + childDropAction: "none", treeViewHideTitle: true, treeViewTruncateTitleWidth: 150, + childContextMenuLabels: new List(["Clear All"]), + childContextMenuScripts: new List([ScriptField.MakeFunction(clearAll)!]), + }; + return Docs.Create.FilterDocument(reqdOpts); } - // setup the list of sidebar mode buttons which determine what is displayed in the sidebar - static async setupSidebarButtons(doc: Doc) { - CurrentUserUtils.setupSidebarContainer(doc); - CurrentUserUtils.setupToolsBtnPanel(doc); - CurrentUserUtils.setupImportSidebar(doc); - CurrentUserUtils.setupDashboards(doc); - CurrentUserUtils.setupPresentations(doc); - CurrentUserUtils.setupFilesystem(doc); - CurrentUserUtils.setupRecentlyClosedDocs(doc); - CurrentUserUtils.setupUserDoc(doc); + /// initializes the left sidebar panel view of the UserDoc + static setupUserDocView(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { + _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view", + boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, + treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 + }; + if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }) + this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([doc], reqdOpts)); } - static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { - ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true, + static linearButtonList = (title: string, opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { + title, ...opts, _gridGap: 0, _xMargin: 5, _yMargin: 5, boxShadow: "0 0", _forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), _lockedPosition: true, system: true, flexDirection: "row" - })) as any as Doc + }) - static createToolButton = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ + static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({ btnType: ButtonType.ToolButton, _forceActive: true, _dropAction: "alias", _hideContextMenu: true, _removeDropProperties: new List(["_dropAction", "_hideContextMenu", "stayInCollection"]), _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, system: true, ...opts, - })) as any as Doc - - /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window - static setupDockedButtons(doc: Doc) { - if (doc.dockedBtns === undefined) { - const dockBtn = (opts: DocumentOptions) => CurrentUserUtils.createToolButton({ _width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...opts }); - const btnDescs = [ - { title: "undo", opts: () => ({ icon: "undo-alt", onClick: ScriptField.MakeScript("undo()"), toolTip: "Click to undo" }) }, - { title: "redo", opts: () => ({ icon: "redo-alt", onClick: ScriptField.MakeScript("redo()"), toolTip: "Click to redo" }) } - ]; - doc.dockedBtns = CurrentUserUtils.linearButtonList({ - title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, - childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true - }, btnDescs.map(desc => doc[`dockedBtn-${desc.title}`] as Doc ?? (doc[`dockedBtn-${desc.title}`] = dockBtn({ title: desc.title, ...desc.opts() })))); + }) + + /// initializes the required buttons in the expanding button menu at the bottom of the Dash window + static setupDockedButtons(doc: Doc, field="myDockedBtns") { + const dockedBtns = DocCast(doc[field]); + const dockBtn = (title:string, onClick: string, opts: DocumentOptions) => { + const btn = this.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === title), opts) ?? + CurrentUserUtils.createToolButton({title, ...opts}); + this.AssignScripts(btn, {onClick}) + return btn; } + const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet + { title: "undo", click: "undo()", opts: { icon: "undo-alt", toolTip: "Click to undo" }}, + { title: "redo", click:"redo()", opts: { icon: "redo-alt", toolTip: "Click to redo" }} + ]; + const btns = btnDescs.map(desc => dockBtn(desc.title, desc.click, {_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts})); + const dockBtnsReqdOpts = { + title: "docked buttons", _height: 40, flexGap: 0, linearViewFloating: true, + childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true + }; + reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); + reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); + return this.AssignOpts(dockedBtns, dockBtnsReqdOpts, btns) ?? (doc[field] = CurrentUserUtils.linearButtonList("dockedBtns", dockBtnsReqdOpts, btns)); } - static textTools(doc: Doc) { - const tools: Button[] = - [ - { - title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - list: ["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", - "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"], - script: 'setFont(value, _readOnly_)' - }, - { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, script: 'setFontSize(value, _readOnly_)' }, - { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, script: 'setFontColor(value, _readOnly_)' }, - { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", click: 'toggleBold(_readOnly_)' }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", click: 'toggleItalic(_readOnly_)' }, - { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", click: 'toggleUnderline(_readOnly_)' }, - { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", click: 'setBulletList("bullet", _readOnly_)' }, - { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", click: 'setBulletList("decimal", _readOnly_)' }, - - // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", click: 'toggleStrikethrough()'}, - // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", click: 'toggleSuperscript()'}, - // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", click: 'toggleSubscript()'}, - { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", click: 'setAlignment("left", _readOnly_)' }, - { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", click: 'setAlignment("center", _readOnly_)' }, - { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", click: 'setAlignment("right", _readOnly_)' }, - { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", click: 'toggleNoAutoLinkAnchor(_readOnly_)' }, - ]; - return tools; + static textTools():Button[] { + return [ + { + title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, + btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]), + scripts :{script : 'setFont(value, _readOnly_)'} + }, + { title: "Size", toolTip: "Font size", width: 75, btnType: ButtonType.NumberButton, numBtnMax: 200, numBtnMin: 0, numBtnType: NumButtonType.DropdownOptions, ignoreClick: true, scripts: {script: '{ return setFontSize(value, _readOnly_);}'} }, + { title: "Color", toolTip: "Font color", btnType: ButtonType.ColorButton, icon: "font", ignoreClick: true, scripts:{script: '{ return setFontColor(value, _readOnly_); }'}}, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", scripts: {onClick: '{ return toggleBold(_readOnly_); }'} }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", scripts: {onClick: '{ return toggleItalic(_readOnly_);}'} }, + { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline",scripts: {onClick:'{ return toggleUnderline(_readOnly_);}'} }, + { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", scripts: {onClick: '{ return setBulletList("bullet", _readOnly_);}'} }, + { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", scripts: {onClick: '{ return setBulletList("decimal", _readOnly_);}'} }, + + // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, + // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}}, + // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}}, + { title: "Left", toolTip: "Left align", btnType: ButtonType.ToggleButton, icon: "align-left", scripts: {onClick:'{ return setAlignment("left", _readOnly_);}' }}, + { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} }, + { title: "Right", toolTip: "Right align", btnType: ButtonType.ToggleButton, icon: "align-right", scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} }, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}}, + ]; } - static inkTools(doc: Doc) { - const tools: Button[] = [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", click: 'setActiveInkTool("pen", _readOnly_)' }, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", click: 'setActiveInkTool("write", _readOnly_)' }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", click: 'setActiveInkTool("eraser", _readOnly_)' }, - // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", click: 'setActiveInkTool("highlighter")' }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", click: 'setActiveInkTool("circle", _readOnly_)' }, + static inkTools():Button[] { + return [ + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveInkTool("pen", _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveInkTool("write", _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveInkTool("eraser", _readOnly_);}' }}, + // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveInkTool("highlighter")'} }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return setActiveInkTool("circle", _readOnly_);}'} }, // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", click: 'setActiveInkTool("line", _readOnly_)' }, - { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setFillColor(value, _readOnly_)" }, - { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, script: 'setStrokeWidth(value, _readOnly_)' }, - { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, script: 'setStrokeColor(value, _readOnly_)' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveInkTool("line", _readOnly_);}' }}, + { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: {script: "{ return setFillColor(value, _readOnly_);}"} }, + { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'} }, + { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, ]; - return tools; } - static schemaTools(doc: Doc) { - const tools: Button[] = - [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", click: 'toggleSchemaPreview(_readOnly_)', }]; - return tools; + static schemaTools():Button[] { + return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{onClick:'toggleSchemaPreview(_readOnly_)'}, }]; } - static webTools(doc: Doc) { - const tools: Button[] = - [ - { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack(_readOnly_)' }, - { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward(_readOnly_)' }, - //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, - { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL(value, _readOnly_)' }, - ]; - - return tools; + static webTools() { + return [ + { title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", click: 'webBack(_readOnly_)' }, + { title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", click: 'webForward(_readOnly_)' }, + //{ title: "Reload", toolTip: "Reload webpage", btnType: ButtonType.ClickButton, icon: "redo-alt", click: 'webReload()' }, + { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, script: 'webSetURL(value, _readOnly_)' }, + ]; } - static contextMenuTools(doc: Doc) { + static contextMenuTools():Button[] { return [ { title: "Perspective", toolTip: "View", width: 100, btnType: ButtonType.DropdownList, ignoreClick: true, - list: [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, - CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, - CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, - CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, - CollectionViewType.Grid], - script: 'setView(value, _readOnly_)', + btnList: new List([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, + CollectionViewType.Stacking, CollectionViewType.Masonry, CollectionViewType.Multicolumn, + CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, + CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, + CollectionViewType.Grid]), + scripts: {script: 'setView(value, _readOnly_)'}, }, // Always show - { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'prevKeyFrame(_readOnly_)', icon: "chevron-left", hidden: 'IsNoviceMode()' }, - { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, click: 'nextKeyFrame(_readOnly_)', icon: "chevron-right", hidden: 'IsNoviceMode()' }, - { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", script: "setBackgroundColor(value, _readOnly_)", hidden: 'selectedDocumentType()' }, // Only when a document is selected - { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", script: "setHeaderColor(value, _readOnly_)", hidden: 'selectedDocumentType()', }, - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", click: 'toggleOverlay(_readOnly_)', hidden: 'selectedDocumentType(undefined, "freeform", true)' }, // Only when floating document is selected in freeform + { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton,icon: "chevron-left", scripts:{onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'} }, + { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, icon: "chevron-right", scripts:{onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, + { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: { script: "setBackgroundColor(value, _readOnly_)"}, funcs:{ hidden: 'selectedDocumentType()' }}, // Only when a document is selected + { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", scripts: {script: "setHeaderColor(value, _readOnly_)"}, funcs : {hidden: 'selectedDocumentType()'} }, + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", scripts: {onClick : 'toggleOverlay(_readOnly_)'}, funcs: {hidden: 'selectedDocumentType(undefined, "freeform", true)'} }, // Only when floating document is selected in freeform // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected - { title: "Text", type: "textTools", subMenu: CurrentUserUtils.textTools(doc), expanded: 'selectedDocumentType("rtf")' }, // Always available - { title: "Ink", type: "inkTools", subMenu: CurrentUserUtils.inkTools(doc), expanded: 'selectedDocumentType("ink")' }, // Always available - { title: "Web", type: "webTools", subMenu: CurrentUserUtils.webTools(doc), hidden: 'selectedDocumentType("web")' }, // Only when Web is selected - { title: "Schema", type: "schemaTools", subMenu: CurrentUserUtils.schemaTools(doc), hidden: 'selectedDocumentType(undefined, "schema")' } // Only when Schema is selected + { title: "Text", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available + { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available + { title: "Web", icon: "web", subMenu: CurrentUserUtils.webTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.WEB}")`, hidden: `!selectedDocumentType("${DocumentType.WEB}")`} }, // Only when Web is selected + { title: "Schema", icon: "schema", subMenu: CurrentUserUtils.schemaTools(), funcs: { linearViewIsExpanded: `selectedDocumentType(undefined, "${CollectionViewType.Schema}")`, hidden: `!selectedDocumentType(undefined, "${CollectionViewType.Schema}")`} } // Only when Schema is selected ]; } - // Sets up the default context menu buttons - static setupContextMenuButtons(doc: Doc) { - const btnFunc = (params: Button) => Docs.Create.FontIconDocument({ - title: params.title, icon: params.icon, toolTip: params.toolTip, color: Colors.WHITE, system: true, dontUndo: true, ignoreClick: params.ignoreClick, + /// initializes a context menu button for the top bar context menu + static setupContextMenuButton(params:Button, btnDoc?:Doc) { + const reqdOpts:DocumentOptions = { + title: params.title, btnType: params.btnType, icon: params.icon, toolTip: params.toolTip, ignoreClick: params.ignoreClick, + numBtnType: params.numBtnType, numBtnMin: params.numBtnMin, numBtnMax: params.numBtnMax,_nativeHeight: 30, btnList: params.btnList, + backgroundColor: params.scripts?.onClick ? undefined: "transparent", /// a bit hacky. if an onClick is specified, then we assume we assume a toggle use onClick to get the backgroundColor (see below). Otherwise, assume a transparent background _nativeWidth: params.width ? params.width : 30, - _nativeHeight: 30, _width: params.width ? params.width : 30, _height: 30, - btnType: params.btnType, - numBtnType: params.numBtnType, numBtnMin: params.numBtnMin, numBtnMax: params.numBtnMax, - btnList: new List(params.list), + color: Colors.WHITE, system: true, dontUndo: true, _stayInCollection: true, _hideContextMenu: true, _lockedPosition: true, _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), - script: params.script ? ScriptField.MakeScript(params.script, { value: "any" }) : undefined, - backgroundColor: params.click ? ComputedField.MakeFunction(params.click) as any : "transparent", - onClick: params.click ? ScriptField.MakeScript(params.click, { scriptContext: "any" }, { _readOnly_: false }) : undefined, - hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, - }); - if (doc.contextMenuBtns === undefined) { - doc.contextMenuBtns = CurrentUserUtils.linearButtonList( - { title: "menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }, - CurrentUserUtils.contextMenuTools(doc).map(params => - !params.subMenu ? - btnFunc(params) : - CurrentUserUtils.linearButtonList({ - title: params.title, - childDontRegisterViews: true, - linearViewSubMenu: true, flexGap: 0, ignoreClick: true, - linearViewExpandable: true, icon: params.title, _height: 30, - linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, - hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, - }, params.subMenu.map(btnFunc)))); - } else { - const menuBtnList = DocListCast((doc.contextMenuBtns as Doc).data); - let prev = ""; - CurrentUserUtils.contextMenuTools(doc).forEach(params => { - const menuBtnDoc = menuBtnList.find(doc => doc.title === params.title); - if (!menuBtnDoc) { - const newMenuBtnDoc = !params.subMenu ? - btnFunc(params) : - CurrentUserUtils.linearButtonList({ - title: params.title, - childDontRegisterViews: true, - linearViewSubMenu: true, flexGap: 0, ignoreClick: true, - linearViewExpandable: true, icon: params.title, _height: 30, - linearViewIsExpanded: params.expanded ? !(ComputedField.MakeFunction(params.expanded) as any) : undefined, - hidden: params.hidden ? ComputedField.MakeFunction(params.hidden) as any : undefined, - }, params.subMenu.map(btnFunc)); - const after = menuBtnList.find(doc => doc.title === prev); - Doc.AddDocToList(doc.contextMenuBtns as Doc, "data", newMenuBtnDoc, after, false, !after); - } - const subMenuBtnList = menuBtnDoc?.data ? DocListCast(menuBtnDoc.data) : undefined; - if (menuBtnDoc && subMenuBtnList && params.subMenu && DocListCast(doc.data).length !== subMenuBtnList.length) { - let prevSub = ""; - params.subMenu.forEach(sub => { - if (!subMenuBtnList.find(doc => doc.title === sub.title)) { - const newSubMenuBtnDoc = btnFunc(sub); - const after = subMenuBtnList.find(doc => doc.title === prevSub); - Doc.AddDocToList(menuBtnDoc, "data", newSubMenuBtnDoc, after, false, !prevSub); - } - prevSub = params.title; - }); - } - prev = params.title; - }); + }; + const reqdFuncs:{[key:string]:any} = { + ...params.funcs, + backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally } + return this.AssignScripts(this.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } - // sets up the default set of documents to be shown in the Overlay layer - static setupOverlays(doc: Doc) { - if (doc.myOverlayDocs === undefined) { - doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true })); - } + /// Initializes all the default buttons for the top bar context menu + static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { + const ctxtMenuBtnsDoc = DocCast(doc[field]); + const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { + const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); + if (!params.subMenu) { + return this.setupContextMenuButton(params, menuBtnDoc); + } else { + const reqdSubMenuOpts = { title: params.title, icon: params.icon, childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, + linearViewSubMenu: true, linearViewExpandable: true, }; + const reqdSubMenuFuncs:{[key:string]:any} = { ...params.funcs}; + return this.AssignScripts(this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? + CurrentUserUtils.linearButtonList("submenu", reqdSubMenuOpts, params.subMenu.map(sub => + this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) + )), undefined, reqdSubMenuFuncs); + } + }); + const reqdCtxtOpts = { title: "menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; + return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts) ?? (doc[field] = CurrentUserUtils.linearButtonList("contextMenuButtons", reqdCtxtOpts, ctxtMenuBtns)); } - // the initial presentation Doc to use - static setupDefaultPresentation(doc: Doc) { - if (doc["template-presentation"] === undefined) { - doc["template-presentation"] = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _fitWidth: true, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true - })); - } + /// collection of documents rendered in the overlay layer above all tabs and other UI + static setupOverlays(doc: Doc, field = "myOverlayDocs") { + const reqdOpts = { title: "overlay documents", backgroundColor: "#aca3a6", system: true }; + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.FreeformDocument([], reqdOpts)); } - - // Sharing sidebar is where shared documents are contained + + /// The database of all links on all documents static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { if (doc.myLinkDatabase === undefined) { let linkDocs = Docs.newAccount ? undefined : await DocServer.GetRefField(linkDatabaseId); @@ -915,6 +842,8 @@ export class CurrentUserUtils { doc.myLinkDatabase = new PrefetchProxy(linkDocs); } } + + /// Shared documents option on the left side button panel // A user's sharing document is where all documents that are shared to that user are placed. // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents @@ -941,37 +870,26 @@ export class CurrentUserUtils { }; const sharedDocs = Docs.newAccount ? undefined : DocCast(doc.mySharedDocs) ?? DocCast(await DocServer.GetRefField(sharingDocumentId + "outer")); - if (!(sharedDocs instanceof Doc)) { - doc.mySharedDocs = new PrefetchProxy( - Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); - } else { - Object.entries(sharedRequiredDocOpts).forEach(pair => { - const targetDoc = pair[0].startsWith("_") ? sharedDocs as Doc : Doc.GetProto(sharedDocs as Doc); - targetDoc[pair[0]] = pair[1]; - }); - } + return this.AssignOpts(DocCast(sharedDocs), sharedRequiredDocOpts) ?? + (doc.mySharedDocs = Docs.Create.TreeDocument([], {...sharedDocOpts, ...sharedRequiredDocOpts}, sharingDocumentId + "outer", sharingDocumentId)); } - // Import sidebar is where shared documents are contained - static setupImportSidebar(doc: Doc) { - if (doc.myImportDocs === undefined) { - const newImportButton: Doc = Docs.Create.FontIconDocument({ onClick: ScriptField.MakeScript("importDocument()"), _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true }); - doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { - title: "My Imports", _forceActive: true, buttonMenu: true, buttonMenuDoc: newImportButton, ignoreClick: true, _showTitle: "title", _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, - childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, - dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." - })); - } - } - - // Search sidebar is where searches within the document are performed - static setupSearchSidebar(doc: Doc) { - if (doc.mySearchPanel === undefined) { - doc.mySearchPanel = new PrefetchProxy(Docs.Create.SearchDocument({ - dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, _searchDoc: true, - childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, title: "Search Panel", system: true - })) as any as Doc; - } + /// Import option on the left side button panel + static setupImportSidebar(doc: Doc, field:string) { + const reqdOpts:DocumentOptions = { + title: "My Imports", _forceActive: true, buttonMenu: true, ignoreClick: true, _showTitle: "title", + _stayInCollection: true, _hideContextMenu: true, childLimitHeight: 0, + childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, + dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." + }; + const myImports = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.StackingDocument([], reqdOpts)); + + const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", + _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, + buttonText: "Import", icon: "upload", system: true }; + const reqdBtnScripts = { onClick: "importDocument()" }; + const newImportBtn = this.AssignOpts(DocCast(myImports.buttonMenuDoc), reqdBtnOpts) ?? (myImports.buttonMenuDoc = Docs.Create.FontIconDocument(reqdBtnOpts)); + this.AssignScripts(newImportBtn, reqdBtnScripts); } static setupClickEditorTemplates(doc: Doc) { @@ -1033,8 +951,8 @@ export class CurrentUserUtils { }, { fireImmediately: true }); // Document properties on load doc.system = true; - doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; + doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc._raiseWhenDragged = true; doc._showLabel = true; doc._showMenuLabel = true; @@ -1053,29 +971,22 @@ export class CurrentUserUtils { doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false); doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); doc.noviceMode = BoolCast(doc.noviceMode, true); - doc.savedFilters = new List(); + !doc.savedFilters && (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; doc.myPublishedDocs = doc.myPublishedDocs ?? new List(); - doc.myHeaderBarDoc = doc.myHeaderBarDoc ?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); + doc.myHeaderBar = doc.myHeaderBar?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents + await this.setupLinkDocs(doc, linkDatabaseId); + await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon this.setupDocTemplates(doc); // sets up the template menu of templates - this.setupImportSidebar(doc); // sets up the import sidebar - this.setupSearchSidebar(doc); // sets up the search sidebar this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile - this.setupOverlays(doc); // documents in overlay layer - this.setupContextMenuButtons(doc); // set up context menu buttons + this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard + this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected this.setupDockedButtons(doc); // the bottom bar of font icons - await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels - await this.setupMenuPanel(doc, sharingDocumentId, linkDatabaseId); + this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); - setTimeout(() => this.setupDefaultPresentation(doc), 0); // presentation that's initially triggered - - // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - doc["dockedBtn-undo"] && reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(doc["dockedBtn-undo"] as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - doc["dockedBtn-redo"] && reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(doc["dockedBtn-redo"] as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); - setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { @@ -1227,7 +1138,7 @@ export class CurrentUserUtils { public static async snapshotDashboard(userDoc: Doc) { const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); + Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); CurrentUserUtils.openDashboard(userDoc, copy); } @@ -1277,15 +1188,24 @@ export class CurrentUserUtils { return tbox; } - public static get DockedBtns() { return Cast(Doc.UserDoc().dockedBtns, Doc, null); } - public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); } - public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); } - public static get MyHeaderBarDoc() { return Cast(Doc.UserDoc().myHeaderBarDoc, Doc, null); } - public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } - public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); } - public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); } - public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); } - public static get OverlayDocs() { return DocListCast((Doc.UserDoc().myOverlayDocs as Doc)?.data); } + public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } + public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); } + public static get MySearcher() { return DocCast(Doc.UserDoc().mySearcher); } + public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } + public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); } + public static get MyTools() { return DocCast(Doc.UserDoc().myTools); } + public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } + public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } + public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } + public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } + public static get MyTrails() { return DocCast(Doc.UserDoc().myTrails); } + public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } + public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } + public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } + public static get MyOverlayDocs() { return DocListCast(DocCast(Doc.UserDoc().myOverlayDocs)?.data); } + public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } + public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } + public static get EmptyPane() { return DocCast(Doc.UserDoc().emptyPane); } public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } } @@ -1298,7 +1218,7 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { view && SelectionManager.SelectView(view, false); } }); -ScriptingGlobals.add(function MySharedDocs() { return Doc.SharingDoc(); }, "document containing all shared Docs"); +ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); @@ -1329,15 +1249,15 @@ ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colTy const parentDoc: Doc = Cast(selected.context, Doc, null); selected = parentDoc; } - if (selected && docType && selected.type === docType) return false; - else if (selected && colType && selected.viewType === colType) return false; - else if (selected && !colType && !docType) return false; - else return true; + if (selected && docType && selected.type === docType) return true; + else if (selected && colType && selected.viewType === colType) return true; + else if (selected && !colType && !docType) return true; + else return false; }); ScriptingGlobals.add(function makeTopLevelFolder() { const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined }; - return Doc.AddDocToList(Doc.UserDoc().myFilesystem as Doc, "data", folder); + return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); }); ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { if (readOnly) return; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index a82f99d5a..0435cd535 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -10,6 +10,7 @@ import { CollectionView } from '../views/collections/CollectionView'; import { LightboxView } from '../views/LightboxView'; import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; +import { CurrentUserUtils } from './CurrentUserUtils'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; @@ -296,7 +297,7 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { Doc.linkFollowHighlight(dv?.props.Document, false); } else { - const context = doc.context !== Doc.UserDoc().myFilesystem && Cast(doc.context, Doc, null); + const context = doc.context !== CurrentUserUtils.MyFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 223b0268c..3791bec73 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -223,7 +223,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp paramList.push(`${key}: ${typeof val === "object" ? Object.getPrototypeOf(val).constructor.name : typeof val}`); } const paramString = paramList.join(", "); - const body = addReturn ? `return ${script};` : script; + const body = addReturn && !script.startsWith("{ return") ? `return ${script};` : script; const reqTypes = requiredType ? `: ${requiredType}` : ''; const funcScript = `(function(${paramString})${reqTypes} { ${body} })`; host.writeFile("file.ts", funcScript); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 8486b3d62..be5927497 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -141,7 +141,7 @@ export function ViewBoxAnnotatableComponent

    () const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { - const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc; + const recent = CurrentUserUtils.MyRecentlyClosed; toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 767efdbc2..16d7c9a23 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -235,7 +235,7 @@ export class KeyManager { if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) { SelectionManager.Views()[0].ComponentView?.search?.("", false, false); } else { - const searchBtn = Doc.UserDoc().searchBtn as Doc; + const searchBtn = CurrentUserUtils.MySearcher; if (searchBtn) { MainView.Instance.selectMenu(searchBtn); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5fd76c388..7e3916b3f 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -10,7 +10,7 @@ import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; -import { PromiseValue, StrCast } from '../../fields/Types'; +import { DocCast, PromiseValue, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; @@ -79,7 +79,7 @@ export class MainView extends React.Component { @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons @observable private _panelContent: string = "none"; - @observable private _sidebarContent: any = this.userDoc?.sidebar; + @observable private _sidebarContent: any = CurrentUserUtils.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; @computed private get dashboardTabHeight() { return 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @@ -92,7 +92,7 @@ export class MainView extends React.Component { @computed private get userDoc() { return Doc.UserDoc(); } @computed private get colorScheme() { return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); } @computed private get mainContainer() { return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; } - @computed private get headerBarDoc() { return this.userDoc ? CurrentUserUtils.MyHeaderBarDoc : CurrentUserUtils.MyHeaderBarDoc; } + @computed private get headerBarDoc() { return CurrentUserUtils.MyHeaderBar; } @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1) ? docs[1] : undefined)(DocListCast(this.mainContainer!.data)); } headerBarDocWidth = () => this.mainDocViewWidth(); @@ -249,24 +249,8 @@ export class MainView extends React.Component { @action createNewFolder = async () => { - if (!await this.userDoc.myFilesystem) { - this.userDoc.myFileOrphans = Docs.Create.TreeDocument([], { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); - const newFolder = ScriptField.MakeFunction(`createNewFolder()`, { scriptContext: "any" })!; - const newFolderButton: Doc = Docs.Create.FontIconDocument({ - onClick: newFolder, _forceActive: true, toolTip: "New folder", _stayInCollection: true, _hideContextMenu: true, title: "New folder", - btnType: ButtonType.ClickButton, _width: 30, _height: 30, buttonText: "New folder", icon: "folder-plus", system: true - }); - this.userDoc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([this.userDoc.myFileOrphans as Doc], { - title: "My Documents", _showTitle: "title", buttonMenu: true, buttonMenuDoc: newFolderButton, _height: 100, - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, ignoreClick: true, - isFolder: true, treeViewType: TreeViewType.fileSystem, childHideLinkButton: true, - _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true, - explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." - })); - } const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); - Doc.AddDocToList(this.userDoc.myFilesystem as Doc, "data", folder); + Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); } @observable _exploreMode = false; @@ -397,7 +381,7 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} - styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === Doc.UserDoc().myFilesystem ? DashboardStyleProvider : DefaultStyleProvider} + styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} ScreenToLocalTransform={this.mainContainerXf} @@ -423,7 +407,7 @@ export class MainView extends React.Component { @computed get leftMenuPanel() { return

    (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); + remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(CurrentUserUtils.MyDockedBtns, "data", doc), true); moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc); - addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.UserDoc().dockedBtns as Doc, "data", doc), true); + addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(CurrentUserUtils.MyDockedBtns, "data", doc), true); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); @@ -543,9 +527,9 @@ export class MainView extends React.Component { @computed get docButtons() { return !(this.userDoc.dockedBtns instanceof Doc) ? (null) : -
    +
    { + return CurrentUserUtils.MyOverlayDocs?.map(d => { let offsetx = 0, offsety = 0; const dref = React.createRef(); const onPointerMove = action((e: PointerEvent, down: number[]) => { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 16eb95cf4..2cf334ae1 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -931,7 +931,7 @@ export class PropertiesView extends React.Component { * If it doesn't exist, it creates it. */ checkFilterDoc() { - if (!this.selectedDoc.currentFilter) CurrentUserUtils.setupFilterDocs(this.selectedDoc); + if (!this.selectedDoc.currentFilter) this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); } /** @@ -944,8 +944,7 @@ export class PropertiesView extends React.Component { this.selectedDoc._docRangeFilters = new List(); (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; - this.selectedDoc.currentFilter = undefined; - CurrentUserUtils.setupFilterDocs(this.selectedDoc); + this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); } /** diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index d2687df17..1ebbe08ef 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -416,7 +416,7 @@ export class CollectionDockingView extends CollectionSubView() { } tabDestroyed = (tab: any) => { - Doc.AddDocToList(CurrentUserUtils.MyHeaderBarDoc, "data", tab.DashDoc); + Doc.AddDocToList(CurrentUserUtils.MyHeaderBar, "data", tab.DashDoc); Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, "data", tab.DashDoc, undefined, true, true); const dview = CollectionDockingView.Instance.props.Document; const fieldKey = CollectionDockingView.Instance.props.fieldKey; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 39f6466d6..1c25421f5 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -99,7 +99,7 @@ export class CollectionMenu extends AntimodeMenu{ } @computed get contMenuButtons() { - const selDoc = Doc.UserDoc().contextMenuBtns; + const selDoc = CurrentUserUtils.MyContextMenuBtns; return !(selDoc instanceof Doc) ? (null) :
    { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }), initialize: (button: Doc) => { - button['target-docFilters'] = (Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) instanceof ObjectField ? - ObjectField.MakeCopy((Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null)._docFilters || Cast(Doc.UserDoc().activeDashboard, Doc, null)._docFilters) as any as ObjectField) : undefined; + button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) instanceof ObjectField ? + ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) as any as ObjectField) : undefined; button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined; }, }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 52e99f26b..c07e44fcc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -985,7 +985,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.MyOverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -1031,7 +1031,7 @@ export class CollectionFreeFormView extends CollectionSubView {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
    - {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.DockedBtns ? null : + {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : e.stopPropagation()} > @@ -226,7 +226,7 @@ export class CollectionLinearView extends CollectionSubView() { } - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.DockedBtns ? (null) : + {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? (null) : Currently playing: diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index 4b33ef8ae..a6f6bd35f 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -68,8 +68,8 @@ export class LinkPopup extends React.Component { className="linkPopup-searchBox searchBox-input" /> */} 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { @@ -563,7 +563,7 @@ export class DocumentViewInternal extends DocComponent 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index e3708d7b9..5add09653 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -35,7 +35,7 @@ export class FilterBox extends ViewBoxBaseComponent() { constructor(props: Readonly) { super(props); const targetDoc = FilterBox.targetDoc; - if (targetDoc && !targetDoc.currentFilter) CurrentUserUtils.setupFilterDocs(targetDoc); + if (targetDoc && !targetDoc.currentFilter) targetDoc.currentFilter = CurrentUserUtils.createFilterDoc(); } public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 10974ca03..967158cbf 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -156,20 +156,22 @@ export class WebBox extends ViewBoxAnnotatableComponent { this._annotationKeySuffix = () => this._urlHash + "-annotations"; + const reqdFuncs:{[key:string]: string} = {}; // bcz: need to make sure that doc.data-annotations points to the currently active web page's annotations (this could/should be when the doc is created) - this.dataDoc[this.fieldKey + "-annotations"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`); - this.dataDoc[this.fieldKey + "-sidebar"] = ComputedField.MakeFunction(`copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`); + reqdFuncs[this.fieldKey + "-annotations"] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-annotations"`; + reqdFuncs[this.fieldKey + "-sidebar"] = `copyField(this["${this.fieldKey}-"+urlHash(this["${this.fieldKey}"]?.url?.toString())+"-sidebar"`; + CurrentUserUtils.AssignScripts(this.dataDoc, {}, reqdFuncs); }); - reaction(() => this.props.isSelected() || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), + reaction(() => this.props.isSelected(true) || this.isAnyChildContentActive() || Doc.isBrushedHighlightedDegree(this.props.Document), async (selected) => { if (selected) { this._webPageHasBeenRendered = true; - } else if ((!this.props.isContentActive() || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) + } else if ((!this.props.isContentActive(true) || SnappingManager.GetIsDragging()) && // update thumnail when unselected AND (no child annotation is active OR we've started dragging the document in which case no additional deselect will occur so this is the only chance to update the thumbnail) !this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && // don't create a thumbnail when double-clicking to enter lightbox because thumbnail will be empty LightboxView.LightboxDoc !== this.rootDoc) { // don't create a thumbnail if entering Lightbox from maximize either, since thumb will be empty. this.updateThumb(); } - }, { fireImmediately: this.props.isSelected() || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegree(this.props.Document) ? true : false) }); + }, { fireImmediately: this.props.isSelected(true) || this.isAnyChildContentActive() || (Doc.isBrushedHighlightedDegreeUnmemoized(this.props.Document) ? true : false) }); this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, autoHeight => { diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 97e6eddfe..f29dfe489 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -521,7 +521,7 @@ export class FontIconBox extends DocComponent() {
    {this.icon === "pres-trail" ? trailsIcon : } {menuLabel} - +
    ); break; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 1dd6fef9b..52b03b0a5 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -623,7 +623,7 @@ export class PresBox extends ViewBoxBaseComponent() { */ @action updateMinimize = async () => { - if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, "right"); @@ -868,7 +868,7 @@ export class PresBox extends ViewBoxBaseComponent() { } break; case "Escape": - if (CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); } + if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); } else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; } else this.layoutDoc.presStatus = "edit"; if (this._presTimer) clearTimeout(this._presTimer); @@ -2511,7 +2511,7 @@ export class PresBox extends ViewBoxBaseComponent() { const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); - return CurrentUserUtils.OverlayDocs.includes(this.rootDoc) ? + return CurrentUserUtils.MyOverlayDocs.includes(this.rootDoc) ?
    e.stopPropagation()}>
    {"Loop"}
    }>
    () {
    : -
    +
    {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 7b72787d1..94be286f5 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -941,6 +941,9 @@ export namespace Doc { export function isBrushedHighlightedDegree(doc: Doc) { return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegree(doc); } + export function isBrushedHighlightedDegreeUnmemoized(doc: Doc) { + return Doc.IsHighlighted(doc) ? DocBrushStatus.highlighted : Doc.IsBrushedDegreeUnmemoized(doc); + } export class DocBrush { BrushedDoc: ObservableMap = new ObservableMap(); @@ -980,7 +983,7 @@ export namespace Doc { export function SearchQuery(): string { return manager._searchQuery; } export function SetSearchQuery(query: string) { runInAction(() => manager._searchQuery = query); } export function UserDoc(): Doc { return manager._user_doc; } - export function SharingDoc(): Doc { return Cast(Doc.UserDoc().mySharedDocs, Doc, null); } + export function SharingDoc(): Doc { return CurrentUserUtils.MySharedDocs; } export function LinkDBDoc(): Doc { return Cast(Doc.UserDoc().myLinkDatabase, Doc, null); } export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); } diff --git a/src/fields/util.ts b/src/fields/util.ts index d1e565774..37b9be31b 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -88,6 +88,9 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number delete target.__fields[prop]; } else { target.__fieldKeys && (target.__fieldKeys[prop] = true); + // if (target.__fields[prop] !== value) { + // console.log("ASSIGN " + prop + " " + value); + // } target.__fields[prop] = value; } //if (typeof value === "object" && !(value instanceof ObjectField)) debugger; diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 78ec706d7..c5c6fb688 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -68,7 +68,7 @@ export class MobileInterface extends React.Component { constructor(props: Readonly<{}>) { super(props); - this._library = CurrentUserUtils.setupLibrary(Doc.UserDoc()); // to access documents in Dash Web + this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc()); // to access documents in Dash Web MobileInterface.Instance = this; } -- cgit v1.2.3-70-g09d2 From b292a055401af6236e0537cfad603016d77a535a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 13:40:07 -0400 Subject: a bunch of changes to make some userDoc() field accesses more typesafe by going through CurrentUserUtils. Some normalization of naming/functionality in CurrentUserUtils --- src/client/apis/youtube/YoutubeBox.tsx | 2 +- src/client/documents/Documents.ts | 6 +- src/client/util/CurrentUserUtils.ts | 118 +++++++++++---------- src/client/util/SettingsManager.tsx | 16 +-- src/client/util/SharingManager.tsx | 4 +- src/client/views/DashboardView.tsx | 4 +- src/client/views/DocComponent.tsx | 6 +- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/GestureOverlay.tsx | 12 +-- src/client/views/GlobalKeyHandler.ts | 8 +- src/client/views/InkStrokeProperties.ts | 4 +- src/client/views/InkTranscription.tsx | 2 +- src/client/views/InkingStroke.tsx | 2 +- src/client/views/MainView.tsx | 4 +- src/client/views/OverlayView.tsx | 8 +- src/client/views/PropertiesButtons.tsx | 4 +- src/client/views/PropertiesView.tsx | 86 ++++++++------- src/client/views/TemplateMenu.tsx | 11 +- .../views/collections/CollectionDockingView.tsx | 22 ++-- src/client/views/collections/CollectionMenu.tsx | 35 +++--- .../views/collections/CollectionStackingView.tsx | 2 +- .../CollectionStackingViewFieldColumn.tsx | 4 +- .../views/collections/CollectionTreeView.tsx | 8 +- src/client/views/collections/CollectionView.tsx | 14 +-- src/client/views/collections/TabDocView.tsx | 2 +- src/client/views/collections/TreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 30 +++--- .../collections/collectionFreeForm/MarqueeView.tsx | 6 +- .../collectionSchema/CollectionSchemaHeaders.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 5 +- src/client/views/nodes/ColorBox.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 40 +++---- src/client/views/nodes/FilterBox.tsx | 47 ++++---- src/client/views/nodes/ImageBox.tsx | 4 +- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/MapBox/MapBox.tsx | 4 +- src/client/views/nodes/VideoBox.tsx | 6 +- src/client/views/nodes/WebBox.tsx | 6 +- src/client/views/nodes/button/FontIconBox.tsx | 35 +++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +-- .../views/nodes/formattedText/RichTextMenu.tsx | 1 - src/client/views/nodes/trails/PresBox.tsx | 30 +++--- src/client/views/nodes/trails/PresElementBox.tsx | 16 +-- src/client/views/pdf/PDFViewer.tsx | 2 +- src/client/views/topbar/TopBar.tsx | 10 +- src/client/views/webcam/DashWebRTCVideo.tsx | 2 +- src/fields/Doc.ts | 18 ++-- src/fields/util.ts | 2 +- src/mobile/MobileInterface.tsx | 20 ++-- 49 files changed, 360 insertions(+), 335 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index c5ff2db68..e14dc60b4 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -351,7 +351,7 @@ export class YoutubeBox extends React.Component { const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <>
    diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 927996af2..f96bffb3c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -687,7 +687,7 @@ export namespace Docs { ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) && - !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", dataDoc); + !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, dataDoc); updateCachedAcls(dataDoc); updateCachedAcls(viewDoc); @@ -802,7 +802,7 @@ export namespace Docs { I.rotation = 0; I.data = new InkField(points); I.creationDate = new DateField; - I["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + I["acl-Public"] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; I["acl-Override"] = "None"; I.links = ComputedField.MakeFunction("links(self)"); I[Initializing] = false; @@ -1204,7 +1204,7 @@ export namespace DocUtils { created = Docs.Create.RecordingDocument((field).url.href, resolved); layout = RecordingBox.LayoutString; } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.SelectedTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); + created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index afb1442bf..7a38886fe 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -74,16 +74,16 @@ export class CurrentUserUtils { @observable public static headerBarHeight: number = 0; @observable public static searchPanelWidth: number = 0; - static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, functions?:{[key:string]: string}) { + static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); } }); - functions && Object.keys(functions).map(key => { + funcs && Object.keys(funcs).map(key => { const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== functions[key] && functions[key]) { - doc[key] = ComputedField.MakeFunction(functions[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); + if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) { + doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); } }); return doc; @@ -180,14 +180,14 @@ export class CurrentUserUtils { } /// Initializes collection of templates for notes and click functions - static setupDocTemplates(doc: Doc) { + static setupDocTemplates(doc: Doc, field="myTemplates") { const templates = [ CurrentUserUtils.setupNoteTemplates(doc), CurrentUserUtils.setupClickEditorTemplates(doc) ]; const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; - return this.AssignScripts(this.AssignOpts(DocCast(doc.templateDocs), reqdOpts, templates) ?? (doc.templateDocs = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts); + return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, templates) ?? (doc[field] = Docs.Create.TreeDocument(templates, reqdOpts)), reqdScripts); } // setup templates for different document types when they are iconified from Document Decorations @@ -329,7 +329,7 @@ export class CurrentUserUtils { static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc { const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => { const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined; - const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["backgroundColor"]).omit, + const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit, _nativeWidth: 50, _nativeHeight: 50, _width: 35, _height: 35, _hideContextMenu: true, _stayInCollection: true, _dropAction: "alias", btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), @@ -562,6 +562,7 @@ export class CurrentUserUtils { if (Cast(myDashboards.childContextMenuFilters, listSpec(ScriptField), null)?.length !== childContextMenuFilters.length) { myDashboards.childContextMenuFilters = new List(childContextMenuFilters.map(script => !script ? script: ScriptField.MakeFunction(script)!)); } + return myDashboards; } /// initializes the left sidebar Trails pane @@ -728,13 +729,13 @@ export class CurrentUserUtils { static inkTools():Button[] { return [ - { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveInkTool("pen", _readOnly_);}' }}, - { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveInkTool("write", _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveInkTool("eraser", _readOnly_);}' }}, - // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveInkTool("highlighter")'} }, - { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return setActiveInkTool("circle", _readOnly_);}'} }, - // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveInkTool("square")' }, - { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveInkTool("line", _readOnly_);}' }}, + { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", scripts:{onClick:'{ return setActiveTool("pen", _readOnly_);}' }}, + { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", scripts:{onClick:'{ return setActiveTool("write", _readOnly_);}'} }, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", scripts:{onClick:'{ return setActiveTool("eraser", _readOnly_);}' }}, + // { title: "Highlighter", toolTip: "Highlighter (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", scripts:{onClick: 'setActiveTool("highlighter")'} }, + { title: "Circle", toolTip: "Circle (Ctrl+Shift+C)", btnType: ButtonType.ToggleButton, icon: "circle", scripts:{onClick:'{ return setActiveTool("circle", _readOnly_);}'} }, + // { title: "Square", toolTip: "Square (Ctrl+Shift+S)", btnType: ButtonType.ToggleButton, icon: "square", click: 'setActiveTool("square")' }, + { title: "Line", toolTip: "Line (Ctrl+Shift+L)", btnType: ButtonType.ToggleButton, icon: "minus", scripts:{onClick: '{ return setActiveTool("line", _readOnly_);}' }}, { title: "Fill", toolTip: "Fill color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: {script: "{ return setFillColor(value, _readOnly_);}"} }, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberButton, numBtnType: NumButtonType.Slider, numBtnMin: 1, ignoreClick: true, scripts: {script: '{ return setStrokeWidth(value, _readOnly_);}'} }, { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", ignoreClick: true, scripts: {script: '{ return setStrokeColor(value, _readOnly_);}'} }, @@ -941,41 +942,39 @@ export class CurrentUserUtils { } static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - if (!doc.globalGroupDatabase) doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); - await DocListCastAsync((doc.globalGroupDatabase as Doc).data); - reaction(() => DateCast((doc.globalGroupDatabase as Doc)["data-lastModified"]), + doc.globalGroupDatabase ?? (doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument()); + await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); + reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]), async () => { - const groups = await DocListCastAsync((doc.globalGroupDatabase as Doc).data); + const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || []; SnappingManager.SetCachedGroups(["Public", ...mygroups?.map(g => StrCast(g.title))]); }, { fireImmediately: true }); // Document properties on load - doc.system = true; - doc.title = Doc.CurrentUserEmail; - doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; - doc._raiseWhenDragged = true; - doc._showLabel = true; - doc._showMenuLabel = true; - doc.textAlign = StrCast(doc.textAlign, "left"); - doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); - doc.activeInkWidth = Number(StrCast(doc.activeInkWidth, "1")); - doc.activeInkBezier = StrCast(doc.activeInkBezier, "0"); - doc.activeFillColor = StrCast(doc.activeFillColor, ""); - doc.activeArrowStart = StrCast(doc.activeArrowStart, ""); - doc.activeArrowEnd = StrCast(doc.activeArrowEnd, ""); - doc.activeDash = StrCast(doc.activeDash, "0"); - doc.fontSize = StrCast(doc.fontSize, "12px"); - doc.fontFamily = StrCast(doc.fontFamily, "Arial"); - doc.fontColor = StrCast(doc.fontColor, "black"); - doc.fontHighlight = StrCast(doc.fontHighlight, ""); - doc.defaultAclPrivate = BoolCast(doc.defaultAclPrivate, false); - doc.activeCollectionNestedBackground = Cast(doc.activeCollectionNestedBackground, "string", null); - doc.noviceMode = BoolCast(doc.noviceMode, true); - !doc.savedFilters && (doc.savedFilters = new List()); + doc.system ?? (doc.system = true); + doc.title ?? (doc.title = Doc.CurrentUserEmail); + Doc.noviceMode ?? (Doc.noviceMode = true); + doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); + doc._showLabel ?? (doc._showLabel = true); + doc._showMenuLabel ?? (doc._showMenuLabel = true); + doc.textAlign ?? (doc.textAlign = "left"); + doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");; + doc.activeInkWidth ?? (doc.activeInkWidth = 1); + doc.activeInkBezier ?? (doc.activeInkBezier = "0"); + doc.activeFillColor ?? (doc.activeFillColor = ""); + doc.activeArrowStart ?? (doc.activeArrowStart = ""); + doc.activeArrowEnd ?? (doc.activeArrowEnd = ""); + doc.activeDash ?? (doc.activeDash == "0"); + doc.fontSize ?? (doc.fontSize = "12px"); + doc.fontFamily ?? (doc.fontFamily = "Arial"); + doc.fontColor ?? (doc.fontColor = "black"); + doc.fontHighlight ?? (doc.fontHighlight = ""); + doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false); + doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; - doc.myPublishedDocs = doc.myPublishedDocs ?? new List(); - doc.myHeaderBar = doc.myHeaderBar?? Docs.Create.MulticolumnDocument([], { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents + doc.myPublishedDocs ?? (doc.myPublishedDocs = new List()); + doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents await this.setupLinkDocs(doc, linkDatabaseId); await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon @@ -985,7 +984,7 @@ export class CurrentUserUtils { this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left - if (!doc.globalScriptDatabase) doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); + doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); @@ -1022,7 +1021,7 @@ export class CurrentUserUtils { const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; Docs.newAccount &&(userDoc.activePage = "home"); const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); - (await DocListCastAsync(Cast(Doc.UserDoc().myLinkDatabase, Doc, null)?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager + (await DocListCastAsync(Doc.LinkDBDoc()?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager const a1 = await Cast(link?.anchor1, Doc, null); const a2 = await Cast(link?.anchor2, Doc, null); }); @@ -1097,7 +1096,7 @@ export class CurrentUserUtils { } } } else if (input.files && input.files.length !== 0) { - const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null); + const importDocs = CurrentUserUtils.MyImports; const disposer = OverlayView.ShowSpinner(); DocListCastAsync(importDocs.data).then(async list => { const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); @@ -1115,9 +1114,10 @@ export class CurrentUserUtils { } public static CaptureDashboardThumbnail() { + const activeDashboard = CurrentUserUtils.ActiveDashboard; const docView = CollectionDockingView.Instance.props.DocumentView?.(); const content = docView?.ContentDiv; - if (docView && content) { + if (docView && content && activeDashboard) { const _width = Number(getComputedStyle(content).width.replace("px","")); const _height = Number(getComputedStyle(content).height.replace("px","")); return CollectionFreeFormView.UpdateIcon( @@ -1130,20 +1130,22 @@ export class CurrentUserUtils { const proto = Cast(img.proto, Doc, null)!; proto["data-nativeWidth"] = _width; proto["data-nativeHeight"] = _height; - Doc.GetProto(CurrentUserUtils.ActiveDashboard).thumb = img; + Doc.GetProto(activeDashboard).thumb = img; }); } } public static async snapshotDashboard(userDoc: Doc) { - const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); - CurrentUserUtils.openDashboard(userDoc, copy); + if (CurrentUserUtils.ActiveDashboard) { + const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); + Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); + CurrentUserUtils.openDashboard(userDoc, copy); + } } public static closeActiveDashboard = () => { - Doc.UserDoc().activeDashboard = undefined; + CurrentUserUtils.ActiveDashboard = undefined; } public static createNewDashboard = async (userDoc: Doc, id?: string) => { @@ -1195,6 +1197,8 @@ export class CurrentUserUtils { public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); } public static get MyTools() { return DocCast(Doc.UserDoc().myTools); } public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } + public static get MyFileOrphans() { return DocCast(Doc.UserDoc().myFileOrphans); } + public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); } public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } @@ -1202,12 +1206,16 @@ export class CurrentUserUtils { public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } - public static get MyOverlayDocs() { return DocListCast(DocCast(Doc.UserDoc().myOverlayDocs)?.data); } + public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); } public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } + public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; } public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } + public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; } + public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); } + public static set ActivePage(val) { Doc.UserDoc().activePage = val; } public static get EmptyPane() { return DocCast(Doc.UserDoc().emptyPane); } - public static set SelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } - @computed public static get SelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } + public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; } + public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } } ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { @@ -1219,7 +1227,7 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { } }); ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); -ScriptingGlobals.add(function IsNoviceMode() { return Doc.UserDoc().noviceMode; }, "is Dash in novice mode"); +ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called"); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 6a26dfdc7..382274462 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -38,7 +38,7 @@ export class SettingsManager extends React.Component<{}> { @observable activeTab = "Accounts"; @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; } - @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard.colorScheme; } + @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme; } constructor(props: {}) { super(props); @@ -59,7 +59,7 @@ export class SettingsManager extends React.Component<{}> { } } - @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice"); + @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.noviceMode = (e.currentTarget as any)?.value === "Novice"); @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value ? "title" : undefined); @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value); @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value); @@ -78,19 +78,21 @@ export class SettingsManager extends React.Component<{}> { @undoBatch @action changeColorScheme = action((e: React.ChangeEvent) => { + const activeDashboard= CurrentUserUtils.ActiveDashboard; + if (!activeDashboard) return; const scheme: ColorScheme = (e.currentTarget as any).value; switch (scheme) { case ColorScheme.Light: - CurrentUserUtils.ActiveDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + activeDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" }); break; case ColorScheme.Dark: - CurrentUserUtils.ActiveDashboard.colorScheme = ColorScheme.Dark; + activeDashboard.colorScheme = ColorScheme.Dark; addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" }); break; case ColorScheme.System: default: window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - CurrentUserUtils.ActiveDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) }); break; } @@ -254,7 +256,7 @@ export class SettingsManager extends React.Component<{}> {
    Modes
    - @@ -270,7 +272,7 @@ export class SettingsManager extends React.Component<{}> {
    Doc.UserDoc().defaultAclPrivate = !Doc.UserDoc().defaultAclPrivate)} /> + onChange={action(() => Doc.defaultAclPrivate = !Doc.defaultAclPrivate)} />
    Default access private
    diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index f439d4488..d77633b8d 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -369,7 +369,7 @@ export class SharingManager extends React.Component<{}> { const dropdownValues: string[] = Object.values(SharingPermissions); if (!uniform) dropdownValues.unshift("-multiple-"); if (override) dropdownValues.unshift("None"); - return dropdownValues.filter(permission => !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => + return dropdownValues.filter(permission => !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => (
    - {Doc.UserDoc().noviceMode ? (null) : + {Doc.noviceMode ? (null) :
    this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} />
    } diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index efc1644fe..9a0f25fe3 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -34,8 +34,8 @@ export class DashboardView extends React.Component { clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => { if (e.detail === 2) { - Doc.UserDoc().activeDashboard = dashboard; - Doc.UserDoc().activePage = "dashboard"; + CurrentUserUtils.ActiveDashboard = dashboard; + CurrentUserUtils.ActivePage = "dashboard"; } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index be5927497..169bd3873 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -65,7 +65,7 @@ export function ViewBoxBaseComponent

    () { isContentActive = (outsideReaction?: boolean) => ( this.props.isContentActive?.() === false ? false : - (CurrentUserUtils.SelectedTool !== InkTool.None || + (CurrentUserUtils.ActiveTool !== InkTool.None || (this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction)) ? true : undefined)) @@ -200,7 +200,7 @@ export function ViewBoxAnnotatableComponent

    () if (effectiveAcl === AclAugment) { added.map(doc => { - if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); + if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); doc.context = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); @@ -213,7 +213,7 @@ export function ViewBoxAnnotatableComponent

    () doc.context = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); + CurrentUserUtils.ActiveDashboard && inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 0bbe473d7..9b8f7238d 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -347,7 +347,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV

    {this.menuButton}
    - {/* {Doc.UserDoc().noviceMode ? (null) :
    + {/* {Doc.noviceMode ? (null) :
    {this.moreButton}
    } */}
    ; diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 6afe64868..e960f5cca 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -129,7 +129,7 @@ export class GestureOverlay extends Touchable { // and this seems to be the only way of differentiating pen and touch on touch events if (pt.radiusX > 1 && pt.radiusY > 1) { InkTranscription.Instance.createInkGroup(); - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; this.prevPoints.set(pt.identifier, pt); } } @@ -498,18 +498,18 @@ export class GestureOverlay extends Touchable { setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => { if (doubleTap) { InkTranscription.Instance.createInkGroup(); - CurrentUserUtils.SelectedTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; return; } })); } - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { - CurrentUserUtils.SelectedTool = InkTool.Write; + CurrentUserUtils.ActiveTool = InkTool.Write; } this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); - // if (CurrentUserUtils.SelectedTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); + // if (CurrentUserUtils.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } } @@ -606,7 +606,7 @@ export class GestureOverlay extends Touchable { this._points = []; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { this.InkShape = ""; - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; } } // if we're not drawing in a toolglass try to recognize as gesture diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 16d7c9a23..f5122df3f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -57,7 +57,7 @@ export class KeyManager { public handle = action((e: KeyboardEvent) => { if (e.key?.toLowerCase() === "shift") DocumentDecorations.Instance.AddToSelection = true; - //if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); + //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); const keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); @@ -112,7 +112,7 @@ export class KeyManager { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; InkStrokeProperties.Instance._controlButton = false; - CurrentUserUtils.SelectedTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; DragManager.CompleteWindowDrag?.(true); var doDeselect = true; if (SnappingManager.GetIsDragging()) { @@ -241,9 +241,9 @@ export class KeyManager { } } break; - case "e": CurrentUserUtils.SelectedTool = InkTool.Eraser; + case "e": CurrentUserUtils.ActiveTool = InkTool.Eraser; break; - case "p": CurrentUserUtils.SelectedTool = InkTool.Pen; + case "p": CurrentUserUtils.ActiveTool = InkTool.Pen; break; case "o": const target = SelectionManager.Docs().lastElement(); diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 0449da483..471ad09e9 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -25,8 +25,8 @@ export class InkStrokeProperties { constructor() { InkStrokeProperties._Instance = this; - reaction(() => this._controlButton, button => button && (CurrentUserUtils.SelectedTool = InkTool.None)); - reaction(() => CurrentUserUtils.SelectedTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); + reaction(() => this._controlButton, button => button && (CurrentUserUtils.ActiveTool = InkTool.None)); + reaction(() => CurrentUserUtils.ActiveTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); } /** diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 8ab54918c..487bbcd00 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -254,7 +254,7 @@ export class InkTranscription extends React.Component { */ createInkGroup() { // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (CurrentUserUtils.SelectedTool === InkTool.Write) { + if (CurrentUserUtils.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those const selected = ffView.unprocessedDocs; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index aa5a815ac..35d89d2b1 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -345,7 +345,7 @@ export class InkingStroke extends ViewBoxBaseComponent() { onClick: (e: React.MouseEvent) => this._handledClick && e.stopPropagation(), onContextMenu: () => { const cm = ContextMenu.Instance; - !Doc.UserDoc().noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" }); + !Doc.noviceMode && cm?.addItem({ description: "Recognize Writing", event: this.analyzeStrokes, icon: "paint-brush" }); cm?.addItem({ description: "Toggle Mask", event: () => InkingStroke.toggleMask(this.rootDoc), icon: "paint-brush" }); cm?.addItem({ description: "Edit Points", event: action(() => InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton), icon: "paint-brush" }); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7e3916b3f..791c64630 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -381,7 +381,7 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} - styleProvider={this._sidebarContent.proto === Doc.UserDoc().myDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} + styleProvider={this._sidebarContent.proto === CurrentUserUtils.MyDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} ScreenToLocalTransform={this.mainContainerXf} @@ -656,7 +656,7 @@ export class MainView extends React.Component { {this.mainDashboardArea} ; case "home": return ; - } })(StrCast(Doc.UserDoc().activePage)) + } })(CurrentUserUtils.ActivePage) } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index bdb3bb857..598fff29a 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -4,7 +4,7 @@ import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import * as React from "react"; import ReactLoading from 'react-loading'; -import { Doc, WidthSym, HeightSym } from "../../fields/Doc"; +import { Doc, WidthSym, HeightSym, DocListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { Cast, NumCast } from "../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../Utils"; @@ -149,7 +149,7 @@ export class OverlayView extends React.Component { } removeOverlayDoc = (doc: Doc | Doc[]) => { - (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc)); + (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc)); return true; } @@ -158,7 +158,7 @@ export class OverlayView extends React.Component { }.bind(this)); @computed get overlayDocs() { - return CurrentUserUtils.MyOverlayDocs?.map(d => { + return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).map(d => { let offsetx = 0, offsety = 0; const dref = React.createRef(); const onPointerMove = action((e: PointerEvent, down: number[]) => { @@ -172,7 +172,7 @@ export class OverlayView extends React.Component { dragData.dropAction = "move"; dragData.removeDocument = (doc: Doc | Doc[]) => { const docs = (doc instanceof Doc) ? [doc] : doc; - docs.forEach(d => Doc.RemoveDocFromList(Cast(Doc.UserDoc().myOverlayDocs, Doc, null), "data", d)); + docs.forEach(d => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, d)); return true; }; dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 74055f057..9c6d9a108 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -200,7 +200,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {list}
    - {Doc.UserDoc().noviceMode ? (null) :
    Edit onClick Script
    } + {Doc.noviceMode ? (null) :
    Edit onClick Script
    }
    ; } @computed @@ -229,7 +229,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree; const isTabView = this.selectedTabView; const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) =>
    {ele}
    ; - const isNovice = Doc.UserDoc().noviceMode; + const isNovice = Doc.noviceMode; return !this.selectedDoc ? (null) :
    {toggle(this.titleButton)} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 2cf334ae1..90c86fa18 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -11,7 +11,7 @@ import { Id } from "../../fields/FieldSymbols"; import { InkField } from "../../fields/InkField"; import { List } from "../../fields/List"; import { ComputedField } from "../../fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; +import { Cast, NumCast, StrCast, DocCast } from "../../fields/Types"; import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils"; import { DocumentType } from "../documents/DocumentTypes"; @@ -340,7 +340,7 @@ export class PropertiesView extends React.Component { */ @undoBatch changePermissions = (e: any, user: string) => { - const docs = SelectionManager.Views().length < 2 ? [this.selectedDoc] : SelectionManager.Views().map(docView => docView.props.Document); + const docs = SelectionManager.Views().length < 2 ? (this.selectedDoc ? [this.selectedDoc]:[]) : SelectionManager.Views().map(docView => docView.props.Document); SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); } @@ -355,7 +355,7 @@ export class PropertiesView extends React.Component { value={permission} onChange={e => this.changePermissions(e, user)}> {dropdownValues.filter(permission => - !Doc.UserDoc().noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => + !Doc.noviceMode || ![SharingPermissions.View, SharingPermissions.SelfEdit].includes(permission as any)).map(permission => )} ; } @@ -419,7 +419,7 @@ export class PropertiesView extends React.Component { // all selected docs const docs = SelectionManager.Views().length < 2 ? - [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc[DataSym]] + [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc?.[DataSym]] : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]); const target = docs[0]; @@ -907,7 +907,7 @@ export class PropertiesView extends React.Component { {!this.openSharing ? (null) :
    - {!Doc.UserDoc().noviceMode ? (
    + {!Doc.noviceMode ? (
    this.layoutDocAcls = !this.layoutDocAcls)} @@ -931,42 +931,46 @@ export class PropertiesView extends React.Component { * If it doesn't exist, it creates it. */ checkFilterDoc() { - if (!this.selectedDoc.currentFilter) this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); + if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = CurrentUserUtils.createFilterDoc(); } /** * Creates a new currentFilter for this.filterDoc, */ createNewFilterDoc = () => { - const currentDocFilters = this.selectedDoc._docFilters; - const currentDocRangeFilters = this.selectedDoc._docRangeFilters; - this.selectedDoc._docFilters = new List(); - this.selectedDoc._docRangeFilters = new List(); - (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; - (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; - this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); + if (this.selectedDoc) { + const currentDocFilters = this.selectedDoc._docFilters; + const currentDocRangeFilters = this.selectedDoc._docRangeFilters; + this.selectedDoc._docFilters = new List(); + this.selectedDoc._docRangeFilters = new List(); + (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; + (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; + this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); + } } /** * Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter */ updateFilterDoc = (doc: Doc) => { - if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc - const savedDocFilters = doc._docFiltersList; - const currentDocFilters = this.selectedDoc._docFilters; - this.selectedDoc._docFilters = new List(); - (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; - this.selectedDoc.currentFilter = doc; - doc._docFiltersList = new List(); - this.selectedDoc._docFilters = savedDocFilters; - - const savedDocRangeFilters = doc._docRangeFiltersList; - const currentDocRangeFilters = this.selectedDoc._docRangeFilters; - this.selectedDoc._docRangeFilters = new List(); - (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; - this.selectedDoc.currentFilter = doc; - doc._docRangeFiltersList = new List(); - this.selectedDoc._docRangeFilters = savedDocRangeFilters; + if (this.selectedDoc) { + if (doc === this.selectedDoc.currentFilter) return; // causes problems if you try to reapply the same doc + const savedDocFilters = doc._docFiltersList; + const currentDocFilters = this.selectedDoc._docFilters; + this.selectedDoc._docFilters = new List(); + (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; + this.selectedDoc.currentFilter = doc; + doc._docFiltersList = new List(); + this.selectedDoc._docFilters = savedDocFilters; + + const savedDocRangeFilters = doc._docRangeFiltersList; + const currentDocRangeFilters = this.selectedDoc._docRangeFilters; + this.selectedDoc._docRangeFilters = new List(); + (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; + this.selectedDoc.currentFilter = doc; + doc._docRangeFiltersList = new List(); + this.selectedDoc._docRangeFilters = savedDocRangeFilters; + } } @computed get filtersSubMenu() { @@ -1059,13 +1063,13 @@ export class PropertiesView extends React.Component {
    - {!Doc.UserDoc().noviceMode && this.openFields ?
    + {!Doc.noviceMode && this.openFields ?
    {this.fieldsCheckbox}
    Layout
    : null} {!this.openFields ? (null) :
    - {Doc.UserDoc().noviceMode ? this.noviceFields : this.expandedField} + {Doc.noviceMode ? this.noviceFields : this.expandedField}
    }
    ; } @@ -1122,7 +1126,7 @@ export class PropertiesView extends React.Component { @undoBatch handleDescriptionChange = action((value: string) => { - if (LinkManager.currentLink) { + if (LinkManager.currentLink && this.selectedDoc) { this.selectedDoc.description = value; this.description = value; return true; @@ -1131,7 +1135,7 @@ export class PropertiesView extends React.Component { @undoBatch handleLinkRelationshipChange = action((value: string) => { - if (LinkManager.currentLink) { + if (LinkManager.currentLink && this.selectedDoc) { this.selectedDoc.linkRelationship = value; this.relationship = value; return true; @@ -1188,7 +1192,7 @@ export class PropertiesView extends React.Component { @undoBatch changeFollowBehavior = action((follow: string) => { - if (LinkManager.currentLink) { + if (LinkManager.currentLink && this.selectedDoc) { this.selectedDoc.followLinkLocation = follow; return true; } @@ -1219,18 +1223,18 @@ export class PropertiesView extends React.Component { } toggleAnchor = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove)))); } toggleArrow = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)))); } toggleZoomToTarget1 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor1, Doc, null).followLinkZoom))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor1).followLinkZoom = !DocCast(this.selectedDoc.anchor1).followLinkZoom)))); } toggleZoomToTarget2 = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom = !Cast(this.selectedDoc.anchor2, Doc, null).followLinkZoom))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor2).followLinkZoom = !DocCast(this.selectedDoc.anchor2).followLinkZoom)))); } @computed @@ -1238,7 +1242,7 @@ export class PropertiesView extends React.Component { return this.handleLinkRelationshipChange(e.currentTarget.value)} @@ -1252,7 +1256,7 @@ export class PropertiesView extends React.Component { return this.handleDescriptionChange(e.currentTarget.value)} @@ -1273,7 +1277,7 @@ export class PropertiesView extends React.Component { // } render() { - const isNovice = BoolCast(Doc.UserDoc().noviceMode); + const isNovice = Doc.noviceMode; if (!this.selectedDoc && !this.isPres) { return
    diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 636f7042f..689ee4fc1 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -15,6 +15,7 @@ import { DocumentView } from "./nodes/DocumentView"; import { DefaultStyleProvider } from "./StyleProvider"; import './TemplateMenu.scss'; import React = require("react"); +import { CurrentUserUtils } from "../util/CurrentUserUtils"; @observer class TemplateToggle extends React.Component<{ template: string, checked: boolean, toggle: (event: React.ChangeEvent, template: string) => void }> { @@ -111,22 +112,22 @@ export class TemplateMenu extends React.Component { const firstDoc = this.props.docViews[0].props.Document; const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", ""); const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data); - const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); + const addedTypes = Doc.noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); const layout = Doc.Layout(firstDoc); const templateMenu: Array = []; this.props.templates?.forEach((checked, template) => templateMenu.push()); templateMenu.push(); templateMenu.push(); - !Doc.UserDoc().noviceMode && templateMenu.push(); + !Doc.noviceMode && templateMenu.push(); addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template)); this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push( this.toggleLayout(e, template)} />)); return
      - {Doc.UserDoc().noviceMode ? (null) : } + {Doc.noviceMode ? (null) : } {templateMenu} - {Doc.UserDoc().noviceMode ? (null) : { stack.header?.element.on('mousedown', (e: any) => { - if (e.target === stack.header?.element[0] && e.button === 2) { - const dashboard= CurrentUserUtils.ActiveDashboard; + const dashboard = CurrentUserUtils.ActiveDashboard; + if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; const docToAdd = Docs.Create.FreeformDocument([], { _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _backgroundGridShow: true, _fitWidth: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}`, @@ -462,12 +462,14 @@ export class CollectionDockingView extends CollectionSubView() { .click(action(() => { // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size const dashboard = CurrentUserUtils.ActiveDashboard; - dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; - const docToAdd = Docs.Create.FreeformDocument([], { - _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}` - }); - this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); - CollectionDockingView.AddSplit(docToAdd, "", stack); + if (dashboard) { + dashboard["pane-count"] = NumCast(dashboard["pane-count"]) + 1; + const docToAdd = Docs.Create.FreeformDocument([], { + _width: this.props.PanelWidth(), _height: this.props.PanelHeight(), _fitWidth: true, _backgroundGridShow: true, title: `Untitled Tab ${NumCast(dashboard["pane-count"])}` + }); + this.props.Document.isShared && inheritParentAcls(this.props.Document, docToAdd); + CollectionDockingView.AddSplit(docToAdd, "", stack); + } })); } @@ -480,6 +482,6 @@ ScriptingGlobals.add(function openInLightbox(doc: any) { LightboxView.AddDocTab( "opens up document in a lightbox", "(doc: any)"); ScriptingGlobals.add(function openOnRight(doc: any) { return CollectionDockingView.AddSplit(doc, "right"); }, "opens up document in tab on right side of the screen", "(doc: any)"); -ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); }, +ScriptingGlobals.add(function openInOverlay(doc: any) { return Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc); }, "opens up document in screen overlay layer", "(doc: any)"); ScriptingGlobals.add(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); }); \ No newline at end of file diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 1c25421f5..9b1bb5b97 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -268,17 +268,20 @@ export class CollectionViewBaseChrome extends React.Component { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }), initialize: (button: Doc) => { - button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) instanceof ObjectField ? - ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || CurrentUserUtils.ActiveDashboard._docFilters) as any as ObjectField) : undefined; - button['target-searchFilterDocs'] = CurrentUserUtils.ActiveDashboard._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(CurrentUserUtils.ActiveDashboard._searchFilterDocs as any as ObjectField) : undefined; + const activeDash = CurrentUserUtils.ActiveDashboard; + if (activeDash) { + button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) instanceof ObjectField ? + ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) as any as ObjectField) : undefined; + button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined; + } }, }; - @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } - @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } - @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } - @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } - @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } + @computed get _freeform_commands() { return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } + @computed get _stacking_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } + @computed get _masonry_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } + @computed get _schema_commands() { return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } + @computed get _doc_commands() { return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } @computed get _tree_commands() { return undefined; } private get _buttonizableCommands() { switch (this.props.type) { @@ -467,7 +470,7 @@ export class CollectionViewBaseChrome extends React.Component { const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); - //Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); + //Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc); CollectionDockingView.AddSplit(doc, "right"); } @@ -691,8 +694,8 @@ export class CollectionFreeFormViewChrome extends React.Component Back Frame
    } placement="bottom">
    @@ -879,7 +882,7 @@ export class CollectionStackingViewChrome extends React.Component key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index dddae4a34..277fcd59c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -598,7 +598,7 @@ export class CollectionStackingView extends CollectionSubView {buttonMenu || noviceExplainer ?
    {buttonMenu ? this.buttonMenu : null} - {Doc.UserDoc().noviceMode && noviceExplainer ? + {Doc.noviceMode && noviceExplainer ?
    {noviceExplainer}
    diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 7f96217b8..f3a798143 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -224,8 +224,8 @@ export class CollectionStackingViewFieldColumn extends React.Component { Doc.GetProto(this.props.Document)[name] = ""; const created = Docs.Create.TextDocument("", { title: name, _width: 250, _autoHeight: true }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index e809bfbce..ba72fb7b9 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -67,7 +67,7 @@ export class CollectionTreeView extends CollectionSubView this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); - isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || + isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.ActiveTool !== InkTool.None || (this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction)) ? true : false) @@ -164,7 +164,7 @@ export class CollectionTreeView extends CollectionSubView { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { const layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: "Make tree state " + (this.doc.treeViewOpenIsTransient ? "persistent" : "transient"), event: () => this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient, icon: "paint-brush" }); layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" }); @@ -286,7 +286,7 @@ export class CollectionTreeView extends CollectionSubView {this.rootDoc.explainer}
    ; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 965f0a352..4b5c5e3fb 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -160,8 +160,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent func(CollectionViewType.Masonry), icon: "columns" }); subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" }); subItems.push({ description: "3D Carousel", event: () => func(CollectionViewType.Carousel3D), icon: "columns" }); - !Doc.UserDoc().noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); - !Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); + !Doc.noviceMode && subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); + !Doc.noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" }); if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.isGroup && !this.rootDoc.annotationOn) { @@ -184,16 +184,16 @@ export class CollectionView extends ViewBoxAnnotatableComponent this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null; + !Doc.noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null; if (this.rootDoc.childLayout instanceof Doc) { optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" }); } if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" }); } - !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" }); + !Doc.noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" }); - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { optionItems.push({ description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram" }); @@ -210,7 +210,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 022b9fa24..70db121d1 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -277,7 +277,7 @@ export class TabDocView extends React.Component { pinDoc && PresBox.Instance?._selectedArray.set(pinDoc, undefined); //Update selected array }); if (!Array.from(CollectionDockingView.Instance.tabMap).map(d => d.DashDoc).includes(curPres)) { - const docs = Cast(Cast(Doc.UserDoc().myOverlayDocs, Doc, null).data, listSpec(Doc), []); + const docs = Cast(CurrentUserUtils.MyOverlayDocs.data, listSpec(Doc), []); if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); CollectionDockingView.AddSplit(curPres, "right"); setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 59dc5671b..704b8989a 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -114,7 +114,7 @@ export class TreeView extends React.Component { return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : this.props.treeView.dashboardMode ? this.fieldKey : this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : // for displaying - this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); + this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); } @computed get doc() { return this.props.document; } @@ -560,8 +560,8 @@ export class TreeView extends React.Component { const links = () => DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? "links" : ""; const data = () => this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ""; const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases"; - const fields = () => Doc.UserDoc().noviceMode ? "" : "fields"; - const layout = (Doc.UserDoc().noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; + const fields = () => Doc.noviceMode ? "" : "fields"; + const layout = (Doc.noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m); } @action diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index c07e44fcc..ffe146ae4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -458,7 +458,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { @@ -834,7 +834,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || CurrentUserUtils.MyOverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -1031,7 +1031,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" }); - !Doc.UserDoc().noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); + !Doc.noviceMode && Doc.UserDoc().defaultTextLayout && appearanceItems.push({ description: "Reset default note style", event: () => Doc.UserDoc().defaultTextLayout = undefined, icon: "eye" }); appearanceItems.push({ description: `${this.fitContentsToBox ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitContentsToBox = !this.fitContentsToBox, icon: !this.fitContentsToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); appearanceItems.push({ description: `Pin View`, event: () => TabDocView.PinDoc(this.rootDoc, {pinDocView:true, panelWidth: this.props.PanelWidth(), panelHeight:this.props.PanelHeight()}), icon: "map-pin" }); //appearanceItems.push({ description: `update icon`, event: this.updateIcon, icon: "compress-arrows-alt" }); @@ -1640,27 +1640,27 @@ export class CollectionFreeFormView extends CollectionSubView s.type === DocumentType.INK).length > 0 && appearanceItems.push({ description: "Ink to math", event: () => this.transcribeStrokes(true), icon: "square-root-alt" }); - !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; + !Doc.noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null; !appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" }); const viewctrls = ContextMenu.Instance.findByDescription("UI Controls..."); const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : []; - !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; - !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); const options = ContextMenu.Instance.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; - !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode && + !this.props.isAnnotationOverlay && !Doc.noviceMode && optionItems.push({ description: (this._showAnimTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this._showAnimTimeline = !this._showAnimTimeline), icon: "eye" }); this.props.renderDepth && optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" }); - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { optionItems.push({ description: (!Doc.NativeWidth(this.layoutDoc) || !Doc.NativeHeight(this.layoutDoc) ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" }); } !options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); const mores = ContextMenu.Instance.findByDescription("More..."); const moreItems = mores && "subitems" in mores ? mores.subitems : []; - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) }); moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) }); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 051da795f..b62020a04 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -330,7 +330,7 @@ export class MarqueeView extends React.Component { if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { - if (CurrentUserUtils.SelectedTool === InkTool.None) { + if (CurrentUserUtils.ActiveTool === InkTool.None) { if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; if (!this.props.trySelectCluster(e.shiftKey)) { @@ -367,7 +367,7 @@ export class MarqueeView extends React.Component { Doc.GetProto(doc).data = new List(selected); Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; - !this.props.isAnnotationOverlay && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(doc)); + !this.props.isAnnotationOverlay && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, Doc.GetProto(doc)); doc._panX = doc._panY = 0; return doc; })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); @@ -643,7 +643,7 @@ export class MarqueeView extends React.Component e.preventDefault()} diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx index 0875c80b3..9653f2808 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaHeaders.tsx @@ -321,7 +321,7 @@ export class KeysDropdown extends React.Component { const whitelistKeys = ["context", "author", "*lastModified", "text", "data", "tags", "creationDate"]; const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const showKeys = new Set(); - [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.UserDoc().noviceMode || + [...keyOptions, ...whitelistKeys].forEach(key => (!Doc.noviceMode || whitelistKeys.includes(key) || ((!key.startsWith("_") && key[0] === key[0].toUpperCase()) || key[0] === "#")) ? showKeys.add(key) : null); return Array.from(showKeys.keys()).filter(key => !this._searchTerm || key.includes(this._searchTerm)); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 669622455..8e24fce11 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -348,11 +348,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { @undoBatch @action static switchColor(color: ColorState) { - // Doc.UserDoc().backgroundColor = Utils.colorString(color); // bcz: this can't go here ... needs a proper home in the settings panel SetActiveInkColor(color.hex); SelectionManager.Views().map(view => { @@ -49,7 +48,7 @@ export class ColorBox extends ViewBoxBaseComponent() { style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }} > CurrentUserUtils.SelectedTool === InkTool.None && ColorBox.switchColor(c)} + onChange={c => CurrentUserUtils.ActiveTool === InkTool.None && ColorBox.switchColor(c)} color={StrCast(SelectionManager.Views()?.[0]?.rootDoc?._backgroundColor, ActiveInkColor())} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e2b37d2b4..4d84a8ad2 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -327,7 +327,7 @@ export class DocumentViewInternal extends DocComponent 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { @@ -543,9 +543,9 @@ export class DocumentViewInternal extends DocComponent { - if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.SelectedTool === InkTool.Eraser) return; + if (this.rootDoc.type === DocumentType.INK && CurrentUserUtils.ActiveTool === InkTool.Eraser) return; // continue if the event hasn't been canceled AND we are using a mouse or this has an onClick or onDragStart function (meaning it is a button document) - if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) { + if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool))) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { e.stopPropagation(); if (SelectionManager.IsSelected(this.props.DocumentView(), true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it @@ -563,7 +563,7 @@ export class DocumentViewInternal extends DocComponent { if (e.cancelBubble) return; - if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool))) return; + if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool))) return; - if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { + if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); @@ -697,7 +697,7 @@ export class DocumentViewInternal extends DocComponent { - if (e && this.rootDoc._hideContextMenu && Doc.UserDoc().noviceMode) { + if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); @@ -747,8 +747,8 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, "add:right"), icon: "eye" }); - !Doc.UserDoc().noviceMode && appearanceItems.push({ + !Doc.noviceMode && templateDoc && appearanceItems.push({ description: "Open Template ", event: () => this.props.addDocTab(templateDoc, "add:right"), icon: "eye" }); + !Doc.noviceMode && appearanceItems.push({ description: "Add a Field", event: () => { const alias = Doc.MakeAlias(this.rootDoc); alias.layout = FormattedTextBox.LayoutString("newfield"); @@ -765,7 +765,7 @@ export class DocumentViewInternal extends DocComponent this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" }); + !Doc.noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" }); const existingOnClick = cm.findByDescription("OnClick..."); const onClicks: ContextMenuProps[] = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; @@ -778,8 +778,8 @@ export class DocumentViewInternal extends DocComponent this.Document.followLinkZoom = !this.Document.followLinkZoom, icon: this.Document.ignoreClick ? "unlock" : "lock" }); if (!this.Document.annotationOn) { @@ -803,7 +803,7 @@ export class DocumentViewInternal extends DocComponent this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getAlias(this.dragFactory)')) }); funcs.push({ description: "Drag a Copy", icon: "edit", event: () => this.Document.dragFactory && (this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)')) }); funcs.push({ description: "Drag Document", icon: "edit", event: () => this.layoutDoc.onDragStart = undefined }); @@ -813,8 +813,8 @@ export class DocumentViewInternal extends DocComponent SharingManager.Instance.open(this.props.DocumentView()), icon: "users" }); - if (!Doc.UserDoc().noviceMode) { + (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && moreItems.push({ description: "Share", event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: "users" }); + if (!Doc.noviceMode) { moreItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc), icon: "concierge-bell" }); moreItems.push({ description: `${this.Document._chromeHidden ? "Show" : "Hide"} Chrome`, event: () => this.Document._chromeHidden = !this.Document._chromeHidden, icon: "project-diagram" }); @@ -835,9 +835,9 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(Docs.Create.KVPDocument(this.props.Document, { _width: 300, _height: 300 }), "add:right"), icon: "layer-group" }); - !Doc.UserDoc().noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); - !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); - !Doc.UserDoc().noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); + !Doc.noviceMode && helpItems.push({ description: "Text Shortcuts Ctrl+/", event: () => this.props.addDocTab(Docs.Create.PdfDocument("/assets/cheat-sheet.pdf", { _width: 300, _height: 300 }), "add:right"), icon: "keyboard" }); + !Doc.noviceMode && helpItems.push({ description: "Print Document in Console", event: () => console.log(this.props.Document), icon: "hand-point-right" }); + !Doc.noviceMode && helpItems.push({ description: "Print DataDoc in Console", event: () => console.log(this.props.Document[DataSym]), icon: "hand-point-right" }); cm.addItem({ description: "Help...", noexpand: true, subitems: helpItems, icon: "question" }); } @@ -860,7 +860,7 @@ export class DocumentViewInternal extends DocComponent Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view); isContentActive = (outsideReaction?: boolean) => { return this.props.isContentActive() === false ? false : ( - CurrentUserUtils.SelectedTool !== InkTool.None || + CurrentUserUtils.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.props.Document.forceActive || diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 5add09653..17b57cb3b 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -8,7 +8,7 @@ import { List } from "../../../fields/List"; import { RichTextField } from "../../../fields/RichTextField"; import { listSpec } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { Cast, NumCast, StrCast, DocCast } from "../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; @@ -84,7 +84,7 @@ export class FilterBox extends ViewBoxBaseComponent() { return "data"; } @computed static get targetDocChildren() { - return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard.data); + return DocListCast(FilterBox.targetDoc?.[FilterBox.targetDocChildKey] || CurrentUserUtils.ActiveDashboard?.data); } @observable _loaded = false; @@ -115,7 +115,7 @@ export class FilterBox extends ViewBoxBaseComponent() { const keys = new Set(noviceFields); this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); - return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.UserDoc().noviceMode).sort(); + return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_")) || noviceFields.includes(key) || !Doc.noviceMode).sort(); } @@ -167,23 +167,25 @@ export class FilterBox extends ViewBoxBaseComponent() { removeFilterDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).map(doc => this.removeFilter(StrCast(doc.title))).length ? true : false; public removeFilter = (filterName: string) => { const targetDoc = FilterBox.targetDoc; - const filterDoc = targetDoc.currentFilter as Doc; - const attributes = DocListCast(filterDoc.data); - const found = attributes.findIndex(doc => doc.title === filterName); - if (found !== -1) { - (filterDoc.data as List).splice(found, 1); - const docFilter = Cast(targetDoc._docFilters, listSpec("string")); - if (docFilter) { - let index: number; - while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) { - docFilter.splice(index, 1); + if (targetDoc) { + const filterDoc = targetDoc.currentFilter as Doc; + const attributes = DocListCast(filterDoc.data); + const found = attributes.findIndex(doc => doc.title === filterName); + if (found !== -1) { + (filterDoc.data as List).splice(found, 1); + const docFilter = Cast(targetDoc._docFilters, listSpec("string")); + if (docFilter) { + let index: number; + while ((index = docFilter.findIndex(item => item.split(":")[0] === filterName)) !== -1) { + docFilter.splice(index, 1); + } } - } - const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string")); - if (docRangeFilters) { - let index: number; - while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) { - docRangeFilters.splice(index, 3); + const docRangeFilters = Cast(targetDoc._docRangeFilters, listSpec("string")); + if (docRangeFilters) { + let index: number; + while ((index = docRangeFilters.findIndex(item => item.split(":")[0] === filterName)) !== -1) { + docRangeFilters.splice(index, 3); + } } } } @@ -194,6 +196,7 @@ export class FilterBox extends ViewBoxBaseComponent() { */ facetClick = (facetHeader: string) => { const { targetDoc, targetDocChildren } = FilterBox; + if (!targetDoc) return; const found = this.activeAttributes.findIndex(doc => doc.title === facetHeader); if (found !== -1) { this.removeFilter(facetHeader); @@ -267,7 +270,7 @@ export class FilterBox extends ViewBoxBaseComponent() { */ @action changeBool = (e: any) => { - (FilterBox.targetDoc.currentFilter as Doc).filterBoolean = e.currentTarget.value; + FilterBox.targetDoc && (DocCast(FilterBox.targetDoc.currentFilter).filterBoolean = e.currentTarget.value); } /** @@ -322,7 +325,7 @@ export class FilterBox extends ViewBoxBaseComponent() { * Changes the title of the filterDoc */ onTitleValueChange = (val: string) => { - this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc.title}`; + this.props.Document.title = val || `FilterDoc for ${FilterBox.targetDoc?.title}`; return true; } @@ -373,7 +376,7 @@ export class FilterBox extends ViewBoxBaseComponent() {
    - diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 8c27b3508..60eb48114 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -179,7 +179,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent Utils.CopyText(this.choosePath(field.url)), icon: "expand-arrows-alt" }); - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { funcs.push({ description: "Export to Google Photos", event: () => GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); const existingAnalyze = ContextMenu.Instance?.findByDescription("Analyzers..."); @@ -357,7 +357,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent; } marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && NumCast(this.rootDoc._viewScale,1) <= NumCast(this.rootDoc.viewScaleMin,1) && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index b0b050cea..b58a9affb 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -52,7 +52,7 @@ export class LabelBox extends ViewBoxBaseComponent<(FieldViewProps & LabelBoxPro get paramsDoc() { return Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? this.dataDoc : this.layoutDoc; } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - !Doc.UserDoc().noviceMode && funcs.push({ + !Doc.noviceMode && funcs.push({ description: "Clear Script Params", event: () => { const params = Cast(this.paramsDoc["onClick-paramFieldKeys"], listSpec("string"), []); params?.map(p => this.paramsDoc[p] = undefined); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 5f4c17ee6..0b7264d79 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -471,7 +471,7 @@ export class MapBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; @@ -570,7 +570,7 @@ export class MapBox extends ViewBoxAnnotatableComponent; + // pointerEvents={CurrentUserUtils.ActiveTool !== InkTool.None || this._isAnnotating || SnappingManager.GetIsDragging() ? "all" : "none"} />; return
    {/*console.log(apiKey)*/} {/* Loading
    :
    @@ -484,7 +484,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.layoutDoc._currentTimecode, () => !this._playing && this.Seek(NumCast(this.layoutDoc._currentTimecode))); this._disposers.youtubeReactionDisposer = reaction( - () => CurrentUserUtils.SelectedTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, + () => CurrentUserUtils.ActiveTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true }); }; if (typeof (YT) === undefined) setTimeout(() => this.loadYouTube(iframe), 100); @@ -707,7 +707,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 967158cbf..d14af49ea 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -536,7 +536,7 @@ export class WebBox extends ViewBoxAnnotatableComponent this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); + !Doc.noviceMode && funcs.push({ description: (this.layoutDoc.useCors ? "Don't Use" : "Use") + " Cors", event: () => this.layoutDoc.useCors = !this.layoutDoc.useCors, icon: "snowflake" }); funcs.push({ description: (this.layoutDoc.allowScripts ? "Prevent" : "Allow") + " Scripts", event: () => { this.layoutDoc.allowScripts = !this.layoutDoc.allowScripts; @@ -561,7 +561,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { setupMoveUpEvents(this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; @@ -678,7 +678,7 @@ export class WebBox extends ViewBoxAnnotatableComponent e.stopPropagation()} style={{ width: !this.layoutDoc.forceReflow ? NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]) || `100%` : "100%", }}> diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index f29dfe489..3af6a3d51 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -1,6 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; +import { StringIterator } from 'lodash'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -70,7 +71,7 @@ export class FontIconBox extends DocComponent() { useAsPrototype = (): void => { this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); }; specificContextMenu = (): void => { - if (!Doc.UserDoc().noviceMode) { + if (!Doc.noviceMode) { const cm = ContextMenu.Instance; cm.addItem({ description: "Show Template", event: this.showTemplate, icon: "tag" }); cm.addItem({ description: "Use as Render Template", event: this.dragAsTemplate, icon: "tag" }); @@ -269,7 +270,7 @@ export class FontIconBox extends DocComponent() { // Get items to place into the list const list = this.buttonList.map((value) => { - if (Doc.UserDoc().noviceMode && !noviceList.includes(value)) { + if (Doc.noviceMode && !noviceList.includes(value)) { return; } return
    = 1) return Colors.MEDIUM_BLUE; + if (checkResult) { + if (NumCast(selected?.Document.z) >= 1) return Colors.MEDIUM_BLUE; return "transparent"; } selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log("[FontIconBox.tsx] toggleOverlay failed"); @@ -710,7 +711,7 @@ ScriptingGlobals.add(function toggleItalic(checkResult?: boolean) { export function checkInksToGroup() { // console.log("getting here to inks group"); - if (CurrentUserUtils.SelectedTool === InkTool.Write) { + if (CurrentUserUtils.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other @@ -723,7 +724,7 @@ export function checkInksToGroup() { export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (CurrentUserUtils.SelectedTool === InkTool.Write) { + if (CurrentUserUtils.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those const selected = ffView.unprocessedDocs; @@ -785,38 +786,38 @@ export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { /** INK - * setActiveInkTool + * setActiveTool * setStrokeWidth * setStrokeColor **/ -ScriptingGlobals.add(function setActiveInkTool(tool: string, checkResult?: boolean) { +ScriptingGlobals.add(function setActiveTool(tool: string, checkResult?: boolean) { InkTranscription.Instance?.createInkGroup(); if (checkResult) { - return ((Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? + return ((CurrentUserUtils.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool) ? Colors.MEDIUM_BLUE : "transparent"; } if (["circle", "square", "line"].includes(tool)) { if (GestureOverlay.Instance.InkShape === tool) { - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; GestureOverlay.Instance.InkShape = InkTool.None; } else { - Doc.UserDoc().activeInkTool = InkTool.Pen; + CurrentUserUtils.ActiveTool = InkTool.Pen; GestureOverlay.Instance.InkShape = tool; } } else if (tool) { // pen or eraser - if (Doc.UserDoc().activeInkTool === tool && !GestureOverlay.Instance.InkShape) { - Doc.UserDoc().activeInkTool = InkTool.None; - } else if (tool == "write") { + if (CurrentUserUtils.ActiveTool === tool && !GestureOverlay.Instance.InkShape) { + CurrentUserUtils.ActiveTool = InkTool.None; + } else if (tool == InkTool.Write) { // console.log("write mode selected - create groupDoc here!", tool) - Doc.UserDoc().activeInkTool = tool; + CurrentUserUtils.ActiveTool = tool; GestureOverlay.Instance.InkShape = ""; } else { - Doc.UserDoc().activeInkTool = tool; + CurrentUserUtils.ActiveTool = tool as any; GestureOverlay.Instance.InkShape = ""; } } else { - Doc.UserDoc().activeInkTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; } }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 16a523b40..90199618b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -648,7 +648,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const highlighting: ContextMenuProps[] = []; const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others", "Bold Text"]; const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; - (Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => + (Doc.noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => highlighting.push({ description: (FormattedTextBox._globalHighlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { e.stopPropagation(); @@ -663,11 +663,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp })); const uicontrols: ContextMenuProps[] = []; - !Doc.UserDoc().noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); + !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ""} Show Menu on Selections`, event: () => FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate, icon: "expand-arrows-alt" }); uicontrols.push({ description: !this.Document._noSidebar ? "Hide Sidebar Handle" : "Show Sidebar Handle", event: () => this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar, icon: "expand-arrows-alt" }); uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); - !Doc.UserDoc().noviceMode && uicontrols.push({ + !Doc.noviceMode && uicontrols.push({ description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" }); @@ -677,7 +677,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : []; appearanceItems.push({ description: "Change Perspective...", noexpand: true, subitems: changeItems, icon: "external-link-alt" }); // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); - !Doc.UserDoc().noviceMode && appearanceItems.push({ + !Doc.noviceMode && appearanceItems.push({ description: "Make Default Layout", event: () => { if (!this.layoutDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); @@ -1636,7 +1636,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp noSidebar={true} fieldKey={this.layoutDoc.sidebarViewType === "translation" ? `${this.fieldKey}-translation` : `${this.fieldKey}-annotations`} />; }; - return
    {renderComponent(StrCast(this.layoutDoc.sidebarViewType))}
    ; @@ -1647,7 +1647,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const active = this.props.isContentActive(); const scale = (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; - const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); + const interactive = (CurrentUserUtils.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); const minimal = this.props.ignoreAutoHeight; const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 9bc2e5628..98343a261 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -67,7 +67,6 @@ export class RichTextMenu extends AntimodeMenu { runInAction(() => { RichTextMenu.Instance = this; this._canFade = false; - //this.Pinned = BoolCast(Doc.UserDoc()["menuRichText-pinned"]); this.Pinned = true; }); } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 52b03b0a5..0d2cffc2c 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -134,7 +134,7 @@ export class PresBox extends ViewBoxBaseComponent() { @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); - if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); + if (CurrentUserUtils.ActivePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" @@ -178,16 +178,16 @@ export class PresBox extends ViewBoxBaseComponent() { this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; this.turnOffEdit(true); - DocListCastAsync((Doc.UserDoc().myTrails as Doc).data).then(pres => - !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.UserDoc().myTrails as Doc, "data", this.rootDoc)); + DocListCastAsync(CurrentUserUtils.MyTrails.data).then(pres => + !pres?.includes(this.rootDoc) && Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", this.rootDoc)); this._disposers.selection = reaction(() => SelectionManager.Views(), views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()); } @action updateCurrentPresentation = (pres?: Doc) => { - if (pres) Doc.UserDoc().activePresentation = pres; - else Doc.UserDoc().activePresentation = this.rootDoc; + if (pres) CurrentUserUtils.ActivePresentation = pres; + else CurrentUserUtils.ActivePresentation = this.rootDoc; document.removeEventListener("keydown", PresBox.keyEventsWrapper, true); document.addEventListener("keydown", PresBox.keyEventsWrapper, true); this._presKeyEventsActive = true; @@ -623,9 +623,9 @@ export class PresBox extends ViewBoxBaseComponent() { */ @action updateMinimize = async () => { - if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { + if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); + Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, "right"); } else { this.layoutDoc.presStatus = PresStatus.Edit; @@ -635,7 +635,7 @@ export class PresBox extends ViewBoxBaseComponent() { this.rootDoc.y = pt[1] + 10; this.rootDoc._height = 30; this.rootDoc._width = 248; - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, this.rootDoc); this.props.removeDocument?.(this.layoutDoc); } } @@ -732,7 +732,7 @@ export class PresBox extends ViewBoxBaseComponent() { removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && !this.layoutDoc._lockedPosition) && + isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) /** @@ -868,7 +868,7 @@ export class PresBox extends ViewBoxBaseComponent() { } break; case "Escape": - if (CurrentUserUtils.MyOverlayDocs.includes(this.layoutDoc)) { this.updateMinimize(); } + if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.updateMinimize(); } else if (this.layoutDoc.presStatus === "edit") { this._selectedArray.clear(); this._eleArray.length = this._dragArray.length = 0; } else this.layoutDoc.presStatus = "edit"; if (this._presTimer) clearTimeout(this._presTimer); @@ -2247,7 +2247,7 @@ export class PresBox extends ViewBoxBaseComponent() { const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Presentation Panel" : "Open Presentation Panel"; const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); + const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; return (mode === CollectionViewType.Carousel3D) ? (null) : ( @@ -2305,7 +2305,7 @@ export class PresBox extends ViewBoxBaseComponent() { value={mode}> - {Doc.UserDoc().noviceMode ? (null) : } + {Doc.noviceMode ? (null) : } }
    @@ -2508,10 +2508,10 @@ export class PresBox extends ViewBoxBaseComponent() { // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; - const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.UserDoc().activePresentation); + const presKeyEvents: boolean = (this.isPres && this._presKeyEventsActive && this.rootDoc === CurrentUserUtils.ActivePresentation); const presEnd: boolean = !this.layoutDoc.presLoop && (this.itemIndex === this.childDocs.length - 1); const presStart: boolean = !this.layoutDoc.presLoop && (this.itemIndex === 0); - return CurrentUserUtils.MyOverlayDocs.includes(this.rootDoc) ? + return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc) ?
    e.stopPropagation()}>
    {"Loop"}
    }>
    () {
    : -
    +
    {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 50df00612..1a2f4b93f 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -309,7 +309,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get recordingIsInOverlay() { let isInOverlay = false - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { + DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides === this.rootDoc) { isInOverlay = true return @@ -319,9 +319,9 @@ export class PresElementBox extends ViewBoxBaseComponent() { } removeAllRecordingInOverlay = () => { - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { + DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides === this.rootDoc) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); + Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc); } }) } @@ -339,7 +339,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { this.removeAllRecordingInOverlay() if (activeItem.recording) { // if we already have an existing recording - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null)); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); } } @@ -348,15 +348,15 @@ export class PresElementBox extends ViewBoxBaseComponent() { @action startRecording = (activeItem: Doc) => { // Remove every recording that already exists in overlay view - DocListCast((Doc.UserDoc().myOverlayDocs as Doc).data).forEach((doc) => { + DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides !== null) { - Doc.RemoveDocFromList((Doc.UserDoc().myOverlayDocs as Doc), undefined, doc); + Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc); } }) if (activeItem.recording) { // if we already have an existing recording - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, Cast(activeItem.recording, Doc, null)); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); } else { // if we dont have any recording @@ -376,7 +376,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { // make recording box appear in the bottom right corner of the screen recording.x = window.innerWidth - recording[WidthSym]() - 20; recording.y = window.innerHeight - recording[HeightSym]() - 20; - Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, recording); + Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, recording); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0ded1bb3c..2869d4f2d 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -340,7 +340,7 @@ export class PDFViewer extends React.Component { if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) { this._setPreviewCursor?.(e.clientX, e.clientY, true, false); } - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.SelectedTool)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { this.props.select(false); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); this._marqueeing = [e.clientX, e.clientY]; diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 0db3950a2..1de0c0b10 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -27,12 +27,12 @@ import "./TopBar.scss"; export class TopBar extends React.Component { navigateToHome = () => { CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => { - Doc.UserDoc().activePage = "home"; + CurrentUserUtils.ActivePage = "home"; CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use }); } render() { - const activeDashboard = Cast(Doc.UserDoc().activeDashboard, Doc, null) + const activeDashboard = CurrentUserUtils.ActiveDashboard; return ( //TODO:glr Add support for light / dark mode
    @@ -44,11 +44,11 @@ export class TopBar extends React.Component { }}>{Doc.CurrentUserEmail}
    : (null)}
    -
    SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}> +
    activeDashboard && SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}> {activeDashboard ? StrCast(activeDashboard.title) : "Dash"}
    { - const dashView = DocumentManager.Instance.getDocumentView(activeDashboard); + const dashView = activeDashboard && DocumentManager.Instance.getDocumentView(activeDashboard); ContextMenu.Instance.addItem({ description: "Open Dashboard View", event: this.navigateToHome, icon: "edit" }); ContextMenu.Instance.addItem({ description: "Snapshot Dashboard", event: async () => { const batch = UndoManager.StartBatch("snapshot"); @@ -69,7 +69,7 @@ export class TopBar extends React.Component {
    {SharingManager.Instance.open(undefined, activeDashboard)}}> {/* TODO: if this is my dashboard, display share if this is a shared dashboard, display "view original or view annotated" */} - { Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author === Doc.CurrentUserEmail ? "Share": "view original" } + { CurrentUserUtils.ActiveDashboard && (Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author === Doc.CurrentUserEmail ? "Share": "view original") }
    window.open( "https://brown-dash.github.io/Dash-Documentation/", "_blank")}> diff --git a/src/client/views/webcam/DashWebRTCVideo.tsx b/src/client/views/webcam/DashWebRTCVideo.tsx index e0d328c89..6c0c9b301 100644 --- a/src/client/views/webcam/DashWebRTCVideo.tsx +++ b/src/client/views/webcam/DashWebRTCVideo.tsx @@ -71,7 +71,7 @@ export class DashWebRTCVideo extends React.Component; const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <> diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 94be286f5..74213652b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -21,7 +21,7 @@ import { FieldId, RefField } from "./RefField"; import { RichTextField } from "./RichTextField"; import { listSpec } from "./Schema"; import { ComputedField, ScriptField } from "./ScriptField"; -import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; +import { BoolCast, Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField"; import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; import JSZip = require("jszip"); @@ -233,6 +233,10 @@ export class Doc extends RefField { } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; + public static get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode); } + public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } + public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } + public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; } public static CurrentUserEmail: string = ""; public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); } public async [HandleUpdate](diff: any) { @@ -805,7 +809,7 @@ export namespace Doc { Doc.AddDocToList(Doc.GetProto(copy)[DataSym], "aliases", copy); } copy.context = undefined; - Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } @@ -861,7 +865,7 @@ export namespace Doc { const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")"); target.layoutKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared"); + Doc.defaultAclPrivate && (applied["acl-Public"] = "Not Shared"); return applied; } return undefined; @@ -1234,19 +1238,19 @@ export namespace Doc { export function isDocPinned(doc: Doc) { //add this new doc to props.Document - const curPres = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + const curPres = CurrentUserUtils.ActivePresentation; return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; } export function copyDragFactory(dragFactory: Doc) { const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); - ndoc && Doc.AddDocToList(Cast(Doc.UserDoc().myFileOrphans, Doc, null), "data", Doc.GetProto(ndoc)); + ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc)); if (ndoc && dragFactory["dragFactory-count"] !== undefined) { dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); } - if (ndoc) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); + if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); return ndoc; } @@ -1448,7 +1452,7 @@ ScriptingGlobals.add(function DOC(id: string) { console.log("Can't parse a docum ScriptingGlobals.add(function assignDoc(doc: Doc, field: string, id: string) { return Doc.assignDocToField(doc, field, id); }); ScriptingGlobals.add(function docCast(doc: FieldResult): any { return DocCastAsync(doc); }); ScriptingGlobals.add(function activePresentationItem() { - const curPres = Doc.UserDoc().activePresentation as Doc; + const curPres = CurrentUserUtils.ActivePresentation; return curPres && DocListCast(curPres[Doc.LayoutFieldKey(curPres)])[NumCast(curPres._itemIndex)]; }); ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { diff --git a/src/fields/util.ts b/src/fields/util.ts index 37b9be31b..8fb35981b 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -145,7 +145,7 @@ export function inheritParentAcls(parent: Doc, child: Doc) { const dataDoc = parent[DataSym]; for (const key of Object.keys(dataDoc)) { // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - const permission = (key === "acl-Public" && Doc.UserDoc().defaultAclPrivate) ? AclPrivate : dataDoc[key]; + const permission = (key === "acl-Public" && Doc.defaultAclPrivate) ? AclPrivate : dataDoc[key]; key.startsWith("acl") && distributeAcls(key, permission, child); } } diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index c5c6fb688..fe8100997 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -51,7 +51,7 @@ library.add(...[faTasks, faReply, faQuoteLeft, faHandPointLeft, faFolderOpen, fa @observer export class MobileInterface extends React.Component { static Instance: MobileInterface; - private _library: Promise; + private _library: Doc; private _mainDoc: any = CurrentUserUtils.setupActiveMobileMenu(Doc.UserDoc()); @observable private _sidebarActive: boolean = false; //to toggle sidebar display @observable private _imageUploadActive: boolean = false; //to toggle image upload @@ -68,7 +68,7 @@ export class MobileInterface extends React.Component { constructor(props: Readonly<{}>) { super(props); - this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc()); // to access documents in Dash Web + this._library = CurrentUserUtils.setupDashboards(Doc.UserDoc(), "myDashboards"); // to access documents in Dash Web MobileInterface.Instance = this; } @@ -76,7 +76,7 @@ export class MobileInterface extends React.Component { componentDidMount = () => { // if the home menu is in list view -> adjust the menu toggle appropriately this._menuListView = this._homeDoc._viewType === "stacking" ? true : false; - CurrentUserUtils.SelectedTool = InkTool.None; // ink should intially be set to none + CurrentUserUtils.ActiveTool = InkTool.None; // ink should intially be set to none Doc.UserDoc().activeMobile = this._homeDoc; // active mobile set to home AudioBox.Enabled = true; @@ -125,7 +125,7 @@ export class MobileInterface extends React.Component { * Method called when 'Library' button is pressed on the home screen */ switchToLibrary = async () => { - this._library.then(library => this.switchCurrentView(library)); + this.switchCurrentView(this._library); runInAction(() => this._homeMenu = false); this.toggleSidebar(); } @@ -140,7 +140,7 @@ export class MobileInterface extends React.Component { // Case 1: Parent document is 'dashboards' if (doc === Cast(this._library, Doc) as Doc) { this.dashboards = null; - this._library.then(library => this.switchCurrentView(library)); + this.switchCurrentView(this._library); // Case 2: Parent document is the 'home' menu (root node) } else if (doc === Cast(this._homeDoc, Doc) as Doc) { this._homeMenu = true; @@ -179,7 +179,7 @@ export class MobileInterface extends React.Component { @action returnMain = () => { this._parents = [this._homeDoc]; - this._library.then(library => this.switchCurrentView(library)); + this.switchCurrentView(this._library); this._homeMenu = false; this.dashboards = null; } @@ -419,10 +419,10 @@ export class MobileInterface extends React.Component { button.style.color = this._ink ? "black" : "white"; if (!this._ink) { - CurrentUserUtils.SelectedTool = InkTool.Pen; + CurrentUserUtils.ActiveTool = InkTool.Pen; this._ink = true; } else { - CurrentUserUtils.SelectedTool = InkTool.None; + CurrentUserUtils.ActiveTool = InkTool.None; this._ink = false; } } @@ -514,7 +514,7 @@ export class MobileInterface extends React.Component { return
    TabDocView.PinDoc(this._activeDoc, { unpin: isPinned })}> + onClick={e => TabDocView.PinDoc(this._activeDoc)}>
    ; } else return (null); @@ -573,7 +573,7 @@ export class MobileInterface extends React.Component { // For setting up the presentation document for the home menu @action setupDefaultPresentation = () => { - const presentation = Cast(Doc.UserDoc().activePresentation, Doc) as Doc; + const presentation = CurrentUserUtils.ActivePresentation; if (presentation) { this.switchCurrentView(presentation); -- cgit v1.2.3-70-g09d2 From c415fb65e3d87b851015fb2ac7c41361609e0719 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 16:27:39 -0400 Subject: fixed contextmenu when hitting enter to not iconify. fixed : menu to add equations properly. --- src/client/documents/Documents.ts | 18 ++---------------- src/client/util/CurrentUserUtils.ts | 9 +++++---- src/client/views/ContextMenu.tsx | 1 + src/client/views/DocumentDecorations.tsx | 7 +++++-- src/fields/Doc.ts | 2 +- 5 files changed, 14 insertions(+), 23 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f96bffb3c..e36103938 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1290,21 +1290,7 @@ export namespace DocUtils { })) as ContextMenuProps[], icon: "sticky-note" }); - const math: ContextMenuProps = ({ - description: ":Math", event: () => { - const created = Docs.Create.EquationDocument(); - if (created) { - created.author = Doc.CurrentUserEmail; - created.x = x; - created.y = y; - created.width = 300; - created.height = 35; - EquationBox.SelectOnLoad = created[Id]; - docAdder?.(created); - } - }, icon: "calculator" - }); - const documentList: ContextMenuProps[] = DocListCast(Cast(Doc.UserDoc().myItemCreators, Doc, null)?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ + const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data).lastElement()?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ description: ":" + StrCast(dragDoc.title), event: undoBatch((args: { x: number, y: number }) => { const newDoc = Doc.copyDragFactory(dragDoc); @@ -1312,13 +1298,13 @@ export namespace DocUtils { newDoc.author = Doc.CurrentUserEmail; newDoc.x = x; newDoc.y = y; + EquationBox.SelectOnLoad = newDoc[Id]; if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id]; docAdder?.(newDoc); } }), icon: Doc.toIcon(dragDoc), })) as ContextMenuProps[]; - documentList.push(math); ContextMenu.Instance.addItem({ description: "Create document", subitems: documentList, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7a38886fe..1d5a9fa09 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -77,7 +77,7 @@ export class CurrentUserUtils { static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) { scripts && Object.keys(scripts).map(key => { if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); + doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any" }, {"_readOnly_": true}); } }); funcs && Object.keys(funcs).map(key => { @@ -248,7 +248,8 @@ export class CurrentUserUtils { }[] = [ {key: "emptyPresentation", creator: Docs.Create.PresDocument, opts: { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, {key: "emptyCollection", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "freeform", _width: 150, _height: 100 }}, - {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts : { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, + {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, + {key: "emptyMath", creator: (opts) => Docs.Create.EquationDocument(opts), opts: { title: "Equation", _backgroundGridShow: true, _width: 300, _height: 35 }}, {key: "emptySlide", creator: (opts) => Docs.Create.TreeDocument([], opts), funcs: {title: 'self.text?.Text'}, opts: { _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, "dragFactory-count": undefined, allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" @@ -265,7 +266,7 @@ export class CurrentUserUtils { ]; emptyThings.forEach(thing => - this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), thing.funcs)) + this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), undefined, thing.funcs)) if (doc.emptyHeader === undefined) { const json = { @@ -309,6 +310,7 @@ export class CurrentUserUtils { return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, clickFactory: doc.emptyPane as Doc, }, + { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyMath as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: {onClick: 'openInOverlay(copyDragFactory(this.dragFactory))',onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, @@ -1213,7 +1215,6 @@ export class CurrentUserUtils { public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; } public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); } public static set ActivePage(val) { Doc.UserDoc().activePage = val; } - public static get EmptyPane() { return DocCast(Doc.UserDoc().emptyPane); } public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; } public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } } diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index 80ff16cf9..e2f98de1e 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -287,6 +287,7 @@ export class ContextMenu extends React.Component { } this.closeMenu(); e.preventDefault(); + e.stopPropagation(); } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b666b5977..d22fb5f01 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -7,8 +7,8 @@ import { DateField } from '../../fields/DateField'; import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from "../../fields/Doc"; import { Document } from '../../fields/documentSchemas'; import { InkField } from "../../fields/InkField"; -import { ScriptField } from '../../fields/ScriptField'; -import { Cast, NumCast, StrCast } from "../../fields/Types"; +import { ComputedField, ScriptField } from '../../fields/ScriptField'; +import { Cast, FieldValue, NumCast, StrCast } from "../../fields/Types"; import { GetEffectiveAcl } from '../../fields/util'; import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../Utils"; import { Docs } from "../documents/Documents"; @@ -461,6 +461,9 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P return ScriptField.MakeFunction(this._titleControlString.substring(1), { doc: Doc.name })!.script.run({ self: selected.rootDoc, this: selected.layoutDoc }, console.log).result?.toString() || ""; } if (this._titleControlString.startsWith("#")) { + const cfield = ComputedField.WithoutComputed(() => FieldValue(selected.props.Document.title)); + console.log(cfield); + (cfield as any)?.script.run().result; return Field.toString(selected.props.Document[this._titleControlString.substring(1)] as Field) || "-unset-"; } return this._accumulatedTitle; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 74213652b..981514b25 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -233,7 +233,7 @@ export class Doc extends RefField { } private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; - public static get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode); } + public static get noviceMode() { return Doc.UserDoc().noviceMode as boolean; } public static set noviceMode(val) { Doc.UserDoc().noviceMode = val; } public static get defaultAclPrivate() { return Doc.UserDoc().defaultAclPrivate; } public static set defaultAclPrivate(val) { Doc.UserDoc().defaultAclPrivate = val; } -- cgit v1.2.3-70-g09d2 From 3e22a44461095d95062e3b809b525a0a87342ac3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 20 Jun 2022 22:18:42 -0400 Subject: more cleanup to get rid of unecessary references to Doc.UserDoc(). --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 48 +++++++++++++--------- src/client/util/DragManager.ts | 4 +- src/client/util/History.ts | 2 +- src/client/util/SettingsManager.tsx | 19 ++++----- src/client/util/SnappingManager.ts | 4 ++ src/client/views/DashboardView.tsx | 2 +- src/client/views/DocumentDecorations.tsx | 4 +- src/client/views/MainView.tsx | 21 ++++------ .../views/collections/CollectionDockingView.tsx | 4 +- src/client/views/collections/CollectionMenu.tsx | 4 +- src/client/views/collections/CollectionView.tsx | 2 +- src/client/views/collections/TabDocView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/button/FontIconBox.tsx | 30 +++++++------- .../nodes/button/colorDropdown/ColorDropdown.tsx | 3 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +- src/client/views/nodes/trails/PresBox.tsx | 11 +---- src/client/views/topbar/TopBar.tsx | 2 +- src/mobile/MobileInterface.tsx | 2 +- 22 files changed, 93 insertions(+), 89 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index e36103938..73b756a8d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -662,7 +662,7 @@ export namespace Docs { const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); dataProps["acl-Override"] = "None"; - dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; dataProps.system = viewProps.system; dataProps.isPrototype = true; @@ -679,7 +679,7 @@ export namespace Docs { const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); const viewFirstProps: { [id: string]: any } = {}; - viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; viewFirstProps["acl-Override"] = "None"; viewFirstProps.author = Doc.CurrentUserEmail; const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 1d5a9fa09..9b5fc5b98 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -181,7 +181,12 @@ export class CurrentUserUtils { /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc, field="myTemplates") { + const preEleOpts:DocumentOptions = { + title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" + }; + this.AssignOpts(DocCast(doc.presElement), preEleOpts) ?? (doc.presElement= Docs.Create.PresElementBoxDocument(preEleOpts)); const templates = [ + DocCast(doc.presElement), CurrentUserUtils.setupNoteTemplates(doc), CurrentUserUtils.setupClickEditorTemplates(doc) ]; @@ -323,8 +328,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, { toolTip: "Tap or drag to create a mobile view", title: "Phone", icon: "mobile", dragFactory: doc.activeMobileMenu as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: 'this.dragFactory'}, funcs: {hidden: 'IsNoviceMode()'} }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, - // { toolTip: "Tap or drag to create a presentation", title: "Trails", icon: "pres-trail", dragFactory: doc.emptyPresentation as Doc,scripts: {onClick: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', onDragStart: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`}, funcs: {hidden: 'IsNoviceMode()'} }, - ]; + ]; } /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools @@ -533,7 +537,7 @@ export class CurrentUserUtils { static setupDashboards(doc: Doc, field:string) { var myDashboards = DocCast(doc[field]); - const newDashboard = `createNewDashboard(Doc.UserDoc())`; + const newDashboard = `createNewDashboard()`; const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true }; const reqdBtnScript = {onClick: newDashboard,} @@ -830,6 +834,11 @@ export class CurrentUserUtils { const reqdOpts = { title: "overlay documents", backgroundColor: "#aca3a6", system: true }; return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.FreeformDocument([], reqdOpts)); } + + static setupPublished(doc:Doc, field = "myPublishedDocs") { + const reqdOpts = { title: "published docs", backgroundColor: "#aca3a6", system: true }; + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + } /// The database of all links on all documents static async setupLinkDocs(doc: Doc, linkDatabaseId: string) { @@ -958,7 +967,6 @@ export class CurrentUserUtils { Doc.noviceMode ?? (Doc.noviceMode = true); doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); doc._showLabel ?? (doc._showLabel = true); - doc._showMenuLabel ?? (doc._showMenuLabel = true); doc.textAlign ?? (doc.textAlign = "left"); doc.activeInkColor ?? (doc.activeInkColor = "rgb(0, 0, 0)");; doc.activeInkWidth ?? (doc.activeInkWidth = 1); @@ -975,17 +983,17 @@ export class CurrentUserUtils { doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; - doc.myPublishedDocs ?? (doc.myPublishedDocs = new List()); doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents await this.setupLinkDocs(doc, linkDatabaseId); await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon - this.setupDocTemplates(doc); // sets up the template menu of templates this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard + this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box) this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left + this.setupDocTemplates(doc); // sets up the template menu of templates doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); @@ -1037,7 +1045,8 @@ export class CurrentUserUtils { public static _urlState: HistoryUtil.DocUrl; - public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => { + public static openDashboard = (doc: Doc, fromHistory = false) => { + const userDoc = Doc.UserDoc(); CurrentUserUtils.MainDocId = doc[Id]; if (!DocListCast(CurrentUserUtils.MyDashboards.data).includes(doc)) { Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); @@ -1045,7 +1054,7 @@ export class CurrentUserUtils { if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); - userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); + userDoc ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); } const state = CurrentUserUtils._urlState; if (state.sharing === true && !userDoc) { @@ -1138,11 +1147,11 @@ export class CurrentUserUtils { } - public static async snapshotDashboard(userDoc: Doc) { + public static async snapshotDashboard() { if (CurrentUserUtils.ActiveDashboard) { const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); - CurrentUserUtils.openDashboard(userDoc, copy); + CurrentUserUtils.openDashboard(copy); } } @@ -1150,9 +1159,9 @@ export class CurrentUserUtils { CurrentUserUtils.ActiveDashboard = undefined; } - public static createNewDashboard = async (userDoc: Doc, id?: string) => { - const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); - const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; + public static createNewDashboard = (id?: string) => { + const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true); + const dashboards = CurrentUserUtils.MyDashboards; const dashboardCount = DocListCast(dashboards.data).length + 1; const freeformOptions: DocumentOptions = { x: 0, @@ -1172,10 +1181,10 @@ export class CurrentUserUtils { dashboardDoc.data = new List(dashboardTabs); dashboardDoc["pane-count"] = 1; - userDoc.activePresentation = presentation; + CurrentUserUtils.ActivePresentation = presentation; Doc.AddDocToList(dashboards, "data", dashboardDoc); - // CurrentUserUtils.openDashboard(userDoc, dashboardDoc); + // CurrentUserUtils.openDashboard(dashboardDoc); } public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { @@ -1209,6 +1218,7 @@ export class CurrentUserUtils { public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); } + public static get MyPublishedDocs() { return DocCast(Doc.UserDoc().myPublishedDocs); } public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; } public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } @@ -1230,8 +1240,8 @@ ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); -ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, "creates a snapshot copy of a dashboard"); -ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, "creates a new dashboard when called"); +ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(); }, "creates a snapshot copy of a dashboard"); +ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(); }, "creates a new dashboard when called"); ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); @@ -1240,7 +1250,7 @@ ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.In ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); if (dashboards && dashboards.length > 1) { - if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboards.find(doc => doc !== dashboard)!); + if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!); Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); } }, @@ -1248,7 +1258,7 @@ ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { const dashboardAlias = Doc.MakeAlias(dashboard); Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); - CurrentUserUtils.openDashboard(Doc.UserDoc(), dashboardAlias); + CurrentUserUtils.openDashboard(dashboardAlias); }, "adds Dashboard to set of Dashboards"); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index dbfba8992..2b62945af 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -6,7 +6,7 @@ import { PrefetchProxy } from "../../fields/Proxy"; import { listSpec } from "../../fields/Schema"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ScriptField } from "../../fields/ScriptField"; -import { Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../fields/Types"; import { emptyFunction, Utils } from "../../Utils"; import { Docs, DocUtils } from "../documents/Documents"; import * as globalCssVariables from "../views/global/globalCssVariables.scss"; @@ -72,6 +72,8 @@ export namespace DragManager { export let StartWindowDrag: Opt<((e: { pageX: number, pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void)>; export let CompleteWindowDrag: Opt<(aborted: boolean) => void>; + export function GetRaiseWhenDragged() { return BoolCast(Doc.UserDoc()._raiseWhenDragged); } + export function SetRaiseWhenDragged(val:boolean) { Doc.UserDoc()._raiseWhenDragged = val } export function Root() { const root = document.getElementById("root"); if (!root) { diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 632348306..7dcff9c56 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -197,7 +197,7 @@ export namespace HistoryUtil { await Promise.all(Object.keys(init).map(id => initDoc(id, init[id]))); } if (field instanceof Doc) { - CurrentUserUtils.openDashboard(Doc.UserDoc(), field, true); + CurrentUserUtils.openDashboard(field, true); } } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 382274462..22e33ab1e 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -10,7 +10,9 @@ import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager import { DocServer } from "../DocServer"; import { Networking } from "../Network"; import { MainViewModal } from "../views/MainViewModal"; +import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { CurrentUserUtils } from "./CurrentUserUtils"; +import { DragManager } from "./DragManager"; import { GroupManager } from "./GroupManager"; import "./SettingsManager.scss"; import { undoBatch } from "./UndoManager"; @@ -155,19 +157,14 @@ export class SettingsManager extends React.Component<{}> {
    Show full toolbar
    - Doc.UserDoc()._raiseWhenDragged = !Doc.UserDoc()._raiseWhenDragged} - checked={BoolCast(Doc.UserDoc()._raiseWhenDragged)} /> + DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())} + checked={DragManager.GetRaiseWhenDragged()} />
    Raise on drag
    - Doc.UserDoc()._showLabel = !Doc.UserDoc()._showLabel} - checked={BoolCast(Doc.UserDoc()._showLabel)} /> -
    Show tool button labels
    -
    -
    - Doc.UserDoc()._showMenuLabel = !Doc.UserDoc()._showMenuLabel} - checked={BoolCast(Doc.UserDoc()._showMenuLabel)} /> -
    Show menu button labels
    + FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} + checked={FontIconBox.GetShowLabels()} /> +
    Show button labels
    ; } @@ -271,7 +268,7 @@ export class SettingsManager extends React.Component<{}> {
    - Doc.defaultAclPrivate = !Doc.defaultAclPrivate)} />
    Default access private
    diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 069f81d38..057843c68 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -1,5 +1,6 @@ import { observable, action, runInAction } from "mobx"; import { computedFn } from "mobx-utils"; +import { Doc } from "../../fields/Doc"; export namespace SnappingManager { @@ -30,6 +31,9 @@ export namespace SnappingManager { export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } export function GetIsDragging() { return manager.IsDragging; } + export function SetShowSnapLines(show: boolean) { runInAction(() => Doc.UserDoc().showSnapLines = show); } + export function GetShowSnapLines() { return Doc.UserDoc().showSnapLines; } + /// bcz; argh!! TODO; These do not belong here, but there were include order problems with leaving them in util.ts // need to investigate further what caused the mobx update problems and move to a better location. const getCachedGroupByNameCache = computedFn(function (name: string) { return manager.cachedGroups.includes(name); }, true); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 9a0f25fe3..a126218c4 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -28,7 +28,7 @@ export class DashboardView extends React.Component { newDashboard = async () => { const batch = UndoManager.StartBatch("new dash"); - await CurrentUserUtils.createNewDashboard(Doc.UserDoc()); + await CurrentUserUtils.createNewDashboard(); batch.end(); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 18cf785b9..4247501bb 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -87,10 +87,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P if (titleFieldKey === "title") { d.dataDoc["title-custom"] = !this._accumulatedTitle.startsWith("-"); if (StrCast(d.rootDoc.title).startsWith("@") && !this._accumulatedTitle.startsWith("@")) { - Doc.RemoveDocFromList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc); + Doc.RemoveDocFromList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); } if (!StrCast(d.rootDoc.title).startsWith("@") && this._accumulatedTitle.startsWith("@")) { - Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", d.rootDoc); + Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); } } //@ts-ignore diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 61cca0421..6ee8065f5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -225,26 +225,21 @@ export class MainView extends React.Component { // Load the user's active dashboard, or create a new one if initial session after signup const received = CurrentUserUtils.MainDocId; if (received && !this.userDoc) { - reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true }); + reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(), { fireImmediately: true }); } else { PromiseValue(this.userDoc.activeDashboard).then(dash => { - if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash); - else CurrentUserUtils.createNewDashboard(this.userDoc); + if (dash instanceof Doc) CurrentUserUtils.openDashboard(dash); + else CurrentUserUtils.createNewDashboard(); }); } } @action createNewPresentation = async () => { - if (!await this.userDoc.myTrails) { - this.userDoc.myTrails = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true - })); - } const pres = Docs.Create.PresDocument({ title: "Untitled Trail", _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: "alias", _chromeHidden: true, boxShadow: "0 0" }); CollectionDockingView.AddSplit(pres, "left"); - this.userDoc.activePresentation = pres; - Doc.AddDocToList(this.userDoc.myTrails as Doc, "data", pres); + CurrentUserUtils.ActivePresentation = pres; + Doc.AddDocToList(CurrentUserUtils.MyTrails, "data", pres); } @action @@ -353,9 +348,9 @@ export class MainView extends React.Component { addDocTabFunc = (doc: Doc, location: string): boolean => { const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); const locationParams = locationFields.length > 1 ? locationFields[1] : ""; - if (doc.dockingConfig) return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + if (doc.dockingConfig) return CurrentUserUtils.openDashboard(doc); switch (locationFields[0]) { - case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + case "dashboard": return CurrentUserUtils.openDashboard(doc); case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "lightbox": return LightboxView.AddDocTab(doc, location); @@ -563,7 +558,7 @@ export class MainView extends React.Component {
    ; } @computed get snapLines() { - return !this.userDoc.showSnapLines ? (null) :
    + return !SnappingManager.GetShowSnapLines() ? (null) :
    {SnappingManager.horizSnapLines().map(l => )} {SnappingManager.vertSnapLines().map(l => )} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 5f36a7a51..19355b8e1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -121,7 +121,7 @@ export class CollectionDockingView extends CollectionSubView() { SelectionManager.DeselectAll(); const instance = CollectionDockingView.Instance; if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === "layout") { - return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + return CurrentUserUtils.openDashboard(doc); } const newItemStackConfig = { type: 'stack', @@ -172,7 +172,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { - if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document); + if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(document); const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); if (tab) { diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 9b1bb5b97..668d82387 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -164,7 +164,7 @@ export class CollectionMenu extends AntimodeMenu{ // // ; - // OLD BUTTONS + // //OLD BUTTONS // return this.getElement(!this.SelectedCollection ? [/*button*/] : // [ { - const target = this.document !== Doc.UserDoc().sidebar ? this.document : this.document.proto as Doc; + const target = this.document !== CurrentUserUtils.MyLeftSidebarPanel ? this.document : this.document.proto as Doc; //@ts-ignore target._viewType = e.target.selectedOptions[0].value; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 4b5c5e3fb..63616263e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -205,7 +205,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent CurrentUserUtils.createNewDashboard(Doc.UserDoc()), icon: "project-diagram" }); + optionItems.push({ description: "Create Dashboard", event: () => CurrentUserUtils.createNewDashboard(), icon: "project-diagram" }); } !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" }); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 70db121d1..62d07b0e4 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -329,7 +329,7 @@ export class TabDocView extends React.Component { const locationFields = doc._viewType === CollectionViewType.Docking ? ["dashboard"] : location.split(":"); const locationParams = locationFields.length > 1 ? locationFields[1] : ""; switch (locationFields[0]) { - case "dashboard": return CurrentUserUtils.openDashboard(Doc.UserDoc(), doc); + case "dashboard": return CurrentUserUtils.openDashboard(doc); case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index ffe146ae4..99eec7892 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -253,7 +253,7 @@ export class CollectionFreeFormView extends CollectionSubView Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null; + !Doc.noviceMode ? viewCtrlItems.push({ description: (SnappingManager.GetShowSnapLines() ? "Hide" : "Show") + " Snap Lines", event: () => SnappingManager.SetShowSnapLines(!SnappingManager.GetShowSnapLines()), icon: "compress-arrows-alt" }) : null; !Doc.noviceMode ? viewCtrlItems.push({ description: (this.Document._useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document._useClusters), icon: "braille" }) : null; !viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b62020a04..081a1a924 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -6,7 +6,7 @@ import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; +import { Cast, DocCast, FieldValue, NumCast, StrCast } from "../../../../fields/Types"; import { ImageField } from "../../../../fields/URLField"; import { GetEffectiveAcl } from "../../../../fields/util"; import { intersectRect, returnFalse, Utils } from "../../../../Utils"; @@ -156,7 +156,7 @@ export class MarqueeView extends React.Component StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name); - (this.rootDoc._raiseWhenDragged === undefined ? Doc.UserDoc()._raiseWhenDragged : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); + (this.rootDoc._raiseWhenDragged === undefined ? DragManager.GetRaiseWhenDragged() : this.rootDoc._raiseWhenDragged) && this.props.bringToFront(this.rootDoc); if (this._doubleTap && (this.props.Document.type !== DocumentType.FONTICON || this.onDoubleClickHandler)) {// && !this.onClickHandler?.script) { // disable double-click to show full screen for things that have an on click behavior since clicking them twice can be misinterpreted as a double click if (this._timeout) { clearTimeout(this._timeout); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 3af6a3d51..1b37e729b 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -79,6 +79,9 @@ export class FontIconBox extends DocComponent() { } } + static GetShowLabels() { return BoolCast(Doc.UserDoc()._showLabel); } + static SetShowLabels(show:boolean) { Doc.UserDoc()._showLabel = show; } + // Determining UI Specs @observable private label = StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); @observable private icon = StrCast(this.dataDoc.icon, "user") as any; @@ -111,7 +114,7 @@ export class FontIconBox extends DocComponent() { // Script for checking the outcome of the toggle const checkResult: number = numScript?.script.run({ value: 0, _readOnly_: true }).result || 0; - const label = !Doc.UserDoc()._showLabel ? (null) : + const label = !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -212,7 +215,7 @@ export class FontIconBox extends DocComponent() { style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} onClick={action(() => this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen)}> - {!this.label || !Doc.UserDoc()._showLabel ? (null) :
    {this.label}
    } + {!this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    }
    @@ -283,7 +286,7 @@ export class FontIconBox extends DocComponent() {
    ; }); - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -337,7 +340,7 @@ export class FontIconBox extends DocComponent() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); const curColor = this.colorScript?.script.run({ value: undefined, _readOnly_: true }).result ?? "transparent"; - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -349,7 +352,7 @@ export class FontIconBox extends DocComponent() {
    ; setTimeout(() => this.colorPicker(curColor)); // cause an update to the color picker rendered in MainView return ( -
    this.colorPickerClosed = !this.colorPickerClosed)} onPointerDown={e => e.stopPropagation()}> @@ -381,7 +384,7 @@ export class FontIconBox extends DocComponent() { const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); // Button label - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -400,7 +403,7 @@ export class FontIconBox extends DocComponent() { ); } else { return ( -
    {label} @@ -423,7 +426,7 @@ export class FontIconBox extends DocComponent() { style={{ backgroundColor: "transparent", borderBottomLeftRadius: this.dropdown ? 0 : undefined }}>
    - {!this.label || !Doc.UserDoc()._showLabel ? (null) : + {!this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    }
    @@ -450,12 +453,12 @@ export class FontIconBox extends DocComponent() { render() { const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const label = !this.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; - const menuLabel = !this.label || !Doc.UserDoc()._showMenuLabel ? (null) : + const menuLabel = !this.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.label}
    ; @@ -497,7 +500,7 @@ export class FontIconBox extends DocComponent() { break; case ButtonType.ToolButton: button = ( -
    +
    {label}
    @@ -509,7 +512,7 @@ export class FontIconBox extends DocComponent() { break; case ButtonType.ClickButton: button = ( -
    +
    {label}
    @@ -675,10 +678,9 @@ ScriptingGlobals.add(function setFontSize(size: string | number, checkResult?: b ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) { const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { - return (editorView ? RichTextMenu.Instance.noAutoLink : Doc.UserDoc().noAutoLink) ? Colors.MEDIUM_BLUE : "transparent"; + return (editorView ? RichTextMenu.Instance.noAutoLink : false) ? Colors.MEDIUM_BLUE : "transparent"; } if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor(); - else Doc.UserDoc().noAutoLink = Doc.UserDoc().noAutoLink ? true : false; }); ScriptingGlobals.add(function toggleBold(checkResult?: boolean) { diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx index 235495250..7f414ddbb 100644 --- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx +++ b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx @@ -5,6 +5,7 @@ import { IButtonProps } from '../ButtonInterface'; import { ColorState, SketchPicker } from 'react-color'; import { ScriptField } from '../../../../../fields/ScriptField'; import { Doc } from '../../../../../fields/Doc'; +import { FontIconBox } from '../FontIconBox'; export class ColorDropdown extends Component { render() { @@ -31,7 +32,7 @@ export class ColorDropdown extends Component { disableAlpha={!stroke} onChange={func} color={boolResult ? boolResult : "#FFFFFF"} presetColors={colorOptions} />; - const label = !this.props.label || !Doc.UserDoc()._showLabel ? (null) : + const label = !this.props.label || !FontIconBox.GetShowLabels() ? (null) :
    {this.props.label}
    ; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 90199618b..600730d87 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -355,7 +355,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp var tr = this._editorView.state.tr as any; const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; tr = tr.removeMark(0, tr.doc.content.size, autoAnch); - DocListCast(Doc.UserDoc().myPublishedDocs).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); + DocListCast(CurrentUserUtils.MyPublishedDocs.data).forEach(term => tr = this.hyperlinkTerm(tr, term, newAutoLinks)); tr = tr.setSelection(new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.anchor2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); @@ -376,7 +376,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!(cfield instanceof ComputedField)) { this.dataDoc.title = prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? "..." : ""); if (str.startsWith("@") && str.length > 1) { - Doc.AddDocToList(Doc.UserDoc(), "myPublishedDocs", this.rootDoc); + Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, this.rootDoc); } } } diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0d2cffc2c..9f858539f 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -10,7 +10,7 @@ import { InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { PrefetchProxy } from "../../../../fields/Proxy"; import { listSpec } from "../../../../fields/Schema"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; import { Docs } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; @@ -131,17 +131,10 @@ export class PresBox extends ViewBoxBaseComponent() { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; else return false; } - @computed get presElement() { return Cast(Doc.UserDoc().presElement, Doc, null); } constructor(props: any) { super(props); if (CurrentUserUtils.ActivePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); - if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. - Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" - })); - } this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox - } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; @@ -728,7 +721,7 @@ export class PresBox extends ViewBoxBaseComponent() { }); return true; } - childLayoutTemplate = () => !this.isTreeOrStack ? undefined : this.presElement; + childLayoutTemplate = () => !this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement); removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 1de0c0b10..6a4deca38 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -52,7 +52,7 @@ export class TopBar extends React.Component { ContextMenu.Instance.addItem({ description: "Open Dashboard View", event: this.navigateToHome, icon: "edit" }); ContextMenu.Instance.addItem({ description: "Snapshot Dashboard", event: async () => { const batch = UndoManager.StartBatch("snapshot"); - await CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); + await CurrentUserUtils.snapshotDashboard(); batch.end(); }, icon: "edit" }); dashView?.showContextMenu(e.clientX+20, e.clientY+30); diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index fe8100997..bf06faeb9 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -391,7 +391,7 @@ export class MobileInterface extends React.Component { * Handles the 'Create New Dashboard' button in the menu (taken from MainView.tsx) */ @action - createNewDashboard = async (id?: string) => { + createNewDashboard = (id?: string) => { const scens = CurrentUserUtils.MyDashboards; const dashboardCount = DocListCast(scens.data).length + 1; const freeformOptions: DocumentOptions = { -- cgit v1.2.3-70-g09d2 From 0dd3bbfc119d48bd2c32f8e55b7051e3ddc530c3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 21 Jun 2022 14:40:04 -0400 Subject: restored experimental templates. fixed (enabled) pointer events on nested column/row views when container is active. got rid of freezeDimensions which wasn't needed and introduced artifacts for views that set it (timeView, GridView) --- src/client/documents/Documents.ts | 1 + src/client/util/CurrentUserUtils.ts | 74 +++++++++++----------- src/client/util/DragManager.ts | 11 ++-- src/client/views/PropertiesView.tsx | 1 - .../views/collections/CollectionStackingView.tsx | 1 - .../views/collections/CollectionTimeView.tsx | 5 +- src/client/views/collections/CollectionView.tsx | 1 - .../collectionFreeForm/CollectionFreeFormView.tsx | 1 - .../collectionGrid/CollectionGridView.tsx | 1 - .../CollectionMulticolumnView.tsx | 14 ++-- .../CollectionMultirowView.tsx | 6 +- .../collectionSchema/CollectionSchemaView.tsx | 1 - .../collections/collectionSchema/SchemaTable.tsx | 1 - src/client/views/nodes/DocumentContentsView.tsx | 1 - src/client/views/nodes/DocumentView.tsx | 7 +- src/client/views/nodes/button/FontIconBox.tsx | 4 +- 16 files changed, 61 insertions(+), 69 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 73b756a8d..59391d150 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -194,6 +194,7 @@ export class DocumentOptions { childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view) childLayoutString?: string; // template string for collection to use to render its children + childDocumentsActive?: boolean; // whether child documents are active when parent is document active childDontRegisterViews?: boolean; childHideLinkButton?: boolean; // hide link buttons on all children childContextMenuFilters?: List; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 9b5fc5b98..e80fe6776 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,6 +1,6 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; -import { DataSym, Doc, DocListCast, DocListCastAsync, StrListCast } from "../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync, Opt, StrListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; @@ -109,40 +109,43 @@ export class CurrentUserUtils { } // initializes experimental advanced template views - slideView, headerView - static setupExperimentalTemplateButtons(doc: Doc, field="template-experimental-buttons") { - const tempDocs = DocCast(doc[field]); - const requiredTypeNameFields:{opts:DocumentOptions, template:() => Doc}[] = [ + static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) { + const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [ { - opts:{type: "slide", icon: "address-card"}, template: () => Docs.Create.MultirowDocument( + btnOpts: { title: "slide", icon: "address-card" }, + templateOpts: { _width: 400, _height: 300, title: "slideView", childDocumentsActive: true, _xMargin: 3, _yMargin: 3, system: true }, + template: (opts:DocumentOptions) => Docs.Create.MultirowDocument( [ - Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), - Docs.Create.TextDocument("", { title: "text", _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) - ], - { _width: 400, _height: 300, title: "slideView", _xMargin: 3, _yMargin: 3, system: true } - ) + Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), + Docs.Create.TextDocument("", { title: "text", _fitWidth:true, _height: 100, system: true, _fontFamily: StrCast(Doc.UserDoc()._fontFamily), _fontSize: StrCast(Doc.UserDoc()._fontSize) }) + ], opts) }, { - opts:{type: "mobile", icon: "mobile"}, template: () => this.mobileButton({ title: "NEW MOBILE BUTTON", onClick: undefined, }, - [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), - this.mobileTextContainer({}, - [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) - ] + btnOpts: { title: "mobile", icon: "mobile" }, + templateOpts: { title: "NEW MOBILE BUTTON", onClick: undefined, }, + template: (opts:DocumentOptions) => this.mobileButton(opts, + [this.createToolButton({ ignoreClick: true, icon: "mobile", backgroundColor: "transparent" }), + this.mobileTextContainer({}, + [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")]) + ] ) }, ]; - const requiredTypes = requiredTypeNameFields.map(({ opts, template }) => { - const docType = DocListCast(tempDocs?.data)?.find(doc => doc.title === opts.type); - const reqdOpts = { - dragFactory: template(), - title: opts.type, - icon: opts.icon + const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => { + const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title); + const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' }; + const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { + if (templateBtn) { + this.AssignOpts(templateBtn,btnOpts); + this.AssignOpts(DocCast(templateBtn.dragFactory), templateOptions) ?? (templateBtn.dragFactory = template(templateOpts)); + } + return templateBtn; }; - const reqdScripts = {onDragStart: 'copyDragFactory(this.dragFactory)'}; const makeTemp = (doc:Doc) => { doc.isTemplateDoc = makeTemplate(doc); return doc; } - return this.AssignScripts(!docType ? makeTemp(CurrentUserUtils.createToolButton(reqdOpts)) : this.AssignOpts(docType, reqdOpts)!, reqdScripts)!; + return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? CurrentUserUtils.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); }); const reqdOpts = { @@ -150,12 +153,9 @@ export class CurrentUserUtils { _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, }; - const reqdScripts = {dropConverter : "convertToButtons(dragData)"}; - const reqdFuncs = {hidden: "IsNoviceMode()"} - return this.AssignScripts(!tempDocs ? - (doc[field] = Docs.Create.MasonryDocument(requiredTypes, reqdOpts)) : - this.AssignOpts(tempDocs, reqdOpts, requiredTypes)!, - reqdScripts, reqdFuncs); + const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; + const reqdFuncs = { hidden: "IsNoviceMode()" }; + return this.AssignScripts(this.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs); } /// Initializes templates that can be applied to notes @@ -525,12 +525,12 @@ export class CurrentUserUtils { static setupToolsBtnPanel(doc: Doc, field:string) { const myTools = DocCast(doc[field]); const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined); - //const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc); + const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined); const reqdToolOps:DocumentOptions = { title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, }; - this.AssignOpts(myTools, reqdToolOps, [creatorBtns, /*templateBtns*/]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, /*templateBtns*/], reqdToolOps)); + this.AssignOpts(myTools, reqdToolOps, [creatorBtns, templateBtns]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, templateBtns], reqdToolOps)); } /// initializes the left sidebar dashboard pane @@ -749,7 +749,7 @@ export class CurrentUserUtils { } static schemaTools():Button[] { - return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{onClick:'toggleSchemaPreview(_readOnly_)'}, }]; + return [{ title: "Show preview", toolTip: "Show preview of selected document", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{return toggleSchemaPreview(_readOnly_);}'}, }]; } static webTools() { @@ -774,9 +774,9 @@ export class CurrentUserUtils { }, // Always show { title: "Back", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton,icon: "chevron-left", scripts:{onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'} }, { title: "Fwd", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, icon: "chevron-right", scripts:{onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, - { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: { script: "setBackgroundColor(value, _readOnly_)"}, funcs:{ hidden: 'selectedDocumentType()' }}, // Only when a document is selected - { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", scripts: {script: "setHeaderColor(value, _readOnly_)"}, funcs : {hidden: 'selectedDocumentType()'} }, - { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", scripts: {onClick : 'toggleOverlay(_readOnly_)'}, funcs: {hidden: 'selectedDocumentType(undefined, "freeform", true)'} }, // Only when floating document is selected in freeform + { title: "Fill", toolTip: "Background Fill Color", width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, icon: "fill-drip", scripts: { script: "setBackgroundColor(value, _readOnly_)"}, funcs:{ hidden: '!selectedDocumentType()' }}, // Only when a document is selected + { title: "Header", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, icon: "heading", scripts: {script: "setHeaderColor(value, _readOnly_)"}, funcs : {hidden: '!selectedDocumentType()'} }, + { title: "Overlay", toolTip: "Overlay", btnType: ButtonType.ToggleButton, icon: "layer-group", scripts: {onClick : 'toggleOverlay(_readOnly_)'}, funcs: {hidden: '!selectedDocumentType(undefined, "freeform", true)'} }, // Only when floating document is selected in freeform // { title: "Alias", btnType: ButtonType.ClickButton, icon: "copy", hidden: 'selectedDocumentType()' }, // Only when a document is selected { title: "Text", icon: "text", subMenu: CurrentUserUtils.textTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.RTF}")`} }, // Always available { title: "Ink", icon: "ink", subMenu: CurrentUserUtils.inkTools(), funcs: { linearViewIsExpanded: `selectedDocumentType("${DocumentType.INK}")`} }, // Always available @@ -983,7 +983,6 @@ export class CurrentUserUtils { doc.savedFilters ?? (doc.savedFilters = new List()); doc.filterDocCount = 0; doc.freezeChildren = "remove|add"; - doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents await this.setupLinkDocs(doc, linkDatabaseId); await this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon @@ -995,7 +994,8 @@ export class CurrentUserUtils { this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); - + doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents + setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 2b62945af..09b463c2f 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -223,10 +223,13 @@ export namespace DragManager { if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; docDragData.droppedDocuments = - await Promise.all(dragData.draggedDocuments.map(async d => !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : - docDragData.dropAction === "alias" ? Doc.MakeAlias(d) : - docDragData.dropAction === "proto" ? Doc.GetProto(d) : - docDragData.dropAction === "copy" ? (await Doc.MakeClone(d)).clone : d)); + await Promise.all(dragData.draggedDocuments.map(async d => + !dragData.isDocDecorationMove && !dragData.userDropAction && ScriptCast(d.onDragStart) ? + addAudioTag(ScriptCast(d.onDragStart).script.run({ this: d }).result) : + docDragData.dropAction === "alias" ? Doc.MakeAlias(d) : + docDragData.dropAction === "proto" ? Doc.GetProto(d) : + docDragData.dropAction === "copy" ? + (await Doc.MakeClone(d)).clone : d)); !["same", "proto"].includes(docDragData.dropAction as any) && docDragData.droppedDocuments.forEach((drop: Doc, i: number) => { const dragProps = Cast(dragData.draggedDocuments[i].removeDropProperties, listSpec("string"), []); const remProps = (dragData?.removeDropProperties || []).concat(Array.from(dragProps)); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 90c86fa18..faab2ed26 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -304,7 +304,6 @@ export class PropertiesView extends React.Component { rootSelected={returnFalse} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - freezeDimensions={true} dontCenter={"y"} isDocumentActive={returnFalse} isContentActive={emptyFunction} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 277fcd59c..684c919bd 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -250,7 +250,6 @@ export class CollectionStackingView extends CollectionSubView { - this._childClickedScript = ScriptField.MakeScript("openInLightbox(self, shiftKey)", { this: Doc.name, shiftKey: "boolean" });//, { detailView: detailView! }); + this._childClickedScript = ScriptField.MakeScript("openInLightbox(self)", { this: Doc.name }); this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }); }); } @@ -138,8 +138,7 @@ export class CollectionTimeView extends CollectionSubView() { fitContentsToBox={returnTrue} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} - childFreezeDimensions={true} - dontScaleFilter={this.dontScaleFilter} + //dontScaleFilter={this.dontScaleFilter} layoutEngine={this.layoutEngine} />
    ; } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 63616263e..b432104a1 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -75,7 +75,6 @@ export interface CollectionViewProps extends FieldViewProps { childHideResizeHandles?: () => boolean; childLayoutTemplate?: () => (Doc | undefined);// specify a layout Doc template to use for children of the collection childLayoutString?: string; - childFreezeDimensions?: boolean; // used by TimeView to coerce documents to treat their width height as their native width/height childIgnoreNativeSize?: boolean; childClickScript?: ScriptField; childDoubleClickScript?: ScriptField; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 99eec7892..542b1fce1 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1265,7 +1265,6 @@ export class CollectionFreeFormView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return this.props.isSelected() || this.props.isContentActive(); - isChildContentActive = () => this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; + isChildContentActive = () => ((this.props.childDocumentsActive?.() || this.Document._childDocumentsActive) && this.props.isDocumentActive?.() && SnappingManager.GetIsDragging()) || this.props.isSelected() || this.props.isAnyChildContentActive() ? true : false; getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => { return { DataDoc={this._showDataDoc} styleProvider={DefaultStyleProvider} docViewPath={returnEmptyDoclist} - freezeDimensions={true} focus={DocUtils.DefaultFocus} renderDepth={this.props.renderDepth} rootSelected={returnFalse} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 70732e74c..db076c069 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -143,7 +143,6 @@ export class DocumentContentsView extends React.Component, onInput: Opt): JsxBindings { const docOnlyProps = [ // these are the properties in DocumentViewProps that need to be removed to pass on only DocumentSharedViewProps to the FieldViews - "freezeDimensions", "hideResizeHandles", "hideTitle", "treeViewDoc", diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 89096c948..263e1dc4c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -160,7 +160,6 @@ export interface DocumentViewSharedProps { // these props are specific to DocuentViews export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView - freezeDimensions?: boolean; hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings @@ -1220,11 +1219,11 @@ export class DocumentView extends React.Component { @computed get layoutDoc() { return Doc.Layout(this.Document, this.props.LayoutTemplate?.()); } @computed get nativeWidth() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : - returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, !this.fitWidth)); } @computed get nativeHeight() { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : - returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, this.props.freezeDimensions)); + returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.fitWidth)); } @computed get shouldNotScale() { return (this.fitWidth && !this.nativeWidth) || @@ -1312,8 +1311,6 @@ export class DocumentView extends React.Component { ContentScale = () => this.nativeScaling; selfView = () => this; screenToLocalTransform = () => { - const oshift = this.fitWidth && this.ComponentView instanceof FormattedTextBox; - const shift = oshift ? -(this.props.PanelHeight() - this.rootDoc[HeightSym]()) / 2 : 0; return this.props.ScreenToLocalTransform().translate(-this.centeringX, -this.centeringY).translate(0, shift).scale(1 / this.nativeScaling); } componentDidMount() { diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 1b37e729b..85efc67a5 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -903,9 +903,9 @@ ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { } else if (selected) { if (NumCast(selected.schemaPreviewWidth) > 0) { - selected.schemaPreviewWidth = 200; - } else { selected.schemaPreviewWidth = 0; + } else { + selected.schemaPreviewWidth = 200; } } }); -- cgit v1.2.3-70-g09d2 From 566b33847cff9217ed6e4e11bed2e1b92798bc95 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Tue, 21 Jun 2022 18:16:46 -0400 Subject: trying to get basic data viz view to work --- package-lock.json | 871 ++++++++++++------------ src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 9 + src/client/util/CurrentUserUtils.ts | 5 +- src/client/views/nodes/DataViz.scss | 0 src/client/views/nodes/DataViz.tsx | 14 + src/client/views/nodes/DocumentContentsView.tsx | 3 +- 7 files changed, 464 insertions(+), 439 deletions(-) create mode 100644 src/client/views/nodes/DataViz.scss create mode 100644 src/client/views/nodes/DataViz.tsx (limited to 'src/client/documents') diff --git a/package-lock.json b/package-lock.json index 9dc7f1b04..e80f370d9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -895,7 +895,7 @@ "@types/bcrypt-nodejs": { "version": "0.0.30", "resolved": "https://registry.npmjs.org/@types/bcrypt-nodejs/-/bcrypt-nodejs-0.0.30.tgz", - "integrity": "sha512-gSWCu7EOXhcM0FYM8tanV0deaX1VM07sgBS8dSUmgnAQ5/3Xn6k+mWF+ZfE+c/OCW14IVnNdTOKcHgvL78oDFQ==", + "integrity": "sha1-TN2WtJKTs5MhIuS34pVD415rrlg=", "dev": true }, "@types/bezier-js": { @@ -1991,7 +1991,7 @@ "@types/strip-bom": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/strip-bom/-/strip-bom-3.0.0.tgz", - "integrity": "sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ==", + "integrity": "sha1-FKjsOVbC6B7bdSB5CuzyHCkK69I=", "dev": true }, "@types/strip-json-comments": { @@ -2027,7 +2027,7 @@ "@types/typescript": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@types/typescript/-/typescript-2.0.0.tgz", - "integrity": "sha512-WMEWfMISiJ2QKyk5/dSdgL0ZwP//PZj0jmDU0hMh51FmLq4WIYzjlngsUQZXejQL+QtkXJUOGjb3G3UCvgZuSQ==", + "integrity": "sha1-xDNTnJi64oaCswfqp6D9IRW4PCg=", "dev": true, "requires": { "typescript": "*" @@ -2381,12 +2381,16 @@ "Base64": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/Base64/-/Base64-0.2.1.tgz", - "integrity": "sha512-reGEWshDmTDQDsCec/HduOO9Wyj6yMOupMfhIf3ugN1TDlK2NQW4DDJSqNNtp380SNcvRfXtO8HSCQot0d0SMw==" + "integrity": "sha1-ujpCMHCOGGcFBl5mur3Uw1z2ACg=" }, "D": { - "version": "1.0.0", + "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==" + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } }, "abab": { "version": "2.0.6", @@ -2424,7 +2428,7 @@ "acorn-globals": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/acorn-globals/-/acorn-globals-3.1.0.tgz", - "integrity": "sha512-uWttZCk96+7itPxK8xCzY86PnxKTMrReKDqrHzv42VQY0K30PUO8WY13WMOuI+cOdX4EIdzdvQ8k6jkuGRFMYw==", + "integrity": "sha1-/YJw9x+7SZawBPqIDuXUZXOnMb8=", "requires": { "acorn": "^4.0.4" }, @@ -2432,7 +2436,7 @@ "acorn": { "version": "4.0.13", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug==" + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" } } }, @@ -2461,7 +2465,7 @@ "after": { "version": "0.8.2", "resolved": "https://registry.npmjs.org/after/-/after-0.8.2.tgz", - "integrity": "sha512-QbJ0NTQ/I9DI3uSJA4cbexiwQeRAfjPScqIbSjUDd9TOrcg6pTkdgziesOqxBMBzit8vFCTwrP27t13vFOORRA==" + "integrity": "sha1-/ts5T58OAqqXaOcCvaI7UF+ufh8=" }, "agent-base": { "version": "6.0.2", @@ -2536,7 +2540,7 @@ "align-text": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/align-text/-/align-text-0.1.4.tgz", - "integrity": "sha512-GrTZLRpmp6wIC2ztrWW9MjjTgSKccffgFagbNDOX95/dcjEcYZibYTeaOntySQLcdw1ztBoFkviiUvTMbb9MYg==", + "integrity": "sha1-DNkKVhCT810KmSVsIrcGlDP60Rc=", "requires": { "kind-of": "^3.0.2", "longest": "^1.0.1", @@ -2551,7 +2555,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -2561,12 +2565,12 @@ "amdefine": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", - "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==" + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=" }, "ansi-align": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ansi-align/-/ansi-align-2.0.0.tgz", - "integrity": "sha512-TdlOggdA/zURfMYa7ABC66j+oqfMew58KpJMbUlH3bcZP1b+cBHIHDDn5uH9INsxrHBPjsqM0tDB4jPTF/vgJA==", + "integrity": "sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=", "requires": { "string-width": "^2.0.0" } @@ -2613,7 +2617,7 @@ "any-promise": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", - "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + "integrity": "sha1-q8av7tzqUugJzcA3au0845Y10X8=" }, "anymatch": { "version": "2.0.0", @@ -2627,7 +2631,7 @@ "normalize-path": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha512-3pKJwH184Xo/lnH6oyP1q2pMd7HcypqqmRs91/6/i2CGtWwIKGCkOOMTm/zXbgTEWHw1uNpNi/igc3ePOYHb6w==", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "requires": { "remove-trailing-separator": "^1.0.1" } @@ -2712,7 +2716,7 @@ "arr-diff": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz", - "integrity": "sha512-YVIQ82gZPGBebQV/a8dar4AitzCQs0jjXwMPZllpXMaGjXPYVUawSxQrRsjhjupyVxEvbHgUmIhKVlND+j02kA==" + "integrity": "sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA=" }, "arr-flatten": { "version": "1.1.0", @@ -2722,7 +2726,7 @@ "arr-union": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/arr-union/-/arr-union-3.1.0.tgz", - "integrity": "sha512-sKpyeERZ02v1FeCZT8lrfJq5u6goHCtpTAzPwJYe7c8SPFOboNjNg1vz2L4VTn9T4PQxEx13TbXLmYUcS6Ug7Q==" + "integrity": "sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ=" }, "array-back": { "version": "2.0.0", @@ -2813,23 +2817,23 @@ "array-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/array-equal/-/array-equal-1.0.0.tgz", - "integrity": "sha512-H3LU5RLiSsGXPhN+Nipar0iR0IofH+8r89G2y1tBKxQ/agagKyAjhkAFDRBfodP2caPrNKHpAWNIM/c9yeL7uA==", + "integrity": "sha1-jCpe8kcv2ep0KwTHenUJO6J1fJM=", "dev": true }, "array-find-index": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", - "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==" + "integrity": "sha1-3wEKoSh+Fku9pvlyOwqWoexBh6E=" }, "array-flatten": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", - "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" }, "array-union": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/array-union/-/array-union-1.0.2.tgz", - "integrity": "sha512-Dxr6QJj/RdU/hCaBjOfxW+q6lyuVE6JFWIrAUpuOOhoJJoQ99cUn3igRaHVB5P9WrgFVN0FfArM3x0cueOU8ng==", + "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { "array-uniq": "^1.0.1" @@ -2838,13 +2842,13 @@ "array-uniq": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", + "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", "dev": true }, "array-unique": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.3.2.tgz", - "integrity": "sha512-SleRWjh9JUud2wH1hPs9rZBZ33H6T9HOiL0uwGnGx9FpE6wKGyfWugmbkEOIs6qWrZhg0LWeLziLrEwQJhs5mQ==" + "integrity": "sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg=" }, "array.prototype.reduce": { "version": "1.0.4", @@ -2871,7 +2875,7 @@ "asap": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", - "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==" + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" }, "asn1": { "version": "0.2.6", @@ -2884,7 +2888,7 @@ "assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha512-NfJ4UzBCcQGLDlQq7nHxH+tv3kyZ0hHQqF5BO6J7tNJeP5do1llPr8dZ8zHonfhAu0PHAdMkSo+8o0wxg9lZWw==" + "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=" }, "assertion-error": { "version": "1.1.0", @@ -2894,7 +2898,7 @@ "assign-symbols": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", - "integrity": "sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==" + "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, "async": { "version": "2.6.4", @@ -2912,7 +2916,7 @@ "async-foreach": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz", - "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==" + "integrity": "sha1-NhIfhFwFeBct5Bmpfb6x0W7DRUI=" }, "async-limiter": { "version": "1.0.1", @@ -2923,7 +2927,7 @@ "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "atob": { "version": "2.1.2", @@ -2973,7 +2977,7 @@ "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", - "integrity": "sha512-08kcGqnYf/YmjoRhfxyu+CLxBjUtHLXLXX/vUfx9l2LYzG3c1m61nrpyFUZI6zeS+Li/wWMMidD9KgrqtGq3mA==" + "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=" }, "aws4": { "version": "1.11.0", @@ -2991,12 +2995,12 @@ "babel": { "version": "6.23.0", "resolved": "https://registry.npmjs.org/babel/-/babel-6.23.0.tgz", - "integrity": "sha512-ZDcCaI8Vlct8PJ3DvmyqUz+5X2Ylz3ZuuItBe/74yXosk2dwyVo/aN7MCJ8HJzhnnJ+6yP4o+lDgG9MBe91DLA==" + "integrity": "sha1-0NHn2APpdHZb7qMjLU4VPA77kPQ=" }, "babel-code-frame": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", - "integrity": "sha512-XqYMR2dfdGMW+hd0IUZ2PwK+fGeFkOxZJ0wY+JaQAHzt1Zx8LcvpiZD2NiGkEG8qx0CfkAOr5xt76d1e8vG90g==", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { "chalk": "^1.1.3", @@ -3007,19 +3011,19 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", "dev": true }, "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { "ansi-styles": "^2.2.1", @@ -3032,7 +3036,7 @@ "js-tokens": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", - "integrity": "sha512-RjTcuD4xjtthQkaWH7dFlH85L+QaVtSoOyGdZ3g6HFhS9dFNDfLyqgm2NFe2X6cQpeFmt0452FJjFG5UameExg==", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", "dev": true }, "strip-ansi": { @@ -3094,12 +3098,12 @@ "babel-plugin-syntax-jsx": { "version": "6.18.0", "resolved": "https://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz", - "integrity": "sha512-qrPaCSo9c8RHNRHIotaufGbuOBN8rtdC4QrrFFc43vyWCCz7Kl7GL1PGaXtMGQZUXrkCjNEgxDfmAuAabr/rlw==" + "integrity": "sha1-CvMqmm4Tyno/1QaeYtew9Y0NiUY=" }, "babel-runtime": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-runtime/-/babel-runtime-6.26.0.tgz", - "integrity": "sha512-ITKNuq2wKlW1fJg9sSW52eepoYgZBggvOAHC0u/CYu/qxQ9EVzThCgR69BnSXLHjy2f7SY5zaQ4yt7H9ZVxY2g==", + "integrity": "sha1-llxwWGaOgrVde/4E/yM3vItWR/4=", "requires": { "core-js": "^2.4.0", "regenerator-runtime": "^0.11.0" @@ -3120,7 +3124,7 @@ "babel-types": { "version": "6.26.0", "resolved": "https://registry.npmjs.org/babel-types/-/babel-types-6.26.0.tgz", - "integrity": "sha512-zhe3V/26rCWsEZK8kZN+HaQj5yQ1CilTObixFzKW1UWjqG7618Twz6YEsCnjfg5gBcJh02DrpCkS9h98ZqDY+g==", + "integrity": "sha1-o7Bz+Uq0nrb6Vc1lInozQ4BjJJc=", "requires": { "babel-runtime": "^6.26.0", "esutils": "^2.0.2", @@ -3143,7 +3147,7 @@ "backo2": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/backo2/-/backo2-1.0.2.tgz", - "integrity": "sha512-zj6Z6M7Eq+PBZ7PQxl5NT665MvJdAkzp0f60nAJ+sLaSCBPMwVak5ZegFbgVCzFcCJTKFoMizvM5Ld7+JrRJHA==" + "integrity": "sha1-MasayLEpNjRj41s+u2n038+6eUc=" }, "balanced-match": { "version": "1.0.2", @@ -3167,7 +3171,7 @@ "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { "is-descriptor": "^1.0.0" } @@ -3203,12 +3207,12 @@ "base16": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/base16/-/base16-1.0.0.tgz", - "integrity": "sha512-pNdYkNPiJUnEhnfXV56+sQy8+AaPcG3POZAUnwr4EeqCUZFz4u2PePbo3e5Gj4ziYPCWGUZT9RHisvJKnwFuBQ==" + "integrity": "sha1-4pf2DX7BAUp6lxo568ipjAtoHnA=" }, "base64-arraybuffer": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz", - "integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==" + "integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=" }, "base64-js": { "version": "1.5.1", @@ -3228,18 +3232,18 @@ "batch": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", + "integrity": "sha1-3DQxT05nkxgJP8dgJyUl+UvyXBY=", "dev": true }, "bcrypt-nodejs": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/bcrypt-nodejs/-/bcrypt-nodejs-0.0.3.tgz", - "integrity": "sha512-NmTbLm867btBHCBZ222FQXkQKzecB0KG6pTXFa6NeTVZaSnLfCsx7EK2PL3J+kX8xJThUquEBbhimRCKKZX9zA==" + "integrity": "sha1-xgkX8m3CNWYVZsaBBhwwPCsohCs=" }, "bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", - "integrity": "sha512-qeFIXtP4MSoi6NLqO12WfqARWWuCKi2Rn/9hJLEmtB5yTNr9DqFWkJRCf2qShWzPeAMRnOgCrq0sg/KLv5ES9w==", + "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "requires": { "tweetnacl": "^0.14.3" } @@ -3247,7 +3251,7 @@ "bezier-curve": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz", - "integrity": "sha512-h6uZJ6qdFfswS1rIRericgouhTeiVi/MnH10OKtCu2IZzXa+ZcjaxRLHY4u/evRsJcxYbbiNkgWQj2Z4UIcpEQ==" + "integrity": "sha1-o9+v6rEqlMRicw1QeYxSqEBdc3k=" }, "bezier-js": { "version": "4.1.1", @@ -3296,7 +3300,7 @@ "block-stream": { "version": "0.0.9", "resolved": "https://registry.npmjs.org/block-stream/-/block-stream-0.0.9.tgz", - "integrity": "sha512-OorbnJVPII4DuUKbjARAe8u8EfqOmkEEaSFIyoQ7OjTHn6kafxWl0wLgoZ2rXaYd7MyLcDaU4TmhfxtwgcccMQ==", + "integrity": "sha1-E+v+d4oDIFz+A3UUgeu0szAMEmo=", "requires": { "inherits": "~2.0.0" } @@ -3364,7 +3368,7 @@ "bonjour": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/bonjour/-/bonjour-3.5.0.tgz", - "integrity": "sha512-RaVTblr+OnEli0r/ud8InrU7D+G0y6aJhlxaLa6Pwty4+xoxboF1BsUI45tujvRpbj9dQVoglChqonGAsjEBYg==", + "integrity": "sha1-jokKGD2O6aI5OzhExpGkK897yfU=", "dev": true, "requires": { "array-flatten": "^2.1.0", @@ -3386,7 +3390,7 @@ "boolbase": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", - "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==" + "integrity": "sha1-aN/1++YMUes3cl6p4+0xDcwed24=" }, "bootstrap": { "version": "4.6.1", @@ -3415,7 +3419,7 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==" + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" } } }, @@ -3448,7 +3452,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -3458,7 +3462,7 @@ "browser-assert": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/browser-assert/-/browser-assert-1.2.1.tgz", - "integrity": "sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==" + "integrity": "sha1-mqpaKox0aFwq4Fv+Ru/WBvBowgA=" }, "browser-process-hrtime": { "version": "1.0.0", @@ -3503,12 +3507,12 @@ "buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==" + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=" }, "buffer-equal-constant-time": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", - "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==" + "integrity": "sha1-+OcRMvf/5uAaXJaXpMbz5I1cyBk=" }, "buffer-from": { "version": "1.1.2", @@ -3524,12 +3528,12 @@ "buffer-shims": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha512-Zy8ZXMyxIT6RMTeY7OP/bDndfj6bwCan7SS98CEndS6deHwWPpseeHlwarNcBim+etXnF9HBc1non5JgDaJU1g==" + "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=" }, "built-in-math-eval": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/built-in-math-eval/-/built-in-math-eval-0.3.0.tgz", - "integrity": "sha512-5XD5cujru60ooKJ4sGZqoH5v2Xvgw7ezV54gJX/OnPkgDKoH3BnlMEi8xW6hl8xaEjxKHebgrsawroeZnGwIMA==", + "integrity": "sha1-JA3CHLOJQ5WIxhxGDrAHZJfvxBw=", "requires": { "math-codegen": "^0.3.5" } @@ -3660,7 +3664,7 @@ "caller-callsite": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz", - "integrity": "sha512-JuG3qI4QOftFsZyOn1qq87fq5grLIyk1JYd5lJmdA+fG7aQ9pA/i3JIJGcO3q0MrRcHlOt1U+ZeHW8Dq9axALQ==", + "integrity": "sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=", "requires": { "callsites": "^2.0.0" }, @@ -3668,14 +3672,14 @@ "callsites": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz", - "integrity": "sha512-ksWePWBloaWPxJYQ8TL0JHvtci6G5QTKwQ95RcWAa/lzoAKuAOflGdAK92hpHXjkwb8zLxoLNUoNYZgVsaJzvQ==" + "integrity": "sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=" } } }, "caller-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz", - "integrity": "sha512-MCL3sf6nCSXOwCTzvPKhN18TU7AHTvdtam8DAogxcrJ8Rjfbbg7Lgng64H9Iy+vUV6VGFClN/TyxBkAebLRR4A==", + "integrity": "sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=", "requires": { "caller-callsite": "^2.0.0" } @@ -3702,7 +3706,7 @@ "camelcase-keys": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-2.1.0.tgz", - "integrity": "sha512-bA/Z/DERHKqoEOrp+qeGKw1QlvEQkGZSc0XaY6VnTxZr+Kv1G5zFwttpjv8qxZ/sBPT4nthwZaAcsAZTJlSKXQ==", + "integrity": "sha1-MIvur/3ygRkFHvodkyITyRuPkuc=", "requires": { "camelcase": "^2.0.0", "map-obj": "^1.0.0" @@ -3711,14 +3715,14 @@ "camelcase": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-2.1.1.tgz", - "integrity": "sha512-DLIsRzJVBQu72meAKPkWQOLcujdXT32hwdfnkI1frSiSRMK1MofjKHf+MEx0SB6fjEFXL8fBDv1dKymBlOp4Qw==" + "integrity": "sha1-fB0W1nmhu+WcoCys7PsBHiAfWh8=" } } }, "camelize": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/camelize/-/camelize-1.0.0.tgz", - "integrity": "sha512-W2lPwkBkMZwFlPCXhIlYgxu+7gC/NUlCtdK652DAJ1JdgV0sTrvuPFshNPrFa1TY2JOkLhgdeEBplB4ezEa+xg==" + "integrity": "sha1-FkpUg+Yw+kMh5a8HAg5TGDGyYJs=" }, "caniuse-lite": { "version": "1.0.30001351", @@ -3743,12 +3747,12 @@ "caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==" + "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=" }, "center-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/center-align/-/center-align-0.1.3.tgz", - "integrity": "sha512-Baz3aNe2gd2LP2qk5U+sDk/m4oSuwSDcBfayTCTBoWpfIGO5XFxPmjILQII4NGiZjD6DoDI6kf7gKaxkf7s3VQ==", + "integrity": "sha1-qg0yYptu6XIgBBHL1EYckHvCt60=", "requires": { "align-text": "^0.1.3", "lazy-cache": "^1.0.3" @@ -3786,12 +3790,12 @@ "change-emitter": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/change-emitter/-/change-emitter-0.1.6.tgz", - "integrity": "sha512-YXzt1cQ4a2jqazhcuSWEOc1K2q8g9H6eWNsyZgi640LDzRWVQ2eDe+Y/kVdftH+vYdPF2rgDb3dLdpxE1jvAxw==" + "integrity": "sha1-6LL+PX8at9aaMhma/5HqaTFAlRU=" }, "character-parser": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", - "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "integrity": "sha1-x84o821LzZdE5f/CxfzeHHMmH8A=", "requires": { "is-regex": "^1.0.3" } @@ -3799,12 +3803,12 @@ "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", - "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==" + "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=" }, "child_process": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz", - "integrity": "sha512-Wmza/JzL0SiWz7kl6MhIKT5ceIlnFPJX+lwUGj7Clhy5MMldsSoJR0+uvRzOS5Kv45Mq7t1PoE8TsOA9bzvb6g==" + "integrity": "sha1-sffn/HPSXn/R1FWtyU4UODAYK1o=" }, "chokidar": { "version": "2.1.8", @@ -3833,7 +3837,7 @@ "chrome": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/chrome/-/chrome-0.1.0.tgz", - "integrity": "sha512-6KYl20U4Taj6YipylsWr2etUvp9AElJKfGNSBmyGTymYmancnOb041ZNadolEZi2nboLXH7jMSqUmm4kpuTzfg==", + "integrity": "sha1-9h2beS/v6MGUxwVt3BAscmqGQyk=", "requires": { "exeq": "^2.2.0", "plist": "^1.1.0" @@ -3853,7 +3857,7 @@ "clamp": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/clamp/-/clamp-1.0.1.tgz", - "integrity": "sha512-kgMuFyE78OC6Dyu3Dy7vcx4uy97EIbVxJB/B0eJ3bUNAkwdNcxYzgKltnyADiYwsR7SEqkkUPsEUT//OVS6XMA==" + "integrity": "sha1-ZqDmQBGBbjcZaCj9yMjBRzEshjQ=" }, "class-transformer": { "version": "0.2.3", @@ -3874,7 +3878,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -3904,12 +3908,12 @@ "cli-boxes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/cli-boxes/-/cli-boxes-1.0.0.tgz", - "integrity": "sha512-3Fo5wu8Ytle8q9iCzS4D2MWVL2X7JVWRiS1BnXbTFDhS9c/REkM9vd1AmabsoZoY5/dGi5TT9iKL8Kb6DeBRQg==" + "integrity": "sha1-T6kXw+WclKAEzWH47lCdplFocUM=" }, "clipboard": { "version": "1.7.1", "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-1.7.1.tgz", - "integrity": "sha512-smkaRaIQsrnKN1F3wd1/vY9Q+DeR4L8ZCXKeHCFC2j8RZuSBbuImcLdnIO4GTxmzJxQuDGNKkyfpGoPW7Ua5bQ==", + "integrity": "sha1-Ng1taUbpmnof7zleQrqStem1oWs=", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -3934,7 +3938,7 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==" + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, "yargs-parser": { "version": "7.0.0", @@ -3984,7 +3988,7 @@ "clj-fuzzy": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/clj-fuzzy/-/clj-fuzzy-0.3.3.tgz", - "integrity": "sha512-9cyh9A8+OphDZeKIG21MgyDHWDkWxTvagwvFLVjtdi6eToFENF7iDLlKwhHrnBQRSQwprKNhazG053nE/UgwfQ==" + "integrity": "sha1-seU0MJHFIC28UlMoY+HEp4RX8D0=" }, "clone-deep": { "version": "4.0.1", @@ -3999,7 +4003,7 @@ "clone-response": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", - "integrity": "sha512-yjLXh88P599UOyPTFX0POsd7WxnbsVsGohcwzHOLspIhhpalPw1BcqED8NblyZLKcGrL8dTgMlcaZxV2jAD41Q==", + "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", "requires": { "mimic-response": "^1.0.0" }, @@ -4019,12 +4023,12 @@ "code-point-at": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha512-RpAVKQA5T63xEj6/giIbUEtZwJ4UFIc3ZtvEkiaUERylqe8xb5IvqcgOurZLahv93CLKfxcw5YI+DZcUBRyLXA==" + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=" }, "collection-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", - "integrity": "sha512-lNkKvzEeMBBjUGHZ+q6z9pSJla0KWAQPvtzhEV9+iGyQYG+pBpl7xKDhxoNSOZH2hhv0v5k0y2yAM4o4SjoSkw==", + "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "requires": { "map-visit": "^1.0.0", "object-visit": "^1.0.0" @@ -4050,7 +4054,7 @@ "color-name": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", - "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=" }, "color-string": { "version": "1.9.1", @@ -4108,13 +4112,13 @@ "commondir": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", - "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, "component-bind": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/component-bind/-/component-bind-1.0.0.tgz", - "integrity": "sha512-WZveuKPeKAG9qY+FkYDeADzdHyTYdIboXS59ixDeRJL5ZhxpqUnxSOwop4FQjMsiYm3/Or8cegVbpAHNA7pHxw==" + "integrity": "sha1-AMYIq33Nk4l8AAllGx06jh5zu9E=" }, "component-emitter": { "version": "1.3.0", @@ -4124,7 +4128,7 @@ "component-inherit": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/component-inherit/-/component-inherit-0.0.3.tgz", - "integrity": "sha512-w+LhYREhatpVqTESyGFg3NlP6Iu0kEKUHETY9GoZP/pQyW4mHFZuFWRUCIqVPZ36ueVLtoOEZaAqbCF2RDndaA==" + "integrity": "sha1-ZF/ErfWLcrZJ1crmUTVhnbJv8UM=" }, "compress-brotli": { "version": "1.3.8", @@ -4189,7 +4193,7 @@ "bytes": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", - "integrity": "sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw==", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=", "dev": true }, "debug": { @@ -4204,7 +4208,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true } } @@ -4212,7 +4216,7 @@ "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "concat-stream": { "version": "1.6.2", @@ -4272,7 +4276,7 @@ "connect-flash": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/connect-flash/-/connect-flash-0.1.1.tgz", - "integrity": "sha512-2rcfELQt/ZMP+SM/pG8PyhJRaLKp+6Hk2IUBNkEit09X+vwn3QsAL3ZbYtxUn7NVPzbMTSLRDhqe0B/eh30RYA==" + "integrity": "sha1-2GMPJtlaf4UfmVax6MxnMvO2qjA=" }, "connect-history-api-fallback": { "version": "1.6.0", @@ -4301,12 +4305,12 @@ "process-nextick-args": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha512-yN0WQmuCX63LP/TMvAg31nvT6m4vDqJEiiv2CAZqWOGNWutc9DfDk1NPYYmKUFmaVM2UwDowH4u5AHWYP/jxKw==" + "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=" }, "readable-stream": { "version": "2.2.7", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.7.tgz", - "integrity": "sha512-a6ibcfWFhgihuTw/chl+u3fB5ykBZFmnvpyZHebY0MCQE4vvYcsCLpCeaQ1BkH7HdJYavNSqF0WDLeo4IPHQaQ==", + "integrity": "sha1-BwV6y+JGeyIELTb5jFrVBwVOlbE=", "requires": { "buffer-shims": "~1.0.0", "core-util-is": "~1.0.0", @@ -4330,7 +4334,7 @@ "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", - "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, "constantinople": { "version": "3.1.2", @@ -4414,7 +4418,7 @@ "cookie-signature": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", - "integrity": "sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==" + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" }, "cookies": { "version": "0.8.0", @@ -4459,7 +4463,7 @@ "copy-descriptor": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/copy-descriptor/-/copy-descriptor-0.1.1.tgz", - "integrity": "sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==" + "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-webpack-plugin": { "version": "4.6.0", @@ -4489,7 +4493,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", "dev": true }, "serialize-javascript": { @@ -4503,7 +4507,7 @@ "core-js": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", - "integrity": "sha512-ZiPp9pZlgxpWRu0M+YWbm6+aQ84XEfH1JRXvfOc/fILWI0VKhLC2LX13X1NYq4fULzLMq7Hfh43CSo2/aIaUPA==" + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" }, "core-js-pure": { "version": "3.22.8", @@ -4567,7 +4571,7 @@ "create-error-class": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/create-error-class/-/create-error-class-3.0.2.tgz", - "integrity": "sha512-gYTKKexFO3kh200H1Nit76sRwRtOY32vQd3jpAQKpLtZqyNsSQNfI4N7o3eP2wUjV35pTWKRYqFUDBvUha/Pkw==", + "integrity": "sha1-Br56vvlHo/FKMP1hBnHUAbyot7Y=", "requires": { "capture-stack-trace": "^1.0.0" } @@ -4632,7 +4636,7 @@ "cross-spawn": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-3.0.1.tgz", - "integrity": "sha512-eZ+m1WNhSZutOa/uRblAc9Ut5MQfukFrFMtPSm3bZCA888NmMd5AWXWdgRZ80zd+pTk1P2JrGjg9pUPTvl2PWQ==", + "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "requires": { "lru-cache": "^4.0.1", "which": "^1.2.9" @@ -4662,7 +4666,7 @@ "crypto-random-string": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-1.0.0.tgz", - "integrity": "sha512-GsVpkFPlycH7/fRR7Dhcmnoii54gV1nz7y4CWyeFS14N+JVBBhY+r8amRHE4BwSYal7BPTDp8isvAlCxyFt3Hg==" + "integrity": "sha1-ojD2T1aDEOFJgAmUB5DsmVRbyn4=" }, "css-box-model": { "version": "1.2.1", @@ -4675,7 +4679,7 @@ "css-color-keywords": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/css-color-keywords/-/css-color-keywords-1.0.0.tgz", - "integrity": "sha512-FyyrDHZKEjXDpNJYvVsV960FiqQyXc/LlYmsxl2BcdMb2WPx0OGRVgTg55rPSyLSNMqP52R9r8geSp7apN3Ofg==" + "integrity": "sha1-/qJhbcZ2spYmhrOvjb2+GAskTgU=" }, "css-in-js-utils": { "version": "2.0.1", @@ -4818,7 +4822,7 @@ "currently-unhandled": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/currently-unhandled/-/currently-unhandled-0.4.1.tgz", - "integrity": "sha512-/fITjgjGU50vjQ4FH6eUoYu+iUoUKIXws2hL15JJpIR+BbTxaXQsMuuyjtNh2WqsSBS5nsaZHFsFecyw5CCAng==", + "integrity": "sha1-mI3zP+qxke95mmE2nddsF635V+o=", "requires": { "array-find-index": "^1.0.1" } @@ -4826,19 +4830,18 @@ "custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", - "integrity": "sha512-GAj5FOq0Hd+RsCGVJxZuKaIDXDf3h6GQoNEjFgbLLI/trgtavwUbSnZ5pVfg27DVCaWjIohryS0JFwIJyT2cMg==" + "integrity": "sha1-XQKkaFCt8bSjF5RqOSj8y1v9BCU=" }, "cyclist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/cyclist/-/cyclist-1.0.1.tgz", - "integrity": "sha512-NJGVKPS81XejHcLhaLJS7plab0fK3slPh11mESeeDq2W4ZI5kUKK/LRRdVDvjJseojbPB7ZwjnyOybg3Igea/A==", + "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, "d": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, "requires": { "es5-ext": "^0.10.50", "type": "^1.0.1" @@ -4972,7 +4975,7 @@ "dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha512-jRFi8UDGo6j+odZiEpjazZaWqEal3w/basFjQHQEwVtZJGDpxbH1MeYluwCS8Xq5wmLJooDlMgvVarmWfGM44g==", + "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "requires": { "assert-plus": "^1.0.0" } @@ -5024,7 +5027,7 @@ "de-indent": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", - "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==" + "integrity": "sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=" }, "debounce": { "version": "1.2.1", @@ -5042,12 +5045,12 @@ "decamelize": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=" }, "decode-uri-component": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.0.tgz", - "integrity": "sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==" + "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=" }, "decompress-response": { "version": "4.2.1", @@ -5212,7 +5215,7 @@ "globby": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", - "integrity": "sha512-KVbFv2TQtbzCoxAnfD6JcHZTYCzyliEaaeM/gH8qQdkKr5s0OP9scEgvdcngyk7AVdY6YVW/TJHd+lQ/Df3Daw==", + "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { "array-union": "^1.0.1", @@ -5225,7 +5228,7 @@ "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } } @@ -5250,7 +5253,7 @@ "delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "delegate": { "version": "3.2.0", @@ -5260,7 +5263,7 @@ "delegates": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, "denque": { "version": "1.5.1", @@ -5363,7 +5366,7 @@ "import-fresh": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz", - "integrity": "sha512-eZ5H8rcgYazHbKC3PG4ClHNykCSxtAhxSSEM+2mb+7evD2CKF5V7c0dNum7AdpDh0ZdICwZY9sRSn8f+KH96sg==", + "integrity": "sha1-2BNVwVYS04bGH53dOSLUMEgipUY=", "requires": { "caller-path": "^2.0.0", "resolve-from": "^3.0.0" @@ -5398,7 +5401,7 @@ "parse-json": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", - "integrity": "sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", "requires": { "error-ex": "^1.3.1", "json-parse-better-errors": "^1.0.1" @@ -5412,7 +5415,7 @@ "resolve-from": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", - "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==" + "integrity": "sha1-six699nWiBvItuZTM17rywoYh0g=" }, "string-width": { "version": "4.2.3", @@ -5479,7 +5482,7 @@ "deps-regex": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deps-regex/-/deps-regex-0.1.4.tgz", - "integrity": "sha512-3tzwGYogSJi8HoG93R5x9NrdefZQOXgHgGih/7eivloOq6yC6O+yoFxZnkgP661twvfILONfoKRdF9GQOGx2RA==" + "integrity": "sha1-UYZnt2kUYKXn4KNBvnbrfOgJAYQ=" }, "destroy": { "version": "1.2.0", @@ -5528,7 +5531,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -5536,7 +5539,7 @@ "dns-equal": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/dns-equal/-/dns-equal-1.0.0.tgz", - "integrity": "sha512-z+paD6YUQsk+AbGCEM4PrOXSss5gd66QfcVBFTKR/HpFL9jCqikS94HYwKww6fQyO7IxrIIyUu+g0Ka9tUS2Cg==", + "integrity": "sha1-s55/HabrCnW6nBcySzR1PEfgZU0=", "dev": true }, "dns-packet": { @@ -5552,7 +5555,7 @@ "dns-txt": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/dns-txt/-/dns-txt-2.0.2.tgz", - "integrity": "sha512-Ix5PrWjphuSoUXV/Zv5gaFHjnaJtb02F2+Si3Ht9dyJ87+Z/lMmy+dpNHtTGraNK958ndXq2i+GLkWsWHcKaBQ==", + "integrity": "sha1-uR2Ab10nGI5Ks+fRB9iBocxGQrY=", "dev": true, "requires": { "buffer-indexof": "^1.0.0" @@ -5561,7 +5564,7 @@ "doctypes": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/doctypes/-/doctypes-1.1.0.tgz", - "integrity": "sha512-LLBi6pEqS6Do3EKQ3J0NqHWV5hhb78Pi8vvESYwyOy2c31ZEZVdtitdzsQsKb7878PEERhzUk0ftqGhG6Mz+pQ==" + "integrity": "sha1-6oCxBqh1OHdOijpKWv4pPeSJ4Kk=" }, "dom-converter": { "version": "0.2.0", @@ -5670,12 +5673,12 @@ "double-bits": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/double-bits/-/double-bits-1.1.1.tgz", - "integrity": "sha512-BCLEIBq0O/DWoA7BsCu/R+RP0ZXiowP8BhtJT3qeuuQEBpnS8LK/Wo6UTJQv6v8mK1fj8n90YziHLwGdM5whSg==" + "integrity": "sha1-WKu6RUlNpND6Nrc60RoobJGEscY=" }, "duplexer3": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.4.tgz", - "integrity": "sha512-CEj8FwwNA4cVH2uFCoHUrmojhYh1vmCdOaneKJXwkeY1i9jnlslVo9dx+hQ5Hl9GnH/Bwy/IjxAyOePyPKYnzA==" + "integrity": "sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=" }, "duplexify": { "version": "3.7.1", @@ -5709,7 +5712,7 @@ "dynamic-dedupe": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz", - "integrity": "sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ==", + "integrity": "sha1-BuRMIj9eTpTXjvnbI6ZRXOL5YqE=", "dev": true, "requires": { "xtend": "^4.0.0" @@ -5718,7 +5721,7 @@ "ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", - "integrity": "sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==", + "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "requires": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" @@ -5735,7 +5738,7 @@ "ee-first": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", - "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==" + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" }, "electron-to-chromium": { "version": "1.4.149", @@ -5764,7 +5767,7 @@ "encodeurl": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", - "integrity": "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w==" + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" }, "encoding": { "version": "0.1.13", @@ -5839,7 +5842,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "ws": { "version": "7.4.6", @@ -5973,7 +5976,6 @@ "version": "0.10.61", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", - "dev": true, "requires": { "es6-iterator": "^2.0.3", "es6-symbol": "^3.1.3", @@ -5983,8 +5985,7 @@ "es6-iterator": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", "requires": { "d": "1", "es5-ext": "^0.10.35", @@ -5994,13 +5995,12 @@ "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", - "integrity": "sha512-oj4jOSXvWglTsc3wrw86iom3LDPOx1nbipQk+jaG3dy+sMRM6ReSgVr/VlmBuF6lXUrflN9DCcQHeSbAwGUl4g==" + "integrity": "sha1-7FYjOGgDKQkgcXDDlEjiREndH8Q=" }, "es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", - "dev": true, "requires": { "d": "^1.0.1", "ext": "^1.1.2" @@ -6014,12 +6014,12 @@ "escape-html": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", - "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==" + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" }, "escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=" }, "escodegen": { "version": "1.14.3", @@ -6089,7 +6089,7 @@ "etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", - "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==" + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" }, "event-target-shim": { "version": "5.0.1", @@ -6117,7 +6117,7 @@ "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", - "integrity": "sha512-RztN09XglpYI7aBBrJCPW95jEH7YF1UEPOoX9yDhUTPdp7mK+CQvnLTuD10BNXZ3byLTu2uehZ8EcKT/4CGiFw==", + "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "requires": { "cross-spawn": "^5.0.1", "get-stream": "^3.0.0", @@ -6131,7 +6131,7 @@ "cross-spawn": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-5.1.0.tgz", - "integrity": "sha512-pTgQJ5KC0d2hcY8eyL1IzlBPYjTkyH72XRZPnLyKus2mBfNjQs3klqbJU2VILqZryAZUt9JOb3h/mWMy23/f5A==", + "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "requires": { "lru-cache": "^4.0.1", "shebang-command": "^1.2.0", @@ -6162,12 +6162,12 @@ "exenv": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/exenv/-/exenv-1.2.2.tgz", - "integrity": "sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw==" + "integrity": "sha1-KueOhdmJQVhnCwPUe+wfA72Ru50=" }, "exeq": { "version": "2.4.0", "resolved": "https://registry.npmjs.org/exeq/-/exeq-2.4.0.tgz", - "integrity": "sha512-B648qbDS00nQZv9UQGLT5RbZm/5dNBX10F8oWeXcgpFHSLm1249u95t/3sn2wXdQjLhlF+edAECdshFtSr1K0Q==", + "integrity": "sha1-Td8qaEZIxCeteZNJzzO9dTWPiEo=", "requires": { "bluebird": "^3.0.3", "native-or-bluebird": "^1.2.0" @@ -6176,7 +6176,7 @@ "exif": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/exif/-/exif-0.6.0.tgz", - "integrity": "sha512-gEwM4uanNMfLnDNKclZ7jPEA99E3rpy4ntoS6QW8u6murZjl1o8qRaPdMoC46Syg3d9/QaET0bYKhWlTwJCPgg==", + "integrity": "sha1-YKYmaAdlQst+T1cZnUrG830sX0o=", "requires": { "debug": "^2.2" }, @@ -6192,7 +6192,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -6204,7 +6204,7 @@ "expand-brackets": { "version": "2.1.4", "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-2.1.4.tgz", - "integrity": "sha512-w/ozOKR9Obk3qoWeY/WDi6MFta9AoMR+zud60mdnbniMcBxRuFJyDt2LdX/14A1UABeqk+Uk+LDfUpvoGKppZA==", + "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "requires": { "debug": "^2.3.3", "define-property": "^0.2.5", @@ -6226,7 +6226,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -6234,7 +6234,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -6242,7 +6242,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -6325,7 +6325,7 @@ "express-flash": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/express-flash/-/express-flash-0.0.2.tgz", - "integrity": "sha512-QVUR0ZZRCaa8+iPHoUQaQJrQWcQuK/Q+19M7IUIdIEtvwhrA/ifHT7y1CVJI41YfGiOQnbGtn3uvd2vOdgu58A==", + "integrity": "sha1-I9GovPP5DXB5KOSJ+Whp7K0KzaI=", "requires": { "connect-flash": "0.1.x" } @@ -6382,13 +6382,12 @@ "expressjs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/expressjs/-/expressjs-1.0.1.tgz", - "integrity": "sha512-eFnQ5bMJxTZ29XwRJPV8ee/OURBBMS6Fm+b5rvMMEyz6u2IxPEh2SRzMZt9WvgnV+SMLmnzkALE1DnGG1HxJCw==" + "integrity": "sha1-IgMoRpoY31rWFeK3oM6ZXxf7ru8=" }, "ext": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", - "dev": true, "requires": { "type": "^2.5.0" }, @@ -6396,8 +6395,7 @@ "type": { "version": "2.6.0", "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", - "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==", - "dev": true + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" } } }, @@ -6409,7 +6407,7 @@ "extend-shallow": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-3.0.2.tgz", - "integrity": "sha512-BwY5b5Ql4+qZoefgMj2NUmx+tehVTH/Kf4k1ZEtOHNFcm2wSxMRo992l6X3TIgni2eZVTZ85xMOjF31fwZAj6Q==", + "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "requires": { "assign-symbols": "^1.0.0", "is-extendable": "^1.0.1" @@ -6443,7 +6441,7 @@ "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { "is-descriptor": "^1.0.0" } @@ -6451,7 +6449,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -6521,7 +6519,7 @@ "extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", - "integrity": "sha512-11Ndz7Nv+mvAC1j0ktTa7fAb0vLyGGX+rMHNBYQviQDGU0Hw7lhctJANqbPhu9nV9/izT/IntTgZ7Im/9LJs9g==" + "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=" }, "fast-deep-equal": { "version": "3.1.3", @@ -6536,7 +6534,7 @@ "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", - "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, "fast-text-encoding": { @@ -6575,7 +6573,7 @@ "fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", + "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "requires": { "pend": "~1.2.0" } @@ -6583,7 +6581,7 @@ "ffmpeg": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/ffmpeg/-/ffmpeg-0.0.4.tgz", - "integrity": "sha512-3TgWUJJlZGQn+crJFyhsO/oNeRRnGTy6GhgS98oUCIfZrOW5haPPV7DUfOm3xJcHr5q3TJpjk2GudPutrNisRA==", + "integrity": "sha1-HEYN+OfaUSf2LO70v6BsWciWMMs=", "requires": { "when": ">= 0.0.1" } @@ -6612,7 +6610,7 @@ "fill-range": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz", - "integrity": "sha512-VcpLTWqWDiTerugjj8e3+esbg+skS3M9e54UuR3iCeIDMXCLTsAH8hTSzDQU/X6/6t3eYkOKoZSef2PlU6U1XQ==", + "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "requires": { "extend-shallow": "^2.0.1", "is-number": "^3.0.0", @@ -6623,7 +6621,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -6633,7 +6631,7 @@ "filter-obj": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==" + "integrity": "sha1-mzERErxsYSehbgFsbF1/GeCAXFs=" }, "finalhandler": { "version": "1.2.0", @@ -6667,7 +6665,7 @@ "find": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/find/-/find-0.1.7.tgz", - "integrity": "sha512-jPrupTOe/pO//3a9Ty2o4NqQCp0L46UG+swUnfFtdmtQVN8pEltKpAqR7Nuf6vWn0GBXx5w+R1MyZzqwjEIqdA==", + "integrity": "sha1-yGyHrxqxjyIrvjjeyGy8dg0Wpvs=", "requires": { "traverse-chain": "~0.1.0" } @@ -6675,7 +6673,7 @@ "find-cache-dir": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-1.0.0.tgz", - "integrity": "sha512-46TFiBOzX7xq/PcSWfFwkyjpemdRnMe31UQF+os0y+1W3k95f6R4SEt02Hj4p3X0Mir9gfrkmOtshFidS0VPUg==", + "integrity": "sha1-kojj6ePMN0hxfTnq3hfPcfww7m8=", "dev": true, "requires": { "commondir": "^1.0.1", @@ -6800,7 +6798,7 @@ "fluent-ffmpeg": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.2.tgz", - "integrity": "sha512-IZTB4kq5GK0DPp7sGQ0q/BWurGHffRtQQwVkiqDgeO6wYJLLV5ZhgNOQ65loZxxuPMKZKZcICCUnaGtlxBiR0Q==", + "integrity": "sha1-yVLeIkD4EuvaCqgAbXd27irPfXQ=", "requires": { "async": ">=0.2.9", "which": "^1.1.1" @@ -6867,7 +6865,7 @@ "for-each-property": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/for-each-property/-/for-each-property-0.0.4.tgz", - "integrity": "sha512-xYs28PM0CKXETFzuGC6ZooH0voZlsSDZwidJcy92flQJi3PK7i3gZx23xHXCPOaD4zmet3bDo+wS7E7SujrlCw==", + "integrity": "sha1-z6hXrsFCLh0Sb/CHhPz2Jim8g/Y=", "requires": { "get-prototype-chain": "^1.0.1" } @@ -6875,7 +6873,7 @@ "for-each-property-deep": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/for-each-property-deep/-/for-each-property-deep-0.0.3.tgz", - "integrity": "sha512-qzP8QkODWVVRPpWiBZacSbBl67cTTWoBfxMG0wE46AsS1yl7qv05sGN+dHvD4s4tnvl/goe6Sp4qBI+rlVBgNg==", + "integrity": "sha1-MTCaSvw4qcygbxsiP1PWSm0IP60=", "requires": { "for-each-property": "0.0.4" } @@ -6883,12 +6881,12 @@ "for-in": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha512-7EwmXrOjyL+ChxMhmG5lnW9MPt1aIeZEwKhQzoBUdTV0N3zuwWDZYVJatDvZ2OyzPUvdIAZDsCetk3coyMfcnQ==" + "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=" }, "forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==" + "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=" }, "fork-ts-checker-webpack-plugin": { "version": "1.6.0", @@ -6942,7 +6940,7 @@ "fragment-cache": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/fragment-cache/-/fragment-cache-0.2.1.tgz", - "integrity": "sha512-GMBAbW9antB8iZRHLoGw0b3HANt57diZYFO/HL1JGIC1MjKrdmhxvrJbupnVvpys0zsz7yBApXdQyfepKly2kA==", + "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "requires": { "map-cache": "^0.2.2" } @@ -6950,12 +6948,12 @@ "fresh": { "version": "0.5.2", "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", - "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==" + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" }, "from2": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/from2/-/from2-2.3.0.tgz", - "integrity": "sha512-OMcX/4IC/uqEPVgGeyfN22LJk6AZrMkRZHxcHBMBvHScDGgwTm2GT2Wkgtocyd3JfZffjj2kYUDXXII0Fk9W0g==", + "integrity": "sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -7022,7 +7020,7 @@ "fs-write-stream-atomic": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz", - "integrity": "sha512-gehEzmPn2nAwr39eay+x3X34Ra+M2QlVUTLhkXPjWdeO8RF9kszk116avgBJM3ZyNHgHXBNx+VmPaFC36k0PzA==", + "integrity": "sha1-tH31NJPvkR33VzHnCp3tAYnbQMk=", "dev": true, "requires": { "graceful-fs": "^4.1.2", @@ -7051,7 +7049,7 @@ "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { "version": "1.2.13", @@ -7226,7 +7224,7 @@ "get-func-name": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", - "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==" + "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=" }, "get-intrinsic": { "version": "1.1.2", @@ -7246,12 +7244,12 @@ "get-prototype-chain": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-prototype-chain/-/get-prototype-chain-1.0.1.tgz", - "integrity": "sha512-2m7WZ0jveIg/dAbCbpUxEToaJ8Dmti5EkgDP8YM3UpHUT6SAORjE2odP8XQGNVGXMHi8q8cCCoy3HTByTaTVTw==" + "integrity": "sha1-oXGhFeoeSQbG7ThDofABwYUQQW8=" }, "get-stdin": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", - "integrity": "sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==" + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=" }, "get-stream": { "version": "6.0.1", @@ -7270,12 +7268,12 @@ "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", - "integrity": "sha512-Ln0UQDlxH1BapMu3GPtf7CuYNwRZf2gwCuPqbyG6pB8WfmFpzqcy4xtAaAMUhnNqjMKTiCPZG2oMT3YSx8U2NA==" + "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==", + "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "requires": { "assert-plus": "^1.0.0" } @@ -7283,7 +7281,7 @@ "github-from-package": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", - "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==" + "integrity": "sha1-l/tdlr/eiXMxPyDoKI75oWf6ZM4=" }, "glob": { "version": "7.2.3", @@ -7301,7 +7299,7 @@ "glob-parent": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-3.1.0.tgz", - "integrity": "sha512-E8Ak/2+dZY6fnzlR7+ueWvhsH1SjHr4jjss4YS/h4py44jY9MhK/VFdaZJAWDz6BbL21KeteKxFSFpq8OS5gVA==", + "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "requires": { "is-glob": "^3.1.0", "path-dirname": "^1.0.0" @@ -7310,7 +7308,7 @@ "is-glob": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-3.1.0.tgz", - "integrity": "sha512-UFpDDrPgM6qpnFNI+rh/p3bUaq9hKLZN8bMUWzxmcnZVS3omf4IPK+BrewlnWjO1WmUsMYuSjKh4UJuV4+Lqmw==", + "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "requires": { "is-extglob": "^2.1.0" } @@ -7326,7 +7324,7 @@ "global-dirs": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-0.1.1.tgz", - "integrity": "sha512-NknMLn7F2J7aflwFOlGdNIuCDpN3VGoSoB+aap3KABFWbHVn1TCgFC+np23J8W2BiZbjfEw3BFBycSMv1AFblg==", + "integrity": "sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU=", "requires": { "ini": "^1.3.4" } @@ -7339,7 +7337,7 @@ "globby": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/globby/-/globby-7.1.1.tgz", - "integrity": "sha512-yANWAN2DUcBtuus5Cpd+SKROzXHs2iVXFZt/Ykrfz6SAXqacLX25NZpltE+39ceMexYF4TtEadjuSTw8+3wX4g==", + "integrity": "sha1-+yzP+UAfhgCUXfral0QMypcrhoA=", "dev": true, "requires": { "array-union": "^1.0.1", @@ -7353,7 +7351,7 @@ "pify": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", "dev": true } } @@ -7394,7 +7392,7 @@ "golden-layout": { "version": "1.5.9", "resolved": "https://registry.npmjs.org/golden-layout/-/golden-layout-1.5.9.tgz", - "integrity": "sha512-iBXDQCXOTgUEQJo96zPbjDoy5bRIk9XW5l+q+pDgLnIyReqaa1aiQctNud4epsskyLt952BG521dew5Z1liSxA==", + "integrity": "sha1-o5vB9qZ+b4hreXwBbdkk6UJrp38=", "requires": { "jquery": "*" } @@ -7402,7 +7400,7 @@ "good-listener": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", - "integrity": "sha512-goW1b+d9q/HIwbVYZzZ6SsTr4IgE+WA44A0GmPIQstuOrgsFcT7VEJ48nmr9GaRtNu0XTKacFLGnBPAM6Afouw==", + "integrity": "sha1-1TswzfkxPf+33JoNR3CWqm0UXFA=", "requires": { "delegate": "^3.1.2" } @@ -7569,7 +7567,7 @@ "har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", - "integrity": "sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==" + "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=" }, "har-validator": { "version": "5.1.5", @@ -7591,7 +7589,7 @@ "has-ansi": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "requires": { "ansi-regex": "^2.0.0" }, @@ -7599,7 +7597,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" } } }, @@ -7619,19 +7617,19 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" } } }, "has-cors": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz", - "integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA==" + "integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk=" }, "has-flag": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=" }, "has-property-descriptors": { "version": "1.0.0", @@ -7657,12 +7655,12 @@ "has-unicode": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", - "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", - "integrity": "sha512-IBXk4GTsLYdQ7Rvt+GRBrFSVEkmuOUy4re0Xjd9kJSUQpnTrWR4/y9RpfexN9vkAPMFuQoeWKwqzPozRTlasGw==", + "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "requires": { "get-value": "^2.0.6", "has-values": "^1.0.0", @@ -7672,7 +7670,7 @@ "has-values": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-values/-/has-values-1.0.0.tgz", - "integrity": "sha512-ODYZC64uqzmtfGMEAX/FvZiRyWLpAC3vYnNunURUnkGVTS+mI0smVsWaPydRBsE3g+ok7h960jChO8mFcWlHaQ==", + "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "requires": { "is-number": "^3.0.0", "kind-of": "^4.0.0" @@ -7686,7 +7684,7 @@ "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", - "integrity": "sha512-24XsCxmEbRwEDbz/qz3stgin8TTzZ1ESR56OMCN0ujYg+vRutNSiOj9bHH9u85DKgXguraugV5sFuvbD4FW/hw==", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "requires": { "is-buffer": "^1.1.5" } @@ -7719,7 +7717,7 @@ "hpack.js": { "version": "2.1.6", "resolved": "https://registry.npmjs.org/hpack.js/-/hpack.js-2.1.6.tgz", - "integrity": "sha512-zJxVehUdMGIKsRaNt7apO2Gqp0BdqW5yaiGHXXmbpvxgBYVZnAql+BJb4RO5ad2MgpbZKn5G6nMnegrH1FcNYQ==", + "integrity": "sha1-h3dMCUnlE/QuhFdbPEVoH63ioLI=", "dev": true, "requires": { "inherits": "^2.0.1", @@ -7748,7 +7746,7 @@ "html": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/html/-/html-1.0.0.tgz", - "integrity": "sha512-lw/7YsdKiP3kk5PnR1INY17iJuzdAtJewxr14ozKJWbbR97znovZ0mh+WEMZ8rjc3lgTK+ID/htTjuyGKB52Kw==", + "integrity": "sha1-pUT6nqVJK/s6LMqCEKEL57WvH2E=", "requires": { "concat-stream": "^1.4.7" } @@ -7826,7 +7824,7 @@ "http-browserify": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/http-browserify/-/http-browserify-1.7.0.tgz", - "integrity": "sha512-Irf/LJXmE3cBzU1eaR4+NEX6bmVLqt1wkmDiA7kBwH7zmb0D8kBAXsDmQ88hhj/qv9iEZKlyGx/hrMcFi8sOHw==", + "integrity": "sha1-M3la3nLfiKz7/TZ3PO/tp2RzWyA=", "requires": { "Base64": "~0.2.0", "inherits": "~2.0.1" @@ -7840,7 +7838,7 @@ "http-deceiver": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/http-deceiver/-/http-deceiver-1.2.7.tgz", - "integrity": "sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==", + "integrity": "sha1-+nFolEq5pRnTN8sL7HKE3D5yPYc=", "dev": true }, "http-errors": { @@ -7887,7 +7885,7 @@ "http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", - "integrity": "sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==", + "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "requires": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", @@ -7906,12 +7904,12 @@ "https": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https/-/https-1.0.0.tgz", - "integrity": "sha512-4EC57ddXrkaF0x83Oj8sM6SLQHAWXw90Skqu2M4AEWENZ3F02dFJE/GARA8igO79tcgYqGrD7ae4f5L3um2lgg==" + "integrity": "sha1-PDfHrhqO65ZpBKKtHpdaGUt+06Q=" }, "https-browserify": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", - "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" + "integrity": "sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM=" }, "https-proxy-agent": { "version": "5.0.1", @@ -7968,7 +7966,7 @@ "icss-replace-symbols": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/icss-replace-symbols/-/icss-replace-symbols-1.1.0.tgz", - "integrity": "sha512-chIaY3Vh2mh2Q3RGXttaDIzeiPvaVXJ+C4DAh/w3c37SKZ/U6PGMmuicR2EQQp9bKG8zLMCl7I+PtIoOOPp8Gg==", + "integrity": "sha1-Bupvg2ead0njhs/h/oEq5dsiPe0=", "dev": true }, "icss-utils": { @@ -7988,7 +7986,7 @@ "iferr": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/iferr/-/iferr-0.1.5.tgz", - "integrity": "sha512-DUNFN5j7Tln0D+TxzloUjKB+CtVu6myn0JEFak6dG18mNt9YkQ6lzGCdafwofISZ1lLF3xRHJ98VKy9ynkcFaA==", + "integrity": "sha1-xg7taebY/bazEEofy8ocGS3FtQE=", "dev": true }, "ignore": { @@ -8000,7 +7998,7 @@ "ignore-by-default": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==" + "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, "iink-js": { "version": "1.5.4", @@ -8044,7 +8042,7 @@ "image-size-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/image-size-stream/-/image-size-stream-1.1.0.tgz", - "integrity": "sha512-N505B5FSy2Xf5l/Haef+99TwfJqTu40hnU560+rC0Cm6cxtwVz2yRFh9WpOk1YEjfv3dI0PgVYAH0hmXQmjDcw==", + "integrity": "sha1-Ivou2mbG31AQh0bacUkmSy0l+Gs=", "requires": { "image-size": "github:netroy/image-size#da2c863807a3e9602617bdd357b0de3ab4a064c1", "readable-stream": "^1.0.33", @@ -8058,12 +8056,12 @@ "isarray": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==" + "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=" }, "readable-stream": { "version": "1.1.14", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==", + "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.1", @@ -8081,7 +8079,7 @@ "immediate": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.0.6.tgz", - "integrity": "sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==" + "integrity": "sha1-nbHb0Pr43m++D13V5Wu2BigN5ps=" }, "import-fresh": { "version": "3.3.0", @@ -8095,7 +8093,7 @@ "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", - "integrity": "sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==" + "integrity": "sha1-BWmOPUXIjo1+nZLLBYTnfwlvPkM=" }, "import-local": { "version": "3.1.0", @@ -8109,7 +8107,7 @@ "imurmurhash": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", - "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=" }, "in-publish": { "version": "2.0.1", @@ -8119,7 +8117,7 @@ "indent-string": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-2.1.0.tgz", - "integrity": "sha512-aqwDFWSgSgfRaEwao5lg5KEcVd/2a+D1rvoG7NdilmYz0NwRk6StWpWdz/Hpk34MKPpx7s8XxUqimfcQK6gGlg==", + "integrity": "sha1-ji1INIdCEhtKghi3oTfppSBJ3IA=", "requires": { "repeating": "^2.0.0" } @@ -8127,12 +8125,12 @@ "indexof": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/indexof/-/indexof-0.0.1.tgz", - "integrity": "sha512-i0G7hLJ1z0DE8dsqJa2rycj9dBmNKgXBvotXtZYXakU9oivfB9Uj2ZBC27qqef2U58/ZLwalxa1X/RDCdkHtVg==" + "integrity": "sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10=" }, "inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { "once": "^1.3.0", "wrappy": "1" @@ -8149,7 +8147,7 @@ "camelcase": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-4.1.0.tgz", - "integrity": "sha512-FxAv7HpHrXbh3aPo4o2qxHay2lkLY3x5Mw3KeE4KQE8ysVfziWeRZDwcjauvwBSGEC/nXUPzZy8zeh4HokqOnw==" + "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" } } }, @@ -8166,7 +8164,7 @@ "inline-style-prefixer": { "version": "3.0.8", "resolved": "https://registry.npmjs.org/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz", - "integrity": "sha512-ne8XIyyqkRaNJ1JfL1NYzNdCNxq+MCBQhC8NgOQlzNm2vv3XxlP0VSLQUbSRCF6KPEoveCVEpayHoHzcMyZsMQ==", + "integrity": "sha1-hVG45bTVcyROZqNLBPfTIHaitTQ=", "requires": { "bowser": "^1.7.3", "css-in-js-utils": "^2.0.0" @@ -8175,7 +8173,7 @@ "inspect-function": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz", - "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "integrity": "sha1-hdoMUli8TDMK4yg7Z0fgdZ2QpjU=", "requires": { "split-skip": "0.0.1", "unpack-string": "0.0.2" @@ -8184,7 +8182,7 @@ "split-skip": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz", - "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA=" } } }, @@ -8207,7 +8205,7 @@ "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -8258,7 +8256,7 @@ "inspect-function": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/inspect-function/-/inspect-function-0.2.2.tgz", - "integrity": "sha512-becs5gzcHwPrlHawscYkyQ/ShiOiosrXPhA5RVZ3qyWH4aWdD52RnMfXq/dwQXciHwiieD8aIPwdIWYv6eL+sQ==", + "integrity": "sha1-hdoMUli8TDMK4yg7Z0fgdZ2QpjU=", "requires": { "split-skip": "0.0.1", "unpack-string": "0.0.2" @@ -8267,14 +8265,14 @@ "split-skip": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz", - "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA=" } } }, "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -8285,14 +8283,14 @@ "split-skip": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz", - "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg==" + "integrity": "sha1-2J2Iu9L3Pka1FYqjcKVhIk6A1GE=" } } }, "split-skip": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.1.tgz", - "integrity": "sha512-7dkvq+gofI4M8zx4iZnEZ3O1s7FP4Y/iaIDHJh5RyWrs8idcPauFi2OZe3TBi36fLvR2j5z3kSzVtz6IhPdncQ==" + "integrity": "sha1-gK2ONumOV2RUzDtmfB3SXYZejwA=" } } }, @@ -8363,7 +8361,7 @@ "ip-regex": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/ip-regex/-/ip-regex-2.1.0.tgz", - "integrity": "sha512-58yWmlHpp7VYfcdTwMTvwMmqx/Elfxjd9RXTDyMsbL7lLWmhMylLEqiYVLKuLzOZqVgiWXD9MfR62Vv89VRxkw==", + "integrity": "sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=", "dev": true }, "ipaddr.js": { @@ -8380,7 +8378,7 @@ "is-accessor-descriptor": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "requires": { "kind-of": "^3.0.2" }, @@ -8393,7 +8391,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -8412,7 +8410,7 @@ "is-arrayish": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==" + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=" }, "is-bigint": { "version": "1.0.4", @@ -8425,7 +8423,7 @@ "is-binary-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-1.0.1.tgz", - "integrity": "sha512-9fRVlXc0uCxEDj1nQzaWONSpbTfx0FmJfzHF7pwlI8DkWGoHBBea4Pg5Ky0ojwwxQmnSifgbKkI06Qv0Ljgj+Q==", + "integrity": "sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg=", "requires": { "binary-extensions": "^1.0.0" } @@ -8468,7 +8466,7 @@ "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "requires": { "kind-of": "^3.0.2" }, @@ -8481,7 +8479,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -8516,12 +8514,12 @@ "is-directory": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz", - "integrity": "sha512-yVChGzahRFvbkscn2MlwGismPO12i9+znNruC5gVEntG3qu0xQMzsGg/JFbrsqDOHtHFPci+V5aP5T9I+yeKqw==" + "integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=" }, "is-expression": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-3.0.0.tgz", - "integrity": "sha512-vyMeQMq+AiH5uUnoBfMTwf18tO3bM6k1QXBE9D6ueAAquEfCZe3AJPtud9g6qS0+4X8xA7ndpZiDyeb2l2qOBw==", + "integrity": "sha1-Oayqa+f9HzRx3ELHQW5hwkMXrJ8=", "requires": { "acorn": "~4.0.2", "object-assign": "^4.0.1" @@ -8530,7 +8528,7 @@ "acorn": { "version": "4.0.13", "resolved": "https://registry.npmjs.org/acorn/-/acorn-4.0.13.tgz", - "integrity": "sha512-fu2ygVGuMmlzG8ZeRJ0bvR41nsAkxxhbyk8bZ1SS521Z7vmgJFTQQlfz/Mp/nJexGBz+v8sC9bM6+lNgskt4Ug==" + "integrity": "sha1-EFSVrlNh1pe9GVyCUZLhrX8lN4c=" } } }, @@ -8542,7 +8540,7 @@ "is-extglob": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=" }, "is-finite": { "version": "1.1.0", @@ -8552,7 +8550,7 @@ "is-fullwidth-code-point": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=" }, "is-generator-function": { "version": "1.0.10", @@ -8573,12 +8571,12 @@ "is-in-browser": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/is-in-browser/-/is-in-browser-1.1.3.tgz", - "integrity": "sha512-FeXIBgG/CPGd/WUxuEyvgGTEfwiG9Z4EKGxjNMRqviiIIfsmgrpnHLffEDdwUHqNva1VEW91o3xBT/m8Elgl9g==" + "integrity": "sha1-Vv9NtoOgeMYILrldrX3GLh0E+DU=" }, "is-installed-globally": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.1.0.tgz", - "integrity": "sha512-ERNhMg+i/XgDwPIPF3u24qpajVreaiSuvpb1Uu0jugw7KKcxGyCX8cgp8P5fwTmAuXku6beDHHECdKArjlg7tw==", + "integrity": "sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA=", "requires": { "global-dirs": "^0.1.0", "is-path-inside": "^1.0.0" @@ -8592,12 +8590,12 @@ "is-npm": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", - "integrity": "sha512-9r39FIr3d+KD9SbX0sfMsHzb5PP3uimOiwr3YupUaUFG4W0l1U57Rx3utpttV7qz5U3jmrO5auUa04LU9pyHsg==" + "integrity": "sha1-8vtjpl5JBbQGyGBydloaTceTufQ=" }, "is-number": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", - "integrity": "sha512-4cboCqIpliH+mAvFNegjZQ4kgKc3ZUhQVr3HvWbSh5q3WH2v82ct+T2Y1hdU5Gdtorx/cLifQjqCbL7bpznLTg==", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "requires": { "kind-of": "^3.0.2" }, @@ -8610,7 +8608,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -8628,7 +8626,7 @@ "is-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-obj/-/is-obj-1.0.1.tgz", - "integrity": "sha512-l4RyHgRqGN4Y3+9JHVrNqO+tN0rV5My76uW5/nuO4K1b6vw5G8d/cmFjP9tRfEsdhZNt0IFdZuK/c2Vr4Nb+Qg==" + "integrity": "sha1-PkcprB9f3gJc19g6iW2rn09n2w8=" }, "is-path-cwd": { "version": "2.2.0", @@ -8659,7 +8657,7 @@ "is-path-inside": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-1.0.1.tgz", - "integrity": "sha512-qhsCR/Esx4U4hg/9I19OVUAJkGWtjRYHMRgUMZE2TDdj+Ag+kttZanLupfddNyglzz50cUlmWzUaI37GDfNx/g==", + "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "requires": { "path-is-inside": "^1.0.1" } @@ -8680,7 +8678,7 @@ "is-redirect": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-redirect/-/is-redirect-1.0.0.tgz", - "integrity": "sha512-cr/SlUEe5zOGmzvj9bUyC4LVvkNVAXu4GytXLNMr1pny+a65MpQ9IJzFHD5vi7FyJgb4qt27+eS3TuQnqB+RQw==" + "integrity": "sha1-HQPd7VO9jbDzDCbk+V02/HyH3CQ=" }, "is-regex": { "version": "1.1.4", @@ -8715,7 +8713,7 @@ "is-stream": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz", - "integrity": "sha512-uQPm8kcs47jx38atAcWTVxyltQYoPT68y9aWYdV6yWXSyW8mzSat0TL6CiWdZeCdF3KrAvpVtnHbTv4RN+rqdQ==" + "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=" }, "is-string": { "version": "1.0.7", @@ -8748,12 +8746,12 @@ "is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==" + "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=" }, "is-utf8": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=" }, "is-weakref": { "version": "1.0.2", @@ -8776,28 +8774,28 @@ "is-wsl": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-1.1.0.tgz", - "integrity": "sha512-gfygJYZ2gLTDlmbWMI0CE2MwnFzSN/2SZfkMlItC4K/JBlsWVDB0bO6XhqcY13YXE7iMcAJnzTCJjPiTeJJ0Mw==", + "integrity": "sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0=", "dev": true }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, "isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=" }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", - "integrity": "sha512-WhB9zCku7EGTj/HQQRz5aUQEUeoQZH2bWcltRErOpymJ4boYE6wL9Tbr23krRPSZ+C5zqNSrSw+Cc7sZZ4b7vg==" + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "isomorphic-fetch": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz", - "integrity": "sha512-9c4TNAKYXM5PRyVcwUZrF3W09nQ+sO7+jydgs4ZGW9dhsLG2VOlISJABombdQqQRXCwuYG3sYV/puGf5rp0qmA==", + "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=", "requires": { "node-fetch": "^1.0.1", "whatwg-fetch": ">=0.10.0" @@ -8806,7 +8804,7 @@ "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha512-Yljz7ffyPbrLpLngrMtZ7NduUgVvi6wG9RJ9IUcyCd59YQ911PBJphODUcbOVbqYfxe1wuYf/LJ8PauMRwsM/g==" + "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, "its-set": { "version": "1.2.3", @@ -8863,7 +8861,7 @@ "js-stringify": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/js-stringify/-/js-stringify-1.0.2.tgz", - "integrity": "sha512-rtS5ATOo2Q5k1G+DADISilDA6lv79zIiwFd6CcjuIxGKLFm5C+RLImRscVap9k55i+MOZwgliw+NejvkLuGD5g==" + "integrity": "sha1-Fzb939lyTyijaCrcYjCufk6Weds=" }, "js-tokens": { "version": "4.0.0", @@ -8882,7 +8880,7 @@ "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha512-UVU9dibq2JcFWxQPA6KCqj5O42VOmAY3zQUfEKxU0KpTGXwNoCjkX1e13eHNvw/xPynt6pU0rZ1htjWTNTSXsg==" + "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=" }, "jsdom": { "version": "15.2.1", @@ -9002,7 +9000,7 @@ "json-css": { "version": "1.5.6", "resolved": "https://registry.npmjs.org/json-css/-/json-css-1.5.6.tgz", - "integrity": "sha512-B/0T0OxZH9tSb93tXV6VOYtXqrPz/Vgz2QrCT/4NXen8HGElYkYr9V+8IrSVTMj/ftxa8cG1kcu7f3iAMlaFlQ==" + "integrity": "sha1-65ZPg0ouTqobwvaY/12wB6JsfAA=" }, "json-parse-better-errors": { "version": "1.0.2", @@ -9027,7 +9025,7 @@ "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==" + "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=" }, "json5": { "version": "1.0.1", @@ -9158,7 +9156,7 @@ "jstransformer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/jstransformer/-/jstransformer-1.0.0.tgz", - "integrity": "sha512-C9YK3Rf8q6VAPDCCU9fnqo3mAfOH6vUGnMcP4AQAYIEpWtfGLpwOTmZ+igtdK5y+VvI2n3CyYSzy4Qh34eq24A==", + "integrity": "sha1-7Yvwkh4vPx7U1cGkT2hwntJHIsM=", "requires": { "is-promise": "^2.0.0", "promise": "^7.0.1" @@ -9256,7 +9254,7 @@ "klaw": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha512-TED5xi9gGQjGpNnvRWknrwAB1eL5GciPfVFOt3Vk1OJCVDQbzuSfrF3hkUQKlsgKrG1F+0t5W0m+Fje1jIt8rw==", + "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", "requires": { "graceful-fs": "^4.1.9" } @@ -9264,7 +9262,7 @@ "latest-version": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/latest-version/-/latest-version-3.1.0.tgz", - "integrity": "sha512-Be1YRHWWlZaSsrz2U+VInk+tO0EwLIyV+23RhWLINJYwg/UIikxjlj3MhH37/6/EDCAusjajvMkMMUXRaMWl/w==", + "integrity": "sha1-ogU4P+oyKzO1rjsYq+4NwvNW7hU=", "requires": { "package-json": "^4.0.0" } @@ -9272,7 +9270,7 @@ "lazy-cache": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/lazy-cache/-/lazy-cache-1.0.4.tgz", - "integrity": "sha512-RE2g0b5VGZsOCFOCgP7omTRYFqydmZkBwl5oNnQ1lDYC57uyO9KqNnNVxT7COSHTxrRCWVcAVOcbjk+tvh/rgQ==" + "integrity": "sha1-odePw6UEdMuAhF07O24dpJpEbo4=" }, "lazystream": { "version": "1.0.1", @@ -9301,7 +9299,7 @@ "levn": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", - "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", "dev": true, "requires": { "prelude-ls": "~1.1.2", @@ -9324,7 +9322,7 @@ "load-json-file": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha512-cy7ZdNRXdablkXYNI049pthVeXFurRyb9+hA/dZzerZ0pGTx42z+y+ssxBaVV2l70t1muq5IdKhn4UtcoGUY9A==", + "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", "requires": { "graceful-fs": "^4.1.2", "parse-json": "^2.2.0", @@ -9336,7 +9334,7 @@ "parse-json": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha512-QR/GGaKCkhwk1ePQNYDRKYZ3mwU9ypsKhB0XyFnLQdomyEqk3e8wpW3V5Jp88zbxK4n5ST1nqo+g9juTpownhQ==", + "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", "requires": { "error-ex": "^1.2.0" } @@ -9381,22 +9379,22 @@ "lodash._getnative": { "version": "3.9.1", "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha512-RrL9VxMEPyDMHOd9uFbvMe8X55X16/cGM5IgOKgRElQZutpX89iS6vwl64duTV1/16w5JY7tuFNXqoekmh1EmA==" + "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=" }, "lodash.chunk": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.chunk/-/lodash.chunk-4.2.0.tgz", - "integrity": "sha512-ZzydJKfUHJwHa+hF5X66zLFCBrWn5GeF28OHEr4WVWtNDXlQ/IjWKPBiikqKo2ne0+v6JgCgJ0GzJp8k8bHC7w==" + "integrity": "sha1-ZuXOH3btJ7QwPYxlEujRIW6BBrw=" }, "lodash.curry": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.curry/-/lodash.curry-4.1.1.tgz", - "integrity": "sha512-/u14pXGviLaweY5JI0IUzgzF2J6Ne8INyzAZjImcryjgkZ+ebruBxy2/JaOOkTqScddcYtakjhSaeemV8lR0tA==" + "integrity": "sha1-JI42By7ekGUB11lmIAqG2riyMXA=" }, "lodash.debounce": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-3.1.1.tgz", - "integrity": "sha512-lcmJwMpdPAtChA4hfiwxTtgFeNAaow701wWUgVUqeD0XJF7vMXIN+bu/2FJSGxT0NUbZy9g9VFrlOFfPjl+0Ew==", + "integrity": "sha1-gSIRw3ipTMKdWqTjNGzwv846ffU=", "requires": { "lodash._getnative": "^3.0.0" } @@ -9404,42 +9402,42 @@ "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", - "integrity": "sha512-qjxPLHd3r5DnsdGacqOMU6pb/avJzdh9tFX2ymgoZE27BmjXrNy/y4LoaiTeAb+O3gL8AfpJGtqfX/ae2leYYQ==" + "integrity": "sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=" }, "lodash.difference": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.difference/-/lodash.difference-4.5.0.tgz", - "integrity": "sha512-dS2j+W26TQ7taQBGN8Lbbq04ssV3emRw4NY58WErlTO29pIqS0HmoT5aJ9+TUQ1N3G+JOZSji4eugsWwGp9yPA==" + "integrity": "sha1-nMtOUF1Ia5FlE0V3KIWi3yf9AXw=" }, "lodash.flatten": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", - "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" + "integrity": "sha1-8xwiIlqWMtK7+OSt2+8kCqdlph8=" }, "lodash.flow": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/lodash.flow/-/lodash.flow-3.5.0.tgz", - "integrity": "sha512-ff3BX/tSioo+XojX4MOsOMhJw0nZoUEF011LX8g8d3gvjVbxd89cCio4BCXronjxcTUIJUoqKEUA+n4CqvvRPw==" + "integrity": "sha1-h79AKSuM+D5OjOGjrkIJ4gBxZ1o=" }, "lodash.get": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==" + "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=" }, "lodash.isequal": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz", - "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==" + "integrity": "sha1-QVxEePK8wwEgwizhDtMib30+GOA=" }, "lodash.isplainobject": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" + "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=" }, "lodash.memoize": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", - "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==" + "integrity": "sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=" }, "lodash.merge": { "version": "4.6.2", @@ -9449,23 +9447,23 @@ "lodash.padend": { "version": "4.6.1", "resolved": "https://registry.npmjs.org/lodash.padend/-/lodash.padend-4.6.1.tgz", - "integrity": "sha512-sOQs2aqGpbl27tmCS1QNZA09Uqp01ZzWfDUoD+xzTii0E7dSQfRKcRetFwa+uXaxaqL+TKm7CgD2JdKP7aZBSw==" + "integrity": "sha1-U8y6BH0G4VjTEfRdpiX05J5vFm4=" }, "lodash.sortby": { "version": "4.7.0", "resolved": "https://registry.npmjs.org/lodash.sortby/-/lodash.sortby-4.7.0.tgz", - "integrity": "sha512-HDWXG8isMntAyRF5vZ7xKuEvOhT4AhlRt/3czTSjvGUxjYCBVRQY48ViDHyfYz9VIoBkW4TMGQNapx+l3RUwdA==", + "integrity": "sha1-7dFMgk4sycHgsKG0K7UhBRakJDg=", "dev": true }, "lodash.throttle": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==" + "integrity": "sha1-wj6RtxAkKscMN/HhzaknTMOb8vQ=" }, "lodash.union": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.union/-/lodash.union-4.6.0.tgz", - "integrity": "sha512-c4pB2CdGrGdjMKYLA+XiRDO7Y0PRQbm/Gzg8qMj+QH+pFVAoTp5sBpO0odL3FjoPCGjK96p6qsP+yQoiLoOBcw==" + "integrity": "sha1-SLtQiECfFvGCFmZkHETdGqrjzYg=" }, "log-symbols": { "version": "2.2.0", @@ -9493,7 +9491,7 @@ "longest": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz", - "integrity": "sha512-k+yt5n3l48JU4k8ftnKG6V7u32wyH2NfKzeMto9F/QRE0amxy/LayxwlvjjkZEIzqR+19IrtFO8p5kB9QaYUFg==" + "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc=" }, "loose-envify": { "version": "1.4.0", @@ -9506,7 +9504,7 @@ "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", - "integrity": "sha512-RPNliZOFkqFumDhvYqOaNY4Uz9oJM2K9tC6JWsJJsNdhuONW4LQHRBpb0qf4pJApVffI5N39SwzWZJuEhfd7eQ==", + "integrity": "sha1-W0b4AUft7leIcPCG0Eghz5mOVR8=", "requires": { "currently-unhandled": "^0.4.1", "signal-exit": "^3.0.0" @@ -9555,7 +9553,7 @@ "find-up": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", - "integrity": "sha512-NWzkk0jSJtTt08+FBFMvXoeZnOJD+jTtsRmBYbAIzJdX6l7dLgR7CTubCM5/eDdPUBvLCeVasP1brfVR/9/EZQ==", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "requires": { "locate-path": "^2.0.0" } @@ -9563,7 +9561,7 @@ "locate-path": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", - "integrity": "sha512-NCI2kiDkyR7VeEKm27Kda/iQHyKJe1Bu0FlTbYp3CqJu+9IFe9bLyAjMxf5ZDDbEg+iMPzB5zYyUTSm8wVTKmA==", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "requires": { "p-locate": "^2.0.0", "path-exists": "^3.0.0" @@ -9580,7 +9578,7 @@ "p-locate": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", - "integrity": "sha512-nQja7m7gSKuewoVRen45CtVfODR3crN3goVQ0DDZ9N3yHxgpkuBhZqsaiotSQRrADUrne346peY7kT3TSACykg==", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "requires": { "p-limit": "^1.1.0" } @@ -9588,7 +9586,7 @@ "p-try": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", - "integrity": "sha512-U1etNYuMJoIz3ZXSrrySFjsXQTWOx2/jdi86L+2pRvph/qMKL6sbcCYdH23fqsbm8TH2Gn0OybpT4eSFlCVHww==" + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=" } } }, @@ -9616,17 +9614,17 @@ "map-cache": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha512-8y/eV9QQZCiyn1SprXSrCmqJN0yNRATe+PO8ztwqrvrbdRLA3eYJF0yaR0YayLWkMbsQSKWS9N2gPcGEc4UsZg==" + "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=" }, "map-obj": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz", - "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==" + "integrity": "sha1-2TPOuSBdgr3PSIb2dCvcK03qFG0=" }, "map-visit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/map-visit/-/map-visit-1.0.0.tgz", - "integrity": "sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==", + "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "requires": { "object-visit": "^1.0.0" } @@ -9671,7 +9669,7 @@ "math-codegen": { "version": "0.3.5", "resolved": "https://registry.npmjs.org/math-codegen/-/math-codegen-0.3.5.tgz", - "integrity": "sha512-SsFYMv33FxMKYxI1PBiaZT+8AeDITK+k/PKhbHNlOPHIz5FIPF4wy78yWqanN6luXdsXENUZgCIC6xH6bfUq1g==", + "integrity": "sha1-R5nuRnfe0Ud2bQA8ykt4ee3UDMo=", "requires": { "extend": "^3.0.0", "mr-parser": "^0.2.1" @@ -9680,7 +9678,7 @@ "mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", - "integrity": "sha512-snSAEwAtwdwBFSor+nVBnWWQtTw67kgAgKMyAIxuz4ZPboy0qkWZmd7BL3lfOXp/INihhRlU1PcfaAtDaRhmzA==", + "integrity": "sha1-vyylaQEAY6w0vNXVKa3Ag3zVPD8=", "requires": { "jquery": "^1.12.3" }, @@ -9688,19 +9686,19 @@ "jquery": { "version": "1.12.4", "resolved": "https://registry.npmjs.org/jquery/-/jquery-1.12.4.tgz", - "integrity": "sha512-UEVp7PPK9xXYSk8xqXCJrkXnKZtlgWkd2GsAQbMRFK6S/ePU2JN5G2Zum8hIVjzR3CpdfSqdqAzId/xd4TJHeg==" + "integrity": "sha1-AeHfuikP5z3rp3zurLD5ui/sngw=" } } }, "max-safe-integer": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/max-safe-integer/-/max-safe-integer-1.0.1.tgz", - "integrity": "sha512-CHZ/Nopqh46UtA0YvLclxj9F95qExLmTnMS5fnYlXvX4zj1RUOC3cPaGEOMhdPE8SSudSQ6wkQUJLA5E/WeL4A==" + "integrity": "sha1-84BgvixWPYwC5tSK85Ei/YO29BA=" }, "media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", - "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==" + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" }, "memfs": { "version": "3.4.4", @@ -9751,12 +9749,12 @@ "memorystream": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz", - "integrity": "sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==" + "integrity": "sha1-htcJCzDORV1j+64S3aUaR93K+bI=" }, "meow": { "version": "3.7.0", "resolved": "https://registry.npmjs.org/meow/-/meow-3.7.0.tgz", - "integrity": "sha512-TNdwZs0skRlpPpCUK25StC4VH+tP5GgeY1HQOOGP+lQ2xtdkN2VtT/5tiX9k3IWpkBPV9b3LsAWXn4GGi/PrSA==", + "integrity": "sha1-cstmi0JSKCkKu/qFaJJYcwioAfs=", "requires": { "camelcase-keys": "^2.0.0", "decamelize": "^1.1.2", @@ -9781,7 +9779,7 @@ "merge-descriptors": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", - "integrity": "sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==" + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" }, "merge-stream": { "version": "2.0.0", @@ -9791,7 +9789,7 @@ "methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", - "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==" + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" }, "microevent.ts": { "version": "0.1.1", @@ -10025,7 +10023,7 @@ "he": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/he/-/he-1.1.1.tgz", - "integrity": "sha512-z/GDPjlRMNOa2XJiB4em8wJpuuBfrFOlYKTZxtpkdr1uPdibHI8rYA3MY0KDObpVyaes0e/aunid/t88ZI2EKA==", + "integrity": "sha1-k0EP0hsAlzUVH4howvJx80J+I/0=", "dev": true }, "minimatch": { @@ -10040,13 +10038,13 @@ "minimist": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha512-miQKw5Hv4NS1Psg2517mV4e4dYNaO3++hjAvLOAzKqZ61rH8NS1SK+vbfBWZ5PY/Me/bEWhUwqMghEW5Fb9T7Q==", + "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", "dev": true }, "mkdirp": { "version": "0.5.1", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==", + "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, "requires": { "minimist": "0.0.8" @@ -10055,7 +10053,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "supports-color": { @@ -10179,7 +10177,7 @@ "move-concurrently": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/move-concurrently/-/move-concurrently-1.0.1.tgz", - "integrity": "sha512-hdrFxZOycD/g6A6SoI2bB5NA/5NEqD0569+S47WZhPvm46sD50ZHdYaFmnua5lndde9rCHGjmfK7Z8BuCt/PcQ==", + "integrity": "sha1-viwAX9oy4LKa8fBdfEszIUxwH5I=", "dev": true, "requires": { "aproba": "^1.1.1", @@ -10240,14 +10238,14 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, "mr-parser": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/mr-parser/-/mr-parser-0.2.1.tgz", - "integrity": "sha512-hug+mpbSSKnH13rFqy3zm+XiG+QTStiDAgMTHK355TIstQE0qBkBtSJsa5YHP94AuarVX9b/4dcebdTRZ9YiEw==" + "integrity": "sha1-hhi5ukF+KOn0OaQcaVtVTq/u2Sc=" }, "ms": { "version": "2.1.1", @@ -10267,7 +10265,7 @@ "multicast-dns-service-types": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/multicast-dns-service-types/-/multicast-dns-service-types-1.1.0.tgz", - "integrity": "sha512-cnAsSVxIDsYt0v7HmC0hWZFwwXSh+E6PgCrREDuN/EsjgLwA5XRmlMHhSiDPrt6HxY1gTivEa/Zh7GtODoLevQ==", + "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, "nan": { @@ -10306,7 +10304,7 @@ "native-or-bluebird": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/native-or-bluebird/-/native-or-bluebird-1.2.0.tgz", - "integrity": "sha512-0SH8UubxDfe382eYiwmd12qxAbiWGzlGZv6CkMA+DPojWa/Y0oH4hE0lRtFfFgJmPQFyKXeB8XxPbZz6TvvKaQ==" + "integrity": "sha1-OcR7/Xgl0fuf+tMiEK4l2q3xAck=" }, "negotiator": { "version": "0.6.3", @@ -10327,7 +10325,7 @@ "nextafter": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/nextafter/-/nextafter-1.0.0.tgz", - "integrity": "sha512-7PO+A89Tll2rSEfyrjtqO0MaI37+nnxBdnQcPypfbEYYuGaJxWGCqaOwQX4a3GHNTS08l1kazuiLEWZniZjMUQ==", + "integrity": "sha1-t9d7U1MQ4+CX5gJauwqQNHfsGjo=", "requires": { "double-bits": "^1.1.0" } @@ -10358,7 +10356,7 @@ "node-ensure": { "version": "0.0.0", "resolved": "https://registry.npmjs.org/node-ensure/-/node-ensure-0.0.0.tgz", - "integrity": "sha512-DRI60hzo2oKN1ma0ckc6nQWlHU69RH6xN0sjQTjMpChPfTYvKZdcQFfdYK2RWbJcKyUizSIy/l8OTGxMAM1QDw==" + "integrity": "sha1-7K52QVDemYYexcgQ/V0Jaxg5Mqc=" }, "node-environment-flags": { "version": "1.0.5", @@ -10447,7 +10445,7 @@ "nopt": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", - "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", + "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=", "requires": { "abbrev": "1" } @@ -10488,7 +10486,7 @@ "semver": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha512-mfmm3/H9+67MCVix1h+IXTpDwL6710LyHuk7+cWC9T1mE0qz4iHhh6r4hU2wrIT9iTsAAC2XQRvfblL028cpLw==" + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=" }, "string-width": { "version": "1.0.2", @@ -10552,12 +10550,12 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==" + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=" }, "ansi-styles": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==" + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=" }, "aproba": { "version": "1.2.0", @@ -10576,7 +10574,7 @@ "chalk": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha512-U3lRVLMSlsCfjqYPbLyVv11M9CPW4I728d6TCKMAOJueEeB9/8o+eSsMnxPJD+Q+K909sdESg7C+tIkoH6on1A==", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "requires": { "ansi-styles": "^2.2.1", "escape-string-regexp": "^1.0.2", @@ -10603,7 +10601,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==" + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" }, "is-fullwidth-code-point": { "version": "1.0.0", @@ -10701,7 +10699,7 @@ "noop-logger": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/noop-logger/-/noop-logger-0.1.1.tgz", - "integrity": "sha512-6kM8CLXvuW5crTxsAtva2YLrRrDaiTIkIePWs9moLHqbFWT94WpNFjwS/5dfLfECg5i/lkmw3aoqVidxt23TEQ==" + "integrity": "sha1-lKKxYzxPExdVMAfYlm/Q6EG2pMI=" }, "nopt": { "version": "5.0.0", @@ -14278,7 +14276,7 @@ "npm-run-path": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz", - "integrity": "sha512-lJxZYlT4DW/bRUtFh1MQIWqmLwQfAxnqWG4HhEdjMlkrJYnJn0Jrr2u3mgxqaWsdiBc76TYkTG/mhrnYTuzfHw==", + "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "requires": { "path-key": "^2.0.0" } @@ -14305,7 +14303,7 @@ "number-is-nan": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha512-4jbtZXNAsfZbAHiiqjLPBiCl16dES1zI4Hpzzxw61Tk+loF+sBDBKx1ICKKKwIqQ7M0mFn1TmkN7euSncWgHiQ==" + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=" }, "nwsapi": { "version": "2.2.0", @@ -14316,7 +14314,7 @@ "oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", - "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE=" }, "oauth-sign": { "version": "0.9.0", @@ -14326,12 +14324,12 @@ "object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", - "integrity": "sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==", + "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "requires": { "copy-descriptor": "^0.1.0", "define-property": "^0.2.5", @@ -14341,7 +14339,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -14354,7 +14352,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -14411,7 +14409,7 @@ "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -14424,7 +14422,7 @@ "object-visit": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", - "integrity": "sha512-GBaMwwAVK9qbQN3Scdo0OyvgPW7l3lnaVMj84uTOZlswkX0KpF6fyDBJhtTthf7pymztoN36/KEr1DyhF96zEA==", + "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "requires": { "isobject": "^3.0.0" } @@ -14454,7 +14452,7 @@ "object.pick": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha512-tqa/UMy/CCoYmj+H5qc07qvSL9dqcs/WZENZ1JbtWBlATP+iVOe778gE6MSijnyCnORzDuX6hU+LA4SZ09YjFQ==", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "requires": { "isobject": "^3.0.1" } @@ -14481,7 +14479,7 @@ "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" } @@ -14542,12 +14540,12 @@ "os-homedir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==" + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" }, "os-tmpdir": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==" + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" }, "osenv": { "version": "0.1.5", @@ -14566,12 +14564,12 @@ "p-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-debounce/-/p-debounce-1.0.0.tgz", - "integrity": "sha512-ttOxn4Yt0hzIsLLqKi/Ry9QRxW+UQKdoWHz7g99Ci57zPkqUU3kbWKAeHuv+HfRLe109acYLUY6kuVCOOqnt4g==" + "integrity": "sha1-y38svu/YegnrqGHhErZ1J+Yh4v0=" }, "p-finally": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==" + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" }, "p-limit": { "version": "2.3.0", @@ -14612,7 +14610,7 @@ "package-json": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha512-q/R5GrMek0vzgoomq6rm9OX+3PQve8sLwTirmK30YB3Cu0Bbt9OX9M/SIUnroN5BGJkzwGsFwDaRGD9EwBOlCA==", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", "requires": { "got": "^6.7.1", "registry-auth-token": "^3.0.1", @@ -14628,7 +14626,7 @@ "got": { "version": "6.7.1", "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha512-Y/K3EDuiQN9rTZhBvPRWMLXIKdeD1Rj0nzunfoi0Yyn5WBEbzxXKU9Ub2X41oZBagVWOBU3MuDonFMgPWQFnwg==", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", "requires": { "create-error-class": "^3.0.0", "duplexer3": "^0.1.4", @@ -14744,7 +14742,7 @@ "pascalcase": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", - "integrity": "sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==" + "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, "passport": { "version": "0.4.1", @@ -14766,7 +14764,7 @@ "passport-local": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-local/-/passport-local-1.0.0.tgz", - "integrity": "sha512-9wCE6qKznvf9mQYYbgJ3sVOHmCWoUNMVFoZzNoznmISbhnNNPhN9xfY3sLmScHMetEJeoY7CXwfhCe7argfQow==", + "integrity": "sha1-H+YyaMkudWBmJkN+O5BmYsFbpu4=", "requires": { "passport-strategy": "1.x.x" } @@ -14786,7 +14784,7 @@ "passport-strategy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/passport-strategy/-/passport-strategy-1.0.0.tgz", - "integrity": "sha512-CB97UUvDKJde2V0KDWWB3lyf6PC3FaZP7YxZ2G8OAtn9p4HI9j9JLP9qjOGZFvyl8uwNT8qM+hGnz/n16NI7oA==" + "integrity": "sha1-tVOaqPwiWj0a0XlHbd8ja0QPUuQ=" }, "path-browserify": { "version": "1.0.1", @@ -14796,27 +14794,27 @@ "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", - "integrity": "sha512-ALzNPpyNq9AqXMBjeymIjFDAkAFH06mHJH/cSBHAgU0s4vfpBn6b2nf8tiRLvagKD8RbTpq2FKTBg7cl9l3c7Q==" + "integrity": "sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA=" }, "path-exists": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", - "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=" }, "path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-is-inside": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", - "integrity": "sha512-DUWJr3+ULp4zXmol/SZkFf3JGsS9/SIv+Y3Rt93/UjPpDpklB5f1er4O3POIbUuUJ3FXgqte2Q7SrU6zAqwk8w==" + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=" }, "path-key": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz", - "integrity": "sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==" + "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=" }, "path-parse": { "version": "1.0.7", @@ -14826,7 +14824,7 @@ "path-to-regexp": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", - "integrity": "sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==" + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" }, "path-type": { "version": "4.0.0", @@ -14841,7 +14839,7 @@ "pause": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/pause/-/pause-0.0.1.tgz", - "integrity": "sha512-KG8UEiEVkR3wGEb4m5yZkVCzigAD+cVEJck2CzYZO37ZGJfctvVptVO192MwrtPhzONn6go8ylnOdMhKqi4nfg==" + "integrity": "sha1-HUCLP9t2kjuVQ9lvtMnf1TXZy10=" }, "pdf-parse": { "version": "1.1.1", @@ -14889,7 +14887,7 @@ "pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==" + "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=" }, "perfect-scrollbar": { "version": "1.5.5", @@ -14899,7 +14897,7 @@ "performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", - "integrity": "sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow==" + "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=" }, "picocolors": { "version": "1.0.0", @@ -14919,12 +14917,12 @@ "pinkie": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha512-MnUuEycAemtSaeFSjXKW/aroV7akBbY+Sv+RkyqFjgAe73F+MR0TBWKBRDkmfWq/HiFmdavfZ1G7h4SPZXaCSg==" + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=" }, "pinkie-promise": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha512-0Gni6D4UcLTbv9c57DfxDGdr41XfgUjqWZu492f0cIGr16zDU06BWP/RAEvOuo7CQ0CNjHaLlM59YJJFm3NWlw==", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "requires": { "pinkie": "^2.0.0" } @@ -14985,7 +14983,7 @@ "plist": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/plist/-/plist-1.2.0.tgz", - "integrity": "sha512-dL9Xc2Aj3YyBnwvCNuHmFl2LWvQacm/HEAsoVwLiuu0POboMChETt5wexpU1P6F6MnibIucXlVsMFFgNUT2IyA==", + "integrity": "sha1-CEtQk93JJQbiWfh0uNmxr7jHlZM=", "requires": { "base64-js": "0.0.8", "util-deprecate": "1.0.2", @@ -14996,7 +14994,7 @@ "base64-js": { "version": "0.0.8", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", - "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==" + "integrity": "sha1-EQHpVE9KdrG8OybUUsqW16NeeXg=" } } }, @@ -15036,7 +15034,7 @@ "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", - "integrity": "sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==" + "integrity": "sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=" }, "postcss": { "version": "7.0.39", @@ -15234,13 +15232,13 @@ "prelude-ls": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", - "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, "prepend-http": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha512-PhmXi5XmoyKw1Un4E+opM2KcsJInDvKyuOumcjjw3waw86ZNjHwVUOOWLc4bCzLdcKNaWBH9e99sbWzDQsVaYg==" + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, "pretty-error": { "version": "4.0.0", @@ -15274,7 +15272,7 @@ "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" }, "process-nextick-args": { "version": "2.0.1", @@ -15297,7 +15295,7 @@ "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", - "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "integrity": "sha1-mEcocL8igTL8vdhoEputEsPAKeM=", "dev": true }, "prop-types": { @@ -15343,7 +15341,7 @@ "prosemirror-find-replace": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/prosemirror-find-replace/-/prosemirror-find-replace-0.9.0.tgz", - "integrity": "sha512-LfhQ/Zr0PkkJpCsr9vTJ5ZPYh49mSVVG+hHJ6djT+chlCW+t2ilSxBpBG+2IeE/I5nlbcvuLLAbxeI1g3pTCpA==" + "integrity": "sha1-QgsENNF5xdBJD44hSNhVGpVJY4I=" }, "prosemirror-history": { "version": "1.3.0", @@ -15435,13 +15433,13 @@ "prr": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz", - "integrity": "sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==", + "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY=", "dev": true }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", - "integrity": "sha512-b/YwNhb8lk1Zz2+bXXpS/LK9OisiZZ1SNsSLxN1x2OXVEhW2Ckr/7mWE5vrC1ZTiJlD9g19jWszTmJsB+oEpFQ==" + "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=" }, "psl": { "version": "1.8.0", @@ -15673,12 +15671,12 @@ "pure-color": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/pure-color/-/pure-color-1.3.0.tgz", - "integrity": "sha512-QFADYnsVoBMw1srW7OVKEYjG+MbIa49s54w1MA1EDY6r2r/sTcKKYqRX1f4GYvnXP7eN/Pe9HFcX+hwzmrXRHA==" + "integrity": "sha1-H+Bk+wrIUfDeYTIKi/eWg2Qi8z4=" }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" }, "qs": { "version": "6.5.3", @@ -15699,7 +15697,7 @@ "querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", - "integrity": "sha512-X/xY82scca2tau62i9mDyU9K+I+djTMUsvwf7xnUX5GLvVzgJybOJf4Y6o9Zx3oJK/LSXg5tTZBjwzqVPaPO2g==", + "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", "dev": true }, "querystringify": { @@ -15721,7 +15719,7 @@ "random-bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz", - "integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==" + "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, "randombytes": { "version": "2.1.0", @@ -15826,7 +15824,7 @@ "react-base16-styling": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/react-base16-styling/-/react-base16-styling-0.5.3.tgz", - "integrity": "sha512-EPuchwVvYPSFFIjGpH0k6wM0HQsmJ0vCk7BSl5ryxMVFIWW4hX4Kksu4PNtxfgOxDebTLkJQ8iC7zwAql0eusg==", + "integrity": "sha1-OFjyTpxN2MvT9wLz901YHKKRcmk=", "requires": { "base16": "^1.0.0", "lodash.curry": "^4.0.1", @@ -15895,7 +15893,7 @@ "react-dock": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/react-dock/-/react-dock-0.2.4.tgz", - "integrity": "sha512-ywUJPC/TIM9PO700skka0fH4aqbrH8RojUXejZFvjtqlc5KZ+xjHqFdo4A3j+dp+0NLFZ3Nai4xzcf3FUJ9BsQ==", + "integrity": "sha1-5yfcdVCztzEWY13LnA4E0Lev4Xw=", "requires": { "lodash.debounce": "^3.1.1", "prop-types": "^15.5.8" @@ -16252,7 +16250,7 @@ "react-themeable": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/react-themeable/-/react-themeable-1.1.0.tgz", - "integrity": "sha512-kl5tQ8K+r9IdQXZd8WLa+xxYN04lLnJXRVhHfdgwsUJr/SlKJxIejoc9z9obEkx1mdqbTw1ry43fxEUwyD9u7w==", + "integrity": "sha1-fURm3ZsrX6dQWHJ4JenxUro3mg4=", "requires": { "object-assign": "^3.0.0" }, @@ -16260,7 +16258,7 @@ "object-assign": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha512-jHP15vXVGeVh1HuaA2wY6lxk+whK/x4KBG88VXeRma7CCun7iGD5qPc4eYykQ9sdQvg8jkwFKsSxHln2ybW3xQ==" + "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=" } } }, @@ -16299,7 +16297,7 @@ "read-pkg": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha512-7BGwRHqt4s/uVbuyoeejRn4YmFnYZiFl4AuaeXHlgZf3sONF0SOGlxs2Pw8g6hCKupo08RafIO5YXFNOKTfwsQ==", + "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", "requires": { "load-json-file": "^1.0.0", "normalize-package-data": "^2.3.2", @@ -16309,7 +16307,7 @@ "path-type": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha512-S4eENJz1pkiQn9Znv33Q+deTOKmbl+jj1Fl+qiP/vYezj+S8x+J3Uo0ISrx/QoEvIlOaDWJhPaRd1flJ9HXZqg==", + "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", "requires": { "graceful-fs": "^4.1.2", "pify": "^2.0.0", @@ -16321,7 +16319,7 @@ "read-pkg-up": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha512-WD9MTlNtI55IwYUS27iHh9tK3YoIVhxis8yKhLpTqWtml739uXc9NWTpxoHkfZf3+DkCCsXox94/VWZniuZm6A==", + "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", "requires": { "find-up": "^1.0.0", "read-pkg": "^1.0.0" @@ -16330,7 +16328,7 @@ "find-up": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha512-jvElSjyuo4EMQGoTwo1uJU5pQMwTW5lS1x05zzfJuTIyLR3zwO27LYrxNg+dlvKpGOuGy/MzBdXh80g0ve5+HA==", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", "requires": { "path-exists": "^2.0.0", "pinkie-promise": "^2.0.0" @@ -16339,7 +16337,7 @@ "path-exists": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha512-yTltuKuhtNeFJKa1PiRzfLAU5182q1y4Eb4XCJ3PBqyzEDkAZRzBrKKBct682ls9reBVHf9udYLN5Nd+K1B9BQ==", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", "requires": { "pinkie-promise": "^2.0.0" } @@ -16385,12 +16383,12 @@ "readline": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/readline/-/readline-1.3.0.tgz", - "integrity": "sha512-k2d6ACCkiNYz222Fs/iNze30rRJ1iIicW7JuX/7/cozvih6YCkFZH+J6mAFDVgv0dRBaAyr4jDqC95R2y4IADg==" + "integrity": "sha1-xYDXfvLPyHUrEySYBg3JeTp6wBw=" }, "rechoir": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "requires": { "resolve": "^1.1.6" } @@ -16416,7 +16414,7 @@ "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", - "integrity": "sha512-qtW5hKzGQZqKoh6JNSD+4lfitfPKGz42e6QwiRmPM5mmKtR0N41AbJRYu0xJi7nhOJ4WDgRkKvAk6tw4WIwR4g==", + "integrity": "sha1-z5Fqsf1fHxbfsggi3W7H9zDCr94=", "requires": { "indent-string": "^2.1.0", "strip-indent": "^1.0.1" @@ -16425,7 +16423,7 @@ "reduce-flatten": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-1.0.1.tgz", - "integrity": "sha512-j5WfFJfc9CoXv/WbwVLHq74i/hdTUpy+iNC534LxczMRP67vJeK3V9JOdnL0N1cIRbn9mYhE2yVjvvKXDxvNXQ==" + "integrity": "sha1-JYx479FT3fk8tWEjf2EYTzaW4yc=" }, "redux": { "version": "4.2.0", @@ -16476,7 +16474,7 @@ "registry-url": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/registry-url/-/registry-url-3.1.0.tgz", - "integrity": "sha512-ZbgR5aZEdf4UKZVBPYIgaglBmSF2Hi94s2PcIHhRGFjKYu+chjJdYfHn4rt3hB6eCKLJ8giVIIfgMa1ehDfZKA==", + "integrity": "sha1-PU74cPc93h138M+aOBQyRE4XSUI=", "requires": { "rc": "^1.0.1" } @@ -16484,12 +16482,12 @@ "relateurl": { "version": "0.2.7", "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz", - "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==" + "integrity": "sha1-VNvzd+UUQKypCkzSdGANP/LYiKk=" }, "remove-trailing-separator": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", - "integrity": "sha512-/hS+Y0u3aOfIETiaiirUFwDBDzmXPvO+jAfKTitUngIPzdKc6Z0LoFjM/CK5PL4C+eKwHohlHAb6H0VFfmmUsw==" + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "renderkid": { "version": "3.0.0", @@ -16575,12 +16573,12 @@ "repeat-string": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha512-PV0dzCYDNfRi1jCDbJzpW7jNNDRuCOG/jI5ctQcGKt/clZD+YcPS3yIlWuTJMmESC8aevCFmWJy5wjAFgNqN6w==" + "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "repeating": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/repeating/-/repeating-2.0.1.tgz", - "integrity": "sha512-ZqtSMuVybkISo2OWvqvm7iHSWngvdaW3IpsT9/uP8v4gMi591LY6h35wdOfvQdWCKFWZWm2Y1Opp4kV7vQKT6A==", + "integrity": "sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo=", "requires": { "is-finite": "^1.0.0" } @@ -16684,7 +16682,7 @@ "require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" }, "require-from-string": { "version": "2.0.2", @@ -16699,7 +16697,7 @@ "require-package-name": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/require-package-name/-/require-package-name-2.0.1.tgz", - "integrity": "sha512-uuoJ1hU/k6M0779t3VMVIYpb2VMJk05cehCaABFhXaibcbvfgR8wKiozLjVFSzJPmQMRqIcO0HMyTFqfV09V6Q==" + "integrity": "sha1-wR6XJ2tluOKSP3Xav1+y7ww4Qbk=" }, "require_optional": { "version": "1.0.1", @@ -16713,14 +16711,14 @@ "resolve-from": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-2.0.0.tgz", - "integrity": "sha512-qpFcKaXsq8+oRoLilkwyc7zHGF5i9Q2/25NIgLQQ/+VVv9rU4qvr6nXVAw1DsnXJyQkZsR4Ytfbtg5ehfcUssQ==" + "integrity": "sha1-lICrIOlP+h2egKgEx+oUdhGWa1c=" } } }, "requires-port": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", - "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==", + "integrity": "sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8=", "dev": true }, "resize-observer-polyfill": { @@ -16766,7 +16764,7 @@ "resolve-url": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/resolve-url/-/resolve-url-0.2.1.tgz", - "integrity": "sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==" + "integrity": "sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo=" }, "responselike": { "version": "2.0.0", @@ -16791,7 +16789,7 @@ "retry": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", - "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=", "dev": true }, "reveal.js": { @@ -16802,7 +16800,7 @@ "right-align": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/right-align/-/right-align-0.1.3.tgz", - "integrity": "sha512-yqINtL/G7vs2v+dFIZmFUDbnVyFUJFKd6gK22Kgo6R4jfJGFtisKyncWDDULgjfqf4ASQuIQyjJ7XZ+3aWpsAg==", + "integrity": "sha1-YTObci/mo1FWiSENJOFMlhSGE+8=", "requires": { "align-text": "^0.1.1" } @@ -16831,7 +16829,7 @@ "run-queue": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/run-queue/-/run-queue-1.0.3.tgz", - "integrity": "sha512-ntymy489o0/QQplUDnpYAYUsO50K9SBrIVaKCWDOJzYJts0f9WH9RFJkyagebkw5+y1oi00R7ynNW/d12GBumg==", + "integrity": "sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec=", "dev": true, "requires": { "aproba": "^1.1.1" @@ -16853,7 +16851,7 @@ "safe-regex": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz", - "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==", + "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "requires": { "ret": "~0.1.10" } @@ -16941,13 +16939,13 @@ "scss-loader": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/scss-loader/-/scss-loader-0.0.1.tgz", - "integrity": "sha512-SbT/smRJjkvvdHSEdAYAplosVkrtaSwwgUlnQCOuDS5sOKNjrS/eYCMvKeV6+YxK5cCOCsOJZd3vltrXatFp+g==", + "integrity": "sha1-6uAXueDzjBKlMtslwiC5Avs05nE=", "dev": true }, "scss-tokenizer": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.2.3.tgz", - "integrity": "sha512-dYE8LhncfBUar6POCxMTm0Ln+erjeczqEvCJib5/7XNkdw1FkUGgwMPY360FY0FgPWQxHWCx29Jl3oejyGLM9Q==", + "integrity": "sha1-jrBtualyMzOCTT9VMGQRSYR85dE=", "requires": { "js-base64": "^2.1.8", "source-map": "^0.4.2" @@ -16956,7 +16954,7 @@ "source-map": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.4.4.tgz", - "integrity": "sha512-Y8nIfcb1s/7DcobUz1yOO1GSp7gyL+D9zLHDehT7iRESqGSxjJ448Sg7rvfgsRJCnKLdSl11uGf0s9X80cH0/A==", + "integrity": "sha1-66T12pwNyZneaAMti092FzZSA2s=", "requires": { "amdefine": ">=0.0.4" } @@ -16971,17 +16969,17 @@ "section-iterator": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/section-iterator/-/section-iterator-2.0.0.tgz", - "integrity": "sha512-xvTNwcbeDayXotnV32zLb3duQsP+4XosHpb/F+tu6VzEZFmIjzPdNk6/O+QOOx5XTh08KL2ufdXeCO33p380pQ==" + "integrity": "sha1-v0RNev7rlK1Dw5rS+yYVFifMuio=" }, "select": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/select/-/select-1.1.2.tgz", - "integrity": "sha512-OwpTSOfy6xSs1+pwcNrv0RBMOzI39Lp3qQKUTPVVPRjCdNa5JH/oPRiqsesIskK8TVgmRiHwO4KXlV2Li9dANA==" + "integrity": "sha1-DnNQrN7ICxEIUoeG7B1EGNEbOW0=" }, "select-hose": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/select-hose/-/select-hose-2.0.0.tgz", - "integrity": "sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==", + "integrity": "sha1-Yl2GWPhlr0Psliv8N2o3NZpJlMo=", "dev": true }, "selfsigned": { @@ -17001,12 +16999,12 @@ "semver-compare": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/semver-compare/-/semver-compare-1.0.0.tgz", - "integrity": "sha512-YM3/ITh2MJ5MtzaM429anh+x2jiLVjqILF4m4oyQB18W7Ggea7BfqdH/wGMK7dDiMghv/6WG7znWMwUDzJiXow==" + "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=" }, "semver-diff": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/semver-diff/-/semver-diff-2.1.0.tgz", - "integrity": "sha512-gL8F8L4ORwsS0+iQ34yCYv///jsOq0ZL7WP55d1HnJ32o7tyFYEFQZQA22mrLIacZdU6xecaBBZ+uEiffGNyXw==", + "integrity": "sha1-S7uEN8jTfksM8aaP1ybsbWRdbTY=", "requires": { "semver": "^5.0.3" } @@ -17070,7 +17068,7 @@ "serve-index": { "version": "1.9.1", "resolved": "https://registry.npmjs.org/serve-index/-/serve-index-1.9.1.tgz", - "integrity": "sha512-pXHfKNP4qujrtteMrSBb0rc8HJ9Ms/GrXwcUtUtD5s4ewDJI8bT3Cz2zTVRMKtri49pLx2e0Ya8ziP5Ya2pZZw==", + "integrity": "sha1-03aNabHn2C5c4FD/9bRTvqEqkjk=", "dev": true, "requires": { "accepts": "~1.3.4", @@ -17100,7 +17098,7 @@ "http-errors": { "version": "1.6.3", "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", "dev": true, "requires": { "depd": "~1.1.2", @@ -17112,13 +17110,13 @@ "inherits": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", "dev": true }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", "dev": true }, "setprototypeof": { @@ -17149,7 +17147,7 @@ "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, "set-value": { "version": "2.0.1", @@ -17165,7 +17163,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -17175,7 +17173,7 @@ "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", - "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" }, "setprototypeof": { "version": "1.2.0", @@ -17337,7 +17335,7 @@ "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", - "integrity": "sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==", + "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "requires": { "shebang-regex": "^1.0.0" } @@ -17345,7 +17343,7 @@ "shebang-regex": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz", - "integrity": "sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==" + "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=" }, "shelljs": { "version": "0.8.5", @@ -17380,7 +17378,7 @@ "simple-assign": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/simple-assign/-/simple-assign-0.1.0.tgz", - "integrity": "sha512-otdSSQzuVsmDoe5MnSm4ZgHd5sl0ak6A1CTjW1R/DUHQ8xoZuU1NUzf9x6n9Dvp3nxpvW51WNMQ/7rQ9432xDg==" + "integrity": "sha1-F/0wZqXz13OPUDIbsPFMooHMS6o=" }, "simple-concat": { "version": "1.0.1", @@ -17400,7 +17398,7 @@ "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "requires": { "is-arrayish": "^0.3.1" }, @@ -17415,13 +17413,13 @@ "slash": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-1.0.0.tgz", - "integrity": "sha512-3TYDR7xWt4dIqV2JauJr+EJeW356RXijHeUlO+8djJ+uBXPn8/2dpzBc8yQhh583sVvc9CvFAeQVgijsH+PNNg==", + "integrity": "sha1-xB8vbDn8FtHNF61LXYlhFK5HDVU=", "dev": true }, "sliced": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/sliced/-/sliced-1.0.1.tgz", - "integrity": "sha512-VZBmZP8WU3sMOZm1bdgTadsQbcscK0UM8oKxKVBs4XAhUo2Xxzm/OFMGBkPusxw9xL3Uy8LrzEqGqJhclsr0yA==" + "integrity": "sha1-CzpmK10Ewxd7GSa+qCsD+Dei70E=" }, "snapdragon": { "version": "0.8.2", @@ -17449,7 +17447,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -17457,7 +17455,7 @@ "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", - "integrity": "sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==", + "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "requires": { "is-extendable": "^0.1.0" } @@ -17465,7 +17463,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -17482,7 +17480,7 @@ "define-property": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha512-cZTYKFWspt9jZsMscWo8sc/5lbPC9Q0N5nBLgb+Yd915iL3udB1uFgS3B8YCx66UVHq018DAVFoee7x+gxggeA==", + "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "requires": { "is-descriptor": "^1.0.0" } @@ -17531,7 +17529,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -17595,12 +17593,12 @@ "isarray": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.1.tgz", - "integrity": "sha512-c2cu3UxbI+b6kR3fy0nRnAhodsvR9dx7U5+znCOzdj6IfP3upFURTr0Xl5BlQZNKZjEtxrmVyfSdeE3O57smoQ==" + "integrity": "sha1-o32U7ZzaLVmGXJ92/llu4fM4dB4=" }, "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" }, "socket.io-parser": { "version": "3.3.2", @@ -17710,7 +17708,7 @@ "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==" + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" }, "source-map-resolve": { "version": "0.5.3", @@ -17748,7 +17746,7 @@ "sparse-bitfield": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz", - "integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==", + "integrity": "sha1-/0rm5oZWBWuks+eSqzM004JzyhE=", "optional": true, "requires": { "memory-pager": "^1.0.2" @@ -17851,7 +17849,7 @@ "split-skip": { "version": "0.0.2", "resolved": "https://registry.npmjs.org/split-skip/-/split-skip-0.0.2.tgz", - "integrity": "sha512-weHOi8BolsDnGIwhhWHbA+wKSuSpvWwjRrdj8SdbIIis2vSwOE37CQP8x3EleuzxanUr3AK8BdUy4MkiOULPZg==" + "integrity": "sha1-2J2Iu9L3Pka1FYqjcKVhIk6A1GE=" }, "split-string": { "version": "3.1.0", @@ -17864,7 +17862,7 @@ "sprintf-js": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=" }, "sshpk": { "version": "1.17.0", @@ -17916,7 +17914,7 @@ "define-property": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/define-property/-/define-property-0.2.5.tgz", - "integrity": "sha512-Rr7ADjQZenceVOAKop6ALkkRAmH1A4Gx9hV/7ZujPUN2rkATqFO0JZLZInbAjpZYoJ1gUx8MRMQVkYemcbMSTA==", + "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "requires": { "is-descriptor": "^0.1.0" } @@ -17995,7 +17993,7 @@ "ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" } } }, @@ -18069,7 +18067,7 @@ "magicli": { "version": "0.0.5", "resolved": "https://registry.npmjs.org/magicli/-/magicli-0.0.5.tgz", - "integrity": "sha512-wZbMtnl2v1b+Jp3xlqA9FU/O4I6YhGXR8xSY/eU2+gDAvut/F+W3gl4qs61iL4LELC7jqSAE6aAD5668EbmQHA==", + "integrity": "sha1-zufQ+7THBRiqyxHsPrfiX/SaSSE=", "requires": { "commander": "^2.9.0", "get-stdin": "^5.0.1", @@ -18116,7 +18114,7 @@ "get-stdin": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz", - "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==" + "integrity": "sha1-uWjGsKBDhDJJAui/Gl3zJXmkUP4=" } } }, @@ -18449,7 +18447,7 @@ "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "requires": { "is-buffer": "^1.1.5" } @@ -18497,7 +18495,7 @@ "nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", - "integrity": "sha512-NWmpvLSqUrgrAC9HCuxEvb+PSloHpqVu+FqcO4eeF2h5qYRhA7ev6KvelyQAKtegUbC6RypJnlEOhd8vloNKYg==", + "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=", "requires": { "abbrev": "1" } @@ -18622,7 +18620,7 @@ "arrify": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz", - "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==", + "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true } } @@ -18921,8 +18919,7 @@ "type": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==" }, "type-check": { "version": "0.3.2", @@ -19000,7 +18997,7 @@ "jsonfile": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", - "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", "requires": { "graceful-fs": "^4.1.6" } @@ -19030,12 +19027,12 @@ "camelcase": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-1.2.1.tgz", - "integrity": "sha512-wzLkDa4K/mzI1OSITC+DUyjgIl/ETNHE9QvYgy6J6Jvqyyz4C0Xfd+lQhb19sX2jMpZV4IssUn0VDVmglV+s4g==" + "integrity": "sha1-m7UwTS4LVmmLLHWLCKPqqdqlijk=" }, "cliui": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cliui/-/cliui-2.1.0.tgz", - "integrity": "sha512-GIOYRizG+TGoc7Wgc1LiOTLare95R3mzKgoln+Q/lE4ceiYH19gUpl0l0Ffq4lJDEf3FxujMe6IBfOCs7pfqNA==", + "integrity": "sha1-S0dXYP+AJkx2LDoXGQMukcf+oNE=", "requires": { "center-align": "^0.1.1", "right-align": "^0.1.1", @@ -19116,7 +19113,7 @@ "pako": { "version": "0.2.9", "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", - "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==" + "integrity": "sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU=" } } }, @@ -19189,7 +19186,7 @@ "has-value": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/has-value/-/has-value-0.3.1.tgz", - "integrity": "sha512-gpG936j8/MzaeID5Yif+577c17TxaDmhuyVgSwtnL/q8UUTySg8Mecb+8Cf1otgLoD7DDH75axp86ER7LFsf3Q==", + "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "requires": { "get-value": "^2.0.3", "has-values": "^0.1.4", @@ -19199,7 +19196,7 @@ "isobject": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==", + "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", "requires": { "isarray": "1.0.0" } @@ -19209,7 +19206,7 @@ "has-values": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha512-J8S0cEdWuQbqD9//tlZxiMuMNmxB8PlEwvYwuxsTmR1G5RXUePEX/SJn7aD0GMLieuZYSwNH0cQuJGwnYunXRQ==" + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" } } }, @@ -19281,7 +19278,7 @@ "punycode": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", "dev": true } } @@ -19805,7 +19802,7 @@ "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha512-TIGnTpdo+E3+pCyAluZvtED5p5wCqLdezCyhPZzKPcxvFplEt4i+W7OONCKgeZFT3+y5NZZfOOS/Bdcanm1MYA==", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", "dev": true }, "debug": { @@ -19830,7 +19827,7 @@ "memory-fs": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/memory-fs/-/memory-fs-0.4.1.tgz", - "integrity": "sha512-cda4JKCxReDXFXRqOHPQscuIYg1PvxbE2S2GP45rnwfEK+vZaXC8C1OFvdHIbgw0DLzowXGVoxLaAmlgRy14GQ==", + "integrity": "sha1-OpoguEYlI+RHz7x+i7gO1me/xVI=", "dev": true, "requires": { "errno": "^0.1.3", @@ -20290,7 +20287,7 @@ "lodash": { "version": "3.10.1", "resolved": "https://registry.npmjs.org/lodash/-/lodash-3.10.1.tgz", - "integrity": "sha512-9mDDwqVIma6OZX79ZlDACZl8sBm0TEnkf99zV3iMA4GzkIT/9hiqP5mY0HoT1iNLCrKc/R1HByV+yJfRWVJryQ==" + "integrity": "sha1-W/Rejkm6QYnhfUgnid/RW9FAt7Y=" } } }, diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index ca942a38a..2343c2f34 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -25,6 +25,7 @@ export enum DocumentType { EQUATION = "equation", // equation editor FUNCPLOT = "funcplot", // function plotter MAP = "map", + DATAVIZ = "dataviz", // special purpose wrappers that either take no data or are compositions of lower level types LINK = "link", diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 0bb6ce27f..c5ec9eaf6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -62,6 +62,7 @@ import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; +import DataViz from "../views/nodes/DataViz"; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { @@ -517,6 +518,10 @@ export namespace Docs { layout: { view: EmptyBox, dataField: defaultDataKey }, options: { links: "@links(self)" } }], + [DocumentType.DATAVIZ, { + layout: { view: DataViz, dataField: defaultDataKey }, + options: { _fitWidth: true, _fitHeight: true, links: "@links(self)" } + }] ]); const suffix = "Proto"; @@ -922,6 +927,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } + export function DataVizDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), undefined, { title: "Data Viz", ...options }); + } + export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 60bfc165b..a96f5cb18 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -262,7 +262,10 @@ export class CurrentUserUtils { doc.emptyHeader = headerTemplate; } if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "Comparer", _width: 300, _height: 300 }); + doc.emptyComparison = Docs.Create.ComparisonDocument({ ...standardOps(), title: "comparer", _width: 300, _height: 300 }); + } + if (doc.emptyDataViz === undefined) { + doc.emptyDataViz = Docs.Create.DataVizDocument({ ...standardOps(), title: "data viz", _width: 300, _height: 300 }); } if (doc.emptyScript === undefined) { doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { ...standardOps(), title: "script", _width: 200, _height: 250, }); diff --git a/src/client/views/nodes/DataViz.scss b/src/client/views/nodes/DataViz.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx new file mode 100644 index 000000000..60b27c19c --- /dev/null +++ b/src/client/views/nodes/DataViz.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import "./DataViz.scss"; +import { FieldView, FieldViewProps } from "./FieldView"; +import { ViewBoxBaseComponent } from '../DocComponent'; + +export class DataViz extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(DataViz, fieldKey); + } + + render() { + return (
    Hi
    ) + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 70732e74c..8715fc33a 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -42,6 +42,7 @@ import React = require("react"); import XRegExp = require("xregexp"); import { MapBox } from "./MapBox/MapBox"; import { RecordingBox } from "./RecordingBox"; +import { DataViz } from "./DataViz"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -229,7 +230,7 @@ export class DocumentContentsView extends React.Component Date: Tue, 21 Jun 2022 19:30:17 -0400 Subject: running into jsx parsing error --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 3 ++- src/client/views/nodes/DataViz.tsx | 14 +++++++++++++- 3 files changed, 16 insertions(+), 3 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index c5ec9eaf6..42191bd7e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -62,7 +62,7 @@ import { DocumentType } from "./DocumentTypes"; import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { MapBox } from "../views/nodes/MapBox/MapBox"; import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; -import DataViz from "../views/nodes/DataViz"; +import { DataViz } from "../views/nodes/DataViz"; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index a96f5cb18..8ccb0d197 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -310,7 +310,8 @@ export class CurrentUserUtils { { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, { toolTip: "Tap to create a custom header note document, drag for a custom header note", title: "Custom", icon: "window-maximize", click: 'openOnRight(delegateDragFactory(this.dragFactory))', drag: 'delegateDragFactory(this.dragFactory)', dragFactory: doc.emptyHeader as Doc }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { toolTip: "Tap to create a map in the new pane, drag for a map", title: "Map", icon: "map-marker-alt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyMap as Doc, noviceMode: true } + { toolTip: "Tap to create a map in the new pane, drag for a map", title: "Map", icon: "map-marker-alt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyMap as Doc, noviceMode: true }, + { toolTip: "Tap to create a data visualization in a new pane", title: "Data Viz", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyDataViz as Doc} ]; } diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx index 60b27c19c..57c5c0259 100644 --- a/src/client/views/nodes/DataViz.tsx +++ b/src/client/views/nodes/DataViz.tsx @@ -4,11 +4,23 @@ import { FieldView, FieldViewProps } from "./FieldView"; import { ViewBoxBaseComponent } from '../DocComponent'; export class DataViz extends ViewBoxBaseComponent() { + + constructor(props: any) { + super(props); + } + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataViz, fieldKey); } render() { - return (
    Hi
    ) + return ( +
    +
    + Hi +
    +
    + ); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From e691d8e72ba632f0ef2d4122fe7233f9a48d4439 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 21 Jun 2022 21:36:40 -0400 Subject: fixed issues with register DataViz jsx. --- src/client/documents/Documents.ts | 17 ++++++++--------- src/client/views/nodes/DataViz.tsx | 6 +++--- src/client/views/nodes/DocumentContentsView.tsx | 13 ++++++------- 3 files changed, 17 insertions(+), 19 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 42191bd7e..29536b642 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,7 +1,8 @@ +import { IconProp } from "@fortawesome/fontawesome-svg-core"; import { action, runInAction } from "mobx"; import { basename } from "path"; import { DateField } from "../../fields/DateField"; -import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Initializing, Opt, updateCachedAcls, WidthSym } from "../../fields/Doc"; +import { Doc, DocListCast, DocListCastAsync, Field, Initializing, Opt, updateCachedAcls } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { HtmlField } from "../../fields/HtmlField"; import { InkField, PointData } from "../../fields/InkField"; @@ -14,7 +15,7 @@ import { Cast, NumCast, StrCast } from "../../fields/Types"; import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; -import { OmitKeys, Utils, aggregateBounds } from "../../Utils"; +import { aggregateBounds, OmitKeys, Utils } from "../../Utils"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; import { DocServer } from "../DocServer"; import { Networking } from "../Network"; @@ -33,13 +34,14 @@ import { ContextMenuProps } from "../views/ContextMenuItem"; import { DFLT_IMAGE_NATIVE_DIM } from "../views/global/globalCssVariables.scss"; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from "../views/InkingStroke"; import { AudioBox } from "../views/nodes/AudioBox"; +import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; +import { DataVizBox } from "../views/nodes/DataViz"; import { DocFocusOptions } from "../views/nodes/DocumentView"; import { EquationBox } from "../views/nodes/EquationBox"; import { FieldViewProps } from "../views/nodes/FieldView"; import { FilterBox } from "../views/nodes/FilterBox"; -import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox"; import { ImageBox } from "../views/nodes/ImageBox"; @@ -47,7 +49,9 @@ import { KeyValueBox } from "../views/nodes/KeyValueBox"; import { LabelBox } from "../views/nodes/LabelBox"; import { LinkBox } from "../views/nodes/LinkBox"; import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup"; +import { MapBox } from "../views/nodes/MapBox/MapBox"; import { PDFBox } from "../views/nodes/PDFBox"; +import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; import { ScriptingBox } from "../views/nodes/ScriptingBox"; import { SliderBox } from "../views/nodes/SliderBox"; @@ -57,12 +61,7 @@ import { PresElementBox } from "../views/nodes/trails/PresElementBox"; import { VideoBox } from "../views/nodes/VideoBox"; import { WebBox } from "../views/nodes/WebBox"; import { SearchBox } from "../views/search/SearchBox"; -import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { MapBox } from "../views/nodes/MapBox/MapBox"; -import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; -import { DataViz } from "../views/nodes/DataViz"; const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); class EmptyBox { @@ -519,7 +518,7 @@ export namespace Docs { options: { links: "@links(self)" } }], [DocumentType.DATAVIZ, { - layout: { view: DataViz, dataField: defaultDataKey }, + layout: { view: DataVizBox, dataField: defaultDataKey }, options: { _fitWidth: true, _fitHeight: true, links: "@links(self)" } }] ]); diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx index 57c5c0259..a3ffade70 100644 --- a/src/client/views/nodes/DataViz.tsx +++ b/src/client/views/nodes/DataViz.tsx @@ -1,9 +1,9 @@ import React from "react"; +import { ViewBoxBaseComponent } from '../DocComponent'; import "./DataViz.scss"; import { FieldView, FieldViewProps } from "./FieldView"; -import { ViewBoxBaseComponent } from '../DocComponent'; -export class DataViz extends ViewBoxBaseComponent() { +export class DataVizBox extends ViewBoxBaseComponent() { constructor(props: any) { super(props); @@ -11,7 +11,7 @@ export class DataViz extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(DataViz, fieldKey); + return FieldView.LayoutString(DataVizBox, fieldKey); } render() { diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 8715fc33a..60eef7cfd 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -16,14 +16,15 @@ import { SearchBox } from "../search/SearchBox"; import { DashWebRTCVideo } from "../webcam/DashWebRTCVideo"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; import { AudioBox } from "./AudioBox"; +import { FontIconBox } from "./button/FontIconBox"; import { ColorBox } from "./ColorBox"; import { ComparisonBox } from "./ComparisonBox"; +import { DataVizBox } from "./DataViz"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { EquationBox } from "./EquationBox"; import { FieldView, FieldViewProps } from "./FieldView"; import { FilterBox } from "./FilterBox"; -import { FontIconBox } from "./button/FontIconBox"; import { FormattedTextBox, FormattedTextBoxProps } from "./formattedText/FormattedTextBox"; import { FunctionPlotBox } from "./FunctionPlotBox"; import { ImageBox } from "./ImageBox"; @@ -31,18 +32,17 @@ import { KeyValueBox } from "./KeyValueBox"; import { LabelBox } from "./LabelBox"; import { LinkAnchorBox } from "./LinkAnchorBox"; import { LinkBox } from "./LinkBox"; +import { MapBox } from "./MapBox/MapBox"; import { PDFBox } from "./PDFBox"; -import { PresBox } from "./trails/PresBox"; +import { RecordingBox } from "./RecordingBox"; import { ScreenshotBox } from "./ScreenshotBox"; import { ScriptingBox } from "./ScriptingBox"; import { SliderBox } from "./SliderBox"; +import { PresBox } from "./trails/PresBox"; import { VideoBox } from "./VideoBox"; import { WebBox } from "./WebBox"; import React = require("react"); import XRegExp = require("xregexp"); -import { MapBox } from "./MapBox/MapBox"; -import { RecordingBox } from "./RecordingBox"; -import { DataViz } from "./DataViz"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -229,8 +229,7 @@ export class DocumentContentsView extends React.Component Date: Wed, 22 Jun 2022 13:46:39 -0400 Subject: more cleanup in currentuserutils. fixed so that removing items from a list in code will remove them from the UI --- src/client/documents/Documents.ts | 6 +- src/client/util/CurrentUserUtils.ts | 209 ++++++++++----------- .../views/collections/CollectionStackingView.tsx | 3 +- 3 files changed, 104 insertions(+), 114 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d0c7d2436..01cfe90da 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -708,8 +708,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List(), options); } - export function ScriptingDocument(script: Opt, options: DocumentOptions = {}, fieldKey?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script, + export function ScriptingDocument(script: Opt|null, options: DocumentOptions = {}, fieldKey?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script: undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } @@ -829,7 +829,7 @@ export namespace Docs { const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; if (!nhght && width && height && nwid) options._nativeHeight = Number(nwid) * Number(height) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.WEB), url ? new WebField(url) : undefined, options); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : "http://www.bing.com/"), options); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index e119828c7..52fbda9a9 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -104,6 +104,7 @@ export class CurrentUserUtils { } }); items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), "data", item)); + items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), "data", item)); } return doc; } @@ -148,9 +149,9 @@ export class CurrentUserUtils { return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? CurrentUserUtils.createToolButton( {...btnOpts, dragFactory: makeTemp(template(templateOpts))}), reqdScripts); }); - const reqdOpts = { + const reqdOpts:DocumentOptions = { title: "Experimental Tools", _xMargin: 0, _showTitle: "title", _chromeHidden: true, - _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, + _stayInCollection: true, _hideContextMenu: true, _forceActive: true, system: true, childDocumentsActive: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 35, ignoreClick: true, _lockedPosition: true, }; const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; @@ -197,17 +198,12 @@ export class CurrentUserUtils { // setup templates for different document types when they are iconified from Document Decorations static setupDefaultIconTemplates(doc: Doc, field="template-icons") { - const templateIconsDoc = DocCast(doc[field]); + const reqdOpts = { title: "icon templates", _height: 75, system: true }; + const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: () => Doc) => { const iconFieldName = "icon" + (type ? "_" + type : ""); - if (!templateIconsDoc?.[iconFieldName]) { - const template = MakeTemplate(iconTemplate(), true, iconFieldName, templateField); - if (templateIconsDoc) { - templateIconsDoc[iconFieldName] = template; - } - return template; - } + return DocCast(templateIconsDoc[iconFieldName] ?? (templateIconsDoc[iconFieldName] = MakeTemplate(iconTemplate(), true, iconFieldName, templateField))) ; }; const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); const labelBox = (extra: object) => Docs.Create.LabelDocument({ @@ -235,68 +231,37 @@ export class CurrentUserUtils { makeIconTemplate("transcription" as any, "transcription", () => labelBox({ _backgroundColor: "orange" })), // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) ].filter(d => d).map(d => d!); - - const reqdOpts = { title: "icon templates", _height: 75, system: true }; - this.AssignOpts(templateIconsDoc, reqdOpts, iconTemplates) ?? (doc[field] = Docs.Create.TreeDocument(iconTemplates, reqdOpts)); + this.AssignOpts(DocCast(doc[field]), {}, iconTemplates); } - /// initalizes the set of default versions of most document types + /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyPresentation static creatorBtnDescriptors(doc: Doc): { title: string, toolTip: string, icon: string, ignoreClick?: boolean, dragFactory?: Doc, backgroundColor?: string, clickFactory?: Doc, scripts?: { onClick?: string, onDragStart?: string}, funcs?: {onDragStart?:string, hidden?: string}, }[] { - const standardOps = () => ({ _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); - const emptyThings:{key:string, // the field name where the empty thing will be stored - opts:DocumentOptions, // the document options that are required for the empty thing - funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing - creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist - }[] = [ - {key: "emptyPresentation", creator: Docs.Create.PresDocument, opts: { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, - {key: "emptyCollection", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "freeform", _width: 150, _height: 100 }}, - {key: "emptyPane", creator: (opts) => Docs.Create.FreeformDocument([], opts), opts: { title: "Untitled Tab", _backgroundGridShow: true, _width: 500, _height: 800 }}, - {key: "emptyMath", creator: (opts) => Docs.Create.EquationDocument(opts), opts: { title: "Equation", _backgroundGridShow: true, _width: 300, _height: 35 }}, - {key: "emptySlide", creator: (opts) => Docs.Create.TreeDocument([], opts), funcs: {title: 'self.text?.Text'}, opts: { - _viewType: CollectionViewType.Tree, treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, - "dragFactory-count": undefined, allowOverlayDrop: true, treeViewType: TreeViewType.outline, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, backgroundColor: "white" - }}, - {key: "emptyComparison", creator: Docs.Create.ComparisonDocument, opts: { title: "Comparer", _width: 300, _height: 300 }}, - {key: "emptyScript", creator: (opts) => Docs.Create.ScriptingDocument(undefined, opts), opts: { title: "script", _width: 200, _height: 250, }}, - {key: "emptyScreenshot", creator: Docs.Create.ScreenshotDocument, opts: { title: "empty screenshot", _width: 400, _height: 200 }}, - {key: "emptyWebCam", creator: (opts) => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, - {key: "emptyAudio", creator: (opts) => Docs.Create.AudioDocument(nullAudio, opts), opts: { title: "audio recording", x: 200, y: 200, _width: 200, _height: 100, }}, - {key: "emptyNote", creator: (opts) => Docs.Create.TextDocument("", opts), opts: { title: "text note", _width: 200, _autoHeight: true }}, - {key: "emptyButton", creator: Docs.Create.ButtonDocument, opts: { title: "Button", _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, - {key: "emptyWebpage", creator: (opts) => Docs.Create.WebDocument("http://www.bing.com/", opts), opts: { title: "webpage", _nativeWidth: 850, _height: 512, _width: 400, useCors: true, }}, - {key: "emptyMap", creator: (opts) => Docs.Create.MapDocument([], opts), opts: { title: "map", _showSidebar: true, _width: 800, _height: 600, }}, - {key: "emptyDataViz", creator: (opts) => Docs.Create.DataVizDocument(opts), opts: { title: "DataViz", _width: 300, _height: 300 }}, - ]; - - emptyThings.forEach(thing => - this.AssignScripts(this.AssignOpts(DocCast(doc[thing.key]), {...standardOps(), ...thing.opts}) ?? (doc[thing.key] = thing.creator({...standardOps(), ...thing.opts})), undefined, thing.funcs)) - - if (doc.emptyHeader === undefined) { - const json = { - doc: { - type: "doc", - content: [ - { - type: "paragraph", attrs: {}, content: [{ - type: "dashField", - attrs: { fieldKey: "author", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }, { - type: "dashField", - attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, - marks: [{ type: "strong" }] - }] + const standardOps = (key:string) => ({ title : "Untitled "+ key, _fitWidth: true, system: true, "dragFactory-count": 0, cloneFieldFilter: new List(["system"]) }); + const json = { + doc: { + type: "doc", + content: [ + { + type: "paragraph", attrs: {}, content: [{ + type: "dashField", + attrs: { fieldKey: "author", docid: "", hideKey: false }, + marks: [{ type: "strong" }] + }, { + type: "dashField", + attrs: { fieldKey: "creationDate", docid: "", hideKey: false }, + marks: [{ type: "strong" }] }] - }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] - }; - const headerBtnHgt = 10; - const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { - ...standardOps(), title: "text", _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, + }] + }, + selection: { type: "text", anchor: 1, head: 1 }, + storedMarks: [] + }; + const headerBtnHgt = 10; + const headerTemplate = (opts:DocumentOptions) => { + const header = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { ...opts, title: "text", layout: "" + ` ` + @@ -309,28 +274,57 @@ export class CurrentUserUtils { // " " + // " " + // "
    "; - Doc.GetProto(headerTemplate).isTemplateDoc = makeTemplate(Doc.GetProto(headerTemplate), true, "headerView"); - doc.emptyHeader = headerTemplate; + Doc.GetProto(header).isTemplateDoc = makeTemplate(Doc.GetProto(header), true, "headerView"); + Doc.GetProto(header).title = "Untitled Header"; + return header; } + const emptyThings:{key:string, // the field name where the empty thing will be stored + opts:DocumentOptions, // the document options that are required for the empty thing + funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing + creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist + }[] = [ + {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, + {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, + {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, + {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, + {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, + {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }}, + {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, + {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, + {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, + {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, + {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, + {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}}, + {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, + {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }}, + {key: "Slide", creator: opts => Docs.Create.TreeDocument([], opts), opts: { _width: 300, _height: 200, _viewType: CollectionViewType.Tree, + treeViewHasOverlay: true, _fontSize: "20px", _autoHeight: true, + allowOverlayDrop: true, treeViewType: TreeViewType.outline, + backgroundColor: "white", _xMargin: 0, _yMargin: 0, _singleLine: true + }, funcs: {title: 'self.text?.Text'}}, + ]; + + emptyThings.forEach(thing => + this.AssignScripts(this.AssignOpts(DocCast(doc["empty"+thing.key]), {...standardOps(thing.key), ...thing.opts}) ?? (doc["empty"+thing.key] = thing.creator({...standardOps(thing.key), ...thing.opts})), undefined, thing.funcs)) + return [ - { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, clickFactory: doc.emptyPane as Doc, }, - { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyMath as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, scripts: {onClick: 'openInOverlay(copyDragFactory(this.dragFactory))',onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a custom note", title: "Custom", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, - { toolTip: "Tap or drag to create a progressive slide",title: "Slide", icon: "file", dragFactory: doc.emptySlide as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreenshot as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs:{ hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: { hidden: 'IsNoviceMode()'}}, - { toolTip: "Tap or drag to create a mobile view", title: "Phone", icon: "mobile", dragFactory: doc.activeMobileMenu as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, funcs: {hidden: 'IsNoviceMode()'} }, - { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, scripts: {onClick: 'openOnRight(Doc.UserDoc().activeMobileMenu)', onDragStart: '{ return copyDragFactory(this.dragFactory);}'} }, + { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, }, + { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab), scripts: { onClick: 'openOnRight(copyDragFactory(this.clickFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, }, + { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, }, + { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, }, + { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, }, + { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, }, + { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, }, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, funcs: { hidden: 'IsNoviceMode()'} }, + { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, funcs: { hidden: 'IsNoviceMode()'}}, + { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "file", dragFactory: doc.emptyDataViz as Doc, }, + { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize", dragFactory: doc.emptyHeader as Doc, scripts: {onClick: 'openOnRight(delegateDragFactory(this.dragFactory))', onDragStart: '{ return delegateDragFactory(this.dragFactory);}'}, }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", scripts: {onClick: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' } }, - ]; + ].map(tuple => ({scripts: {onClick: 'openOnRight(copyDragFactory(this.dragFactory))', onDragStart: '{ return copyDragFactory(this.dragFactory);}'}, ...tuple, })) } /// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools @@ -345,9 +339,10 @@ export class CurrentUserUtils { return this.AssignScripts(this.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs); }); - const reqdOpts = { + const reqdOpts:DocumentOptions = { title: "Basic Item Creators", _showTitle: "title", _xMargin: 0, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, system: true, _autoHeight: true, _width: 500, _height: 300, _fitWidth: true, _columnWidth: 40, ignoreClick: true, _lockedPosition: true, _forceActive: true, + childDocumentsActive: true }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; return this.AssignScripts(this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); @@ -357,34 +352,26 @@ export class CurrentUserUtils { static leftSidebarMenuBtnDescriptions(doc: Doc):{title:string, target:Doc, icon:string, scripts:{[key:string]:any}, funcs?:{[key:string]:any}}[] { const badgeValue = "((len) => len && len !== '0' ? len: undefined)(docList(self.target.data).filter(doc => !docList(self.target.viewed).includes(doc)).length.toString())"; return [ - { title: "Dashboards", target: CurrentUserUtils.MyDashboards, icon: "desktop", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Search", target: CurrentUserUtils.MySearcher, icon: "search", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Files", target: CurrentUserUtils.MyFilesystem, icon: "folder-open", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Tools", target: CurrentUserUtils.MyTools, icon: "wrench", scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, - { title: "Imports", target: CurrentUserUtils.MyImports, icon: "upload", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Recently Closed", target: CurrentUserUtils.MyRecentlyClosed, icon: "archive", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "Shared with me", target: CurrentUserUtils.MySharedDocs, icon: "users", scripts:{onClick: 'selectMainMenu(self)'}, funcs:{badgeValue:badgeValue}}, - { title: "Trails", target: CurrentUserUtils.MyTrails, icon: "pres-trail", scripts:{onClick: 'selectMainMenu(self)'} }, - { title: "User Doc", target: CurrentUserUtils.MyUserDocView, icon: "address-card",scripts:{onClick: 'selectMainMenu(self)'}, funcs: {hidden: "IsNoviceMode()"} }, - ]; + { title: "Dashboards", target: this.setupDashboards(doc, "myDashboards"), icon: "desktop", }, + { title: "Search", target: this.setupSearcher(doc, "mySearcher"), icon: "search", }, + { title: "Files", target: this.setupFilesystem(doc, "myFilesystem"), icon: "folder-open", }, + { title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} }, + { title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", }, + { title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", }, + { title: "Shared Docs", target: this.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}}, + { title: "Trails", target: this.setupTrails(doc, "myTrails"), icon: "pres-trail", }, + { title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} }, + ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}})); } - /// setup the left sidebar container and panels that can be displayed within it + /// the empty panel that is filled with whichever left menu button's panel has been selected static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { this.AssignOpts(DocCast(doc[field]), {}) ?? (doc[field] = ((doc:Doc) => {doc.system = true; return doc;})(new Doc())); - CurrentUserUtils.setupSearcher(doc, "mySearcher"); - CurrentUserUtils.setupToolsBtnPanel(doc, "myTools"); - CurrentUserUtils.setupImportSidebar(doc, "myImports"); - CurrentUserUtils.setupDashboards(doc, "myDashboards"); - CurrentUserUtils.setupTrails(doc, "myTrails"); - CurrentUserUtils.setupFilesystem(doc, "myFilesystem"); - CurrentUserUtils.setupRecentlyClosedDocs(doc, "myRecentlyClosed"); - CurrentUserUtils.setupUserDocView(doc, "myUserDocView"); } /// Initializes the left sidebar menu buttons and the panels they open up static setupLeftSidebarMenu(doc: Doc, field="myLeftSidebarMenu") { - this.setupLeftSidebarPanel(doc); // the tools/panels opened up by the menu buttons + this.setupLeftSidebarPanel(doc); const myLeftSidebarMenu = DocCast(doc[field]); const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => { const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; @@ -520,7 +507,7 @@ export class CurrentUserUtils { dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, }; - this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.SearchDocument(reqdOpts)); + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.SearchDocument(reqdOpts)); } /// Initializes the panel of draggable tools that is opened from the left sidebar. @@ -532,7 +519,7 @@ export class CurrentUserUtils { title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, }; - this.AssignOpts(myTools, reqdToolOps, [creatorBtns, templateBtns]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, templateBtns], reqdToolOps)); + return this.AssignOpts(myTools, reqdToolOps, [creatorBtns, templateBtns]) ?? (doc[field] = Docs.Create.StackingDocument([creatorBtns, templateBtns], reqdToolOps)); } /// initializes the left sidebar dashboard pane @@ -595,6 +582,7 @@ export class CurrentUserUtils { if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } + return myTrails; } /// initializes the left sidebar File system pane @@ -624,10 +612,11 @@ export class CurrentUserUtils { if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); } + return myFilesystem; } /// initializes the panel displaying docs that have been recently closed - static setupRecentlyClosedDocs(doc: Doc, field:string) { + static setupRecentlyClosed(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { _showTitle: "title", _lockedPosition: true, _gridGap: 5, _forceActive: true, title: "My Recently Closed", buttonMenu: true, childHideLinkButton: true, treeViewHideTitle: true, childDropAction: "alias", system: true, treeViewTruncateTitleWidth: 150, ignoreClick: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", @@ -651,6 +640,7 @@ export class CurrentUserUtils { if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script.script.originalScript === clearAll("self"))) { recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("self"))!]) } + return recentlyClosed; } /// creates a new, empty filter doc @@ -674,7 +664,7 @@ export class CurrentUserUtils { treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 }; if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }) - this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([doc], reqdOpts)); + return this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([doc], reqdOpts)); } static linearButtonList = (title: string, opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { @@ -903,7 +893,7 @@ export class CurrentUserUtils { buttonText: "Import", icon: "upload", system: true }; const reqdBtnScripts = { onClick: "importDocument()" }; const newImportBtn = this.AssignOpts(DocCast(myImports.buttonMenuDoc), reqdBtnOpts) ?? (myImports.buttonMenuDoc = Docs.Create.FontIconDocument(reqdBtnOpts)); - this.AssignScripts(newImportBtn, reqdBtnScripts); + return this.AssignScripts(newImportBtn, reqdBtnScripts); } static setupClickEditorTemplates(doc: Doc) { @@ -1055,7 +1045,6 @@ export class CurrentUserUtils { } if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard - !("presentationView" in doc) && (doc.presentationView = new List([Docs.Create.TreeDocument([], { title: "Presentation" })])); userDoc ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); } const state = CurrentUserUtils._urlState; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 684c919bd..4e8c14039 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -229,6 +229,7 @@ export class CollectionStackingView extends CollectionSubView this.props.isSelected() || this.props.isContentActive(); + isChildContentActive = () => this.props.isDocumentActive?.() && (this.props.childDocumentsActive?.() || BoolCast(this.rootDoc.childDocumentsActive)); getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); @@ -245,7 +246,7 @@ export class CollectionStackingView extends CollectionSubView Date: Wed, 22 Jun 2022 23:08:56 -0400 Subject: fixing up Field Infos to work with new initialization. --- src/client/documents/Documents.ts | 124 ++++++++++++++---------------------- src/client/util/CurrentUserUtils.ts | 57 +++++++++++------ 2 files changed, 85 insertions(+), 96 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 01cfe90da..ad6a4dd3c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -69,53 +69,57 @@ class EmptyBox { return ""; } } -abstract class FInfo { +export abstract class FInfo { description: string = ""; - type?: string; + fieldType?: string; values?: Field[]; - layoutField?: boolean; // is this field a layout (or datadoc) field // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string - constructor(d: string, l: boolean = false) { this.description = d; this.layoutField = l; } + constructor(d: string) { this.description = d; } } -class BoolInfo extends FInfo { type?= "boolean"; values?: boolean[] = [true, false]; } -class NumInfo extends FInfo { type?= "number"; values?: number[] = []; } -class StrInfo extends FInfo { type?= "string"; values?: string[] = []; } -class DocInfo extends FInfo { type?= "Doc"; values?: Doc[] = []; } -class DimInfo extends FInfo { type?= "DimUnit"; values?= [DimUnit.Pixel, DimUnit.Ratio]; } -class PEInfo extends FInfo { type?= "pointerEvents"; values?= ["all", "none"]; } -class DAInfo extends FInfo { type?= "dropActionType"; values?= ["alias", "copy", "move", "same", "proto", "none"]; } +class BoolInfo extends FInfo { fieldType?= "boolean"; values?: boolean[] = [true, false]; } +class NumInfo extends FInfo { fieldType?= "number"; values?: number[] = []; constructor(d:string, values?:number[]) { super(d); this.values = values; }} +class StrInfo extends FInfo { fieldType?= "string"; values?: string[] = []; constructor(d:string, values?:string[]) { super(d); this.values = values; }} +class DocInfo extends FInfo { fieldType?= "Doc"; values?: Doc[] = []; constructor(d:string, values?:Doc[]) { super(d); this.values = values; }} +class DimInfo extends FInfo { fieldType?= "DimUnit"; values?= [DimUnit.Pixel, DimUnit.Ratio]; } +class PEInfo extends FInfo { fieldType?= "pointerEvents"; values?= ["all", "none"]; } +class DAInfo extends FInfo { fieldType?= "dropActionType"; values?= ["alias", "copy", "move", "same", "proto", "none"]; } type BOOLt = BoolInfo | boolean; -type NUMt = NumInfo | number; -type STRt = StrInfo | string; -type DOCt = DocInfo | Doc; -type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; -type PEVt = PEInfo | "none" | "all"; +type NUMt = NumInfo | number; +type STRt = StrInfo | string; +type DOCt = DocInfo | Doc; +type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; +type PEVt = PEInfo | "none" | "all"; type DROPt = DAInfo | dropActionType; export class DocumentOptions { + x?: NUMt = new NumInfo("x coordinate of document in a freeform view"); + y?: NUMt = new NumInfo("y coordinage of document in a freeform view"); + z?: NUMt = new NumInfo("whether document is in overlay (1) or not (0)", [1,0]); system?: BOOLt = new BoolInfo("is this a system created/owned doc"); + type?: STRt = new StrInfo("type of document", Array.from(Object.keys(DocumentType))); + title?: string; _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); - userColor?: string; // color associated with a Dash user (seen in header fields of shared documents) - color?: string; // foreground color data doc + userColor?: STRt = new StrInfo("color associated with a Dash user (seen in header fields of shared documents)"); + color?: STRt = new StrInfo("foreground color data doc"); backgroundColor?: STRt = new StrInfo("background color for data doc"); - _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)", true); - _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents", true); - _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title", true); - _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes", true); - _headerPointerEvents?: PEVt = new PEInfo("types of events the header of a custom text document can consume", true); - _panX?: NUMt = new NumInfo("horizontal pan location of a freeform view", true); - _panY?: NUMt = new NumInfo("vertical pan location of a freeform view", true); - _width?: NUMt = new NumInfo("displayed width of a document", true); - _height?: NUMt = new NumInfo("displayed height of document", true); - _nativeWidth?: NUMt = new NumInfo("native width of document contents (e.g., the pixel width of an image)", true); - _nativeHeight?: NUMt = new NumInfo("native height of document contents (e.g., the pixel height of an image)", true); - _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", true); - _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units", true); - _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)", true); - _fitContentsToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents + _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)"); + _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents"); + _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title"); + _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes"); + _headerPointerEvents?: PEVt = new PEInfo("types of events the header of a custom text document can consume"); + _panX?: NUMt = new NumInfo("horizontal pan location of a freeform view"); + _panY?: NUMt = new NumInfo("vertical pan location of a freeform view"); + _width?: NUMt = new NumInfo("displayed width of a document"); + _height?: NUMt = new NumInfo("displayed height of document"); + _nativeWidth?: NUMt = new NumInfo("native width of document contents (e.g., the pixel width of an image)"); + _nativeHeight?: NUMt = new NumInfo("native height of document contents (e.g., the pixel height of an image)"); + _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height"); + _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units"); + _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)"); + _fitContentsToBox?: BOOLt = new BoolInfo("whether a freeformview should zoom/scale to create a shrinkwrapped view of its content"); _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed @@ -155,23 +159,20 @@ export class DocumentOptions { _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) _timecodeToHide?: number; // the time that a document should be hidden _timelineLabel?: boolean; // whether the document exists on a timeline - "_carousel-caption-xMargin"?: number; - "_carousel-caption-yMargin"?: number; - "icon-nativeWidth"?: number; - "icon-nativeHeight"?: number; - "dragFactory-count"?: number; // number of items created from a drag button (used for setting title with incrementing index) - x?: number; - y?: number; - z?: number; // whether document is in overlay (1) or not (0 or undefined) + "_carousel-caption-xMargin"?: NUMt = new NumInfo("x margin of caption inside of a carouself collection"); + "_carousel-caption-yMargin"?: NUMt = new NumInfo("y margin of caption inside of a carouself collection"); + "icon-nativeWidth"?: NUMt = new NumInfo("native width of icon view"); + "icon-nativeHeight"?: NUMt = new NumInfo("native height of icon view"); + "dragFactory-count"?: NUMt = new NumInfo("number of items created from a drag button (used for setting title with incrementing index)"); lat?: number; lng?: number; infoWindowOpen?: boolean; author?: string; _layoutKey?: string; + fieldValues?: List; // possible field values used by fieldInfos + fieldType?: string; // type of afield used by fieldInfos unrendered?: boolean; // denotes an annotation that is not rendered with a DocumentView (e.g, rtf/pdf text selections and links to scroll locations in web/pdf) - type?: string; - title?: string; - "acl-Public"?: string; // public permissions + "acl-Public"?: string; // public permissions "_acl-Public"?: string; // public permissions version?: string; // version identifier for a document label?: string; @@ -340,33 +341,6 @@ export class DocumentOptions { } export namespace Docs { - const _docOptions = new DocumentOptions(); - - export async function setupFieldInfos() { - return await DocServer.GetRefField("FieldInfos8") as Doc ?? - runInAction(() => { - const infos = new Doc("FieldInfos8", true); - const keys = Object.keys(new DocumentOptions()); - for (const key of keys) { - const options = (_docOptions as any)[key] as FInfo; - const finfo = new Doc(); - finfo.name = key; - switch (options.type) { - case "boolean": finfo.options = new List(options.values as any as boolean[]); break; - case "number": finfo.options = new List(options.values as any as number[]); break; - case "Doc": finfo.options = new List(options.values as any as Doc[]); break; - default: // string, pointerEvents, dimUnit, dropActionType - finfo.options = new List(options.values as any as string[]); break; - } - finfo.layoutField = options.layoutField; - finfo.description = options.description; - finfo.type = options.type; - infos[key] = finfo; - } - return infos; - }); - } - export let newAccount: boolean = false; export namespace Prototypes { @@ -798,8 +772,8 @@ export namespace Docs { I.tool = tool; I["text-align"] = "center"; I.title = "ink"; - I.x = options.x; - I.y = options.y; + I.x = options.x as number; + I.y = options.y as number; I._width = options._width as number; I._height = options._height as number; I._fontFamily = "cursive"; @@ -1266,8 +1240,8 @@ export namespace DocUtils { return DocServer.GetRefField(id).then(field => { if (field instanceof Doc) { const alias = Doc.MakeAlias(field); - alias.x = options.x || 0; - alias.y = options.y || 0; + alias.x = options.x as number || 0; + alias.y = options.y as number || 0; alias._width = (options._width as number) || 300; alias._height = (options._height as number) || (options._width as number) || 300; return alias; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fe9a9b2d9..b9724051a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -13,7 +13,7 @@ import { ImageField, nullAudio } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; -import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; +import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { CollectionFreeFormView } from "../views/collections/collectionFreeForm"; @@ -39,6 +39,7 @@ import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; interface Button { + // DocumentOptions fields a button can set title?: string; toolTip?: string; icon?: string; @@ -51,6 +52,8 @@ interface Button { btnList?: List; ignoreClick?: boolean; buttonText?: string; + + // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; } funcs?: { [key:string]: string }; subMenu?: Button[]; @@ -861,8 +864,7 @@ export class CurrentUserUtils { const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true }; - const reqdBtnScripts = { onClick: "importDocument()" }; - return this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, reqdBtnScripts); + return this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); } static setupClickEditorTemplates(doc: Doc) { @@ -875,9 +877,8 @@ export class CurrentUserUtils { targetScriptKey: "onChildClick", system: true }); - const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( - "openOnRight(self.doubleClickView)", - {}), { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); + const openDetail = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "openOnRight(self.doubleClickView)", {}), + { title: "Double click to open doubleClickView", _width: 300, _height: 200, targetScriptKey: "onChildDoubleClick", system: true }); doc["clickFuncs-child"] = Docs.Create.TreeDocument([openInTarget, openDetail], { title: "on Child Click function templates", system: true }); } @@ -914,7 +915,7 @@ export class CurrentUserUtils { } static async updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - doc.globalGroupDatabase ?? (doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument()); + this.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {}); await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]), async () => { @@ -954,17 +955,35 @@ export class CurrentUserUtils { this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates - doc.globalScriptDatabase ?? ( doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument()); - doc.myHeaderBar ?? (doc.myHeaderBar = Docs.Create.MulticolumnDocument([], { title: "header bar", system: true })); // drop down panel at top of dashboard for stashing documents + this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption + this.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {}); + this.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); - doc.fieldInfos = await Docs.setupFieldInfos(); if (doc.activeDashboard instanceof Doc) { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } return doc; } + static setupFieldInfos(doc:Doc, field="fieldInfos") { + const fieldInfoOpts = { title: "Field Infos", system: true}; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object + const infos = this.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts); + const entries = Object.entries(new DocumentOptions()); + entries.forEach(pair => { + if (!Array.from(Object.keys(fieldInfoOpts)).includes(pair[0])) { + const options = pair[1] as FInfo; + const opts:DocumentOptions = { system: true, title: pair[0], ...OmitKeys(options, ["values"]).omit, fieldIsLayout: pair[0].startsWith("_")}; + switch (options.fieldType) { + case "boolean": opts.fieldValues = new List(options.values as any); break; + case "number": opts.fieldValues = new List(options.values as any); break; + case "Doc": opts.fieldValues = new List(options.values as any); break; + default: opts.fieldValues = new List(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType + } + this.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts); + } + }); + } public static async loadCurrentUser() { return rp.get(Utils.prepend("/getCurrentUser")).then(async response => { @@ -1007,17 +1026,14 @@ export class CurrentUserUtils { public static _urlState: HistoryUtil.DocUrl; public static openDashboard = (doc: Doc, fromHistory = false) => { - const userDoc = Doc.UserDoc(); CurrentUserUtils.MainDocId = doc[Id]; - if (!DocListCast(CurrentUserUtils.MyDashboards.data).includes(doc)) { - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); - } + Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); - if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard - userDoc ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); - } + // this has the side-effect of setting the main container since we're assigning the active/guest dashboard + Doc.UserDoc() ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); + const state = CurrentUserUtils._urlState; - if (state.sharing === true && !userDoc) { + if (state.sharing === true && !Doc.UserDoc()) { DocServer.Control.makeReadOnly(); } else { fromHistory || HistoryUtil.pushState({ @@ -1054,7 +1070,7 @@ export class CurrentUserUtils { const upload = Utils.prepend("/uploadDoc"); const formData = new FormData(); const file = input.files && input.files[0]; - if (file && file.type === 'application/zip') { + if (file?.type === 'application/zip') { formData.append('file', file); formData.append('remap', "true"); const response = await fetch(upload, { method: "POST", body: formData }); @@ -1067,9 +1083,8 @@ export class CurrentUserUtils { } } } else if (input.files && input.files.length !== 0) { - const importDocs = CurrentUserUtils.MyImports; const disposer = OverlayView.ShowSpinner(); - DocListCastAsync(importDocs.data).then(async list => { + DocListCastAsync(CurrentUserUtils.MyImports.data).then(async list => { const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); if (results.length !== input.files?.length) { alert("Error uploading files - possibly due to unsupported file types"); -- cgit v1.2.3-70-g09d2 From 8eb794e233f905daaa5b9a25c6720e567512653e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Jun 2022 12:15:47 -0400 Subject: fixed : contextMenu . fixed some fitWidth issues for text where heights weren't working right --- src/client/documents/Documents.ts | 27 +++------ src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/ContextMenu.tsx | 64 ++++++---------------- src/client/views/ContextMenuItem.tsx | 10 +--- src/client/views/nodes/DocumentView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- 6 files changed, 33 insertions(+), 77 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ad6a4dd3c..fc73deb36 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -363,7 +363,7 @@ export namespace Docs { layout: { view: FormattedTextBox, dataField: "text" }, options: { _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, - links: "@links(self)" + forceReflow: true, links: "@links(self)" } }], [DocumentType.SEARCH, { @@ -400,7 +400,7 @@ export namespace Docs { }], [DocumentType.AUDIO, { layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: "lightGray", links: "@links(self)" } + options: { _height: 100, backgroundColor: "lightGray", forceReflow: true, nativeDimModifiable: true, links: "@links(self)" } }], [DocumentType.REC, { layout: { view: VideoBox, dataField: defaultDataKey }, @@ -412,7 +412,7 @@ export namespace Docs { }], [DocumentType.MAP, { layout: { view: MapBox, dataField: defaultDataKey }, - options: { _height: 600, _width: 800, links: "@links(self)" } + options: { _height: 600, _width: 800, nativeDimModifiable: true, links: "@links(self)" } }], [DocumentType.IMPORT, { layout: { view: DirectoryImportBox, dataField: defaultDataKey }, @@ -450,11 +450,11 @@ export namespace Docs { }], [DocumentType.EQUATION, { layout: { view: EquationBox, dataField: defaultDataKey }, - options: { links: "@links(self)", hideResizeHandles: true, hideDecorationTitle: true } + options: { links: "@links(self)", nativeDimModifiable: true, hideResizeHandles: true, hideDecorationTitle: true } }], [DocumentType.FUNCPLOT, { layout: { view: FunctionPlotBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } + options: { nativeDimModifiable: true, links: "@links(self)" } }], [DocumentType.BUTTON, { layout: { view: LabelBox, dataField: "onClick" }, @@ -493,7 +493,7 @@ export namespace Docs { }], [DocumentType.COMPARISON, { layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { clipWidth: 50, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" } + options: { clipWidth: 50, nativeDimModifiable: true, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" } }], [DocumentType.GROUPDB, { data: new List(), @@ -506,7 +506,7 @@ export namespace Docs { }], [DocumentType.DATAVIZ, { layout: { view: DataVizBox, dataField: defaultDataKey }, - options: { _fitWidth: true, _fitHeight: true, links: "@links(self)" } + options: { _fitWidth: true, nativeDimModifiable: true, links: "@links(self)" } }] ]); @@ -530,15 +530,6 @@ export namespace Docs { const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix); // fetch the actual prototype documents from the server const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds); - if (!Docs.newAccount) { - Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeHeightUnfrozen = true; // to avoid having to recreate the DB - Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeHeightUnfrozen = true; - Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeHeightUnfrozen = true; - Cast(actualProtos[DocumentType.WEB + suffix], Doc, null).nativeDimModifiable = true; // to avoid having to recreate the DB - Cast(actualProtos[DocumentType.PDF + suffix], Doc, null).nativeDimModifiable = true; - Cast(actualProtos[DocumentType.RTF + suffix], Doc, null).nativeDimModifiable = true; - } - // update this object to include any default values: DocumentOptions for all prototypes prototypeIds.map(id => { const existing = actualProtos[id] as Doc; @@ -1273,8 +1264,8 @@ export namespace DocUtils { })) as ContextMenuProps[], icon: "sticky-note" }); - const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data).lastElement()?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ - description: ":" + StrCast(dragDoc.title), + const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data)[0]?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ + description: ":" + StrCast(dragDoc.title).replace("Untitled ",""), event: undoBatch((args: { x: number, y: number }) => { const newDoc = Doc.copyDragFactory(dragDoc); if (newDoc) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index b9724051a..28d20c56b 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -282,13 +282,13 @@ export class CurrentUserUtils { }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _autoHeight: true }}, {key: "Collection", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 150, _height: 100 }}, - {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _backgroundGridShow: true, }}, + {key: "Equation", creator: opts => Docs.Create.EquationDocument(opts), opts: { _width: 300, _height: 35, _fitWidth:false, _backgroundGridShow: true, }}, {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _showSidebar: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, - {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, + {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index e2f98de1e..9034cacb2 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,10 +1,10 @@ import React = require("react"); -import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem"; -import { observable, action, computed, runInAction, IReactionDisposer, reaction } from "mobx"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, IReactionDisposer, observable } from "mobx"; import { observer } from "mobx-react"; import "./ContextMenu.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import Measure from "react-measure"; +import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem"; +import { Utils } from "../../Utils"; @observer export class ContextMenu extends React.Component { @@ -116,10 +116,6 @@ export class ContextMenu extends React.Component { this._defaultItem = item; } - getItems() { - return this._items; - } - static readonly buffer = 20; get pageX() { const x = this._pageX; @@ -199,37 +195,25 @@ export class ContextMenu extends React.Component { return eles; }; - return flattenItems(this._items, name => [name]); + return flattenItems(this._items.slice(), name => [name]); } @computed get flatItems(): OriginalMenuProps[] { return this.filteredItems.filter(item => !Array.isArray(item)) as OriginalMenuProps[]; } - @computed get filteredViews() { - const createGroupHeader = (contents: any) => { - return ( -
    -
    {contents}
    -
    - ); - }; - const createItem = (item: ContextMenuProps, selected: boolean) => ; - let itemIndex = 0; - return this.filteredItems.map(value => { - if (Array.isArray(value)) { - return createGroupHeader(value.join(" -> ")); - } else { - return createItem(value, itemIndex++ === this.selectedIndex); - } - }); - } - @computed get menuItems() { if (!this._searchString) { return this._items.map((item, ind) => ); } - return this.filteredViews; + return this.filteredItems.map((value, index) => + Array.isArray(value) ? +
    +
    {value.join(" -> ")}
    +
    + : + + ); } @computed get itemsNeedSearch() { @@ -237,14 +221,8 @@ export class ContextMenu extends React.Component { } render() { - if (!this._display) { - return null; - } - const style = this._yRelativeToTop ? { left: this.pageX, top: this.pageY } : - { left: this.pageX, bottom: this.pageY }; - - const contents = ( - <> + return !this._display ? (null) : +
    {!this.itemsNeedSearch ? (null) : @@ -253,17 +231,7 @@ export class ContextMenu extends React.Component { } {this.menuItems} - - ); - return ( - { this._width = r.offset.width; this._height = r.offset.height; })}> - {({ measureRef }) => ( -
    - {contents} -
    - )} -
    - ); +
    ; } @action diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 25d00f701..c136de1c3 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -28,11 +28,10 @@ export type ContextMenuProps = OriginalMenuProps | SubmenuProps; export class ContextMenuItem extends React.Component { @observable private _items: Array = []; @observable private overItem = false; - @observable private subRef = React.createRef(); - constructor(props: ContextMenuProps | SubmenuProps) { - super(props); - if ((this.props as SubmenuProps).subitems) { + componentDidMount() { + this._items.length = 0; + if ((this.props as SubmenuProps)?.subitems) { (this.props as SubmenuProps).subitems?.forEach(i => this._items.push(i)); } } @@ -78,9 +77,6 @@ export class ContextMenuItem extends React.Component diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5d2adc321..8d4fd376f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1251,7 +1251,7 @@ export class DocumentView extends React.Component { @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } - @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } + @computed get centeringY() { return this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 600730d87..d3dee3c89 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -862,6 +862,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (nh) this.layoutDoc._nativeHeight = scrollHeight; } + @computed get contentScaling() { return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;} componentDidMount() { !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._cachedLinks = DocListCast(this.Document.links); @@ -875,7 +876,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { - autoHeight && this.props.setHeight?.((this.props.scaling?.() || 1) * (marginsHeight + Math.max(sidebarHeight, textHeight))); + autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight))); }, { fireImmediately: true }); this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { -- cgit v1.2.3-70-g09d2 From e4ebdbbefb2696ea6b75464a93a5714ab0245135 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 23 Jun 2022 14:11:57 -0400 Subject: fixes for resizing native sized documents with fitwidth on/off and nativeHeightUnfrozen on/off --- src/client/documents/Documents.ts | 2 +- src/client/views/DocumentDecorations.tsx | 23 ++++++++++++----------- src/client/views/nodes/DocumentView.tsx | 3 +-- 3 files changed, 14 insertions(+), 14 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fc73deb36..f3f9bd1d9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -362,7 +362,7 @@ export namespace Docs { [DocumentType.RTF, { layout: { view: FormattedTextBox, dataField: "text" }, options: { - _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, nativeHeightUnfrozen: true, treeViewGrowsHorizontally: true, + _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, treeViewGrowsHorizontally: true, forceReflow: true, links: "@links(self)" } }], diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4247501bb..bef7c85a4 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -311,7 +311,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P move[1] = thisPt.y - this._snapY; this._snapX = thisPt.x; this._snapY = thisPt.y; - let dragBottom = false, dragRight = false, dragBotRight = false; + let dragBottom = false, dragRight = false, dragBotRight = false, dragTop = false; let dX = 0, dY = 0, dW = 0, dH = 0; switch (this._resizeHdlId.split(" ")[0]) { case "": break; @@ -329,7 +329,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P case "documentDecorations-topResizer": dY = -1; dH = -move[1]; - dragBottom = true; + dragTop = true; break; case "documentDecorations-bottomLeftResizer": dX = -1; @@ -361,27 +361,28 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P const doc = Document(docView.rootDoc); const nwidth = docView.nativeWidth; const nheight = docView.nativeHeight; - const docheight = doc._height || 0; - const docwidth = doc._width || 0; + let docheight = doc._height || 0; + let docwidth = doc._width || 0; const width = docwidth; let height = (docheight || (nheight / nwidth * width)); height = !height || isNaN(height) ? 20 : height; const scale = docView.props.ScreenToLocalTransform().Scale; - const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable; + const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || doc.nativeHeightUnfrozen); if (nwidth && nheight) { - if (nwidth / nheight !== width / height && !dragBottom) { + if (nwidth / nheight !== width / height && !dragBottom && !dragTop) { height = nheight / nwidth * width; } - if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction + if (modifyNativeDim && !dragBottom && !dragTop) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; else dW = dH * nwidth / nheight; } } let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); - const fixedAspect = (nwidth && nheight && !doc._fitWidth); + const fixedAspect = (nwidth && nheight && (!doc._fitWidth || doc.nativeHeightUnfrozen)); + console.log(fixedAspect); if (fixedAspect) { - if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) { + if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop)|| !modifyNativeDim)) || dragRight) { if (dragRight && modifyNativeDim) { doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc); } else { @@ -394,7 +395,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P doc._width = actualdW; } else { - if (dragBottom && (modifyNativeDim || + if ((dragBottom|| dragTop) && (modifyNativeDim || (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc); doc._autoHeight = false; @@ -417,7 +418,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P dH && (doc._autoHeight = false); } doc.x = (doc.x || 0) + dX * (actualdW - docwidth); - doc.y = (doc.y || 0) + dY * (actualdH - docheight); + doc.y = (doc.y || 0) + (dragBottom ? 0: dY * (actualdH - docheight)); doc._lastModified = new DateField(); } const val = this._dragHeights.get(docView.layoutDoc); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 8d4fd376f..4c612891b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1249,7 +1249,7 @@ export class DocumentView extends React.Component { return this.props.PanelHeight(); } @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } - @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } + @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && !this.layoutDoc.nativeHeightUnfrozen ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2) : 0; } @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } @computed get centeringY() { return this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } @@ -1343,7 +1343,6 @@ export class DocumentView extends React.Component { transition: this.props.dataTransition, position: this.props.Document.isInkMask ? "absolute" : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, - margin: this.fitWidth ? "auto" : undefined, width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), -- cgit v1.2.3-70-g09d2 From 550b5e93097e0708199a9cf88714754d197b3b97 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 27 Jun 2022 12:48:54 -0400 Subject: fixed initialization issues with creating user documents unnecessarily. fixed updating doc icon templates to apply to documents already created. --- src/client/documents/Documents.ts | 10 +- src/client/util/CurrentUserUtils.ts | 64 ++++++----- src/client/util/LinkManager.ts | 123 +++++++++++---------- src/client/views/Main.tsx | 3 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/fields/Doc.ts | 2 +- 6 files changed, 104 insertions(+), 100 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 84e6258ac..3780df5b9 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -105,7 +105,6 @@ export class DocumentOptions { userColor?: STRt = new StrInfo("color associated with a Dash user (seen in header fields of shared documents)"); color?: STRt = new StrInfo("foreground color data doc"); backgroundColor?: STRt = new StrInfo("background color for data doc"); - _backgroundColor?: STRt = new StrInfo("background color for each template layout doc (overrides backgroundColor)"); _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents"); _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title"); _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes"); @@ -328,6 +327,7 @@ export class DocumentOptions { text?: string; textTransform?: string; // is linear view expanded letterSpacing?: string; // is linear view expanded + iconTemplate?: string; // name of icon template style selectedIndex?: number; // which item in a linear view has been selected using the "thumb doc" ui clipboard?: Doc; searchQuery?: string; // for quersyBox @@ -1288,9 +1288,7 @@ export namespace DocUtils { const batch = UndoManager.StartBatch("makeCustomViewClicked"); runInAction(() => { doc.layoutKey = "layout_" + templateSignature; - if (doc[doc.layoutKey] === undefined) { - createCustomView(doc, creator, templateSignature, docLayoutTemplate); - } + createCustomView(doc, creator, templateSignature, docLayoutTemplate); }); batch.end(); return doc; @@ -1318,7 +1316,9 @@ export namespace DocUtils { const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; if (docLayoutTemplate) { - Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); + if (docLayoutTemplate !== doc[customName]) { + Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); + } } else { let fieldTemplate: Opt; if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 5c77041e0..f585277d8 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -205,29 +205,37 @@ export class CurrentUserUtils { const reqdOpts = { title: "icon templates", _height: 75, system: true }; const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); - const makeIconTemplate = (type: DocumentType | undefined, templateField: string, iconTemplate: (opts:DocumentOptions) => Doc) => { + const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => { const iconFieldName = "icon" + (type ? "_" + type : ""); - return DocCast(templateIconsDoc[iconFieldName] ?? (templateIconsDoc[iconFieldName] = MakeTemplate(iconTemplate({onClick:deiconifyScript(), system: true}), true, iconFieldName, templateField))) ; + const curIcon = DocCast(templateIconsDoc[iconFieldName]); + let creator = labelBox; + switch (opts.iconTemplate) { + case DocumentType.IMG : creator = imageBox; break; + case DocumentType.FONTICON: creator = fontBox; break; + } + const allopts = {system: true, ...opts}; + return this.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? + this.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), + {onClick:"deiconifyView(documentView)"}); }; - const deiconifyScript = () => ScriptField.MakeScript("deiconifyView(documentView)", { documentView: "any" }); - const labelBox = (opts: object) => Docs.Create.LabelDocument({ + const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ textTransform: "unset", letterSpacing: "unset", _singleLine: false, _minFontSize: 14, _maxFontSize: 24, borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts }); - const imageBox = (url: Opt, opts: object) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", ...opts }); - const fontBox = (opts:DocumentOptions) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); + const imageBox = (opts: DocumentOptions, url?:string) => Docs.Create.ImageDocument(url ?? "http://www.cs.brown.edu/~bcz/noImage.png", { "icon-nativeWidth": 360 / 4, "icon-nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _showTitle: "title", ...opts }); + const fontBox = (opts:DocumentOptions, data?:string) => Docs.Create.FontIconDocument({ _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts }); const iconTemplates = [ - makeIconTemplate(undefined, "title", (opts) => labelBox({ ...opts, _backgroundColor: "dimgray" })), - makeIconTemplate(DocumentType.AUDIO, "title", (opts) => labelBox({ ...opts, _backgroundColor: "lightgreen" })), - makeIconTemplate(DocumentType.PDF, "title", (opts) => labelBox({ ...opts, _backgroundColor: "pink" })), - makeIconTemplate(DocumentType.WEB, "title", (opts) => labelBox({...opts, _backgroundColor: "brown" })), - makeIconTemplate(DocumentType.RTF, "text", (opts) => labelBox({ ...opts, _showTitle: "creationDate" })), - makeIconTemplate(DocumentType.IMG, "data", (opts) => imageBox("", { ...opts, _height: undefined, })), - makeIconTemplate(DocumentType.COL, "icon", (opts) => imageBox(undefined, opts)), - makeIconTemplate(DocumentType.VID, "icon", (opts) => imageBox(undefined, opts)), - makeIconTemplate(DocumentType.BUTTON, "data", (opts) => fontBox(opts)), + makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}), + makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}), + makeIconTemplate(DocumentType.PDF, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "pink"}), + makeIconTemplate(DocumentType.WEB, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "brown"}), + makeIconTemplate(DocumentType.RTF, "text", { iconTemplate:DocumentType.LABEL, _showTitle: "creationDate"}), + makeIconTemplate(DocumentType.IMG, "data", { iconTemplate:DocumentType.IMG, _height: undefined}), + makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), + makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}), + makeIconTemplate(DocumentType.BUTTON,"data", { iconTemplate:DocumentType.FONTICON}), //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now - makeIconTemplate("transcription" as any, "transcription", (opts) => labelBox({ ...opts, _backgroundColor: "orange" })), - // makeIconTemplate(DocumentType.PDF, "icon", () => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", {})) + makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }), + //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts)) ].filter(d => d).map(d => d!); this.AssignOpts(DocCast(doc[field]), {}, iconTemplates); } @@ -779,23 +787,23 @@ export class CurrentUserUtils { /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const ctxtMenuBtnsDoc = DocCast(doc[field]); + const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; + const ctxtMenuBtnsDoc = this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); if (!params.subMenu) { return this.setupContextMenuButton(params, menuBtnDoc); } else { - const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, title:"submenu", + const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: true, linearViewSubMenu: true, linearViewExpandable: true, }; return this.AssignScripts(this.AssignOpts(menuBtnDoc, reqdSubMenuOpts) ?? - this.linearButtonList(reqdSubMenuOpts, params.subMenu.map(sub => + (ctxtMenuBtnsDoc[StrCast(params.title)]= this.linearButtonList(reqdSubMenuOpts, params.subMenu.map(sub => this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) - )), undefined, params.funcs); + ))), undefined, params.funcs); } }); - const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; - return this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, ctxtMenuBtns); + return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); } /// collection of documents rendered in the overlay layer above all tabs and other UI @@ -957,12 +965,13 @@ export class CurrentUserUtils { this.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {}); this.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents - setTimeout(() => DocServer.UPDATE_SERVER_CACHE(), 2500); if (doc.activeDashboard instanceof Doc) { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } + new LinkManager(); + DocServer.UPDATE_SERVER_CACHE(); return doc; } static setupFieldInfos(doc:Doc, field="fieldInfos") { @@ -1009,12 +1018,7 @@ export class CurrentUserUtils { await Docs.Prototypes.initialize(); const userDoc = Docs.newAccount ? new Doc(userDocumentId, true) : field as Doc; Docs.newAccount &&(userDoc.activePage = "home"); - const updated = this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); - (await DocListCastAsync(Doc.LinkDBDoc()?.data))?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager - const a1 = await Cast(link?.anchor1, Doc, null); - const a2 = await Cast(link?.anchor2, Doc, null); - }); - return updated; + return this.updateUserDocument(Doc.SetUserDoc(userDoc), sharingDocumentId, linkDatabaseId); }); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 2100b1195..d51cd350d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,7 +1,6 @@ -import { validationResult } from "express-validator/check"; import { action, observable, observe } from "mobx"; import { computedFn } from "mobx-utils"; -import { DirectLinksSym, Doc, DocListCast, Field, Opt } from "../../fields/Doc"; +import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../fields/Doc"; import { List } from "../../fields/List"; import { ProxyField } from "../../fields/Proxy"; import { BoolCast, Cast, StrCast } from "../../fields/Types"; @@ -35,72 +34,74 @@ export class LinkManager { constructor() { LinkManager._instance = this; this.createLinkrelationshipLists(); - setTimeout(() => { - LinkManager.userLinkDBs = []; - const addLinkToDoc = (link: Doc) => { - const a1Prom = link?.anchor1; - const a2Prom = link?.anchor2; - Promise.all([a1Prom, a2Prom]).then((all) => { - const a1 = all[0]; - const a2 = all[1]; - const a1ProtoProm = (link?.anchor1 as Doc)?.proto; - const a2ProtoProm = (link?.anchor2 as Doc)?.proto; - Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => { - if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinksSym].add(link); - Doc.GetProto(a2)[DirectLinksSym].add(link); - Doc.GetProto(link)[DirectLinksSym].add(link); - } - })); - }); - }; - const remLinkFromDoc = (link: Doc) => { - const a1 = link?.anchor1; - const a2 = link?.anchor2; - Promise.all([a1, a2]).then(action(() => { + LinkManager.userLinkDBs = []; + const addLinkToDoc = (link: Doc) => { + const a1Prom = link?.anchor1; + const a2Prom = link?.anchor2; + Promise.all([a1Prom, a2Prom]).then((all) => { + const a1 = all[0]; + const a2 = all[1]; + const a1ProtoProm = (link?.anchor1 as Doc)?.proto; + const a2ProtoProm = (link?.anchor2 as Doc)?.proto; + Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => { if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinksSym].delete(link); - Doc.GetProto(a2)[DirectLinksSym].delete(link); - Doc.GetProto(link)[DirectLinksSym].delete(link); + Doc.GetProto(a1)[DirectLinksSym].add(link); + Doc.GetProto(a2)[DirectLinksSym].add(link); + Doc.GetProto(link)[DirectLinksSym].add(link); } })); - }; - const watchUserLinkDB = (userLinkDBDoc: Doc) => { - LinkManager.links.push(...DocListCast(userLinkDBDoc.data)); - const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields - observe(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) - switch (change.type as any) { - case "splice": - (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); - (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); - break; - case "update": //let oldValue = change.oldValue; - } - }, true); - observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link) - change => { - switch (change.type as any) { - case "update": - Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => { - const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length); - const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length); - - const added = newDocs?.filter(link => !(oldDocs || []).includes(link)); - const removed = oldDocs?.filter(link => !(newDocs || []).includes(link)); - added?.forEach((link: any) => addLinkToDoc(toRealField(link))); - removed?.forEach((link: any) => remLinkFromDoc(toRealField(link))); - }); - } - }, true); - }; - observe(LinkManager.userLinkDBs, change => { + }); + }; + const remLinkFromDoc = (link: Doc) => { + const a1 = link?.anchor1; + const a2 = link?.anchor2; + Promise.all([a1, a2]).then(action(() => { + if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { + Doc.GetProto(a1)[DirectLinksSym].delete(link); + Doc.GetProto(a2)[DirectLinksSym].delete(link); + Doc.GetProto(link)[DirectLinksSym].delete(link); + } + })); + }; + const watchUserLinkDB = (userLinkDBDoc: Doc) => { + LinkManager.links.push(...DocListCast(userLinkDBDoc.data)); + const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields + observe(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) switch (change.type as any) { - case "splice": (change as any).added.forEach(watchUserLinkDB); break; + case "splice": + (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); + (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); + break; case "update": //let oldValue = change.oldValue; } }, true); - LinkManager.addLinkDB(Doc.LinkDBDoc()); - }); + observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link) + change => { + switch (change.type as any) { + case "update": + Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => { + const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length); + const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length); + + const added = newDocs?.filter(link => !(oldDocs || []).includes(link)); + const removed = oldDocs?.filter(link => !(newDocs || []).includes(link)); + added?.forEach((link: any) => addLinkToDoc(toRealField(link))); + removed?.forEach((link: any) => remLinkFromDoc(toRealField(link))); + }); + } + }, true); + }; + observe(LinkManager.userLinkDBs, change => { + switch (change.type as any) { + case "splice": (change as any).added.forEach(watchUserLinkDB); break; + case "update": //let oldValue = change.oldValue; + } + }, true); + LinkManager.addLinkDB(Doc.LinkDBDoc()); + DocListCastAsync(Doc.LinkDBDoc()?.data).then(dblist => dblist?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager + const a1 = await Cast(link?.anchor1, Doc, null); + const a2 = await Cast(link?.anchor2, Doc, null); + })); } diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 49c2dcf34..17841c055 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -10,7 +10,6 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { LinkManager } from "../util/LinkManager"; import { RecordingApi } from "../util/RecordingApi"; import { CollectionView } from "./collections/CollectionView"; -import { DashboardView } from './DashboardView'; import { MainView } from "./MainView"; AssignAllExtensions(); @@ -24,6 +23,7 @@ AssignAllExtensions(); await CurrentUserUtils.loadUserDocument(info.id); } else { await Docs.Prototypes.initialize(); + new LinkManager(); } document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { @@ -37,7 +37,6 @@ AssignAllExtensions(); d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000)); const expires = "expires=" + d.toUTCString(); document.cookie = `loadtime=${loading};${expires};path=/`; - new LinkManager(); new RecordingApi; ReactDOM.render(, document.getElementById('root')); })(); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 081a1a924..17f1106d0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -517,7 +517,7 @@ export class MarqueeView extends React.Component Date: Tue, 28 Jun 2022 12:23:44 -0400 Subject: cleaned up some more currentUserUtils - fixed some issues with prev/next key frame appearing. --- src/client/documents/Documents.ts | 29 +++++- src/client/util/CurrentUserUtils.ts | 111 ++++----------------- .../views/collections/CollectionDockingView.tsx | 37 +++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 19 +++- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/topbar/TopBar.tsx | 3 +- src/fields/Doc.ts | 25 +---- 7 files changed, 97 insertions(+), 129 deletions(-) (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 3780df5b9..4690d856d 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -13,7 +13,7 @@ import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; -import { SharingPermissions } from "../../fields/util"; +import { inheritParentAcls, SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; import { aggregateBounds, OmitKeys, Utils } from "../../Utils"; import { YoutubeBox } from "../apis/youtube/YoutubeBox"; @@ -1266,7 +1266,7 @@ export namespace DocUtils { const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data)[0]?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ description: ":" + StrCast(dragDoc.title).replace("Untitled ",""), event: undoBatch((args: { x: number, y: number }) => { - const newDoc = Doc.copyDragFactory(dragDoc); + const newDoc = DocUtils.copyDragFactory(dragDoc); if (newDoc) { newDoc.author = Doc.CurrentUserEmail; newDoc.x = x; @@ -1479,9 +1479,34 @@ export namespace DocUtils { } return generatedDocuments; } + + // copies the specified drag factory document + export function copyDragFactory(dragFactory: Doc) { + if (!dragFactory) return undefined; + const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); + ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc)); + if (ndoc && dragFactory["dragFactory-count"] !== undefined) { + dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; + Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); + } + + if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); + + return ndoc; + } + export function delegateDragFactory(dragFactory: Doc) { + const ndoc = Doc.MakeDelegateWithProto(dragFactory); + if (ndoc && dragFactory["dragFactory-count"] !== undefined) { + dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; + Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(); + } + return ndoc; + } } ScriptingGlobals.add("Docs", Docs); +ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return DocUtils.copyDragFactory(dragFactory); }); +ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return DocUtils.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); ScriptingGlobals.add(function generateLinkTitle(self: Doc) { const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : ""; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 6e115ea3c..2a0702a58 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -755,8 +755,8 @@ export class CurrentUserUtils { CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid]), title: "Perspective", toolTip: "View", width: 100,btnType: ButtonType.DropdownList,ignoreClick: true, scripts: { script: 'setView(value, _readOnly_)'}}, - { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, - { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode()'}}, + { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}}, + { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", width: 20, btnType: ButtonType.ClickButton, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}, funcs: {hidden: 'IsNoviceMode() || !selectedDocumentType(undefined, "freeform")'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",width: 20, btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setBackgroundColor(value, _readOnly_)'},funcs: {hidden: '!selectedDocumentType()'}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, ignoreClick: true, scripts: { script: 'setHeaderColor(value, _readOnly_)'}, funcs: {hidden: '!selectedDocumentType()'}}, { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, scripts: { onClick: 'toggleOverlay(_readOnly_)'}, funcs: {hidden: '!selectedDocumentType(undefined, "freeform", true)'}}, // Only when floating document is selected in freeform @@ -1028,8 +1028,10 @@ export class CurrentUserUtils { public static _urlState: HistoryUtil.DocUrl; - public static openDashboard = (doc: Doc, fromHistory = false) => { - if (!doc) return; + /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). + /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) + public static openDashboard = (doc: Doc|undefined, fromHistory = false) => { + if (!doc) return false; CurrentUserUtils.MainDocId = doc[Id]; Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); @@ -1097,46 +1099,15 @@ export class CurrentUserUtils { }; input.click(); } + + public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(CurrentUserUtils.ActiveDashboard); } - public static CaptureDashboardThumbnail() { - const activeDashboard = CurrentUserUtils.ActiveDashboard; - const docView = CollectionDockingView.Instance.props.DocumentView?.(); - const content = docView?.ContentDiv; - if (docView && content && activeDashboard) { - const _width = Number(getComputedStyle(content).width.replace("px","")); - const _height = Number(getComputedStyle(content).height.replace("px","")); - return CollectionFreeFormView.UpdateIcon( - docView.layoutDoc[Id] + "-icon" + (new Date()).getTime(), - content, - _width, _height, - _width, _height, 0, 1, true, docView.layoutDoc[Id] + "-icon", - (iconFile, _nativeWidth, _nativeHeight) => { - const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: docView.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); - const proto = Cast(img.proto, Doc, null)!; - proto["data-nativeWidth"] = _width; - proto["data-nativeHeight"] = _height; - Doc.GetProto(activeDashboard).thumb = img; - }); - } - - } - - public static async snapshotDashboard() { - if (CurrentUserUtils.ActiveDashboard) { - const copy = await CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", copy); - CurrentUserUtils.openDashboard(copy); - } - } - - public static closeActiveDashboard = () => { - CurrentUserUtils.ActiveDashboard = undefined; - } + public static closeActiveDashboard = () => { CurrentUserUtils.ActiveDashboard = undefined; } public static removeDashboard = async (dashboard:Doc) => { const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); if (dashboards?.length) { - if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)!); + if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)); Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); if (!dashboards.length) CurrentUserUtils.ActivePage = "home"; } @@ -1214,14 +1185,6 @@ export class CurrentUserUtils { public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } } -ScriptingGlobals.add(function openDragFactory(dragFactory: Doc) { - const copy = Doc.copyDragFactory(dragFactory); - if (copy) { - CollectionDockingView.AddSplit(copy, "right"); - const view = DocumentManager.Instance.getFirstDocumentView(copy); - view && SelectionManager.SelectView(view, false); - } -}); ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); @@ -1232,52 +1195,14 @@ ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.creat ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); -ScriptingGlobals.add(async function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); -ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { - const dashboardAlias = Doc.MakeAlias(dashboard); - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", dashboardAlias); - CurrentUserUtils.openDashboard(dashboardAlias); -}, - "adds Dashboard to set of Dashboards"); - -ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkParent?: boolean) { - let selected = SelectionManager.Docs().length ? SelectionManager.Docs()[0] : undefined; - if (selected && checkParent) { - const parentDoc: Doc = Cast(selected.context, Doc, null); - selected = parentDoc; - } - if (selected && docType && selected.type === docType) return true; - else if (selected && colType && selected.viewType === colType) return true; - else if (selected && !colType && !docType) return true; - else return false; +ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); +ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { CurrentUserUtils.openDashboard( Doc.MakeAlias(dashboard)); }, "adds Dashboard to set of Dashboards"); +ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) { + let selected = (sel => checkContext ? DocCast(sel?.context) : sel)(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); + return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true; }); ScriptingGlobals.add(function makeTopLevelFolder() { - const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); - TreeView._editTitleOnLoad = { id: folder[Id], parent: undefined }; - return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", folder); -}); -ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { - if (readOnly) return; - const sel = SelectionManager.Views()[0]; - const col = (sel.ComponentView as CollectionFreeFormView); - const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); - if (currentFrame === undefined) { - sel.props.Document._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0); - } - CollectionFreeFormDocumentView.updateKeyframe(col.childDocs, currentFrame || 0); - sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); - sel.rootDoc.lastFrame = Math.max(NumCast(sel.rootDoc._currentFrame), NumCast(sel.rootDoc.lastFrame)); -}); -ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { - if (readOnly) return; - const sel = SelectionManager.Views()[0]; - const col = (sel.ComponentView as CollectionFreeFormView); - const currentFrame = Cast(sel.props.Document._currentFrame, "number", null); - if (currentFrame === undefined) { - sel.props.Document._currentFrame = 0; - CollectionFreeFormDocumentView.setupKeyframes(col.childDocs, 0); - } - CollectionFreeFormDocumentView.gotoKeyframe(col.childDocs.slice()); - sel.rootDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); + TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; + const opts = { title: "Untitled folder", _stayInCollection: true, isFolder: true }; + return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); }); \ No newline at end of file diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 07fcd6a7d..0830b6fdf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -4,12 +4,12 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from "mo import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import * as GoldenLayout from "../../../client/goldenLayout"; -import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { ImageField } from '../../../fields/URLField'; import { inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from "../../DocServer"; @@ -19,14 +19,15 @@ import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DragManager } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; +import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { LightboxView } from '../LightboxView'; import "./CollectionDockingView.scss"; +import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require("react"); -import { SelectionManager } from '../../util/SelectionManager'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -373,13 +374,34 @@ export class CollectionDockingView extends CollectionSubView() { } } - public static async Copy(doc: Doc, clone = false) { + public CaptureThumbnail() { + const content = this.props.DocumentView?.()?.ContentDiv; + if (content) { + const _width = Number(getComputedStyle(content).width.replace("px","")); + const _height = Number(getComputedStyle(content).height.replace("px","")); + return CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + content, + _width, _height, + _width, _height, 0, 1, true, this.layoutDoc[Id] + "-icon", + (iconFile, _nativeWidth, _nativeHeight) => { + const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); + const proto = Cast(img.proto, Doc, null)!; + proto["data-nativeWidth"] = _width; + proto["data-nativeHeight"] = _height; + this.dataDoc.thumb = img; + }); + } + + } + public static async TakeSnapshot(doc: Doc|undefined, clone = false) { + if (!doc) return undefined; let json = StrCast(doc.dockingConfig); if (clone) { - const cloned = (await Doc.MakeClone(doc)); + const cloned = await Doc.MakeClone(doc); Array.from(cloned.map.entries()).map(entry => json = json.replace(entry[0], entry[1][Id])); Doc.GetProto(cloned.clone).dockingConfig = json; - return cloned.clone; + return CurrentUserUtils.openDashboard(cloned.clone); } const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); const origtabids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || []; @@ -395,7 +417,8 @@ export class CollectionDockingView extends CollectionSubView() { json = json.replace(origtab[Id], newtab[Id]); return newtab; }); - return Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); + const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); + return CurrentUserUtils.openDashboard(await copy); } @action diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 3c2047db7..b9da4faa4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -153,6 +153,21 @@ export class CollectionFreeFormView extends CollectionSubView { + const currentFrame = Cast(this.Document._currentFrame, "number", null); + if (currentFrame === undefined) { + this.Document._currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); + } + if (back) { + CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); + this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1); + } else { + CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); + this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1); + this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); + } + } @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set; getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); @@ -2086,4 +2101,6 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY }); Doc.linkFollowHighlight(dv?.props.Document, false); } -ScriptingGlobals.add(CollectionBrowseClick); \ No newline at end of file +ScriptingGlobals.add(CollectionBrowseClick); +ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); }); +ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); }); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 17f1106d0..ab8a34d5a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -156,7 +156,7 @@ export class MarqueeView extends React.Component { - CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => { + CollectionDockingView.Instance.CaptureThumbnail()?.then(() => { CurrentUserUtils.ActivePage = "home"; CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use }); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index a5d952176..b30ca644d 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -23,7 +23,7 @@ import { listSpec } from "./Schema"; import { ComputedField, ScriptField } from "./ScriptField"; import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; import { AudioField, ImageField, MapField, PdfField, VideoField, WebField } from "./URLField"; -import { deleteProperty, GetEffectiveAcl, getField, getter, inheritParentAcls, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; +import { deleteProperty, GetEffectiveAcl, getField, getter, makeEditable, makeReadOnly, normalizeEmail, setter, SharingPermissions, updateFunction } from "./util"; import JSZip = require("jszip"); export namespace Field { @@ -1242,27 +1242,6 @@ export namespace Doc { return !curPres ? false : DocListCast(curPres.data).findIndex((val) => Doc.AreProtosEqual(val, doc)) !== -1; } - export function copyDragFactory(dragFactory: Doc) { - const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); - ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc)); - if (ndoc && dragFactory["dragFactory-count"] !== undefined) { - dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; - Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); - } - - if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); - - return ndoc; - } - export function delegateDragFactory(dragFactory: Doc) { - const ndoc = Doc.MakeDelegateWithProto(dragFactory); - if (ndoc && dragFactory["dragFactory-count"] !== undefined) { - dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; - Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(); - } - return ndoc; - } - export function toIcon(doc?: Doc, isOpen?: boolean) { switch (StrCast(doc?.type)) { case DocumentType.IMG: return "image"; @@ -1454,8 +1433,6 @@ ScriptingGlobals.add(function getProto(doc: any) { return Doc.GetProto(doc); }); ScriptingGlobals.add(function getDocTemplate(doc?: any) { return Doc.getDocTemplate(doc); }); ScriptingGlobals.add(function getAlias(doc: any) { return Doc.MakeAlias(doc); }); ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return Doc.copyDragFactory(dragFactory); }); -ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return Doc.delegateDragFactory(dragFactory); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); }); ScriptingGlobals.add(function docList(field: any) { return DocListCast(field); }); ScriptingGlobals.add(function addDocToList(doc: Doc, field: string, added:Doc) { return Doc.AddDocToList(doc,field, added); }); -- cgit v1.2.3-70-g09d2 From 71790cb5ee62fa443a24e6a5181383cf46350c32 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 29 Jun 2022 16:16:18 -0400 Subject: basic graph not working --- src/client/documents/Documents.ts | 2 +- src/client/views/nodes/DataViz.scss | 0 src/client/views/nodes/DataViz.tsx | 21 -- src/client/views/nodes/DataVizBox/DataVizBox.scss | 0 src/client/views/nodes/DataVizBox/DataVizBox.tsx | 90 ++++++++ src/client/views/nodes/DataVizBox/DrawHelper.ts | 247 +++++++++++++++++++++ .../views/nodes/DataVizBox/HistogramBox.scss | 18 ++ src/client/views/nodes/DataVizBox/HistogramBox.tsx | 159 +++++++++++++ src/client/views/nodes/DataVizBox/TableBox.scss | 22 ++ src/client/views/nodes/DataVizBox/TableBox.tsx | 37 +++ src/client/views/nodes/DocumentContentsView.tsx | 2 +- 11 files changed, 575 insertions(+), 23 deletions(-) delete mode 100644 src/client/views/nodes/DataViz.scss delete mode 100644 src/client/views/nodes/DataViz.tsx create mode 100644 src/client/views/nodes/DataVizBox/DataVizBox.scss create mode 100644 src/client/views/nodes/DataVizBox/DataVizBox.tsx create mode 100644 src/client/views/nodes/DataVizBox/DrawHelper.ts create mode 100644 src/client/views/nodes/DataVizBox/HistogramBox.scss create mode 100644 src/client/views/nodes/DataVizBox/HistogramBox.tsx create mode 100644 src/client/views/nodes/DataVizBox/TableBox.scss create mode 100644 src/client/views/nodes/DataVizBox/TableBox.tsx (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 01cfe90da..59a1d41a8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -37,7 +37,7 @@ import { AudioBox } from "../views/nodes/AudioBox"; import { FontIconBox } from "../views/nodes/button/FontIconBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; -import { DataVizBox } from "../views/nodes/DataViz"; +import { DataVizBox } from "../views/nodes/DataVizBox/DataVizBox"; import { DocFocusOptions } from "../views/nodes/DocumentView"; import { EquationBox } from "../views/nodes/EquationBox"; import { FieldViewProps } from "../views/nodes/FieldView"; diff --git a/src/client/views/nodes/DataViz.scss b/src/client/views/nodes/DataViz.scss deleted file mode 100644 index e69de29bb..000000000 diff --git a/src/client/views/nodes/DataViz.tsx b/src/client/views/nodes/DataViz.tsx deleted file mode 100644 index d9541dba0..000000000 --- a/src/client/views/nodes/DataViz.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { observer } from "mobx-react"; -import * as React from "react"; -import { ViewBoxBaseComponent } from '../DocComponent'; -import "./DataViz.scss"; -import { FieldView, FieldViewProps } from "./FieldView"; - -@observer -export class DataVizBox extends ViewBoxBaseComponent() { - - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } - - render() { - return ( -
    -
    - Hi -
    -
    - ); - } -} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.scss b/src/client/views/nodes/DataVizBox/DataVizBox.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx new file mode 100644 index 000000000..592723ee9 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -0,0 +1,90 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { StrCast } from "../../../../fields/Types"; +import { ViewBoxBaseComponent } from "../../DocComponent"; +import { FieldViewProps, FieldView } from "../FieldView"; +import "./DataVizBox.scss"; +import { HistogramBox } from "./HistogramBox"; +import { TableBox } from "./TableBox"; + +enum DataVizView { + TABLE = "table", + HISTOGRAM= "histogram" +} + + +@observer +export class DataVizBox extends ViewBoxBaseComponent() { + @observable private pairs: {x: number, y:number}[] = [{x: 1, y:2}]; + + // TODO: nda - make this use enum values instead + // @observable private currView: DataVizView = DataVizView.TABLE; + @computed get currView() { + if (this.rootDoc._dataVizView) { + return StrCast(this.rootDoc._dataVizView); + } else { + return "table"; + } + } + + constructor(props: any) { + super(props); + if (!this.rootDoc._dataVizView) { + // TODO: nda - this might not always want to default to "table" + this.rootDoc._dataVizView = "table"; + } + } + + public static LayoutString(fieldKey: string) { return FieldView.LayoutString(DataVizBox, fieldKey); } + + @action + private createPairs() { + const xVals: number[] = [0, 1, 2, 3, 4, 5]; + // const yVals: number[] = [10, 20, 30, 40, 50, 60]; + const yVals: number[] = [1, 2, 3, 4, 5, 6]; + let pairs: { + x: number, + y:number + }[] = []; + if (xVals.length != yVals.length) return pairs; + for (let i = 0; i < xVals.length; i++) { + pairs.push({x: xVals[i], y: yVals[i]}); + } + this.pairs = pairs; + return pairs; + } + + @computed get selectView() { + switch(this.currView) { + case "table": + return () + case "histogram": + return () + } + } + + @computed get pairVals() { + return this.createPairs(); + } + + componentDidMount() { + this.createPairs(); + } + + // handle changing the view using a button + @action changeViewHandler(e: React.MouseEvent) { + e.preventDefault(); + e.stopPropagation(); + this.rootDoc._dataVizView = this.currView == "table" ? "histogram" : "table"; + } + + render() { + return ( +
    + + {this.selectView} +
    + ); + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/DrawHelper.ts b/src/client/views/nodes/DataVizBox/DrawHelper.ts new file mode 100644 index 000000000..595cecebf --- /dev/null +++ b/src/client/views/nodes/DataVizBox/DrawHelper.ts @@ -0,0 +1,247 @@ +export class PIXIPoint { + public get x() { return this.coords[0]; } + public get y() { return this.coords[1]; } + public set x(value: number) { this.coords[0] = value; } + public set y(value: number) { this.coords[1] = value; } + public coords: number[] = [0, 0]; + constructor(x: number, y: number) { + this.coords[0] = x; + this.coords[1] = y; + } +} + +export class PIXIRectangle { + public x: number; + public y: number; + public width: number; + public height: number; + public get left() { return this.x; } + public get right() { return this.x + this.width; } + public get top() { return this.y; } + public get bottom() { return this.top + this.height; } + public static get EMPTY() { return new PIXIRectangle(0, 0, -1, -1); } + constructor(x: number, y: number, width: number, height: number) { + this.x = x; + this.y = y; + this.width = width; + this.height = height; + } +} + +export class MathUtil { + + public static EPSILON: number = 0.001; + + public static Sign(value: number): number { + return value >= 0 ? 1 : -1; + } + + public static AddPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x += p2.x; + p1.y += p2.y; + return p1; + } + else { + return new PIXIPoint(p1.x + p2.x, p1.y + p2.y); + } + } + + public static Perp(p1: PIXIPoint): PIXIPoint { + return new PIXIPoint(-p1.y, p1.x); + } + + public static DividePoint(p1: PIXIPoint, by: number, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x /= by; + p1.y /= by; + return p1; + } + else { + return new PIXIPoint(p1.x / by, p1.y / by); + } + } + + public static MultiplyConstant(p1: PIXIPoint, by: number, inline: boolean = false) { + if (inline) { + p1.x *= by; + p1.y *= by; + return p1; + } + else { + return new PIXIPoint(p1.x * by, p1.y * by); + } + } + + public static SubtractPoint(p1: PIXIPoint, p2: PIXIPoint, inline: boolean = false): PIXIPoint { + if (inline) { + p1.x -= p2.x; + p1.y -= p2.y; + return p1; + } + else { + return new PIXIPoint(p1.x - p2.x, p1.y - p2.y); + } + } + + public static Area(rect: PIXIRectangle): number { + return rect.width * rect.height; + } + + public static DistToLineSegment(v: PIXIPoint, w: PIXIPoint, p: PIXIPoint) { + // Return minimum distance between line segment vw and point p + var l2 = MathUtil.DistSquared(v, w); // i.e. |w-v|^2 - avoid a sqrt + if (l2 === 0.0) return MathUtil.Dist(p, v); // v === w case + // Consider the line extending the segment, parameterized as v + t (w - v). + // We find projection of point p onto the line. + // It falls where t = [(p-v) . (w-v)] / |w-v|^2 + // We clamp t from [0,1] to handle points outside the segment vw. + var dot = MathUtil.Dot( + MathUtil.SubtractPoint(p, v), + MathUtil.SubtractPoint(w, v)) / l2; + var t = Math.max(0, Math.min(1, dot)); + // Projection falls on the segment + var projection = MathUtil.AddPoint(v, + MathUtil.MultiplyConstant( + MathUtil.SubtractPoint(w, v), t)); + return MathUtil.Dist(p, projection); + } + + public static LineSegmentIntersection(ps1: PIXIPoint, pe1: PIXIPoint, ps2: PIXIPoint, pe2: PIXIPoint): PIXIPoint | undefined { + var a1 = pe1.y - ps1.y; + var b1 = ps1.x - pe1.x; + + var a2 = pe2.y - ps2.y; + var b2 = ps2.x - pe2.x; + + var delta = a1 * b2 - a2 * b1; + if (delta === 0) { + return undefined; + } + var c2 = a2 * ps2.x + b2 * ps2.y; + var c1 = a1 * ps1.x + b1 * ps1.y; + var invdelta = 1 / delta; + return new PIXIPoint((b2 * c1 - b1 * c2) * invdelta, (a1 * c2 - a2 * c1) * invdelta); + } + + public static PointInPIXIRectangle(p: PIXIPoint, rect: PIXIRectangle): boolean { + if (p.x < rect.left - this.EPSILON) { + return false; + } + if (p.x > rect.right + this.EPSILON) { + return false; + } + if (p.y < rect.top - this.EPSILON) { + return false; + } + if (p.y > rect.bottom + this.EPSILON) { + return false; + } + + return true; + } + + public static LinePIXIRectangleIntersection(lineFrom: PIXIPoint, lineTo: PIXIPoint, rect: PIXIRectangle): Array { + var r1 = new PIXIPoint(rect.left, rect.top); + var r2 = new PIXIPoint(rect.right, rect.top); + var r3 = new PIXIPoint(rect.right, rect.bottom); + var r4 = new PIXIPoint(rect.left, rect.bottom); + var ret = new Array(); + var dist = this.Dist(lineFrom, lineTo); + var inter = this.LineSegmentIntersection(lineFrom, lineTo, r1, r2); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + inter = this.LineSegmentIntersection(lineFrom, lineTo, r2, r3); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + inter = this.LineSegmentIntersection(lineFrom, lineTo, r3, r4); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + inter = this.LineSegmentIntersection(lineFrom, lineTo, r4, r1); + if (inter && this.PointInPIXIRectangle(inter, rect) && + this.Dist(inter, lineFrom) < dist && this.Dist(inter, lineTo) < dist) { + ret.push(inter); + } + return ret; + } + + public static Intersection(rect1: PIXIRectangle, rect2: PIXIRectangle): PIXIRectangle { + const left = Math.max(rect1.x, rect2.x); + const right = Math.min(rect1.x + rect1.width, rect2.x + rect2.width); + const top = Math.max(rect1.y, rect2.y); + const bottom = Math.min(rect1.y + rect1.height, rect2.y + rect2.height); + return new PIXIRectangle(left, top, right - left, bottom - top); + } + + public static Dist(p1: PIXIPoint, p2: PIXIPoint): number { + return Math.sqrt(MathUtil.DistSquared(p1, p2)); + } + + public static Dot(p1: PIXIPoint, p2: PIXIPoint): number { + return p1.x * p2.x + p1.y * p2.y; + } + + public static Normalize(p1: PIXIPoint) { + var d = this.Length(p1); + return new PIXIPoint(p1.x / d, p1.y / d); + } + + public static Length(p1: PIXIPoint): number { + return Math.sqrt(p1.x * p1.x + p1.y * p1.y); + } + + public static DistSquared(p1: PIXIPoint, p2: PIXIPoint): number { + const a = p1.x - p2.x; + const b = p1.y - p2.y; + return (a * a + b * b); + } + + public static RectIntersectsRect(r1: PIXIRectangle, r2: PIXIRectangle): boolean { + return !(r2.x > r1.x + r1.width || + r2.x + r2.width < r1.x || + r2.y > r1.y + r1.height || + r2.y + r2.height < r1.y); + } + + public static ArgMin(temp: number[]): number { + let index = 0; + let value = temp[0]; + for (let i = 1; i < temp.length; i++) { + if (temp[i] < value) { + value = temp[i]; + index = i; + } + } + return index; + } + + public static ArgMax(temp: number[]): number { + let index = 0; + let value = temp[0]; + for (let i = 1; i < temp.length; i++) { + if (temp[i] > value) { + value = temp[i]; + index = i; + } + } + return index; + } + + public static Combinations(chars: T[]) { + let result = new Array(); + let f = (prefix: any, chars: any) => { + for (let i = 0; i < chars.length; i++) { + result.push(prefix.concat(chars[i])); + f(prefix.concat(chars[i]), chars.slice(i + 1)); + } + }; + f([], chars); + return result; + } +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.scss b/src/client/views/nodes/DataVizBox/HistogramBox.scss new file mode 100644 index 000000000..5aac9dc77 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramBox.scss @@ -0,0 +1,18 @@ +// change the stroke color of line-svg class +.svgLine { + position: absolute; + background: darkGray; + stroke: #000; + stroke-width: 1px; + width:100%; + height:100%; + opacity: 0.4; +} + +.svgContainer { + position: absolute; + top:0; + left:0; + width:100%; + height: 100%; +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/HistogramBox.tsx b/src/client/views/nodes/DataVizBox/HistogramBox.tsx new file mode 100644 index 000000000..00dc2ef46 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/HistogramBox.tsx @@ -0,0 +1,159 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { Doc } from "../../../../fields/Doc"; +import { NumCast } from "../../../../fields/Types"; +import "./HistogramBox.scss"; + +interface HistogramBoxProps { + rootDoc: Doc; + pairs: { + x: number, + y: number + }[] +} + + +export class HistogramBox extends React.Component { + + private origin = {x: 0.1 * this.width, y: 0.9 * this.height}; + + @computed get width() { + return NumCast(this.props.rootDoc.width); + } + + @computed get height() { + return NumCast(this.props.rootDoc.height); + } + + @computed get x() { + return NumCast(this.props.rootDoc.x); + } + + @computed get y() { + return NumCast(this.props.rootDoc.y); + } + + @computed get generatePoints() { + // evenly distribute points along the x axis + const xVals: number[] = this.props.pairs.map(p => p.x); + const yVals: number[] = this.props.pairs.map(p => p.y); + + const xMin = Math.min(...xVals); + const xMax = Math.max(...xVals); + const yMin = Math.min(...yVals); + const yMax = Math.max(...yVals); + + const xRange = xMax - xMin; + const yRange = yMax - yMin; + + const xScale = this.width / xRange; + const yScale = this.height / yRange; + + const xOffset = (this.x + (0.1 * this.width)) - xMin * xScale; + const yOffset = (this.y + (0.25 * this.height)) - yMin * yScale; + + const points: { + x: number, + y: number + }[] = this.props.pairs.map(p => { + return { + x: (p.x * xScale + xOffset) + this.origin.x, + y: (p.y * yScale + yOffset) + } + }); + + return points; + } + + @computed get generateGraphLine() { + const points = this.generatePoints; + // loop through points and create a line from each point to the next + let lines: { + x1: number, + y1: number, + x2: number, + y2: number + }[] = []; + for (let i = 0; i < points.length - 1; i++) { + lines.push({ + x1: points[i].x, + y1: points[i].y, + x2: points[i + 1].x, + y2: points[i + 1].y + }); + } + // generate array of svg with lines + let svgLines: JSX.Element[] = []; + for (let i = 0; i < lines.length; i++) { + svgLines.push( + + ); + } + + let res = []; + for (let i = 0; i < svgLines.length; i++) { + res.push({svgLines[i]}) + } + return res; + } + + @computed get generateAxes() { + + const xAxis = { + x1: 0.1 * this.width, + x2: 0.9 * this.width, + y1: 0.9 * this.height, + y2: 0.9 * this.height, + }; + + const yAxis = { + x1: 0.1 * this.width, + x2: 0.1 * this.width, + y1: 0.25 * this.height, + y2: 0.9 * this.height, + }; + + + return ( + [ + ( + {/* */} + + + {/* */} + ), + ( + + + {/* */} + ) + ] + ) + } + + + render() { + return ( +
    histogram box + {/* + {this.generateSVGLine} + */} + {this.generateAxes[0]} + {this.generateAxes[1]} + {this.generateGraphLine.map(line => line)} +
    + ) + + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/TableBox.scss b/src/client/views/nodes/DataVizBox/TableBox.scss new file mode 100644 index 000000000..1264d6a46 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/TableBox.scss @@ -0,0 +1,22 @@ +.table { + margin-top: 10px; + margin-bottom: 10px; + margin-left: 10px; + margin-right: 10px; +} + +.table-row { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + padding: 5px; + border-bottom: 1px solid #ccc; +} + +.table-container { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; +} \ No newline at end of file diff --git a/src/client/views/nodes/DataVizBox/TableBox.tsx b/src/client/views/nodes/DataVizBox/TableBox.tsx new file mode 100644 index 000000000..dfa8262d8 --- /dev/null +++ b/src/client/views/nodes/DataVizBox/TableBox.tsx @@ -0,0 +1,37 @@ +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; + +interface TableBoxProps { + pairs: {x: number, y:number}[] +} + + +export class TableBox extends React.Component { + + + + render() { + return ( +
    + + + + + + + + + {this.props.pairs.map(p => { + return ( + + + ) + })} + +
    xy
    {p.x}{p.y}
    +
    + ) + } + +} \ No newline at end of file diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 371d85a32..96ac3e332 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -19,7 +19,7 @@ import { AudioBox } from "./AudioBox"; import { FontIconBox } from "./button/FontIconBox"; import { ColorBox } from "./ColorBox"; import { ComparisonBox } from "./ComparisonBox"; -import { DataVizBox } from "./DataViz"; +import { DataVizBox } from "./DataVizBox/DataVizBox"; import { DocumentViewProps } from "./DocumentView"; import "./DocumentView.scss"; import { EquationBox } from "./EquationBox"; -- cgit v1.2.3-70-g09d2 From aa979bcd302e48982707fb6f12403a48a721f147 Mon Sep 17 00:00:00 2001 From: Naafiyan Ahmed Date: Wed, 29 Jun 2022 19:17:00 -0400 Subject: got basic file upload to work --- src/client/documents/Documents.ts | 12 +++++++++--- src/client/util/CurrentUserUtils.ts | 2 +- src/fields/URLField.ts | 1 + src/server/ApiManagers/UploadManager.ts | 1 + src/server/DashUploadUtils.ts | 18 +++++++++++++++++- src/server/DataVizUtils.ts | 13 +++++++++++++ 6 files changed, 42 insertions(+), 5 deletions(-) create mode 100644 src/server/DataVizUtils.ts (limited to 'src/client/documents') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 59a1d41a8..b9d879c55 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -12,7 +12,7 @@ import { RichTextField } from "../../fields/RichTextField"; import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; import { ComputedField, ScriptField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { AudioField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; +import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; import { SharingPermissions } from "../../fields/util"; import { Upload } from "../../server/SharedMediaTypes"; import { aggregateBounds, OmitKeys, Utils } from "../../Utils"; @@ -940,8 +940,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRESELEMENT), undefined, { ...(options || {}) }); } - export function DataVizDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), undefined, { title: "Data Viz", ...options }); + export function DataVizDocument(url: string, options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: "Data Viz", ...options }); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { @@ -1253,6 +1253,11 @@ export namespace DocUtils { if (!options._width) options._width = 400; if (!options._height) options._height = (options._width as number) * 1200 / 927; } + if (type.indexOf("csv") !== -1) { + ctor = Docs.Create.DataVizDocument; + if (!options._width) options._width = 400; + if (!options._height) options._height = (options._width as number) * 1200 / 927; + } //TODO:al+glr // if (type.indexOf("map") !== -1) { // ctor = Docs.Create.MapDocument; @@ -1278,6 +1283,7 @@ export namespace DocUtils { ctor = Docs.Create.WebDocument; options = { ...options, _width: 400, _height: 512, title: path, }; } + return ctor ? ctor(path, options) : undefined; } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 52fbda9a9..40268693e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -294,7 +294,7 @@ export class CurrentUserUtils { {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, title: "recording", recording:true, system: true, cloneFieldFilter: new List(["system"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, }}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, - {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, + // {key: "DataViz", creator: opts => Docs.Create.DataVizDocument(opts), opts: { _width: 300, _height: 300 }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 70, _headerPointerEvents: "all", _headerHeight: 12, _headerFontSize: 9, _autoHeight: true,}}, {key: "Presentation",creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 500, _viewType: CollectionViewType.Stacking, targetDropAction: "alias" as any, _chromeHidden: true, boxShadow: "0 0" }}, {key: "Tab", creator: opts => Docs.Create.FreeformDocument([], opts), opts: { _width: 500, _height: 800, _backgroundGridShow: true, }}, diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 3e7542e46..36dd56a1a 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -59,6 +59,7 @@ export const nullAudio = "https://actions.google.com/sounds/v1/alarms/beep_short @scriptingGlobal @Deserializable("pdf") export class PdfField extends URLField { } @scriptingGlobal @Deserializable("web") export class WebField extends URLField { } @scriptingGlobal @Deserializable("map") export class MapField extends URLField { } +@scriptingGlobal @Deserializable("csv") export class CsvField extends URLField { } @scriptingGlobal @Deserializable("youtube") export class YoutubeField extends URLField { } @scriptingGlobal @Deserializable("webcam") export class WebCamField extends URLField { } diff --git a/src/server/ApiManagers/UploadManager.ts b/src/server/ApiManagers/UploadManager.ts index e7b7056a1..04a11f410 100644 --- a/src/server/ApiManagers/UploadManager.ts +++ b/src/server/ApiManagers/UploadManager.ts @@ -24,6 +24,7 @@ export enum Directory { text = "text", pdf_thumbnails = "pdf_thumbnails", audio = "audio", + csv = "csv", } export function serverPathToFile(directory: Directory, filename: string) { diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts index 552ab57a5..5f46bcc88 100644 --- a/src/server/DashUploadUtils.ts +++ b/src/server/DashUploadUtils.ts @@ -17,6 +17,7 @@ import { resolvedServerUrl } from "./server_Initialization"; import { AcceptableMedia, Upload } from './SharedMediaTypes'; import request = require('request-promise'); import formidable = require('formidable'); +import { csvParser } from './DataVizUtils'; const { exec } = require("child_process"); const parse = require('pdf-parse'); const ffmpeg = require("fluent-ffmpeg"); @@ -85,7 +86,7 @@ export namespace DashUploadUtils { const category = types[0]; let format = `.${types[1]}`; console.log(green(`Processing upload of file (${name}) and format (${format}) with upload type (${type}) in category (${category}).`)); - + switch (category) { case "image": if (imageFormats.includes(format)) { @@ -116,6 +117,11 @@ export namespace DashUploadUtils { if (audioFormats.includes(format)) { return UploadAudio(file, format); } + case "text": + if (types[1] == "csv") { + return UploadCsv(file); + } + } console.log(red(`Ignoring unsupported file (${name}) with upload type (${type}).`)); @@ -135,6 +141,16 @@ export namespace DashUploadUtils { return MoveParsedFile(file, Directory.pdfs, undefined, result.text); } + async function UploadCsv(file: File) { + const { path: sourcePath } = file; + // read the file as a string + const data = readFileSync(sourcePath, 'utf8'); + // split the string into an array of lines + return MoveParsedFile(file, Directory.csv, undefined, data); + // console.log(csvParser(data)); + + } + const manualSuffixes = [".webm"]; async function UploadAudio(file: File, format: string) { diff --git a/src/server/DataVizUtils.ts b/src/server/DataVizUtils.ts new file mode 100644 index 000000000..4fd0ca6ff --- /dev/null +++ b/src/server/DataVizUtils.ts @@ -0,0 +1,13 @@ +export function csvParser(csv: string) { + const lines = csv.split("\n"); + const headers = lines[0].split(","); + const data = lines.slice(1).map(line => { + const values = line.split(","); + const obj: any = {}; + for (let i = 0; i < headers.length; i++) { + obj[headers[i]] = values[i]; + } + return obj; + }); + return data; +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 146f8622d5bac2edc6b09f57c173bd057dfbcfad Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 8 Jul 2022 00:17:26 -0400 Subject: restructured currentUserUtils to avoid having import cycles. --- package-lock.json | 39 - src/client/DocServer.ts | 91 +- src/client/apis/youtube/YoutubeBox.tsx | 281 ++-- src/client/documents/DocumentTypes.ts | 100 +- src/client/documents/Documents.ts | 1494 ++++++++++------- src/client/util/CurrentUserUtils.ts | 349 +--- src/client/util/DocumentManager.ts | 184 ++- src/client/util/History.ts | 45 +- src/client/util/LinkFollower.ts | 112 ++ src/client/util/LinkManager.ts | 259 ++- src/client/util/SelectionManager.ts | 29 +- src/client/util/SettingsManager.tsx | 471 +++--- src/client/util/SharingManager.tsx | 11 +- src/client/views/DashboardView.tsx | 400 +++-- src/client/views/DocComponent.tsx | 143 +- src/client/views/DocumentButtonBar.tsx | 449 +++-- src/client/views/DocumentDecorations.tsx | 68 +- src/client/views/GestureOverlay.tsx | 740 +++++---- src/client/views/GlobalKeyHandler.ts | 279 ++-- src/client/views/InkStrokeProperties.ts | 278 ++-- src/client/views/InkTranscription.tsx | 3 +- src/client/views/LightboxView.tsx | 258 +-- src/client/views/Main.tsx | 2 +- src/client/views/MainView.tsx | 65 +- src/client/views/MarqueeAnnotator.tsx | 226 +-- src/client/views/OverlayView.tsx | 197 ++- src/client/views/PreviewCursor.tsx | 200 ++- src/client/views/PropertiesButtons.tsx | 386 +++-- src/client/views/PropertiesDocContextSelector.tsx | 67 +- src/client/views/PropertiesView.tsx | 1729 +++++++++++--------- src/client/views/SidebarAnnos.tsx | 128 +- src/client/views/StyleProvider.tsx | 11 +- src/client/views/TemplateMenu.tsx | 189 +-- .../views/collections/CollectionDockingView.tsx | 25 +- src/client/views/collections/CollectionMenu.tsx | 1246 +++++++------- .../collections/CollectionStackedTimeline.tsx | 716 ++++---- .../views/collections/CollectionStackingView.tsx | 6 +- src/client/views/collections/CollectionSubView.tsx | 286 ++-- .../views/collections/CollectionTreeView.tsx | 9 +- src/client/views/collections/CollectionView.tsx | 75 +- src/client/views/collections/TabDocView.tsx | 16 +- src/client/views/collections/TreeView.tsx | 1168 +++++++------ .../CollectionFreeFormLayoutEngines.tsx | 248 ++- .../CollectionFreeFormLinkView.tsx | 201 ++- .../CollectionFreeFormRemoteCursors.tsx | 105 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 28 +- .../collections/collectionFreeForm/MarqueeView.tsx | 517 +++--- .../collectionLinear/CollectionLinearView.tsx | 9 +- src/client/views/linking/LinkMenuItem.tsx | 3 +- src/client/views/linking/LinkPopup.tsx | 31 +- src/client/views/nodes/AudioBox.tsx | 613 +++---- src/client/views/nodes/ColorBox.tsx | 96 +- src/client/views/nodes/DocumentView.tsx | 1460 ++++++++++------- src/client/views/nodes/FilterBox.tsx | 486 +++--- src/client/views/nodes/ImageBox.tsx | 362 ++-- src/client/views/nodes/LinkAnchorBox.tsx | 167 +- src/client/views/nodes/LinkDocPreview.tsx | 3 +- src/client/views/nodes/MapBox/MapBox.tsx | 5 +- src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx | 111 +- src/client/views/nodes/ScreenshotBox.tsx | 489 +++--- src/client/views/nodes/VideoBox.tsx | 958 ++++++----- src/client/views/nodes/WebBox.tsx | 818 +++++---- src/client/views/nodes/button/FontIconBox.tsx | 25 +- .../views/nodes/formattedText/DashDocView.tsx | 15 +- .../views/nodes/formattedText/DashFieldView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 11 +- src/client/views/nodes/trails/PresBox.tsx | 48 +- src/client/views/nodes/trails/PresElementBox.tsx | 440 ++--- src/client/views/pdf/Annotation.tsx | 108 +- src/client/views/pdf/PDFViewer.tsx | 455 +++--- src/client/views/topbar/TopBar.tsx | 180 +- src/client/views/webcam/DashWebRTCVideo.tsx | 88 +- src/fields/Doc.ts | 151 +- src/mobile/MobileInterface.tsx | 19 +- src/mobile/MobileMain.tsx | 32 +- 75 files changed, 11685 insertions(+), 9429 deletions(-) create mode 100644 src/client/util/LinkFollower.ts (limited to 'src/client/documents') diff --git a/package-lock.json b/package-lock.json index ed3160696..3b9cda8bb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5059,16 +5059,6 @@ "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=", "dev": true }, - "d": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", - "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", - "dev": true, - "requires": { - "es5-ext": "^0.10.50", - "type": "^1.0.1" - } - }, "d3-array": { "version": "2.12.1", "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-2.12.1.tgz", @@ -6218,28 +6208,6 @@ "is-symbol": "^1.0.2" } }, - "es5-ext": { - "version": "0.10.61", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.61.tgz", - "integrity": "sha512-yFhIqQAzu2Ca2I4SE2Au3rxVfmohU9Y7wqGR+s7+H7krk26NXhIRAZDgqd6xqjCEFUomDEA3/Bo/7fKmIkW1kA==", - "dev": true, - "requires": { - "es6-iterator": "^2.0.3", - "es6-symbol": "^3.1.3", - "next-tick": "^1.1.0" - } - }, - "es6-iterator": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", - "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==", - "dev": true, - "requires": { - "d": "1", - "es5-ext": "^0.10.35", - "es6-symbol": "^3.1.1" - } - }, "es6-promise": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz", @@ -6251,7 +6219,6 @@ "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", "dev": true, "requires": { - "d": "^1.0.1", "ext": "^1.1.2" } }, @@ -20748,12 +20715,6 @@ "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" }, - "type": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", - "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", - "dev": true - }, "type-check": { "version": "0.3.2", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index c9a30b8e3..1b0ba6bc3 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,27 +1,27 @@ -import * as io from 'socket.io-client'; -import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; -import { Opt, Doc, UpdatingFromServer, updateCachedAcls } from '../fields/Doc'; -import { Utils, emptyFunction } from '../Utils'; -import { SerializationHelper } from './util/SerializationHelper'; -import { RefField } from '../fields/RefField'; -import { Id, HandleUpdate, Parent } from '../fields/FieldSymbols'; -import { GestureOverlay } from './views/GestureOverlay'; -import MobileInkOverlay from '../mobile/MobileInkOverlay'; import { runInAction } from 'mobx'; +import * as rp from 'request-promise'; +import * as io from 'socket.io-client'; +import { Doc, Opt, UpdatingFromServer } from '../fields/Doc'; +import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols'; import { ObjectField } from '../fields/ObjectField'; +import { RefField } from '../fields/RefField'; import { StrCast } from '../fields/Types'; -import * as rp from 'request-promise'; +import MobileInkOverlay from '../mobile/MobileInkOverlay'; +import { emptyFunction, Utils } from '../Utils'; +import { GestureContent, MessageStore, MobileDocumentUploadContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, YoutubeQueryTypes } from './../server/Message'; +import { SerializationHelper } from './util/SerializationHelper'; +import { GestureOverlay } from './views/GestureOverlay'; /** * This class encapsulates the transfer and cross-client synchronization of * data stored only in documents (RefFields). In the process, it also * creates and maintains a cache of documents so that they can be accessed * more efficiently. Currently, there is no cache eviction scheme in place. - * + * * NOTE: while this class is technically abstracted to work with any [RefField], because * [Doc] instances are the only [RefField] we need / have implemented at the moment, the documentation * will treat all data used here as [Doc]s - * + * * Any time we want to write a new field to the database (via the server) * or update ourselves based on the server's update message, that occurs here */ @@ -32,12 +32,12 @@ export namespace DocServer { const strings: string[] = []; Array.from(Object.keys(_cache)).forEach(key => { const doc = _cache[key]; - if (doc instanceof Doc) strings.push(StrCast(doc.author) + " " + StrCast(doc.title) + " " + StrCast(Doc.GetT(doc, "title", "string", true))); + if (doc instanceof Doc) strings.push(StrCast(doc.author) + ' ' + StrCast(doc.title) + ' ' + StrCast(Doc.GetT(doc, 'title', 'string', true))); }); - print && strings.sort().forEach((str, i) => console.log(i.toString() + " " + str)); - rp.post(Utils.prepend("/setCacheDocumentIds"), { + print && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str)); + rp.post(Utils.prepend('/setCacheDocumentIds'), { body: { - cacheDocumentIds: Array.from(Object.keys(_cache)).join(";"), + cacheDocumentIds: Array.from(Object.keys(_cache)).join(';'), }, json: true, }); @@ -50,8 +50,8 @@ export namespace DocServer { export enum WriteMode { Default = 0, //Anything goes Playground = 1, //Playground (write own/no read other updates) - LiveReadonly = 2,//Live Readonly (no write/read others) - LivePlayground = 3,//Live Playground (write own/read others) + LiveReadonly = 2, //Live Readonly (no write/read others) + LivePlayground = 3, //Live Playground (write own/read others) } const fieldWriteModes: { [field: string]: WriteMode } = {}; const docsWithUpdates: { [field: string]: Set } = {}; @@ -62,7 +62,7 @@ export namespace DocServer { livePlaygroundFields.forEach(f => DocServer.setFieldWriteMode(f, DocServer.WriteMode.Playground)); } export function IsPlaygroundField(field: string) { - return DocServer.PlaygroundFields?.includes(field.replace(/^_/, "")); + return DocServer.PlaygroundFields?.includes(field.replace(/^_/, '')); } export function setFieldWriteMode(field: string, writeMode: WriteMode) { @@ -83,7 +83,7 @@ export namespace DocServer { export function registerDocWithCachedUpdate(doc: Doc, field: string, oldValue: any) { let list = docsWithUpdates[field]; if (!list) { - list = docsWithUpdates[field] = new Set; + list = docsWithUpdates[field] = new Set(); } if (!list.has(doc)) { Doc.AddCachedUpdate(doc, field, oldValue); @@ -92,7 +92,6 @@ export namespace DocServer { } export namespace Mobile { - export function dispatchGesturePoints(content: GestureContent) { Utils.Emit(_socket, MessageStore.GesturePoints, content); } @@ -111,17 +110,17 @@ export namespace DocServer { } } - const instructions = "This page will automatically refresh after this alert is closed. Expect to reconnect after about 30 seconds."; + const instructions = 'This page will automatically refresh after this alert is closed. Expect to reconnect after about 30 seconds.'; function alertUser(connectionTerminationReason: string) { switch (connectionTerminationReason) { - case "crash": + case 'crash': alert(`Dash has temporarily crashed. Administrators have been notified and the server is restarting itself. ${instructions}`); break; - case "temporary": + case 'temporary': alert(`An administrator has chosen to restart the server. ${instructions}`); break; - case "exit": - alert("An administrator has chosen to kill the server. Do not expect to reconnect until administrators start the server."); + case 'exit': + alert('An administrator has chosen to kill the server. Do not expect to reconnect until administrators start the server.'); break; default: console.log(`Received an unknown ConnectionTerminated message: ${connectionTerminationReason}`); @@ -132,7 +131,7 @@ export namespace DocServer { export function init(protocol: string, hostname: string, port: number, identifier: string) { _cache = {}; GUID = identifier; - protocol = protocol.startsWith("https") ? "wss" : "ws"; + protocol = protocol.startsWith('https') ? 'wss' : 'ws'; _socket = io.connect(`${protocol}://${hostname}:${port}`); // io.connect(`https://7f079dda.ngrok.io`);// if using ngrok, create a special address for the websocket @@ -153,17 +152,17 @@ export namespace DocServer { Utils.AddServerHandler(_socket, MessageStore.ConnectionTerminated, alertUser); // mobile ink overlay socket events to communicate between mobile view and desktop view - _socket.addEventListener("receiveGesturePoints", (content: GestureContent) => { + _socket.addEventListener('receiveGesturePoints', (content: GestureContent) => { MobileInkOverlay.Instance.drawStroke(content); }); - _socket.addEventListener("receiveOverlayTrigger", (content: MobileInkOverlayContent) => { + _socket.addEventListener('receiveOverlayTrigger', (content: MobileInkOverlayContent) => { GestureOverlay.Instance.enableMobileInkOverlay(content); MobileInkOverlay.Instance.initMobileInkOverlay(content); }); - _socket.addEventListener("receiveUpdateOverlayPosition", (content: UpdateMobileInkOverlayPositionContent) => { + _socket.addEventListener('receiveUpdateOverlayPosition', (content: UpdateMobileInkOverlayPositionContent) => { MobileInkOverlay.Instance.updatePosition(content); }); - _socket.addEventListener("receiveMobileDocumentUpload", (content: MobileDocumentUploadContent) => { + _socket.addEventListener('receiveMobileDocumentUpload', (content: MobileDocumentUploadContent) => { MobileInkOverlay.Instance.uploadDocument(content); }); } @@ -173,12 +172,11 @@ export namespace DocServer { } export namespace Control { - let _isReadOnly = false; export function makeReadOnly() { if (!_isReadOnly) { _isReadOnly = true; - _CreateField = field => _cache[field[Id]] = field; + _CreateField = field => (_cache[field[Id]] = field); _UpdateField = emptyFunction; _RespondToUpdate = emptyFunction; // bcz: option: don't clear RespondToUpdate to continue to receive updates as others change the DB } @@ -195,8 +193,9 @@ export namespace DocServer { } } - export function isReadOnly() { return _isReadOnly; } - + export function isReadOnly() { + return _isReadOnly; + } } /** @@ -209,7 +208,6 @@ export namespace DocServer { } export namespace Util { - /** * Emits a message to the server that wipes * all documents in the database. @@ -217,7 +215,6 @@ export namespace DocServer { export function deleteDatabase() { Utils.Emit(_socket, MessageStore.DeleteAll, {}); } - } // RETRIEVE DOCS FROM SERVER @@ -258,8 +255,7 @@ export namespace DocServer { }); cached[UpdatingFromServer] = false; return cached; - } - else if (field !== undefined) { + } else if (field !== undefined) { _cache[id] = field; } else { delete _cache[id]; @@ -272,7 +268,7 @@ export namespace DocServer { // here, indicate that the document associated with this id is currently // being retrieved and cached !force && (_cache[id] = deserializeField); - return force ? cached as any : deserializeField; + return force ? (cached as any) : deserializeField; } else if (cached instanceof Promise) { // BEING RETRIEVED AND CACHED => some other caller previously (likely recently) called GetRefField(s), // and requested the document I'm looking for. Shouldn't fetch again, just @@ -316,7 +312,6 @@ export namespace DocServer { Utils.EmitCallback(_socket, MessageStore.YoutubeApiQuery, { type: YoutubeQueryTypes.VideoDetails, videoIds: videoIds }, callBack); } - /** * Given a list of Doc GUIDs, this utility function will asynchronously attempt to each id's associated * field, first looking in the RefField cache and then communicating with @@ -381,7 +376,7 @@ export namespace DocServer { return deserialized; }); // 4) here, for each of the documents we've requested *ourselves* (i.e. weren't promises or found in the cache) - // we set the value at the field's id to a promise that will resolve to the field. + // we set the value at the field's id to a promise that will resolve to the field. // When we find that promises exist at keys in the cache, THIS is where they were set, just by some other caller (method). // The mapping in the .then call ensures that when other callers await these promises, they'll // get the resolved field @@ -391,7 +386,7 @@ export namespace DocServer { proms.push(prom); } else if (cached instanceof Promise) { proms.push(cached as any); - cached.then((f: any) => fieldMap[field.id] = f); + cached.then((f: any) => (fieldMap[field.id] = f)); } else if (field) { proms.push(cached as any); fieldMap[field.id] = DocServer.GetCachedRefField(field.id) || field; @@ -413,20 +408,19 @@ export namespace DocServer { const field = fields[id]; map[id] = field; }); - } // 7) those promises we encountered in the else if of 1), which represent // other callers having already submitted a request to the server for (a) document(s) // in which we're interested, must still be awaited so that we can return the proper - // values for those as well. + // values for those as well. // // fortunately, those other callers will also hit their own version of 6) and clean up // the shared cache when these promises resolve, so all we have to do is... const otherCallersFetching = await Promise.all(promises); // ...extract the RefFields returned from the resolution of those promises and add them to our // own map. - waitingIds.forEach((id, index) => map[id] = otherCallersFetching[index]); + waitingIds.forEach((id, index) => (map[id] = otherCallersFetching[index])); // now, we return our completed mapping from all of the ids that were passed into the method // to their actual RefField | undefined values. This return value either becomes the input @@ -476,7 +470,7 @@ export namespace DocServer { } function _UpdateFieldImpl(id: string, diff: any) { - (!DocServer.Control.isReadOnly()) && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff }); + !DocServer.Control.isReadOnly() && Utils.Emit(_socket, MessageStore.UpdateField, { id, diff }); } let _UpdateField: (id: string, diff: any) => void = errorFunc; @@ -520,12 +514,11 @@ export namespace DocServer { Utils.Emit(_socket, MessageStore.DeleteFields, ids); } - function _respondToDeleteImpl(ids: string | string[]) { function deleteId(id: string) { delete _cache[id]; } - if (typeof ids === "string") { + if (typeof ids === 'string') { deleteId(ids); } else if (Array.isArray(ids)) { ids.map(deleteId); diff --git a/src/client/apis/youtube/YoutubeBox.tsx b/src/client/apis/youtube/YoutubeBox.tsx index e14dc60b4..05879a247 100644 --- a/src/client/apis/youtube/YoutubeBox.tsx +++ b/src/client/apis/youtube/YoutubeBox.tsx @@ -1,17 +1,16 @@ import { action, observable, runInAction } from 'mobx'; -import { observer } from "mobx-react"; -import { Doc, DocListCastAsync } from "../../../fields/Doc"; -import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { Utils } from "../../../Utils"; -import { DocServer } from "../../DocServer"; -import { Docs } from "../../documents/Documents"; -import { DocumentDecorations } from "../../views/DocumentDecorations"; -import { FieldView, FieldViewProps } from "../../views/nodes/FieldView"; -import "../../views/nodes/WebBox.scss"; -import "./YoutubeBox.scss"; -import React = require("react"); +import { observer } from 'mobx-react'; +import { Doc, DocListCastAsync } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; +import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { Utils } from '../../../Utils'; +import { DocServer } from '../../DocServer'; +import { Docs } from '../../documents/Documents'; +import { DocumentDecorations } from '../../views/DocumentDecorations'; +import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; +import '../../views/nodes/WebBox.scss'; +import './YoutubeBox.scss'; +import React = require('react'); interface VideoTemplate { thumbnailUrl: string; @@ -29,19 +28,19 @@ interface VideoTemplate { */ @observer export class YoutubeBox extends React.Component { - @observable YoutubeSearchElement: HTMLInputElement | undefined; @observable searchResultsFound: boolean = false; @observable searchResults: any[] = []; @observable videoClicked: boolean = false; - @observable selectedVideoUrl: string = ""; + @observable selectedVideoUrl: string = ''; @observable lisOfBackUp: JSX.Element[] = []; @observable videoIds: string | undefined; @observable videoDetails: any[] = []; @observable curVideoTemplates: VideoTemplate[] = []; - - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(YoutubeBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(YoutubeBox, fieldKey); + } /** * When component mounts, last search's results are laoded in based on the back up stored @@ -54,19 +53,15 @@ export class YoutubeBox extends React.Component { const castedDetailBackUp = Cast(this.props.Document.cachedDetails, Doc); const awaitedDetails = await castedDetailBackUp; - if (awaitedBackUp) { - - const jsonList = await DocListCastAsync(awaitedBackUp.json); const jsonDetailList = await DocListCastAsync(awaitedDetails!.json); if (jsonList!.length !== 0) { - runInAction(() => this.searchResultsFound = true); + runInAction(() => (this.searchResultsFound = true)); let index = 0; //getting the necessary information from backUps and building templates that will be used to map in render for (const video of jsonList!) { - const videoId = await Cast(video.id, Doc); const id = StrCast(videoId!.videoId); const snippet = await Cast(video.snippet, Doc); @@ -75,10 +70,10 @@ export class YoutubeBox extends React.Component { const thumbnailMedium = await Cast(thumbnail!.medium, Doc); const thumbnailUrl = StrCast(thumbnailMedium!.url); const videoDescription = StrCast(snippet!.description); - const pusblishDate = (this.roundPublishTime(StrCast(snippet!.publishedAt)))!; + const pusblishDate = this.roundPublishTime(StrCast(snippet!.publishedAt))!; const channelTitle = StrCast(snippet!.channelTitle); - let duration: string = ""; - let viewCount: string = ""; + let duration: string = ''; + let viewCount: string = ''; if (jsonDetailList!.length !== 0) { const contentDetails = await Cast(jsonDetailList![index].contentDetails, Doc); const statistics = await Cast(jsonDetailList![index].statistics, Doc); @@ -86,7 +81,16 @@ export class YoutubeBox extends React.Component { viewCount = this.abbreviateViewCount(parseInt(StrCast(statistics!.viewCount)))!; } index = index + 1; - const newTemplate: VideoTemplate = { videoId: id, videoTitle: videoTitle, thumbnailUrl: thumbnailUrl, publishDate: pusblishDate, channelTitle: channelTitle, videoDescription: videoDescription, duration: duration, viewCount: viewCount }; + const newTemplate: VideoTemplate = { + videoId: id, + videoTitle: videoTitle, + thumbnailUrl: thumbnailUrl, + publishDate: pusblishDate, + channelTitle: channelTitle, + videoDescription: videoDescription, + duration: duration, + viewCount: viewCount, + }; runInAction(() => this.curVideoTemplates.push(newTemplate)); } } @@ -96,20 +100,20 @@ export class YoutubeBox extends React.Component { _ignore = 0; onPreWheel = (e: React.WheelEvent) => { this._ignore = e.timeStamp; - } + }; onPrePointer = (e: React.PointerEvent) => { this._ignore = e.timeStamp; - } + }; onPostPointer = (e: React.PointerEvent) => { if (this._ignore !== e.timeStamp) { e.stopPropagation(); } - } + }; onPostWheel = (e: React.WheelEvent) => { if (this._ignore !== e.timeStamp) { e.stopPropagation(); } - } + }; /** * Function that submits the title entered by user on enter press. @@ -117,12 +121,11 @@ export class YoutubeBox extends React.Component { onEnterKeyDown = (e: React.KeyboardEvent) => { if (e.keyCode === 13) { const submittedTitle = this.YoutubeSearchElement!.value; - this.YoutubeSearchElement!.value = ""; + this.YoutubeSearchElement!.value = ''; this.YoutubeSearchElement!.blur(); DocServer.getYoutubeVideos(submittedTitle, this.processesVideoResults); - } - } + }; /** * The callback that is passed in to server, which functions as a way to @@ -134,12 +137,12 @@ export class YoutubeBox extends React.Component { this.searchResults = videos; if (this.searchResults.length > 0) { this.searchResultsFound = true; - this.videoIds = ""; - videos.forEach((video) => { - if (this.videoIds === "") { + this.videoIds = ''; + videos.forEach(video => { + if (this.videoIds === '') { this.videoIds = video.id.videoId; } else { - this.videoIds = this.videoIds! + ", " + video.id.videoId; + this.videoIds = this.videoIds! + ', ' + video.id.videoId; } }); //Asking for details that include duration and viewCount from server for videoIds @@ -149,7 +152,7 @@ export class YoutubeBox extends React.Component { this.videoClicked = false; } } - } + }; /** * The callback that is given to server to process and receive returned details about the videos. @@ -157,28 +160,26 @@ export class YoutubeBox extends React.Component { @action processVideoDetails = (videoDetails: any[]) => { this.videoDetails = videoDetails; - this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: "detailBackUp" }); - } + this.props.Document.cachedDetails = Doc.Get.FromJson({ data: videoDetails, title: 'detailBackUp' }); + }; /** * The function that stores the search results in the props document. */ backUpSearchResults = (videos: any[]) => { - this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: "videosBackUp" }); - } + this.props.Document.cachedSearchResults = Doc.Get.FromJson({ data: videos, title: 'videosBackUp' }); + }; /** * The function that filters out escaped characters returned by the api * in the title of the videos. */ filterYoutubeTitleResult = (resultTitle: string) => { - let processedTitle: string = resultTitle.replace(/&/g, "&");//.ReplaceAll("&", "&"); + let processedTitle: string = resultTitle.replace(/&/g, '&'); //.ReplaceAll("&", "&"); processedTitle = processedTitle.replace(/"'/g, "'"); - processedTitle = processedTitle.replace(/"/g, "\""); + processedTitle = processedTitle.replace(/"/g, '"'); return processedTitle; - } - - + }; /** * The function that converts ISO date, which is passed in, to normal date and finds the @@ -195,7 +196,6 @@ export class YoutubeBox extends React.Component { const totalMonths = totalDays / 30.417; const totalYears = totalMonths / 12; - const truncYears = Math.trunc(totalYears); const truncMonths = Math.trunc(totalMonths); const truncDays = Math.trunc(totalDays); @@ -203,62 +203,61 @@ export class YoutubeBox extends React.Component { const truncMin = Math.trunc(totalMin); const truncSec = Math.trunc(totalSeconds); - let pluralCase = ""; + let pluralCase = ''; if (truncYears !== 0) { - truncYears > 1 ? pluralCase = "s" : pluralCase = ""; - return truncYears + " year" + pluralCase + " ago"; + truncYears > 1 ? (pluralCase = 's') : (pluralCase = ''); + return truncYears + ' year' + pluralCase + ' ago'; } else if (truncMonths !== 0) { - truncMonths > 1 ? pluralCase = "s" : pluralCase = ""; - return truncMonths + " month" + pluralCase + " ago"; + truncMonths > 1 ? (pluralCase = 's') : (pluralCase = ''); + return truncMonths + ' month' + pluralCase + ' ago'; } else if (truncDays !== 0) { - truncDays > 1 ? pluralCase = "s" : pluralCase = ""; - return truncDays + " day" + pluralCase + " ago"; + truncDays > 1 ? (pluralCase = 's') : (pluralCase = ''); + return truncDays + ' day' + pluralCase + ' ago'; } else if (truncHours !== 0) { - truncHours > 1 ? pluralCase = "s" : pluralCase = ""; - return truncHours + " hour" + pluralCase + " ago"; + truncHours > 1 ? (pluralCase = 's') : (pluralCase = ''); + return truncHours + ' hour' + pluralCase + ' ago'; } else if (truncMin !== 0) { - truncMin > 1 ? pluralCase = "s" : pluralCase = ""; - return truncMin + " minute" + pluralCase + " ago"; + truncMin > 1 ? (pluralCase = 's') : (pluralCase = ''); + return truncMin + ' minute' + pluralCase + ' ago'; } else if (truncSec !== 0) { - truncSec > 1 ? pluralCase = "s" : pluralCase = ""; - return truncSec + " second" + pluralCase + " ago"; + truncSec > 1 ? (pluralCase = 's') : (pluralCase = ''); + return truncSec + ' second' + pluralCase + ' ago'; } - } + }; /** * The function that converts the passed in ISO time to normal duration time. */ convertIsoTimeToDuration = (isoDur: string) => { - - const convertedTime = isoDur.replace(/D|H|M/g, ":").replace(/P|T|S/g, "").split(":"); + const convertedTime = isoDur.replace(/D|H|M/g, ':').replace(/P|T|S/g, '').split(':'); if (1 === convertedTime.length) { - 2 !== convertedTime[0].length && (convertedTime[0] = "0" + convertedTime[0]), convertedTime[0] = "0:" + convertedTime[0]; + 2 !== convertedTime[0].length && (convertedTime[0] = '0' + convertedTime[0]), (convertedTime[0] = '0:' + convertedTime[0]); } else { for (var r = 1, l = convertedTime.length - 1; l >= r; r++) { - 2 !== convertedTime[r].length && (convertedTime[r] = "0" + convertedTime[r]); + 2 !== convertedTime[r].length && (convertedTime[r] = '0' + convertedTime[r]); } } - return convertedTime.join(":"); - } + return convertedTime.join(':'); + }; /** - * The function that rounds the viewCount to the nearest + * The function that rounds the viewCount to the nearest * thousand, million or billion, given a viewCount number. */ abbreviateViewCount = (viewCount: number) => { if (viewCount < 1000) { return viewCount.toString(); } else if (viewCount >= 1000 && viewCount < 1000000) { - return (Math.trunc(viewCount / 1000)) + "K"; + return Math.trunc(viewCount / 1000) + 'K'; } else if (viewCount >= 1000000 && viewCount < 1000000000) { - return (Math.trunc(viewCount / 1000000)) + "M"; + return Math.trunc(viewCount / 1000000) + 'M'; } else if (viewCount >= 1000000000) { - return (Math.trunc(viewCount / 1000000000)) + "B"; + return Math.trunc(viewCount / 1000000000) + 'B'; } - } + }; /** * The function that is called to decide on what'll be rendered by the component. @@ -268,63 +267,69 @@ export class YoutubeBox extends React.Component { renderSearchResultsOrVideo = () => { if (this.searchResultsFound) { if (this.searchResults.length !== 0) { - return
      - {this.searchResults.map((video, index) => { - const filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); - const channelTitle = video.snippet.channelTitle; - const videoDescription = video.snippet.description; - const pusblishDate = this.roundPublishTime(video.snippet.publishedAt); - let duration; - let viewCount; - if (this.videoDetails.length !== 0) { - duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration); - viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount); - } - - - return
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}> -
      -
      - - {duration} -
      -
      - {filteredTitle} - {channelTitle} - {viewCount} - {pusblishDate} -

      {videoDescription}

      - -
      -
      -
    • ; - })} -
    ; + return ( +
      + {this.searchResults.map((video, index) => { + const filteredTitle = this.filterYoutubeTitleResult(video.snippet.title); + const channelTitle = video.snippet.channelTitle; + const videoDescription = video.snippet.description; + const pusblishDate = this.roundPublishTime(video.snippet.publishedAt); + let duration; + let viewCount; + if (this.videoDetails.length !== 0) { + duration = this.convertIsoTimeToDuration(this.videoDetails[index].contentDetails.duration); + viewCount = this.abbreviateViewCount(this.videoDetails[index].statistics.viewCount); + } + + return ( +
    • this.embedVideoOnClick(video.id.videoId, filteredTitle)} key={Utils.GenerateGuid()}> +
      +
      + + {duration} +
      +
      + {filteredTitle} + {channelTitle} + {viewCount} + {pusblishDate} +

      {videoDescription}

      +
      +
      +
    • + ); + })} +
    + ); } else if (this.curVideoTemplates.length !== 0) { - return
      - {this.curVideoTemplates.map((video: VideoTemplate) => { - return
    • this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}> -
      -
      - - {video.duration} -
      -
      - {video.videoTitle} - {video.channelTitle} - {video.viewCount} - {video.publishDate} -

      {video.videoDescription}

      -
      -
      -
    • ; - })} -
    ; + return ( +
      + {this.curVideoTemplates.map((video: VideoTemplate) => { + return ( +
    • this.embedVideoOnClick(video.videoId, video.videoTitle)} key={Utils.GenerateGuid()}> +
      +
      + + {video.duration} +
      +
      + {video.videoTitle} + {video.channelTitle} + {video.viewCount} + {video.publishDate} +

      {video.videoDescription}

      +
      +
      +
    • + ); + })} +
    + ); } } else { - return (null); + return null; } - } + }; /** * Given a videoId and title, creates a new youtube embedded url, and uses that @@ -332,7 +337,7 @@ export class YoutubeBox extends React.Component { */ @action embedVideoOnClick = (videoId: string, filteredTitle: string) => { - const embeddedUrl = "https://www.youtube.com/embed/" + videoId; + const embeddedUrl = 'https://www.youtube.com/embed/' + videoId; this.selectedVideoUrl = embeddedUrl; const addFunction = this.props.addDocument!; const newVideoX = NumCast(this.props.Document.x); @@ -340,24 +345,24 @@ export class YoutubeBox extends React.Component { addFunction(Docs.Create.VideoDocument(embeddedUrl, { title: filteredTitle, _width: 400, _height: 315, x: newVideoX, y: newVideoY })); this.videoClicked = true; - } + }; render() { - const content = -
    - this.YoutubeSearchElement = e!} /> + const content = ( +
    + (this.YoutubeSearchElement = e!)} /> {this.renderSearchResultsOrVideo()} -
    ; +
    + ); const frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; - const classname = "webBox-cont" + (this.props.isSelected() && CurrentUserUtils.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); + const classname = 'webBox-cont' + (this.props.isSelected() && Doc.ActiveTool === InkTool.None && !DocumentDecorations.Instance.Interacting ? '-interactive' : ''); return ( <> -
    - {content} -
    - {!frozen ? (null) :
    } - ); +
    {content}
    + {!frozen ? null :
    } + + ); } -} \ No newline at end of file +} diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index 2343c2f34..4e98a90a3 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -1,46 +1,66 @@ export enum DocumentType { - NONE = "none", + NONE = 'none', // core data types - RTF = "rtf", - IMG = "image", - WEB = "web", - COL = "collection", - KVP = "kvp", - VID = "video", - AUDIO = "audio", - REC = "recording", - PDF = "pdf", - INK = "inks", - SCREENSHOT = "screenshot", - FONTICON = "fonticonbox", - FILTER = "filter", - SEARCH = "search", // search query - LABEL = "label", // simple text label - BUTTON = "button", // onClick button - WEBCAM = "webcam", // webcam - MARKER = "marker", // generic marker document not intended to be viewed independently of its context (e.g., for text selections in PDF/Web/RTF) - DATE = "date", // calendar view of a date - SCRIPTING = "script", // script editor - EQUATION = "equation", // equation editor - FUNCPLOT = "funcplot", // function plotter - MAP = "map", - DATAVIZ = "dataviz", + RTF = 'rtf', + IMG = 'image', + WEB = 'web', + COL = 'collection', + KVP = 'kvp', + VID = 'video', + AUDIO = 'audio', + REC = 'recording', + PDF = 'pdf', + INK = 'inks', + SCREENSHOT = 'screenshot', + FONTICON = 'fonticonbox', + FILTER = 'filter', + SEARCH = 'search', // search query + LABEL = 'label', // simple text label + BUTTON = 'button', // onClick button + WEBCAM = 'webcam', // webcam + MARKER = 'marker', // generic marker document not intended to be viewed independently of its context (e.g., for text selections in PDF/Web/RTF) + DATE = 'date', // calendar view of a date + SCRIPTING = 'script', // script editor + EQUATION = 'equation', // equation editor + FUNCPLOT = 'funcplot', // function plotter + MAP = 'map', + DATAVIZ = 'dataviz', // special purpose wrappers that either take no data or are compositions of lower level types - LINK = "link", - LINKANCHOR = "linkanchor", - IMPORT = "import", - SLIDER = "slider", - PRES = "presentation", - PRESELEMENT = "preselement", - COLOR = "color", - YOUTUBE = "youtube", - SEARCHITEM = "searchitem", - COMPARISON = "comparison", - GROUP = "group", + LINK = 'link', + LINKANCHOR = 'linkanchor', + IMPORT = 'import', + SLIDER = 'slider', + PRES = 'presentation', + PRESELEMENT = 'preselement', + COLOR = 'color', + YOUTUBE = 'youtube', + SEARCHITEM = 'searchitem', + COMPARISON = 'comparison', + GROUP = 'group', - LINKDB = "linkdb", // database of links ??? why do we have this - SCRIPTDB = "scriptdb", // database of scripts - GROUPDB = "groupdb", // database of groups -} \ No newline at end of file + LINKDB = 'linkdb', // database of links ??? why do we have this + SCRIPTDB = 'scriptdb', // database of scripts + GROUPDB = 'groupdb', // database of groups +} +export enum CollectionViewType { + Invalid = 'invalid', + Freeform = 'freeform', + Schema = 'schema', + Docking = 'docking', + Tree = 'tree', + Stacking = 'stacking', + Masonry = 'masonry', + Multicolumn = 'multicolumn', + Multirow = 'multirow', + Time = 'time', + Carousel = 'carousel', + Carousel3D = '3D Carousel', + Linear = 'linear', + //Staff = "staff", + Map = 'map', + Grid = 'grid', + Pile = 'pileup', + StackedTimeline = 'stacked timeline', +} diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 65b3db0e2..ed4c99d70 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,124 +1,158 @@ -import { IconProp } from "@fortawesome/fontawesome-svg-core"; -import { action, runInAction } from "mobx"; -import { basename } from "path"; -import { DateField } from "../../fields/DateField"; -import { Doc, DocListCast, DocListCastAsync, Field, Initializing, Opt, updateCachedAcls } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { HtmlField } from "../../fields/HtmlField"; -import { InkField, PointData } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { ProxyField } from "../../fields/Proxy"; -import { RichTextField } from "../../fields/RichTextField"; -import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; -import { ComputedField, ScriptField } from "../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from "../../fields/URLField"; -import { inheritParentAcls, SharingPermissions } from "../../fields/util"; -import { Upload } from "../../server/SharedMediaTypes"; -import { aggregateBounds, OmitKeys, Utils } from "../../Utils"; -import { YoutubeBox } from "../apis/youtube/YoutubeBox"; -import { DocServer } from "../DocServer"; -import { Networking } from "../Network"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { DocumentManager } from "../util/DocumentManager"; -import { dropActionType } from "../util/DragManager"; -import { DirectoryImportBox } from "../util/Import & Export/DirectoryImportBox"; -import { LinkManager } from "../util/LinkManager"; -import { ScriptingGlobals } from "../util/ScriptingGlobals"; -import { undoBatch, UndoManager } from "../util/UndoManager"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; -import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; -import { ContextMenu } from "../views/ContextMenu"; -import { ContextMenuProps } from "../views/ContextMenuItem"; -import { DFLT_IMAGE_NATIVE_DIM } from "../views/global/globalCssVariables.scss"; -import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from "../views/InkingStroke"; -import { AudioBox } from "../views/nodes/AudioBox"; -import { FontIconBox } from "../views/nodes/button/FontIconBox"; -import { ColorBox } from "../views/nodes/ColorBox"; -import { ComparisonBox } from "../views/nodes/ComparisonBox"; -import { DataVizBox } from "../views/nodes/DataVizBox/DataVizBox"; -import { DocFocusOptions } from "../views/nodes/DocumentView"; -import { EquationBox } from "../views/nodes/EquationBox"; -import { FieldViewProps } from "../views/nodes/FieldView"; -import { FilterBox } from "../views/nodes/FilterBox"; -import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; -import { FunctionPlotBox } from "../views/nodes/FunctionPlotBox"; -import { ImageBox } from "../views/nodes/ImageBox"; -import { KeyValueBox } from "../views/nodes/KeyValueBox"; -import { LabelBox } from "../views/nodes/LabelBox"; -import { LinkBox } from "../views/nodes/LinkBox"; -import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup"; -import { MapBox } from "../views/nodes/MapBox/MapBox"; -import { PDFBox } from "../views/nodes/PDFBox"; -import { RecordingBox } from "../views/nodes/RecordingBox/RecordingBox"; -import { ScreenshotBox } from "../views/nodes/ScreenshotBox"; -import { ScriptingBox } from "../views/nodes/ScriptingBox"; -import { SliderBox } from "../views/nodes/SliderBox"; -import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import { PresBox } from "../views/nodes/trails/PresBox"; -import { PresElementBox } from "../views/nodes/trails/PresElementBox"; -import { VideoBox } from "../views/nodes/VideoBox"; -import { WebBox } from "../views/nodes/WebBox"; -import { SearchBox } from "../views/search/SearchBox"; -import { DocumentType } from "./DocumentTypes"; - -const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { action, runInAction } from 'mobx'; +import { basename } from 'path'; +import { DateField } from '../../fields/DateField'; +import { Doc, DocListCast, DocListCastAsync, Field, Initializing, Opt, updateCachedAcls } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { HtmlField } from '../../fields/HtmlField'; +import { InkField, PointData } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { ProxyField } from '../../fields/Proxy'; +import { RichTextField } from '../../fields/RichTextField'; +import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; +import { ComputedField, ScriptField } from '../../fields/ScriptField'; +import { Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { AudioField, CsvField, ImageField, MapField, PdfField, RecordingField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; +import { inheritParentAcls, SharingPermissions } from '../../fields/util'; +import { Upload } from '../../server/SharedMediaTypes'; +import { aggregateBounds, OmitKeys, Utils } from '../../Utils'; +import { YoutubeBox } from '../apis/youtube/YoutubeBox'; +import { DocServer } from '../DocServer'; +import { Networking } from '../Network'; +import { DocumentManager } from '../util/DocumentManager'; +import { DragManager, dropActionType } from '../util/DragManager'; +import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox'; +import { LinkManager } from '../util/LinkManager'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { undoBatch, UndoManager } from '../util/UndoManager'; +import { CollectionDockingView } from '../views/collections/CollectionDockingView'; +import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; +import { CollectionView } from '../views/collections/CollectionView'; +import { ContextMenu } from '../views/ContextMenu'; +import { ContextMenuProps } from '../views/ContextMenuItem'; +import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss'; +import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, InkingStroke } from '../views/InkingStroke'; +import { AudioBox } from '../views/nodes/AudioBox'; +import { FontIconBox } from '../views/nodes/button/FontIconBox'; +import { ColorBox } from '../views/nodes/ColorBox'; +import { ComparisonBox } from '../views/nodes/ComparisonBox'; +import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; +import { DocFocusOptions } from '../views/nodes/DocumentView'; +import { EquationBox } from '../views/nodes/EquationBox'; +import { FieldViewProps } from '../views/nodes/FieldView'; +import { FilterBox } from '../views/nodes/FilterBox'; +import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; +import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox'; +import { ImageBox } from '../views/nodes/ImageBox'; +import { KeyValueBox } from '../views/nodes/KeyValueBox'; +import { LabelBox } from '../views/nodes/LabelBox'; +import { LinkBox } from '../views/nodes/LinkBox'; +import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup'; +import { MapBox } from '../views/nodes/MapBox/MapBox'; +import { PDFBox } from '../views/nodes/PDFBox'; +import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox'; +import { ScreenshotBox } from '../views/nodes/ScreenshotBox'; +import { ScriptingBox } from '../views/nodes/ScriptingBox'; +import { SliderBox } from '../views/nodes/SliderBox'; +import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; +import { PresBox } from '../views/nodes/trails/PresBox'; +import { PresElementBox } from '../views/nodes/trails/PresElementBox'; +import { VideoBox } from '../views/nodes/VideoBox'; +import { WebBox } from '../views/nodes/WebBox'; +import { SearchBox } from '../views/search/SearchBox'; +import { CollectionViewType, DocumentType } from './DocumentTypes'; + +const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); class EmptyBox { public static LayoutString() { - return ""; + return ''; } } export abstract class FInfo { - description: string = ""; + description: string = ''; fieldType?: string; values?: Field[]; // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string - constructor(d: string) { this.description = d; } + constructor(d: string) { + this.description = d; + } +} +class BoolInfo extends FInfo { + fieldType? = 'boolean'; + values?: boolean[] = [true, false]; +} +class NumInfo extends FInfo { + fieldType? = 'number'; + values?: number[] = []; + constructor(d: string, values?: number[]) { + super(d); + this.values = values; + } +} +class StrInfo extends FInfo { + fieldType? = 'string'; + values?: string[] = []; + constructor(d: string, values?: string[]) { + super(d); + this.values = values; + } +} +class DocInfo extends FInfo { + fieldType? = 'Doc'; + values?: Doc[] = []; + constructor(d: string, values?: Doc[]) { + super(d); + this.values = values; + } +} +class DimInfo extends FInfo { + fieldType? = 'DimUnit'; + values? = [DimUnit.Pixel, DimUnit.Ratio]; +} +class PEInfo extends FInfo { + fieldType? = 'pointerEvents'; + values? = ['all', 'none']; +} +class DAInfo extends FInfo { + fieldType? = 'dropActionType'; + values? = ['alias', 'copy', 'move', 'same', 'proto', 'none']; } -class BoolInfo extends FInfo { fieldType?= "boolean"; values?: boolean[] = [true, false]; } -class NumInfo extends FInfo { fieldType?= "number"; values?: number[] = []; constructor(d:string, values?:number[]) { super(d); this.values = values; }} -class StrInfo extends FInfo { fieldType?= "string"; values?: string[] = []; constructor(d:string, values?:string[]) { super(d); this.values = values; }} -class DocInfo extends FInfo { fieldType?= "Doc"; values?: Doc[] = []; constructor(d:string, values?:Doc[]) { super(d); this.values = values; }} -class DimInfo extends FInfo { fieldType?= "DimUnit"; values?= [DimUnit.Pixel, DimUnit.Ratio]; } -class PEInfo extends FInfo { fieldType?= "pointerEvents"; values?= ["all", "none"]; } -class DAInfo extends FInfo { fieldType?= "dropActionType"; values?= ["alias", "copy", "move", "same", "proto", "none"]; } type BOOLt = BoolInfo | boolean; -type NUMt = NumInfo | number; -type STRt = StrInfo | string; -type DOCt = DocInfo | Doc; -type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; -type PEVt = PEInfo | "none" | "all"; +type NUMt = NumInfo | number; +type STRt = StrInfo | string; +type DOCt = DocInfo | Doc; +type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; +type PEVt = PEInfo | 'none' | 'all'; type DROPt = DAInfo | dropActionType; export class DocumentOptions { - x?: NUMt = new NumInfo("x coordinate of document in a freeform view"); - y?: NUMt = new NumInfo("y coordinage of document in a freeform view"); - z?: NUMt = new NumInfo("whether document is in overlay (1) or not (0)", [1,0]); - system?: BOOLt = new BoolInfo("is this a system created/owned doc"); - type?: STRt = new StrInfo("type of document", Array.from(Object.keys(DocumentType))); + x?: NUMt = new NumInfo('x coordinate of document in a freeform view'); + y?: NUMt = new NumInfo('y coordinage of document in a freeform view'); + z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', [1, 0]); + system?: BOOLt = new BoolInfo('is this a system created/owned doc'); + type?: STRt = new StrInfo('type of document', Array.from(Object.keys(DocumentType))); title?: string; _dropAction?: DROPt = new DAInfo("what should happen to this document when it's dropped somewhere else"); - allowOverlayDrop?: BOOLt = new BoolInfo("can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?"); + allowOverlayDrop?: BOOLt = new BoolInfo('can documents be dropped onto this document without using dragging title bar or holding down embed key (ctrl)?'); childDropAction?: DROPt = new DAInfo("what should happen to the source document when it's dropped onto a child of a collection "); - targetDropAction?: DROPt = new DAInfo("what should happen to the source document when ??? "); - userColor?: STRt = new StrInfo("color associated with a Dash user (seen in header fields of shared documents)"); - color?: STRt = new StrInfo("foreground color data doc"); - backgroundColor?: STRt = new StrInfo("background color for data doc"); - _autoHeight?: BOOLt = new BoolInfo("whether document automatically resizes vertically to display contents"); - _headerHeight?: NUMt = new NumInfo("height of document header used for displaying title"); - _headerFontSize?: NUMt = new NumInfo("font size of header of custom notes"); - _headerPointerEvents?: PEVt = new PEInfo("types of events the header of a custom text document can consume"); - _panX?: NUMt = new NumInfo("horizontal pan location of a freeform view"); - _panY?: NUMt = new NumInfo("vertical pan location of a freeform view"); - _width?: NUMt = new NumInfo("displayed width of a document"); - _height?: NUMt = new NumInfo("displayed height of document"); - _nativeWidth?: NUMt = new NumInfo("native width of document contents (e.g., the pixel width of an image)"); - _nativeHeight?: NUMt = new NumInfo("native height of document contents (e.g., the pixel height of an image)"); + targetDropAction?: DROPt = new DAInfo('what should happen to the source document when ??? '); + userColor?: STRt = new StrInfo('color associated with a Dash user (seen in header fields of shared documents)'); + color?: STRt = new StrInfo('foreground color data doc'); + backgroundColor?: STRt = new StrInfo('background color for data doc'); + _autoHeight?: BOOLt = new BoolInfo('whether document automatically resizes vertically to display contents'); + _headerHeight?: NUMt = new NumInfo('height of document header used for displaying title'); + _headerFontSize?: NUMt = new NumInfo('font size of header of custom notes'); + _headerPointerEvents?: PEVt = new PEInfo('types of events the header of a custom text document can consume'); + _panX?: NUMt = new NumInfo('horizontal pan location of a freeform view'); + _panY?: NUMt = new NumInfo('vertical pan location of a freeform view'); + _width?: NUMt = new NumInfo('displayed width of a document'); + _height?: NUMt = new NumInfo('displayed height of document'); + _nativeWidth?: NUMt = new NumInfo('native width of document contents (e.g., the pixel width of an image)'); + _nativeHeight?: NUMt = new NumInfo('native height of document contents (e.g., the pixel height of an image)'); _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height"); _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units"); - _fitWidth?: BOOLt = new BoolInfo("whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)"); - _fitContentsToBox?: BOOLt = new BoolInfo("whether a freeformview should zoom/scale to create a shrinkwrapped view of its content"); + _fitWidth?: BOOLt = new BoolInfo('whether document should scale its contents to fit its rendered width or not (e.g., for PDFviews)'); + _fitContentsToBox?: BOOLt = new BoolInfo('whether a freeformview should zoom/scale to create a shrinkwrapped view of its content'); _contentBounds?: List; // the (forced) bounds of the document to display. format is: [left, top, right, bottom] _lockedPosition?: boolean; // lock the x,y coordinates of the document so that it can't be dragged _lockedTransform?: boolean; // lock the panx,pany and scale parameters of the document so that it be panned/zoomed @@ -126,11 +160,11 @@ export class DocumentOptions { _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs - _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged + _noAutoscroll?: boolean; // whether collections autoscroll when this item is dragged _chromeHidden?: boolean; // whether the editing chrome for a document is hidden _searchDoc?: boolean; // is this a search document (used to change UI for search results in schema view) _forceActive?: boolean; // flag to handle pointer events when not selected (or otherwise active) - _stayInCollection?: boolean;// whether the document should remain in its collection when someone tries to drag and drop it elsewhere + _stayInCollection?: boolean; // whether the document should remain in its collection when someone tries to drag and drop it elsewhere _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged. _hideContextMenu?: boolean; // whether the context menu can be shown _viewType?: string; // sub type of a collection @@ -142,7 +176,7 @@ export class DocumentOptions { _xPadding?: number; _yPadding?: number; _itemIndex?: number; // which item index the carousel viewer is showing - _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts + _showSidebar?: boolean; //whether an annotationsidebar should be displayed for text docuemnts _singleLine?: boolean; // whether text document is restricted to a single line (carriage returns make new document) _minFontSize?: number; // minimum font size for labelBoxes _maxFontSize?: number; // maximum font size for labelBoxes @@ -158,11 +192,11 @@ export class DocumentOptions { _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) _timecodeToHide?: number; // the time that a document should be hidden _timelineLabel?: boolean; // whether the document exists on a timeline - "_carousel-caption-xMargin"?: NUMt = new NumInfo("x margin of caption inside of a carouself collection"); - "_carousel-caption-yMargin"?: NUMt = new NumInfo("y margin of caption inside of a carouself collection"); - "icon-nativeWidth"?: NUMt = new NumInfo("native width of icon view"); - "icon-nativeHeight"?: NUMt = new NumInfo("native height of icon view"); - "dragFactory-count"?: NUMt = new NumInfo("number of items created from a drag button (used for setting title with incrementing index)"); + '_carousel-caption-xMargin'?: NUMt = new NumInfo('x margin of caption inside of a carouself collection'); + '_carousel-caption-yMargin'?: NUMt = new NumInfo('y margin of caption inside of a carouself collection'); + 'icon-nativeWidth'?: NUMt = new NumInfo('native width of icon view'); + 'icon-nativeHeight'?: NUMt = new NumInfo('native height of icon view'); + 'dragFactory-count'?: NUMt = new NumInfo('number of items created from a drag button (used for setting title with incrementing index)'); lat?: number; lng?: number; infoWindowOpen?: boolean; @@ -171,8 +205,8 @@ export class DocumentOptions { fieldValues?: List; // possible field values used by fieldInfos fieldType?: string; // type of afield used by fieldInfos unrendered?: boolean; // denotes an annotation that is not rendered with a DocumentView (e.g, rtf/pdf text selections and links to scroll locations in web/pdf) - "acl-Public"?: string; // public permissions - "_acl-Public"?: string; // public permissions + 'acl-Public'?: string; // public permissions + '_acl-Public'?: string; // public permissions version?: string; // version identifier for a document label?: string; hidden?: boolean; @@ -181,7 +215,7 @@ export class DocumentOptions { mediaState?: string; // status of audio/video media document: "pendingRecording", "recording", "paused", "playing" recording?: boolean; // whether WebCam is recording or not autoPlayAnchors?: boolean; // whether to play audio/video when an anchor is clicked in a stackedTimeline. - dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. + dontPlayLinkOnSelect?: boolean; // whether an audio/video should start playing when a link is followed to it. toolTip?: string; // tooltip to display on hover contextMenuFilters?: List; contextMenuScripts?: List; @@ -190,7 +224,7 @@ export class DocumentOptions { dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) description?: string; // added for links layout?: string | Doc; // default layout string for a document - contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents + contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox layout in tree view) childLayoutString?: string; // template string for collection to use to render its children @@ -230,12 +264,12 @@ export class DocumentOptions { baseProto?: boolean; // is this a base prototoype dontRegisterView?: boolean; lookupField?: ScriptField; // script that returns the value of a field. This script is passed the rootDoc, layoutDoc, field, and container of the document. see PresBox. - "onDoubleClick-rawScript"?: string; // onDoubleClick script in raw text form - "onChildDoubleClick-rawScript"?: string; // onChildDoubleClick script in raw text form - "onChildClick-rawScript"?: string; // on ChildClick script in raw text form - "onClick-rawScript"?: string; // onClick script in raw text form - "onCheckedClick-rawScript"?: string; // onChecked script in raw text form - "onCheckedClick-params"?: List; // parameter list for onChecked treeview functions + 'onDoubleClick-rawScript'?: string; // onDoubleClick script in raw text form + 'onChildDoubleClick-rawScript'?: string; // onChildDoubleClick script in raw text form + 'onChildClick-rawScript'?: string; // on ChildClick script in raw text form + 'onClick-rawScript'?: string; // onClick script in raw text form + 'onCheckedClick-rawScript'?: string; // onChecked script in raw text form + 'onCheckedClick-params'?: List; // parameter list for onChecked treeview functions columnHeaders?: List; // headers for stacking views schemaHeaders?: List; // headers for schema view clipWidth?: number; // percent transition from before to after in comparisonBox @@ -270,7 +304,7 @@ export class DocumentOptions { linearViewSubMenu?: boolean; linearViewFloating?: boolean; flexGap?: number; // Linear view flex gap - flexDirection?: "unset" | "row" | "column" | "row-reverse" | "column-reverse"; + flexDirection?: 'unset' | 'row' | 'column' | 'row-reverse' | 'column-reverse'; layout_linkView?: Doc; // view template for a link document layout_keyValue?: string; // view tempalte for key value docs @@ -278,8 +312,8 @@ export class DocumentOptions { linkDisplay?: boolean; // whether a link line should be dipslayed between the two link anchors anchor1?: Doc; anchor2?: Doc; - "anchor1-useLinkSmallAnchor"?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection) - "anchor2-useLinkSmallAnchor"?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection) + 'anchor1-useLinkSmallAnchor'?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection) + 'anchor2-useLinkSmallAnchor'?: boolean; // whether anchor1 of a link should use a miniature anchor dot (as when the anchor is a text selection) ignoreClick?: boolean; onClick?: ScriptField; onDoubleClick?: ScriptField; @@ -292,7 +326,7 @@ export class DocumentOptions { clickFactory?: Doc; // document to create when clicking on a button with a suitable onClick script onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop cloneFieldFilter?: List; // fields not to copy when the document is clonedclipboard?: Doc; - filterBoolean ?: string; + filterBoolean?: string; useCors?: boolean; icon?: string; target?: Doc; // available for use in scripts as the primary target document @@ -306,9 +340,9 @@ export class DocumentOptions { treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically - treeViewChildDoubleClick?: ScriptField; // + treeViewChildDoubleClick?: ScriptField; // // Action Button - buttonMenu?: boolean; // whether a action button should be displayed + buttonMenu?: boolean; // whether a action button should be displayed buttonMenuDoc?: Doc; explainer?: string; @@ -321,7 +355,7 @@ export class DocumentOptions { treeViewTruncateTitleWidth?: number; treeViewHasOverlay?: boolean; // whether the treeview has an overlay for freeform annotations treeViewType?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy - sidebarColor?: string; // background color of text sidebar + sidebarColor?: string; // background color of text sidebar sidebarViewType?: string; // collection type of text sidebar docMaxAutoHeight?: number; // maximum height for newly created (eg, from pasting) text documents text?: string; @@ -331,192 +365,305 @@ export class DocumentOptions { selectedIndex?: number; // which item in a linear view has been selected using the "thumb doc" ui clipboard?: Doc; searchQuery?: string; // for quersyBox - useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox + useLinkSmallAnchor?: boolean; // whether links to this document should use a miniature linkAnchorBox border?: string; //for searchbox hoverBackgroundColor?: string; // background color of a label when hovered linkRelationshipList?: List; // for storing different link relationships (when set by user in the link editor) - linkRelationshipSizes?: List; //stores number of links contained in each relationship + linkRelationshipSizes?: List; //stores number of links contained in each relationship linkColorList?: List; // colors of links corresponding to specific link relationships } export namespace Docs { - export let newAccount: boolean = false; export namespace Prototypes { - type LayoutSource = { LayoutString: (key: string) => string }; type PrototypeTemplate = { layout: { - view: LayoutSource, - dataField: string - }, - data?: any, - options?: Partial + view: LayoutSource; + dataField: string; + }; + data?: any; + options?: Partial; }; type TemplateMap = Map; type PrototypeMap = Map; - const defaultDataKey = "data"; + const defaultDataKey = 'data'; const TemplateMap: TemplateMap = new Map([ - [DocumentType.RTF, { - layout: { view: FormattedTextBox, dataField: "text" }, - options: { - _height: 35, _xMargin: 10, _yMargin: 10, nativeDimModifiable: true, treeViewGrowsHorizontally: true, - forceReflow: true, links: "@links(self)" - } - }], - [DocumentType.SEARCH, { - layout: { view: SearchBox, dataField: defaultDataKey }, - options: { _width: 400, links: "@links(self)" } - }], - [DocumentType.FILTER, { - layout: { view: FilterBox, dataField: defaultDataKey }, - options: { _width: 400, links: "@links(self)" } - }], - [DocumentType.COLOR, { - layout: { view: ColorBox, dataField: defaultDataKey }, - options: { _nativeWidth: 220, _nativeHeight: 300, links: "@links(self)" } - }], - [DocumentType.IMG, { - layout: { view: ImageBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } - }], - [DocumentType.WEB, { - layout: { view: WebBox, dataField: defaultDataKey }, - options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } - }], - [DocumentType.COL, { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: "@links(self)" } - }], - [DocumentType.KVP, { - layout: { view: KeyValueBox, dataField: defaultDataKey }, - options: { _fitWidth: true, _height: 150 } - }], - [DocumentType.VID, { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { _currentTimecode: 0, links: "@links(self)" }, - }], - [DocumentType.AUDIO, { - layout: { view: AudioBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: "lightGray", forceReflow: true, nativeDimModifiable: true, links: "@links(self)" } - }], - [DocumentType.REC, { - layout: { view: VideoBox, dataField: defaultDataKey }, - options: { _height: 100, backgroundColor: "pink", links: "@links(self)" } - }], - [DocumentType.PDF, { - layout: { view: PDFBox, dataField: defaultDataKey }, - options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: "@links(self)" } - }], - [DocumentType.MAP, { - layout: { view: MapBox, dataField: defaultDataKey }, - options: { _height: 600, _width: 800, nativeDimModifiable: true, links: "@links(self)" } - }], - [DocumentType.IMPORT, { - layout: { view: DirectoryImportBox, dataField: defaultDataKey }, - options: { _height: 150 } - }], - [DocumentType.LINK, { - layout: { view: LinkBox, dataField: defaultDataKey }, - options: { - childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", showCaption: "description", - backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area - links: "@links(self)", - _removeDropProperties: new List(["isLinkButton"]), - } - }], - [DocumentType.LINKDB, { - data: new List(), - layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { childDropAction: "alias", title: "Global Link Database" } - }], - [DocumentType.SCRIPTDB, { - data: new List(), - layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { childDropAction: "alias", title: "Global Script Database" } - }], - [DocumentType.SCRIPTING, { - layout: { view: ScriptingBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } - }], - [DocumentType.YOUTUBE, { - layout: { view: YoutubeBox, dataField: defaultDataKey } - }], - [DocumentType.LABEL, { - layout: { view: LabelBox, dataField: defaultDataKey }, - options: { links: "@links(self)", _singleLine: true } - }], - [DocumentType.EQUATION, { - layout: { view: EquationBox, dataField: defaultDataKey }, - options: { links: "@links(self)", nativeDimModifiable: true, hideResizeHandles: true, hideDecorationTitle: true } - }], - [DocumentType.FUNCPLOT, { - layout: { view: FunctionPlotBox, dataField: defaultDataKey }, - options: { nativeDimModifiable: true, links: "@links(self)" } - }], - [DocumentType.BUTTON, { - layout: { view: LabelBox, dataField: "onClick" }, - options: { links: "@links(self)" } - }], - [DocumentType.SLIDER, { - layout: { view: SliderBox, dataField: defaultDataKey, }, - options: { links: "@links(self)", treeViewGrowsHorizontally: true } - }], - [DocumentType.PRES, { - layout: { view: PresBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } - }], - [DocumentType.FONTICON, { - layout: { view: FontIconBox, dataField: defaultDataKey }, - options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: "100%", links: "@links(self)" }, - }], - [DocumentType.WEBCAM, { - layout: { view: RecordingBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } - }], - [DocumentType.PRESELEMENT, { - layout: { view: PresElementBox, dataField: defaultDataKey } - }], - [DocumentType.MARKER, { - layout: { view: CollectionView, dataField: defaultDataKey }, - options: { links: "@links(self)", hideLinkButton: true, pointerEvents: "none" } - }], - [DocumentType.INK, { // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method - layout: { view: InkingStroke, dataField: defaultDataKey }, - options: { links: "@links(self)" } - }], - [DocumentType.SCREENSHOT, { - layout: { view: ScreenshotBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } - }], - [DocumentType.COMPARISON, { - layout: { view: ComparisonBox, dataField: defaultDataKey }, - options: { clipWidth: 50, nativeDimModifiable: true, backgroundColor: "gray", targetDropAction: "alias", links: "@links(self)" } - }], - [DocumentType.GROUPDB, { - data: new List(), - layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { childDropAction: "alias", title: "Global Group Database" } - }], - [DocumentType.GROUP, { - layout: { view: EmptyBox, dataField: defaultDataKey }, - options: { links: "@links(self)" } - }], - [DocumentType.DATAVIZ, { - layout: { view: DataVizBox, dataField: defaultDataKey }, - options: { _fitWidth: true, nativeDimModifiable: true, links: "@links(self)" } - }] + [ + DocumentType.RTF, + { + layout: { view: FormattedTextBox, dataField: 'text' }, + options: { + _height: 35, + _xMargin: 10, + _yMargin: 10, + nativeDimModifiable: true, + treeViewGrowsHorizontally: true, + forceReflow: true, + links: '@links(self)', + }, + }, + ], + [ + DocumentType.SEARCH, + { + layout: { view: SearchBox, dataField: defaultDataKey }, + options: { _width: 400, links: '@links(self)' }, + }, + ], + [ + DocumentType.FILTER, + { + layout: { view: FilterBox, dataField: defaultDataKey }, + options: { _width: 400, links: '@links(self)' }, + }, + ], + [ + DocumentType.COLOR, + { + layout: { view: ColorBox, dataField: defaultDataKey }, + options: { _nativeWidth: 220, _nativeHeight: 300, links: '@links(self)' }, + }, + ], + [ + DocumentType.IMG, + { + layout: { view: ImageBox, dataField: defaultDataKey }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.WEB, + { + layout: { view: WebBox, dataField: defaultDataKey }, + options: { _height: 300, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: '@links(self)' }, + }, + ], + [ + DocumentType.COL, + { + layout: { view: CollectionView, dataField: defaultDataKey }, + options: { _fitWidth: true, _panX: 0, _panY: 0, _viewScale: 1, links: '@links(self)' }, + }, + ], + [ + DocumentType.KVP, + { + layout: { view: KeyValueBox, dataField: defaultDataKey }, + options: { _fitWidth: true, _height: 150 }, + }, + ], + [ + DocumentType.VID, + { + layout: { view: VideoBox, dataField: defaultDataKey }, + options: { _currentTimecode: 0, links: '@links(self)' }, + }, + ], + [ + DocumentType.AUDIO, + { + layout: { view: AudioBox, dataField: defaultDataKey }, + options: { _height: 100, backgroundColor: 'lightGray', forceReflow: true, nativeDimModifiable: true, links: '@links(self)' }, + }, + ], + [ + DocumentType.REC, + { + layout: { view: VideoBox, dataField: defaultDataKey }, + options: { _height: 100, backgroundColor: 'pink', links: '@links(self)' }, + }, + ], + [ + DocumentType.PDF, + { + layout: { view: PDFBox, dataField: defaultDataKey }, + options: { _curPage: 1, _fitWidth: true, nativeDimModifiable: true, nativeHeightUnfrozen: true, links: '@links(self)' }, + }, + ], + [ + DocumentType.MAP, + { + layout: { view: MapBox, dataField: defaultDataKey }, + options: { _height: 600, _width: 800, nativeDimModifiable: true, links: '@links(self)' }, + }, + ], + [ + DocumentType.IMPORT, + { + layout: { view: DirectoryImportBox, dataField: defaultDataKey }, + options: { _height: 150 }, + }, + ], + [ + DocumentType.LINK, + { + layout: { view: LinkBox, dataField: defaultDataKey }, + options: { + childDontRegisterViews: true, + _isLinkButton: true, + _height: 150, + description: '', + showCaption: 'description', + backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area + links: '@links(self)', + _removeDropProperties: new List(['isLinkButton']), + }, + }, + ], + [ + DocumentType.LINKDB, + { + data: new List(), + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { childDropAction: 'alias', title: 'Global Link Database' }, + }, + ], + [ + DocumentType.SCRIPTDB, + { + data: new List(), + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { childDropAction: 'alias', title: 'Global Script Database' }, + }, + ], + [ + DocumentType.SCRIPTING, + { + layout: { view: ScriptingBox, dataField: defaultDataKey }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.YOUTUBE, + { + layout: { view: YoutubeBox, dataField: defaultDataKey }, + }, + ], + [ + DocumentType.LABEL, + { + layout: { view: LabelBox, dataField: defaultDataKey }, + options: { links: '@links(self)', _singleLine: true }, + }, + ], + [ + DocumentType.EQUATION, + { + layout: { view: EquationBox, dataField: defaultDataKey }, + options: { links: '@links(self)', nativeDimModifiable: true, hideResizeHandles: true, hideDecorationTitle: true }, + }, + ], + [ + DocumentType.FUNCPLOT, + { + layout: { view: FunctionPlotBox, dataField: defaultDataKey }, + options: { nativeDimModifiable: true, links: '@links(self)' }, + }, + ], + [ + DocumentType.BUTTON, + { + layout: { view: LabelBox, dataField: 'onClick' }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.SLIDER, + { + layout: { view: SliderBox, dataField: defaultDataKey }, + options: { links: '@links(self)', treeViewGrowsHorizontally: true }, + }, + ], + [ + DocumentType.PRES, + { + layout: { view: PresBox, dataField: defaultDataKey }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.FONTICON, + { + layout: { view: FontIconBox, dataField: defaultDataKey }, + options: { hideLinkButton: true, _width: 40, _height: 40, borderRounding: '100%', links: '@links(self)' }, + }, + ], + [ + DocumentType.WEBCAM, + { + layout: { view: RecordingBox, dataField: defaultDataKey }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.PRESELEMENT, + { + layout: { view: PresElementBox, dataField: defaultDataKey }, + }, + ], + [ + DocumentType.MARKER, + { + layout: { view: CollectionView, dataField: defaultDataKey }, + options: { links: '@links(self)', hideLinkButton: true, pointerEvents: 'none' }, + }, + ], + [ + DocumentType.INK, + { + // NOTE: this is unused!! ink fields are filled in directly within the InkDocument() method + layout: { view: InkingStroke, dataField: defaultDataKey }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.SCREENSHOT, + { + layout: { view: ScreenshotBox, dataField: defaultDataKey }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.COMPARISON, + { + layout: { view: ComparisonBox, dataField: defaultDataKey }, + options: { clipWidth: 50, nativeDimModifiable: true, backgroundColor: 'gray', targetDropAction: 'alias', links: '@links(self)' }, + }, + ], + [ + DocumentType.GROUPDB, + { + data: new List(), + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { childDropAction: 'alias', title: 'Global Group Database' }, + }, + ], + [ + DocumentType.GROUP, + { + layout: { view: EmptyBox, dataField: defaultDataKey }, + options: { links: '@links(self)' }, + }, + ], + [ + DocumentType.DATAVIZ, + { + layout: { view: DataVizBox, dataField: defaultDataKey }, + options: { _fitWidth: true, nativeDimModifiable: true, links: '@links(self)' }, + }, + ], ]); - const suffix = "Proto"; + const suffix = 'Proto'; /** * This function loads or initializes the prototype for each docment type. - * + * * This is an asynchronous function because it has to attempt * to fetch the prototype documents from the server. - * + * * Once we have this object that maps the prototype ids to a potentially * undefined document, we either initialize our private prototype * variables with the document returned from the server or, if prototypes @@ -526,13 +673,15 @@ export namespace Docs { ProxyField.initPlugin(); ComputedField.initPlugin(); // non-guid string ids for each document prototype - const prototypeIds = Object.values(DocumentType).filter(type => type !== DocumentType.NONE).map(type => type + suffix); + const prototypeIds = Object.values(DocumentType) + .filter(type => type !== DocumentType.NONE) + .map(type => type + suffix); // fetch the actual prototype documents from the server const actualProtos = Docs.newAccount ? {} : await DocServer.GetRefFields(prototypeIds); // update this object to include any default values: DocumentOptions for all prototypes prototypeIds.map(id => { const existing = actualProtos[id] as Doc; - const type = id.replace(suffix, "") as DocumentType; + const type = id.replace(suffix, '') as DocumentType; // get or create prototype of the specified type... const target = existing || buildPrototype(type, id); // ...and set it if not undefined (can be undefined only if TemplateMap does not contain @@ -545,30 +694,38 @@ export namespace Docs { * Retrieves the prototype for the given document type, or * undefined if that type's proto doesn't have a configuration * in the template map. - * @param type + * @param type */ const PrototypeMap: PrototypeMap = new Map(); - export function get(type: DocumentType): Doc { return PrototypeMap.get(type)!; } + export function get(type: DocumentType): Doc { + return PrototypeMap.get(type)!; + } /** * A collection of all links in the database. Ideally, this would be a search, but for now all links are cached here. */ - export function MainLinkDocument() { return Prototypes.get(DocumentType.LINKDB); } + export function MainLinkDocument() { + return Prototypes.get(DocumentType.LINKDB); + } /** * A collection of all scripts in the database */ - export function MainScriptDocument() { return Prototypes.get(DocumentType.SCRIPTDB); } + export function MainScriptDocument() { + return Prototypes.get(DocumentType.SCRIPTDB); + } /** * A collection of all user acl groups in the database */ - export function MainGroupDocument() { return Prototypes.get(DocumentType.GROUPDB); } + export function MainGroupDocument() { + return Prototypes.get(DocumentType.GROUPDB); + } /** * This is a convenience method that is used to initialize * prototype documents for the first time. - * + * * @param protoId the id of the prototype, indicating the specific prototype * to initialize (see the *protoId list at the top of the namespace) * @param title the prototype document's title, follows *-PROTO @@ -590,11 +747,21 @@ export namespace Docs { // synthesize the default options, the type and title from computed values and // whatever options pertain to this specific prototype const options: DocumentOptions = { - system: true, _layoutKey: "layout", title, type, baseProto: true, x: 0, y: 0, _width: 300, ...(template.options || {}), - layout: layout.view?.LayoutString(layout.dataField), data: template.data, layout_keyValue: KeyValueBox.LayoutString("") + system: true, + _layoutKey: 'layout', + title, + type, + baseProto: true, + x: 0, + y: 0, + _width: 300, + ...(template.options || {}), + layout: layout.view?.LayoutString(layout.dataField), + data: template.data, + layout_keyValue: KeyValueBox.LayoutString(''), }; Object.entries(options).map(pair => { - if (typeof pair[1] === "string" && pair[1].startsWith("@")) { + if (typeof pair[1] === 'string' && pair[1].startsWith('@')) { (options as any)[pair[0]] = ComputedField.MakeFunction(pair[1].substring(1)); } }); @@ -607,16 +774,15 @@ export namespace Docs { * delegated from top-level prototypes */ export namespace Create { - /** * This function receives the relevant document prototype and uses * it to create a new of that base-level prototype, or the - * underlying data document, which it then delegates again + * underlying data document, which it then delegates again * to create the view document. - * + * * It also takes the opportunity to register the user * that created the document and the time of creation. - * + * * @param proto the specific document prototype off of which to model * this new instance (textProto, imageProto, etc.) * @param data the Field to store at this new instance's data key @@ -626,45 +792,48 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) { - const viewKeys = ["x", "y", "system"]; // keys that should be addded to the view document even though they don't begin with an "_" - const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, "^_"); + function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = 'data', protoId?: string) { + const viewKeys = ['x', 'y', 'system']; // keys that should be addded to the view document even though they don't begin with an "_" + const { omit: dataProps, extract: viewProps } = OmitKeys(options, viewKeys, '^_'); - dataProps["acl-Override"] = "None"; - dataProps["acl-Public"] = options["acl-Public"] ? options["acl-Public"] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + dataProps['acl-Override'] = 'None'; + dataProps['acl-Public'] = options['acl-Public'] ? options['acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; dataProps.system = viewProps.system; dataProps.isPrototype = true; dataProps.author = Doc.CurrentUserEmail; - dataProps.creationDate = new DateField; - dataProps[`${fieldKey}-lastModified`] = new DateField; + dataProps.creationDate = new DateField(); + dataProps[`${fieldKey}-lastModified`] = new DateField(); dataProps[fieldKey] = data; // so that the list of annotations is already initialised, prevents issues in addonly. // without this, if a doc has no annotations but the user has AddOnly privileges, they won't be able to add an annotation because they would have needed to create the field's list which they don't have permissions to do. - dataProps[fieldKey + "-annotations"] = new List(); - dataProps[fieldKey + "-sidebar"] = new List(); + dataProps[fieldKey + '-annotations'] = new List(); + dataProps[fieldKey + '-sidebar'] = new List(); const dataDoc = Doc.assign(Doc.MakeDelegate(proto, protoId), dataProps, undefined, true); const viewFirstProps: { [id: string]: any } = {}; - viewFirstProps["acl-Public"] = options["_acl-Public"] ? options["_acl-Public"] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - viewFirstProps["acl-Override"] = "None"; + viewFirstProps['acl-Public'] = options['_acl-Public'] ? options['_acl-Public'] : Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + viewFirstProps['acl-Override'] = 'None'; viewFirstProps.author = Doc.CurrentUserEmail; const viewDoc = Doc.assign(Doc.MakeDelegate(dataDoc, delegId), viewFirstProps, true, true); Doc.assign(viewDoc, viewProps, true, true); ![DocumentType.LINK, DocumentType.MARKER, DocumentType.LABEL].includes(viewDoc.type as any) && DocUtils.MakeLinkToActiveAudio(() => viewDoc); - !Doc.IsSystem(dataDoc) && ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) && - !dataDoc.isFolder && !dataProps.annotationOn && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, dataDoc); + !Doc.IsSystem(dataDoc) && + ![DocumentType.MARKER, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(proto.type as any) && + !dataDoc.isFolder && + !dataProps.annotationOn && + Doc.AddDocToList(Doc.MyFileOrphans, undefined, dataDoc); updateCachedAcls(dataDoc); updateCachedAcls(viewDoc); return viewDoc; } - export function ImageDocument(url: string|ImageField, options: DocumentOptions = {}) { - const imgField = url instanceof ImageField ? url : new ImageField(url); + export function ImageDocument(url: string | ImageField, options: DocumentOptions = {}) { + const imgField = url instanceof ImageField ? url : new ImageField(url); return InstanceFromProto(Prototypes.get(DocumentType.IMG), imgField, { title: basename(imgField.url.href), ...options }); } @@ -672,9 +841,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.PRES), new List(), options); } - export function ScriptingDocument(script: Opt|null, options: DocumentOptions = {}, fieldKey?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script: undefined, - { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); + export function ScriptingDocument(script: Opt | null, options: DocumentOptions = {}, fieldKey?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script ? script : undefined, { ...options, layout: fieldKey ? ScriptingBox.LayoutString(fieldKey) : undefined }); } export function VideoDocument(url: string, options: DocumentOptions = {}) { @@ -686,24 +854,23 @@ export namespace Docs { } export function WebCamDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), "", options); + return InstanceFromProto(Prototypes.get(DocumentType.WEBCAM), '', options); } export function ScreenshotDocument(options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), "", options); + return InstanceFromProto(Prototypes.get(DocumentType.SCREENSHOT), '', options); } - export function ComparisonDocument(options: DocumentOptions = { title: "Comparison Box" }) { - return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), "", options); + export function ComparisonDocument(options: DocumentOptions = { title: 'Comparison Box' }) { + return InstanceFromProto(Prototypes.get(DocumentType.COMPARISON), '', options); } export function AudioDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), - { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }); + return InstanceFromProto(Prototypes.get(DocumentType.AUDIO), new AudioField(url), { ...options, backgroundColor: ComputedField.MakeFunction("this._mediaState === 'playing' ? 'green':'gray'") as any }); } export function RecordingDocument(url: string, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.REC), "", options); + return InstanceFromProto(Prototypes.get(DocumentType.REC), '', options); } export function SearchDocument(options: DocumentOptions = {}) { @@ -711,34 +878,46 @@ export namespace Docs { } export function ColorDocument(options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.COLOR), "", options); + return InstanceFromProto(Prototypes.get(DocumentType.COLOR), '', options); } - export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = "text") { + export function RTFDocument(field: RichTextField, options: DocumentOptions = {}, fieldKey: string = 'text') { return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } - export function TextDocument(text: string, options: DocumentOptions = {}, fieldKey: string = "text") { + export function TextDocument(text: string, options: DocumentOptions = {}, fieldKey: string = 'text') { const rtf = { doc: { - type: "doc", content: [{ - type: "paragraph", - content: [{ - type: "text", - text - }] - }] + type: 'doc', + content: [ + { + type: 'paragraph', + content: [ + { + type: 'text', + text, + }, + ], + }, + ], }, - selection: { type: "text", anchor: 1, head: 1 }, - storedMarks: [] + selection: { type: 'text', anchor: 1, head: 1 }, + storedMarks: [], }; const field = text ? new RichTextField(JSON.stringify(rtf), text) : undefined; return InstanceFromProto(Prototypes.get(DocumentType.RTF), field, options, undefined, fieldKey); } - export function LinkDocument(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, options: DocumentOptions = {}, id?: string) { - const linkDoc = InstanceFromProto(Prototypes.get(DocumentType.LINK), undefined, { - anchor1: source.doc, anchor2: target.doc, ...options - }, id); + export function LinkDocument(source: { doc: Doc; ctx?: Doc }, target: { doc: Doc; ctx?: Doc }, options: DocumentOptions = {}, id?: string) { + const linkDoc = InstanceFromProto( + Prototypes.get(DocumentType.LINK), + undefined, + { + anchor1: source.doc, + anchor2: target.doc, + ...options, + }, + id + ); LinkManager.Instance.addLink(linkDoc); @@ -749,9 +928,9 @@ export namespace Docs { const I = new Doc(); I[Initializing] = true; I.type = DocumentType.INK; - I.layout = InkingStroke.LayoutString("data"); + I.layout = InkingStroke.LayoutString('data'); I.color = color; - I.hideDecorationTitle = true; // don't show title when selected + I.hideDecorationTitle = true; // don't show title when selected // I.hideOpenButton = true; // don't show open full screen button when selected I.fillColor = fillColor; I.strokeWidth = strokeWidth; @@ -760,20 +939,20 @@ export namespace Docs { I.strokeEndMarker = arrowEnd; I.strokeDash = dash; I.tool = tool; - I["text-align"] = "center"; - I.title = "ink"; + I['text-align'] = 'center'; + I.title = 'ink'; I.x = options.x as number; I.y = options.y as number; I._width = options._width as number; I._height = options._height as number; - I._fontFamily = "cursive"; + I._fontFamily = 'cursive'; I.author = Doc.CurrentUserEmail; I.rotation = 0; I.data = new InkField(points); - I.creationDate = new DateField; - I["acl-Public"] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - I["acl-Override"] = "None"; - I.links = ComputedField.MakeFunction("links(self)"); + I.creationDate = new DateField(); + I['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; + I['acl-Override'] = 'None'; + I.links = ComputedField.MakeFunction('links(self)'); I[Initializing] = false; return I; } @@ -783,7 +962,7 @@ export namespace Docs { const height = options._height || undefined; const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; - if (!nhght && width && height && nwid) options._nativeHeight = Number(nwid) * Number(height) / Number(width); + if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width); return InstanceFromProto(Prototypes.get(DocumentType.PDF), new PdfField(url), options); } @@ -792,8 +971,8 @@ export namespace Docs { const height = options._height || undefined; const nwid = options._nativeWidth || undefined; const nhght = options._nativeHeight || undefined; - if (!nhght && width && height && nwid) options._nativeHeight = Number(nwid) * Number(height) / Number(width); - return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : "http://www.bing.com/"), options); + if (!nhght && width && height && nwid) options._nativeHeight = (Number(nwid) * Number(height)) / Number(width); + return InstanceFromProto(Prototypes.get(DocumentType.WEB), new WebField(url ? url : 'http://www.bing.com/'), options); } export function HtmlDocument(html: string, options: DocumentOptions = {}) { @@ -809,12 +988,12 @@ export namespace Docs { } export function KVPDocument(document: Doc, options: DocumentOptions = {}) { - return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + ".kvp", ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.KVP), document, { title: document.title + '.kvp', ...options }); } export function FreeformDocument(documents: Array, options: DocumentOptions, id?: string) { const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _xPadding: 20, _yPadding: 20, ...options, _viewType: CollectionViewType.Freeform }, id); - documents.map(d => d.context = inst); + documents.map(d => (d.context = inst)); return inst; } @@ -831,7 +1010,7 @@ export namespace Docs { } export function PileDocument(documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: "visible", _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _overflow: 'visible', _forceActive: true, _noAutoscroll: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array, options: DocumentOptions, id?: string) { @@ -886,7 +1065,7 @@ export namespace Docs { } export function ButtonDocument(options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), "onClick-rawScript": "-script-" }); + return InstanceFromProto(Prototypes.get(DocumentType.BUTTON), undefined, { ...(options || {}), 'onClick-rawScript': '-script-' }); } export function SliderDocument(options?: DocumentOptions) { @@ -905,11 +1084,11 @@ export namespace Docs { } export function DataVizDocument(url: string, options?: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: "Data Viz", ...options }); + return InstanceFromProto(Prototypes.get(DocumentType.DATAVIZ), new CsvField(url), { title: 'Data Viz', ...options }); } export function DockDocument(documents: Array, config: string, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: 'remove|add', ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { @@ -917,23 +1096,26 @@ export namespace Docs { } export type DocConfig = { - doc: Doc, - initialWidth?: number, - path?: Doc[] + doc: Doc; + initialWidth?: number; + path?: Doc[]; }; - export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = "row") { + export function StandardCollectionDockingDocument(configs: Array, options: DocumentOptions, id?: string, type: string = 'row') { const layoutConfig = { content: [ { type: type, - content: [ - ...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth)) - ] - } - ] + content: [...configs.map(config => CollectionDockingView.makeDocumentConfig(config.doc, undefined, config.initialWidth))], + }, + ], }; - return DockDocument(configs.map(c => c.doc), JSON.stringify(layoutConfig), options, id); + return DockDocument( + configs.map(c => c.doc), + JSON.stringify(layoutConfig), + options, + id + ); } export function DelegateDocument(proto: Doc, options: DocumentOptions = {}) { @@ -944,9 +1126,9 @@ export namespace Docs { export namespace DocUtils { export function Excluded(d: Doc, docFilters: string[]) { - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields + const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields docFilters.forEach(filter => { - const fields = filter.split(":"); + const fields = filter.split(':'); const key = fields[0]; const value = fields[1]; const modifiers = fields[2]; @@ -959,7 +1141,7 @@ export namespace DocUtils { if (d.z) return false; for (const facetKey of Object.keys(filterFacets)) { const facet = filterFacets[facetKey]; - const xs = Object.keys(facet).filter(value => facet[value] === "x"); + const xs = Object.keys(facet).filter(value => facet[value] === 'x'); const failsNotEqualFacets = xs?.some(value => Doc.matchFieldValue(d, facetKey, value)); if (failsNotEqualFacets) { return true; @@ -968,21 +1150,21 @@ export namespace DocUtils { return false; } /** - * @param docs - * @param docFilters - * @param docRangeFilters - * @param viewSpecScript - * Given a list of docs and docFilters, @returns the list of Docs that match those filters + * @param docs + * @param docFilters + * @param docRangeFilters + * @param viewSpecScript + * Given a list of docs and docFilters, @returns the list of Docs that match those filters */ export function FilterDocs(docs: Doc[], docFilters: string[], docRangeFilters: string[], viewSpecScript?: ScriptField, parentCollection?: Doc) { const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; if (!docFilters?.length && !docRangeFilters?.length) { - return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one + return childDocs.filter(d => !d.cookies); // remove documents that need a cookie if there are no filters to provide one } - const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields + const filterFacets: { [key: string]: { [value: string]: string } } = {}; // maps each filter key to an object with value=>modifier fields docFilters.forEach(filter => { - const fields = filter.split(":"); + const fields = filter.split(':'); const key = fields[0]; const value = fields[1]; const modifiers = fields[2]; @@ -992,63 +1174,67 @@ export namespace DocUtils { filterFacets[key][value] = modifiers; }); - const filteredDocs = docFilters.length ? childDocs.filter(d => { - if (d.z) return true; - // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter - if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { - return false; - } - - for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== "cookies")) { - const facet = filterFacets[facetKey]; - - // facets that match some value in the field of the document (e.g. some text field) - const matches = Object.keys(facet).filter(value => value !== "cookies" && facet[value] === "match"); - - // facets that have a check next to them - const checks = Object.keys(facet).filter(value => facet[value] === "check"); - - // metadata facets that exist - const exists = Object.keys(facet).filter(value => facet[value] === "exists"); - - // metadata facets that exist - const unsets = Object.keys(facet).filter(value => facet[value] === "unset"); - - // facets that have an x next to them - const xs = Object.keys(facet).filter(value => facet[value] === "x"); - - if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; - const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value)); - const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value)); - const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined); - const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined); - const satisfiesMatchFacets = !matches.length ? true : matches.some(value => { - if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc - const allKeys = Array.from(Object.keys(d)); - allKeys.push(...Object.keys(Doc.GetProto(d))); - const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); - return keys.some(key => Field.toString(d[key] as Field).includes(value)); - } - return Field.toString(d[facetKey] as Field).includes(value); - }); - // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria - if ((parentCollection?.currentFilter as Doc)?.filterBoolean === "OR") { - if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; - } - // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria - else { - if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; - } - - } - return (parentCollection?.currentFilter as Doc)?.filterBoolean === "OR" ? false : true; - }) : childDocs; + const filteredDocs = docFilters.length + ? childDocs.filter(d => { + if (d.z) return true; + // if the document needs a cookie but no filter provides the cookie, then the document does not pass the filter + if (d.cookies && (!filterFacets.cookies || !Object.keys(filterFacets.cookies).some(key => d.cookies === key))) { + return false; + } + + for (const facetKey of Object.keys(filterFacets).filter(fkey => fkey !== 'cookies')) { + const facet = filterFacets[facetKey]; + + // facets that match some value in the field of the document (e.g. some text field) + const matches = Object.keys(facet).filter(value => value !== 'cookies' && facet[value] === 'match'); + + // facets that have a check next to them + const checks = Object.keys(facet).filter(value => facet[value] === 'check'); + + // metadata facets that exist + const exists = Object.keys(facet).filter(value => facet[value] === 'exists'); + + // metadata facets that exist + const unsets = Object.keys(facet).filter(value => facet[value] === 'unset'); + + // facets that have an x next to them + const xs = Object.keys(facet).filter(value => facet[value] === 'x'); + + if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; + const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value)); + const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value)); + const satisfiesExistsFacets = !exists.length ? true : exists.some(value => d[facetKey] !== undefined); + const satisfiesUnsetsFacets = !unsets.length ? true : unsets.some(value => d[facetKey] === undefined); + const satisfiesMatchFacets = !matches.length + ? true + : matches.some(value => { + if (facetKey.startsWith('*')) { + // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc + const allKeys = Array.from(Object.keys(d)); + allKeys.push(...Object.keys(Doc.GetProto(d))); + const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); + return keys.some(key => Field.toString(d[key] as Field).includes(value)); + } + return Field.toString(d[facetKey] as Field).includes(value); + }); + // if we're ORing them together, the default return is false, and we return true for a doc if it satisfies any one set of criteria + if ((parentCollection?.currentFilter as Doc)?.filterBoolean === 'OR') { + if (satisfiesUnsetsFacets && satisfiesExistsFacets && satisfiesCheckFacets && !failsNotEqualFacets && satisfiesMatchFacets) return true; + } + // if we're ANDing them together, the default return is true, and we return false for a doc if it doesn't satisfy any set of criteria + else { + if (!satisfiesUnsetsFacets || !satisfiesExistsFacets || !satisfiesCheckFacets || failsNotEqualFacets || (matches.length && !satisfiesMatchFacets)) return false; + } + } + return (parentCollection?.currentFilter as Doc)?.filterBoolean === 'OR' ? false : true; + }) + : childDocs; const rangeFilteredDocs = filteredDocs.filter(d => { for (let i = 0; i < docRangeFilters.length; i += 3) { const key = docRangeFilters[i]; const min = Number(docRangeFilters[i + 1]); const max = Number(docRangeFilters[i + 2]); - const val = typeof d[key] === "string" ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], "number", null); + const val = typeof d[key] === 'string' ? (Number(StrCast(d[key])).toString() === StrCast(d[key]) ? Number(StrCast(d[key])) : undefined) : Cast(d[key], 'number', null); if (val === undefined) { //console.log("Should 'undefined' pass range filter or not?") } else if (val < min || val > max) return false; @@ -1059,7 +1245,7 @@ export namespace DocUtils { } export function Publish(promoteDoc: Doc, targetID: string, addDoc: any, remDoc: any) { - targetID = targetID.replace(/^-/, "").replace(/\([0-9]*\)$/, ""); + targetID = targetID.replace(/^-/, '').replace(/\([0-9]*\)$/, ''); DocServer.GetRefField(targetID).then(doc => { if (promoteDoc !== doc) { let copy = doc as Doc; @@ -1073,16 +1259,17 @@ export namespace DocUtils { remDoc && remDoc(promoteDoc); if (!doc) { DocListCastAsync(promoteDoc.links).then(links => { - links && links.map(async link => { - if (link) { - const a1 = await Cast(link.anchor1, Doc); - if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy; - const a2 = await Cast(link.anchor2, Doc); - if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy; - LinkManager.Instance.deleteLink(link); - LinkManager.Instance.addLink(link); - } - }); + links && + links.map(async link => { + if (link) { + const a1 = await Cast(link.anchor1, Doc); + if (a1 && Doc.AreProtosEqual(a1, promoteDoc)) link.anchor1 = copy; + const a2 = await Cast(link.anchor2, Doc); + if (a2 && Doc.AreProtosEqual(a2, promoteDoc)) link.anchor2 = copy; + LinkManager.Instance.deleteLink(link); + LinkManager.Instance.addLink(link); + } + }); }); } } @@ -1093,19 +1280,19 @@ export namespace DocUtils { options?.afterFocus?.(false); } - export let ActiveRecordings: { props: FieldViewProps, getAnchor: () => Doc }[] = []; + export let ActiveRecordings: { props: FieldViewProps; getAnchor: () => Doc }[] = []; export function MakeLinkToActiveAudio(getSourceDoc: () => Doc, broadcastEvent = true) { - broadcastEvent && runInAction(() => DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1); + broadcastEvent && runInAction(() => (DocumentManager.Instance.RecordingEvent = DocumentManager.Instance.RecordingEvent + 1)); return DocUtils.ActiveRecordings.map(audio => { - const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, "recording annotation:linked recording", "recording timeline"); - link && (link.followLinkLocation = "add:right"); + const link = DocUtils.MakeLink({ doc: getSourceDoc() }, { doc: audio.getAnchor() || audio.props.Document }, 'recording annotation:linked recording', 'recording timeline'); + link && (link.followLinkLocation = 'add:right'); return link; }); } - export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { - if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? "Commentary:Comments On" : "link"; + export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = '', description: string = '', id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) { + if (!linkRelationship) linkRelationship = target.doc.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; const sv = DocumentManager.Instance.getDocumentView(source.doc); if (!allowParCollectionLink && sv?.props.ContainingCollectionDoc === target.doc) return; if (target.doc === Doc.UserDoc()) return undefined; @@ -1114,7 +1301,7 @@ export namespace DocUtils { if (showPopup) { LinkManager.currentLink = linkDoc; - TaskCompletionBox.textDisplayed = "Link Created"; + TaskCompletionBox.textDisplayed = 'Link Created'; TaskCompletionBox.popupX = showPopup[0]; TaskCompletionBox.popupY = showPopup[1] - 33; TaskCompletionBox.taskCompleted = true; @@ -1133,25 +1320,75 @@ export namespace DocUtils { TaskCompletionBox.popupY -= 40; } - setTimeout(action(() => TaskCompletionBox.taskCompleted = false), 2500); + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2500 + ); } return linkDoc; }); - return makeLink(Docs.Create.LinkDocument(source, target, { - title: ComputedField.MakeFunction("generateLinkTitle(self)") as any, - "anchor1-useLinkSmallAnchor": source.doc.useLinkSmallAnchor ? true : undefined, - "anchor2-useLinkSmallAnchor": target.doc.useLinkSmallAnchor ? true : undefined, - "acl-Public": SharingPermissions.Augment, - "_acl-Public": SharingPermissions.Augment, - linkDisplay: true, - _hidden: true, - _linkAutoMove: true, - linkRelationship, - _showCaption: "description", - _showTitle: "linkRelationship", - description - }, id), showPopup); + return makeLink( + Docs.Create.LinkDocument( + source, + target, + { + title: ComputedField.MakeFunction('generateLinkTitle(self)') as any, + 'anchor1-useLinkSmallAnchor': source.doc.useLinkSmallAnchor ? true : undefined, + 'anchor2-useLinkSmallAnchor': target.doc.useLinkSmallAnchor ? true : undefined, + 'acl-Public': SharingPermissions.Augment, + '_acl-Public': SharingPermissions.Augment, + linkDisplay: true, + _hidden: true, + _linkAutoMove: true, + linkRelationship, + _showCaption: 'description', + _showTitle: 'linkRelationship', + description, + }, + id + ), + showPopup + ); + } + + export function AssignScripts(doc: Doc, scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { + scripts && + Object.keys(scripts).map(key => { + if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { + doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value: 'any', scriptContext: 'any', documentView: Doc.name }, { _readOnly_: true }); + } + }); + funcs && + Object.keys(funcs).map(key => { + const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); + if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) { + doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, { _readOnly_: true }); + } + }); + return doc; + } + export function AssignOpts(doc: Doc | undefined, reqdOpts: DocumentOptions, items?: Doc[]) { + if (doc) { + const compareValues = (val1: any, val2: any) => { + if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) { + return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v)); + } + return val1 === val2; + }; + Object.entries(reqdOpts).forEach(pair => { + const targetDoc = pair[0].startsWith('_') ? doc : Doc.GetProto(doc as Doc); + if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/, '')) || !compareValues(pair[1], targetDoc[pair[0]])) { + targetDoc[pair[0]] = pair[1]; + } + }); + items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item)); + items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item)); + } + return doc; + } + export function AssignDocField(doc: Doc, field: string, creator: (reqdOpts: DocumentOptions, items?: Doc[]) => Doc, reqdOpts: DocumentOptions, items?: Doc[], scripts?: { [key: string]: string }, funcs?: { [key: string]: string }) { + return DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); } export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { @@ -1160,24 +1397,24 @@ export namespace DocUtils { const field = target[fieldKey]; const resolved = options || {}; if (field instanceof ImageField) { - created = Docs.Create.ImageDocument((field).url.href, resolved); + created = Docs.Create.ImageDocument(field.url.href, resolved); layout = ImageBox.LayoutString; } else if (field instanceof Doc) { created = field; } else if (field instanceof VideoField) { - created = Docs.Create.VideoDocument((field).url.href, resolved); + created = Docs.Create.VideoDocument(field.url.href, resolved); layout = VideoBox.LayoutString; } else if (field instanceof PdfField) { - created = Docs.Create.PdfDocument((field).url.href, resolved); + created = Docs.Create.PdfDocument(field.url.href, resolved); layout = PDFBox.LayoutString; } else if (field instanceof AudioField) { - created = Docs.Create.AudioDocument((field).url.href, resolved); + created = Docs.Create.AudioDocument(field.url.href, resolved); layout = AudioBox.LayoutString; } else if (field instanceof RecordingField) { - created = Docs.Create.RecordingDocument((field).url.href, resolved); + created = Docs.Create.RecordingDocument(field.url.href, resolved); layout = RecordingBox.LayoutString; } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(ActiveInkColor(), CurrentUserUtils.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), (field).inkData, resolved); + created = Docs.Create.InkDocument(ActiveInkColor(), Doc.ActiveTool, ActiveInkWidth(), ActiveInkBezierApprox(), ActiveFillColor(), ActiveArrowStart(), ActiveArrowEnd(), ActiveDash(), field.inkData, resolved); layout = InkingStroke.LayoutString; } else if (field instanceof List && field[0] instanceof Doc) { created = Docs.Create.StackingDocument(DocListCast(field), resolved); @@ -1185,9 +1422,8 @@ export namespace DocUtils { } else if (field instanceof MapField) { created = Docs.Create.MapDocument(DocListCast(field), resolved); layout = MapBox.LayoutString; - } - else { - created = Docs.Create.TextDocument("", { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved }); + } else { + created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _autoHeight: true }, ...resolved }); layout = FormattedTextBox.LayoutString; } if (created) { @@ -1199,28 +1435,28 @@ export namespace DocUtils { } export async function DocumentFromType(type: string, path: string, options: DocumentOptions): Promise> { - let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise)) | undefined = undefined; - if (type.indexOf("image") !== -1) { + let ctor: ((path: string, options: DocumentOptions) => Doc | Promise) | undefined = undefined; + if (type.indexOf('image') !== -1) { ctor = Docs.Create.ImageDocument; if (!options._width) options._width = 300; } - if (type.indexOf("video") !== -1) { + if (type.indexOf('video') !== -1) { ctor = Docs.Create.VideoDocument; if (!options._width) options._width = 600; - if (!options._height) options._height = (options._width as number) * 2 / 3; + if (!options._height) options._height = ((options._width as number) * 2) / 3; } - if (type.indexOf("audio") !== -1) { + if (type.indexOf('audio') !== -1) { ctor = Docs.Create.AudioDocument; } - if (type.indexOf("pdf") !== -1) { + if (type.indexOf('pdf') !== -1) { ctor = Docs.Create.PdfDocument; if (!options._width) options._width = 400; - if (!options._height) options._height = (options._width as number) * 1200 / 927; + if (!options._height) options._height = ((options._width as number) * 1200) / 927; } - if (type.indexOf("csv") !== -1) { + if (type.indexOf('csv') !== -1) { ctor = Docs.Create.DataVizDocument; if (!options._width) options._width = 400; - if (!options._height) options._height = (options._width as number) * 1200 / 927; + if (!options._height) options._height = ((options._width as number) * 1200) / 927; } //TODO:al+glr // if (type.indexOf("map") !== -1) { @@ -1228,15 +1464,15 @@ export namespace DocUtils { // if (!options._width) options._width = 800; // if (!options._height) options._height = (options._width as number) * 3 / 4; // } - if (type.indexOf("html") !== -1) { + if (type.indexOf('html') !== -1) { if (path.includes(window.location.hostname)) { const s = path.split('/'); const id = s[s.length - 1]; return DocServer.GetRefField(id).then(field => { if (field instanceof Doc) { const alias = Doc.MakeAlias(field); - alias.x = options.x as number || 0; - alias.y = options.y as number || 0; + alias.x = (options.x as number) || 0; + alias.y = (options.y as number) || 0; alias._width = (options._width as number) || 300; alias._height = (options._height as number) || (options._width as number) || 300; return alias; @@ -1245,55 +1481,63 @@ export namespace DocUtils { }); } ctor = Docs.Create.WebDocument; - options = { ...options, _width: 400, _height: 512, title: path, }; + options = { ...options, _width: 400, _height: 512, title: path }; } - + return ctor ? ctor(path, options) : undefined; } export function addDocumentCreatorMenuItems(docTextAdder: (d: Doc) => void, docAdder: (d: Doc) => void, x: number, y: number, simpleMenu: boolean = false): void { - !simpleMenu && ContextMenu.Instance.addItem({ - description: "Quick Notes", - subitems: DocListCast((Doc.UserDoc()["template-notes"] as Doc).data).map((note, i) => ({ - description: ":" + StrCast(note.title), - event: undoBatch((args: { x: number, y: number }) => { - const textDoc = Docs.Create.TextDocument("", { - _width: 200, x, y, _autoHeight: note._autoHeight !== false, - title: StrCast(note.title) + "#" + (note.aliasCount = NumCast(note.aliasCount) + 1) - }); - textDoc.layoutKey = "layout_" + note.title; - textDoc[textDoc.layoutKey] = note; - docTextAdder(textDoc); + !simpleMenu && + ContextMenu.Instance.addItem({ + description: 'Quick Notes', + subitems: DocListCast((Doc.UserDoc()['template-notes'] as Doc).data).map((note, i) => ({ + description: ':' + StrCast(note.title), + event: undoBatch((args: { x: number; y: number }) => { + const textDoc = Docs.Create.TextDocument('', { + _width: 200, + x, + y, + _autoHeight: note._autoHeight !== false, + title: StrCast(note.title) + '#' + (note.aliasCount = NumCast(note.aliasCount) + 1), + }); + textDoc.layoutKey = 'layout_' + note.title; + textDoc[textDoc.layoutKey] = note; + docTextAdder(textDoc); + }), + icon: StrCast(note.icon) as IconProp, + })) as ContextMenuProps[], + icon: 'sticky-note', + }); + const documentList: ContextMenuProps[] = DocListCast(DocListCast(Doc.MyTools?.data)[0]?.data) + .filter(btnDoc => !btnDoc.hidden) + .map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)) + .filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation) + .map((dragDoc, i) => ({ + description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), + event: undoBatch((args: { x: number; y: number }) => { + const newDoc = DocUtils.copyDragFactory(dragDoc); + if (newDoc) { + newDoc.author = Doc.CurrentUserEmail; + newDoc.x = x; + newDoc.y = y; + EquationBox.SelectOnLoad = newDoc[Id]; + if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id]; + docAdder?.(newDoc); + } }), - icon: StrCast(note.icon) as IconProp - })) as ContextMenuProps[], - icon: "sticky-note" - }); - const documentList: ContextMenuProps[] = DocListCast(DocListCast(CurrentUserUtils.MyTools?.data)[0]?.data).filter(btnDoc => !btnDoc.hidden).map(btnDoc => Cast(btnDoc?.dragFactory, Doc, null)).filter(doc => doc && doc !== Doc.UserDoc().emptyPresentation).map((dragDoc, i) => ({ - description: ":" + StrCast(dragDoc.title).replace("Untitled ",""), - event: undoBatch((args: { x: number, y: number }) => { - const newDoc = DocUtils.copyDragFactory(dragDoc); - if (newDoc) { - newDoc.author = Doc.CurrentUserEmail; - newDoc.x = x; - newDoc.y = y; - EquationBox.SelectOnLoad = newDoc[Id]; - if (newDoc.type === DocumentType.RTF) FormattedTextBox.SelectOnLoad = newDoc[Id]; - docAdder?.(newDoc); - } - }), - icon: Doc.toIcon(dragDoc), - })) as ContextMenuProps[]; + icon: Doc.toIcon(dragDoc), + })) as ContextMenuProps[]; ContextMenu.Instance.addItem({ - description: "Create document", + description: 'Create document', subitems: documentList, - icon: "file" + icon: 'file', }); - }// applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) - export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) { - const batch = UndoManager.StartBatch("makeCustomViewClicked"); + } // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) + export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { + const batch = UndoManager.StartBatch('makeCustomViewClicked'); runInAction(() => { - doc.layoutKey = "layout_" + templateSignature; + doc.layoutKey = 'layout_' + templateSignature; createCustomView(doc, creator, templateSignature, docLayoutTemplate); }); batch.end(); @@ -1301,44 +1545,49 @@ export namespace DocUtils { } export function findTemplate(templateName: string, type: string, signature: string) { let docLayoutTemplate: Opt; - const iconViews = DocListCast(Cast(Doc.UserDoc()["template-icons"], Doc, null)?.data); - const templBtns = DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); - const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data); + const iconViews = DocListCast(Cast(Doc.UserDoc()['template-icons'], Doc, null)?.data); + const templBtns = DocListCast(Cast(Doc.UserDoc()['template-buttons'], Doc, null)?.data); + const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data); const clickFuncs = DocListCast(Cast(Doc.UserDoc().clickFuncs, Doc, null)?.data); - const allTemplates = iconViews.concat(templBtns).concat(noteTypes).concat(clickFuncs).map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc).filter(doc => doc.isTemplateDoc); + const allTemplates = iconViews + .concat(templBtns) + .concat(noteTypes) + .concat(clickFuncs) + .map(btnDoc => (btnDoc.dragFactory as Doc) || btnDoc) + .filter(doc => doc.isTemplateDoc); // bcz: this is hacky -- want to have different templates be applied depending on the "type" of a document. but type is not reliable and there could be other types of template searches so this should be generalized // first try to find a template that matches the specific document type (_). otherwise, fallback to a general match on - !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + "_" + type && (docLayoutTemplate = tempDoc)); + !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName + '_' + type && (docLayoutTemplate = tempDoc)); !docLayoutTemplate && allTemplates.forEach(tempDoc => StrCast(tempDoc.title) === templateName && (docLayoutTemplate = tempDoc)); return docLayoutTemplate; } - export function createCustomView(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = "custom", docLayoutTemplate?: Doc) { - const templateName = templateSignature.replace(/\(.*\)/, ""); - docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc._isGroup && doc.transcription ? "transcription" : doc.type), templateSignature); + export function createCustomView(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { + const templateName = templateSignature.replace(/\(.*\)/, ''); + docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc._isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature); - const customName = "layout_" + templateSignature; + const customName = 'layout_' + templateSignature; const _width = NumCast(doc._width); const _height = NumCast(doc._height); - const options = { title: "data", backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: - _height / 2, _showSidebar: false }; + const options = { title: 'data', backgroundColor: StrCast(doc.backgroundColor), _autoHeight: true, _width, x: -_width / 2, y: -_height / 2, _showSidebar: false }; if (docLayoutTemplate) { - if (docLayoutTemplate !== doc[customName]) { + if (docLayoutTemplate !== doc[customName]) { Doc.ApplyTemplateTo(docLayoutTemplate, doc, customName, undefined); } } else { let fieldTemplate: Opt; - if (doc.data instanceof RichTextField || typeof (doc.data) === "string") { - fieldTemplate = Docs.Create.TextDocument("", options); + if (doc.data instanceof RichTextField || typeof doc.data === 'string') { + fieldTemplate = Docs.Create.TextDocument('', options); } else if (doc.data instanceof PdfField) { - fieldTemplate = Docs.Create.PdfDocument("http://www.msn.com", options); + fieldTemplate = Docs.Create.PdfDocument('http://www.msn.com', options); } else if (doc.data instanceof VideoField) { - fieldTemplate = Docs.Create.VideoDocument("http://www.cs.brown.edu", options); + fieldTemplate = Docs.Create.VideoDocument('http://www.cs.brown.edu', options); } else if (doc.data instanceof AudioField) { - fieldTemplate = Docs.Create.AudioDocument("http://www.cs.brown.edu", options); + fieldTemplate = Docs.Create.AudioDocument('http://www.cs.brown.edu', options); } else if (doc.data instanceof ImageField) { - fieldTemplate = Docs.Create.ImageDocument("http://www.cs.brown.edu", options); + fieldTemplate = Docs.Create.ImageDocument('http://www.cs.brown.edu', options); } - const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + "(" + doc.title + ")", isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); + const docTemplate = creator?.(fieldTemplate ? [fieldTemplate] : [], { title: customName + '(' + doc.title + ')', isTemplateDoc: true, _width: _width + 20, _height: Math.max(100, _height + 45) }); fieldTemplate && Doc.MakeMetadataFieldTemplate(fieldTemplate, docTemplate ? Doc.GetProto(docTemplate) : docTemplate); docTemplate && Doc.ApplyTemplateTo(docTemplate, doc, customName, undefined); } @@ -1350,13 +1599,14 @@ export namespace DocUtils { } } export function iconify(doc: Doc) { - const layoutKey = Cast(doc.layoutKey, "string", null); - DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, "icon", undefined); - if (layoutKey && layoutKey !== "layout" && layoutKey !== "layout_icon") doc.deiconifyLayout = layoutKey.replace("layout_", ""); + const layoutKey = Cast(doc.layoutKey, 'string', null); + DocUtils.makeCustomViewClicked(doc, Docs.Create.StackingDocument, 'icon', undefined); + if (layoutKey && layoutKey !== 'layout' && layoutKey !== 'layout_icon') doc.deiconifyLayout = layoutKey.replace('layout_', ''); } export function pileup(docList: Doc[], x?: number, y?: number, size: number = 55, create: boolean = true) { - let w = 0, h = 0; + let w = 0, + h = 0; runInAction(() => { docList.forEach(d => { DocUtils.iconify(d); @@ -1364,19 +1614,23 @@ export namespace DocUtils { h = Math.max(NumCast(d._height), h); }); docList.forEach((d, i) => { - d.x = Math.cos(Math.PI * 2 * i / docList.length) * size; - d.y = Math.sin(Math.PI * 2 * i / docList.length) * size; - d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + d.x = Math.cos((Math.PI * 2 * i) / docList.length) * size; + d.y = Math.sin((Math.PI * 2 * i) / docList.length) * size; + d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection }); - const aggBounds = aggregateBounds(docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })), 0, 0); + const aggBounds = aggregateBounds( + docList.map(d => ({ x: NumCast(d.x), y: NumCast(d.y), width: NumCast(d._width), height: NumCast(d._height) })), + 0, + 0 + ); docList.forEach((d, i) => { - d.x = NumCast(d.x) - ((aggBounds.r + aggBounds.x) / 2); - d.y = NumCast(d.y) - ((aggBounds.b + aggBounds.y) / 2); - d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + d.x = NumCast(d.x) - (aggBounds.r + aggBounds.x) / 2; + d.y = NumCast(d.y) - (aggBounds.b + aggBounds.y) / 2; + d._timecodeToShow = undefined; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection }); }); if (create) { - const newCollection = Docs.Create.PileDocument(docList, { title: "pileup", x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2, }); + const newCollection = Docs.Create.PileDocument(docList, { title: 'pileup', x: (x || 0) - size, y: (y || 0) - size, _width: size * 2, _height: size * 2 }); newCollection.x = NumCast(newCollection.x) + NumCast(newCollection._width) / 2 - size; newCollection.y = NumCast(newCollection.y) + NumCast(newCollection._height) / 2 - size; newCollection._width = newCollection._height = size * 2; @@ -1388,18 +1642,25 @@ export namespace DocUtils { export function LeavePushpin(doc: Doc, annotationField: string) { if (doc.isPushpin) return undefined; const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); - const hasContextAnchor = DocListCast(doc.links). - some(l => - (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || - (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); + const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { const pushpin = Docs.Create.FontIconDocument({ - title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true, - icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), backgroundColor: "#ACCEF7", - _width: 15, _height: 15, _xPadding: 0, _isLinkButton: true, _timecodeToShow: Cast(doc._timecodeToShow, "number", null) + title: 'pushpin', + label: '', + annotationOn: Cast(doc.annotationOn, Doc, null), + isPushpin: true, + icon: 'map-pin', + x: Cast(doc.x, 'number', null), + y: Cast(doc.y, 'number', null), + backgroundColor: '#ACCEF7', + _width: 15, + _height: 15, + _xPadding: 0, + _isLinkButton: true, + _timecodeToShow: Cast(doc._timecodeToShow, 'number', null), }); Doc.AddDocToList(context, annotationField, pushpin); - const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", ""); + const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, 'pushpin', ''); doc._timecodeToShow = undefined; return pushpin; } @@ -1407,7 +1668,7 @@ export namespace DocUtils { } // /** - // * + // * // * @param dms Degree Minute Second format exif gps data // * @param ref ref that determines negativity of decimal coordinates // * @returns a decimal format of gps latitude / longitude @@ -1427,7 +1688,7 @@ export namespace DocUtils { function ConvertDMSToDD(degrees: number, minutes: number, seconds: number, direction: string) { var dd = degrees + minutes / 60 + seconds / (60 * 60); - if (direction === "S" || direction === "W") { + if (direction === 'S' || direction === 'W') { dd = dd * -1; } // Don't do anything for N or E return dd; @@ -1444,15 +1705,18 @@ export namespace DocUtils { if (doc) { const proto = Doc.GetProto(doc); proto.text = result.rawText; - proto.fileUpload = pathname.replace(/.*\//, "").replace("upload_", "").replace(/\.[a-z0-9]*$/, ""); + proto.fileUpload = pathname + .replace(/.*\//, '') + .replace('upload_', '') + .replace(/\.[a-z0-9]*$/, ''); if (Upload.isImageInformation(result)) { const maxNativeDim = Math.min(Math.max(result.nativeHeight, result.nativeWidth), defaultNativeImageDim); - proto["data-nativeOrientation"] = result.exifData?.data?.image?.Orientation ?? ((StrCast((result.exifData?.data as any)?.Orientation).includes("Rotate 90")) ? 5 : undefined); - proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim; - proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); - if (NumCast(proto["data-nativeOrientation"]) >= 5) { - proto["data-nativeHeight"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim * result.nativeWidth / result.nativeHeight : maxNativeDim; - proto["data-nativeWidth"] = (result.nativeWidth < result.nativeHeight) ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + proto['data-nativeOrientation'] = result.exifData?.data?.image?.Orientation ?? (StrCast((result.exifData?.data as any)?.Orientation).includes('Rotate 90') ? 5 : undefined); + proto['data-nativeWidth'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto['data-nativeHeight'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); + if (NumCast(proto['data-nativeOrientation']) >= 5) { + proto['data-nativeHeight'] = result.nativeWidth < result.nativeHeight ? (maxNativeDim * result.nativeWidth) / result.nativeHeight : maxNativeDim; + proto['data-nativeWidth'] = result.nativeWidth < result.nativeHeight ? maxNativeDim : maxNativeDim / (result.nativeWidth / result.nativeHeight); } proto.contentSize = result.contentSize; // exif gps data coordinates are stored in DMS (Degrees Minutes Seconds), the following operation converts that to decimal coordinates @@ -1464,15 +1728,41 @@ export namespace DocUtils { proto.lat = ConvertDMSToDD(latitude[0], latitude[1], latitude[2], latitudeDirection); proto.lng = ConvertDMSToDD(longitude[0], longitude[1], longitude[2], longitudeDirection); } - } generatedDocuments.push(doc); } } + export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { + const tbox = Docs.Create.TextDocument('', { + _xMargin: noMargins ? 0 : undefined, + _yMargin: noMargins ? 0 : undefined, + annotationOn, + docMaxAutoHeight: maxHeight, + backgroundColor: backgroundColor, + _width: width || 200, + _height: 35, + x: x, + y: y, + _fitWidth: true, + _autoHeight: true, + title, + }); + const template = Doc.UserDoc().defaultTextLayout; + if (template instanceof Doc) { + tbox._width = NumCast(template._width); + tbox.layoutKey = 'layout_' + StrCast(template.title); + Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template; + } + return tbox; + } + export async function uploadYoutubeVideo(videoId: string, options: DocumentOptions) { const generatedDocuments: Doc[] = []; - for (const { source: { name, type }, result } of await Networking.UploadYoutubeToServer(videoId)) { + for (const { + source: { name, type }, + result, + } of await Networking.UploadYoutubeToServer(videoId)) { name && type && processFileupload(generatedDocuments, name, type, result, options); } return generatedDocuments; @@ -1480,7 +1770,10 @@ export namespace DocUtils { export async function uploadFilesToDocs(files: File[], options: DocumentOptions) { const generatedDocuments: Doc[] = []; const upfiles = await Networking.UploadFilesToServer(files); - for (const { source: { name, type }, result } of upfiles) { + for (const { + source: { name, type }, + result, + } of upfiles) { name && type && processFileupload(generatedDocuments, name, type, result, options); } return generatedDocuments; @@ -1490,36 +1783,43 @@ export namespace DocUtils { export function copyDragFactory(dragFactory: Doc) { if (!dragFactory) return undefined; const ndoc = dragFactory.isTemplateDoc ? Doc.ApplyTemplate(dragFactory) : Doc.MakeCopy(dragFactory, true); - ndoc && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, "data", Doc.GetProto(ndoc)); - if (ndoc && dragFactory["dragFactory-count"] !== undefined) { - dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; - Doc.SetInPlace(ndoc, "title", ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(), true); + ndoc && Doc.AddDocToList(Doc.MyFileOrphans, 'data', Doc.GetProto(ndoc)); + if (ndoc && dragFactory['dragFactory-count'] !== undefined) { + dragFactory['dragFactory-count'] = NumCast(dragFactory['dragFactory-count']) + 1; + Doc.SetInPlace(ndoc, 'title', ndoc.title + ' ' + NumCast(dragFactory['dragFactory-count']).toString(), true); } - if (ndoc && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, ndoc); + if (ndoc && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, ndoc); return ndoc; } export function delegateDragFactory(dragFactory: Doc) { const ndoc = Doc.MakeDelegateWithProto(dragFactory); - if (ndoc && dragFactory["dragFactory-count"] !== undefined) { - dragFactory["dragFactory-count"] = NumCast(dragFactory["dragFactory-count"]) + 1; - Doc.GetProto(ndoc).title = ndoc.title + " " + NumCast(dragFactory["dragFactory-count"]).toString(); + if (ndoc && dragFactory['dragFactory-count'] !== undefined) { + dragFactory['dragFactory-count'] = NumCast(dragFactory['dragFactory-count']) + 1; + Doc.GetProto(ndoc).title = ndoc.title + ' ' + NumCast(dragFactory['dragFactory-count']).toString(); } return ndoc; } } -ScriptingGlobals.add("Docs", Docs); -ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { return DocUtils.copyDragFactory(dragFactory); }); -ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { return DocUtils.delegateDragFactory(dragFactory); }); -ScriptingGlobals.add(function makeDelegate(proto: any) { const d = Docs.Create.DelegateDocument(proto, { title: "child of " + proto.title }); return d; }); +ScriptingGlobals.add('Docs', Docs); +ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc) { + return DocUtils.copyDragFactory(dragFactory); +}); +ScriptingGlobals.add(function delegateDragFactory(dragFactory: Doc) { + return DocUtils.delegateDragFactory(dragFactory); +}); +ScriptingGlobals.add(function makeDelegate(proto: any) { + const d = Docs.Create.DelegateDocument(proto, { title: 'child of ' + proto.title }); + return d; +}); ScriptingGlobals.add(function generateLinkTitle(self: Doc) { - const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : ""; - const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : ""; - const relation = self.linkRelationship || "to"; + const anchor1title = self.anchor1 && self.anchor1 !== self ? Cast(self.anchor1, Doc, null).title : ''; + const anchor2title = self.anchor2 && self.anchor2 !== self ? Cast(self.anchor2, Doc, null).title : ''; + const relation = self.linkRelationship || 'to'; return `${anchor1title} (${relation}) ${anchor2title}`; }); ScriptingGlobals.add(function openTabAlias(tab: Doc) { - CollectionDockingView.AddSplit(Doc.MakeAlias(tab), "right"); -}); \ No newline at end of file + CollectionDockingView.AddSplit(Doc.MakeAlias(tab), 'right'); +}); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 84efcb966..02d43088d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -2,7 +2,6 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; -import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; import { RichTextField } from "../../fields/RichTextField"; @@ -14,11 +13,12 @@ import { SharingPermissions } from "../../fields/util"; import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; -import { DocumentType } from "../documents/DocumentTypes"; +import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { TreeViewType } from "../views/collections/CollectionTreeView"; -import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; +import { CollectionView } from "../views/collections/CollectionView"; import { TreeView } from "../views/collections/TreeView"; +import { DashboardView } from "../views/DashboardView"; import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; @@ -57,59 +57,6 @@ interface Button { export let resolvedPorts: { server: number, socket: number }; export class CurrentUserUtils { - private static curr_id: string; - //TODO tfs: these should be temporary... - private static mainDocId: string | undefined; - - public static get id() { return this.curr_id; } - public static get MainDocId() { return this.mainDocId; } - public static set MainDocId(id: string | undefined) { this.mainDocId = id; } - @computed public static get UserDocument() { return Doc.UserDoc(); } - - @observable public static GuestTarget: Doc | undefined; - @observable public static GuestDashboard: Doc | undefined; - @observable public static GuestMobile: Doc | undefined; - @observable public static propertiesWidth: number = 0; - @observable public static headerBarHeight: number = 0; - @observable public static searchPanelWidth: number = 0; - - static AssignScripts(doc:Doc, scripts?:{ [key: string]: string;}, funcs?:{[key:string]: string}) { - scripts && Object.keys(scripts).map(key => { - if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && scripts[key]) { - doc[key] = ScriptField.MakeScript(scripts[key], { dragData: DragManager.DocumentDragData.name, value:"any", scriptContext: "any", documentView:Doc.name}, {"_readOnly_": true}); - } - }); - funcs && Object.keys(funcs).map(key => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (ScriptCast(cfield)?.script.originalScript !== funcs[key] && funcs[key]) { - doc[key] = ComputedField.MakeFunction(funcs[key], { dragData: DragManager.DocumentDragData.name }, {"_readOnly_": true}); - } - }); - return doc; - } - static AssignOpts(doc:Doc|undefined, reqdOpts:DocumentOptions, items?:Doc[]) { - if (doc) { - const compareValues = (val1:any, val2:any) => { - if (val1 instanceof List && val2 instanceof List && val1.length === val2.length) { - return !val1.some(v => !val2.includes(v)) || !val2.some(v => val1.includes(v)); - } - return val1 === val2; - } - Object.entries(reqdOpts).forEach(pair => { - const targetDoc = pair[0].startsWith("_") ? doc : Doc.GetProto(doc as Doc); - if (!Object.getOwnPropertyNames(targetDoc).includes(pair[0].replace(/^_/,"")) || - !compareValues(pair[1], targetDoc[pair[0]])) { - targetDoc[pair[0]] = pair[1]; - } - }); - items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), "data", item)); - items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), "data", item)); - } - return doc; - } - static AssignDocField(doc:Doc, field:string, creator:(reqdOpts:DocumentOptions, items?:Doc[]) => Doc, reqdOpts:DocumentOptions, items?: Doc[], scripts?:{[key:string]:string}, funcs?:{[key:string]:string}) { - return this.AssignScripts(this.AssignOpts(DocCast(doc[field]), reqdOpts, items) ?? (doc[field] = creator(reqdOpts, items)), scripts, funcs); - } // initializes experimental advanced template views - slideView, headerView static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) { @@ -139,12 +86,12 @@ export class CurrentUserUtils { const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory); }' }; const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { if (templateBtn) { - this.AssignOpts(templateBtn,btnOpts); - this.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions); + DocUtils.AssignOpts(templateBtn,btnOpts); + DocUtils.AssignDocField(templateBtn, "dragFactory", opts => template(opts), templateOptions); } return templateBtn; }; - return this.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts); + return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts); }); const reqdOpts:DocumentOptions = { @@ -154,7 +101,7 @@ export class CurrentUserUtils { }; const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; const reqdFuncs = { hidden: "IsNoviceMode()" }; - return this.AssignScripts(this.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs); + return DocUtils.AssignScripts(DocUtils.AssignOpts(tempDocs, reqdOpts, requiredTypes) ?? Docs.Create.MasonryDocument(requiredTypes, reqdOpts), reqdScripts, reqdFuncs); } /// Initializes templates that can be applied to notes @@ -167,16 +114,16 @@ export class CurrentUserUtils { const reqdNoteList = reqdTempOpts.map(opts => { const reqdOpts = {...opts, title: "text", system: true}; const noteType = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.noteType === opts.noteType): undefined; - return this.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note"); + return DocUtils.AssignOpts(noteType, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts), true, opts.noteType??"Note"); }); const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, system: true }; - return this.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); + return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); } /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc, field="myTemplates") { - this.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"}); + DocUtils.AssignDocField(doc, "presElement", opts => Docs.Create.PresElementBoxDocument(opts), { title: "pres element template", type: DocumentType.PRESELEMENT, _fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data"}); const templates = [ DocCast(doc.presElement), CurrentUserUtils.setupNoteTemplates(doc), @@ -184,13 +131,13 @@ export class CurrentUserUtils { ]; const reqdOpts = { title: "template layouts", _xMargin: 0, system: true, }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; - return this.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts); + return DocUtils.AssignDocField(doc, field, (opts,items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, templates, reqdScripts); } // setup templates for different document types when they are iconified from Document Decorations static setupDefaultIconTemplates(doc: Doc, field="template-icons") { const reqdOpts = { title: "icon templates", _height: 75, system: true }; - const templateIconsDoc = this.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); + const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts)); const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => { const iconFieldName = "icon" + (type ? "_" + type : ""); @@ -201,8 +148,8 @@ export class CurrentUserUtils { case DocumentType.FONTICON: creator = fontBox; break; } const allopts = {system: true, ...opts}; - return this.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? - this.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), + return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ? + DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[iconFieldName] = MakeTemplate(creator(allopts), true, iconFieldName, templateField))), {onClick:"deiconifyView(documentView)"}); }; const labelBox = (opts: DocumentOptions, data?:string) => Docs.Create.LabelDocument({ @@ -224,7 +171,7 @@ export class CurrentUserUtils { makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }), //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts)) ].filter(d => d).map(d => d!); - this.AssignOpts(DocCast(doc[field]), {}, iconTemplates); + DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates); } /// initalizes the set of "empty" versions of each document type with default fields. e.g.,. emptyNote, emptyPresentation @@ -297,7 +244,7 @@ export class CurrentUserUtils { }, funcs: {title: 'self.text?.Text'}}, ]; - emptyThings.forEach(thing => this.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs)); + emptyThings.forEach(thing => DocUtils.AssignDocField(doc, "empty"+thing.key, (opts) => thing.creator(opts), {...standardOps(thing.key), ...thing.opts}, undefined, undefined, thing.funcs)); return [ { toolTip: "Tap or drag to create a note", title: "Note", icon: "sticky-note", dragFactory: doc.emptyNote as Doc, }, @@ -326,7 +273,7 @@ export class CurrentUserUtils { btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, system: true, _removeDropProperties: new List(["_stayInCollection"]), }; - return this.AssignScripts(this.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs); + return DocUtils.AssignScripts(DocUtils.AssignOpts(btn, opts) ?? Docs.Create.FontIconDocument(opts), reqdOpts.scripts, reqdOpts.funcs); }); const reqdOpts:DocumentOptions = { @@ -335,7 +282,7 @@ export class CurrentUserUtils { childDocumentsActive: true }; const reqdScripts = { dropConverter: "convertToButtons(dragData)" }; - return this.AssignScripts(this.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); + return DocUtils.AssignScripts(DocUtils.AssignOpts(dragCreatorDoc, reqdOpts, creatorBtns) ?? Docs.Create.MasonryDocument(creatorBtns, reqdOpts), reqdScripts); } /// returns descriptions needed to buttons for the left sidebar to open up panes displaying different collections of documents @@ -348,7 +295,7 @@ export class CurrentUserUtils { { title: "Tools", target: this.setupToolsBtnPanel(doc, "myTools"), icon: "wrench", funcs: {hidden: "IsNoviceMode()"} }, { title: "Imports", target: this.setupImportSidebar(doc, "myImports"), icon: "upload", }, { title: "Recently Closed", target: this.setupRecentlyClosed(doc, "myRecentlyClosed"), icon: "archive", }, - { title: "Shared Docs", target: this.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}}, + { title: "Shared Docs", target: Doc.MySharedDocs, icon: "users", funcs:{badgeValue:badgeValue}}, { title: "Trails", target: this.setupTrails(doc, "myTrails"), icon: "pres-trail", }, { title: "User Doc View", target: this.setupUserDocView(doc, "myUserDocView"), icon: "address-card",funcs: {hidden: "IsNoviceMode()"} }, ].map(tuple => ({...tuple, scripts:{onClick: 'selectMainMenu(self)'}})); @@ -356,7 +303,7 @@ export class CurrentUserUtils { /// the empty panel that is filled with whichever left menu button's panel has been selected static setupLeftSidebarPanel(doc: Doc, field="myLeftSidebarPanel") { - this.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.system = true; return doc;})(new Doc()), {system:true}); + DocUtils.AssignDocField(doc, field, (opts) => ((doc:Doc) => {doc.system = true; return doc;})(new Doc()), {system:true}); } /// Initializes the left sidebar menu buttons and the panels they open up @@ -370,20 +317,20 @@ export class CurrentUserUtils { _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, _dropAction: "alias", _removeDropProperties: new List(["dropAction", "_stayInCollection"]), }; - return this.AssignScripts(this.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs); + return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), scripts, funcs); }); const reqdStackOpts:DocumentOptions ={ title: "menuItemPanel", childDropAction: "alias", backgroundColor: Colors.DARK_GRAY, boxShadow: "rgba(0,0,0,0)", dontRegisterView: true, ignoreClick: true, _chromeHidden: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, _lockedPosition: true, system: true }; - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); + return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); } // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") { const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, system: true, _chromeHidden: true,}; - this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts); + DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts); } // Sets up mobile buttons for inside mobile menu @@ -436,42 +383,6 @@ export class CurrentUserUtils { }) as any as Doc - static setupThumbButtons(doc: Doc) { - const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, pointerDown?: string, pointerUp?: string, clipboard?: Doc, backgroundColor?: string, dragFactory?: Doc }[] = [ - { title: "use pen", icon: "pen-nib", pointerUp: "resetPen()", pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: "blue" }, - { title: "use highlighter", icon: "highlighter", pointerUp: "resetPen()", pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: "yellow" }, - { title: "notepad", icon: "clipboard", pointerUp: "GestureOverlay.Instance.closeFloatingDoc()", pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), backgroundColor: "orange" }, - { title: "interpret text", icon: "font", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: "orange" }, - { title: "ignore gestures", icon: "signature", pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: "green" }, - ]; - return docProtoData.map(data => Docs.Create.FontIconDocument({ - _nativeWidth: 10, _nativeHeight: 10, _width: 10, _height: 10, title: data.title, icon: data.icon, - _dropAction: data.pointerDown ? "copy" : undefined, - ignoreClick: data.ignoreClick, - onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, - clipboard: data.clipboard, - onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, - onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, - backgroundColor: data.backgroundColor, - _removeDropProperties: new List(["dropAction"]), - dragFactory: data.dragFactory, - system: true - })); - } - - static setupThumbDoc(userDoc: Doc) { - if (!userDoc.thumbDoc) { - const thumbDoc = Docs.Create.LinearDocument(CurrentUserUtils.setupThumbButtons(userDoc), { - _width: 100, _height: 50, ignoreClick: true, _lockedPosition: true, title: "buttons", - _autoHeight: true, _yMargin: 5, linearViewIsExpanded: true, backgroundColor: "white", system: true - }); - thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { - _width: 300, _height: 25, _autoHeight: true, linearViewIsExpanded: true, flexDirection: "column", system: true - }); - userDoc.thumbDoc = thumbDoc; - } - return Cast(userDoc.thumbDoc, Doc); - } static setupMobileInkingDoc(userDoc: Doc) { return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", system: true }); @@ -492,7 +403,7 @@ export class CurrentUserUtils { /// Search option on the left side button panel static setupSearcher(doc: Doc, field:string) { - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), { + return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), { dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", system: true, childDropAction: "alias", _lockedPosition: true, _viewType: CollectionViewType.Schema, _searchDoc: true, }); } @@ -506,7 +417,7 @@ export class CurrentUserUtils { title: "My Tools", system: true, ignoreClick: true, boxShadow: "0 0", _showTitle: "title", _width: 500, _yMargin: 20, _lockedPosition: true, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, _chromeHidden: true, }; - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]); + return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdToolOps, [creatorBtns, templateBtns]); } /// initializes the left sidebar dashboard pane @@ -517,7 +428,7 @@ export class CurrentUserUtils { const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "new dashboard", btnType: ButtonType.ClickButton, toolTip: "Create new dashboard", buttonText: "New trail", icon: "plus", system: true }; const reqdBtnScript = {onClick: newDashboard,} - const newDashboardButton = this.AssignScripts(this.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); + const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); const reqdOpts:DocumentOptions = { title: "My Dashboards", childHideLinkButton: true, freezeChildren: "remove|add", treeViewHideTitle: true, boxShadow: "0 0", childDontRegisterViews: true, @@ -530,7 +441,7 @@ export class CurrentUserUtils { childContextMenuIcons: new List(["chalkboard", "tv", "camera", "users", "times"]), // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters explainer: "This is your collection of dashboards. A dashboard represents the tab configuration of your workspace. To manage documents as folders, go to the Files." }; - myDashboards = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); + myDashboards = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; const contextMenuScripts = [newDashboard]; const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters @@ -553,7 +464,7 @@ export class CurrentUserUtils { const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "New trail", toolTip: "Create new trail", btnType: ButtonType.ClickButton, buttonText: "New trail", icon: "plus", system: true }; const reqdBtnScript = {onClick: `createNewPresentation()`}; - const newTrailButton = this.AssignScripts(this.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); + const newTrailButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myTrails?.buttonMenuDoc), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript); const reqdOpts:DocumentOptions = { title: "My Trails", _showTitle: "title", _height: 100, @@ -564,7 +475,7 @@ export class CurrentUserUtils { _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true, explainer: "All of the trails that you have created will appear here." }; - myTrails = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); + myTrails = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); const contextMenuScripts = [reqdBtnScript.onClick]; if (Cast(myTrails.contextMenuScripts, listSpec(ScriptField), null)?.length !== contextMenuScripts.length) { myTrails.contextMenuScripts = new List(contextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); @@ -575,7 +486,7 @@ export class CurrentUserUtils { /// initializes the left sidebar File system pane static setupFilesystem(doc: Doc, field:string) { var myFilesystem = DocCast(doc[field]); - const myFileOrphans = this.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); + const myFileOrphans = DocUtils.AssignDocField(doc, "myFileOrphans", (opts) => Docs.Create.TreeDocument([], opts), { title: "Unfiled", _stayInCollection: true, system: true, isFolder: true }); const newFolder = `makeTopLevelFolder()`; const newFolderOpts: DocumentOptions = { @@ -583,7 +494,7 @@ export class CurrentUserUtils { title: "New folder", btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", system: true }; const newFolderScript = { onClick: newFolder}; - const newFolderButton = this.AssignScripts(this.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); + const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.buttonMenuDoc), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); const reqdOpts:DocumentOptions = { _showTitle: "title", _height: 100, _gridGap: 5, _forceActive: true, _lockedPosition: true, title: "My Documents", buttonMenu: true, buttonMenuDoc: newFolderButton, treeViewHideTitle: true, targetDropAction: "proto", system: true, @@ -593,7 +504,7 @@ export class CurrentUserUtils { childContextMenuIcons: new List(["plus"]), explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." }; - myFilesystem = this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]); + myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [myFileOrphans]); const childContextMenuScripts = [newFolder]; if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); @@ -610,13 +521,13 @@ export class CurrentUserUtils { contextMenuIcons:new List(["trash"]), explainer: "Recently closed documents appear in this menu. They will only be deleted if you explicity empty this list." }; - const recentlyClosed = this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); + const recentlyClosed = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), reqdOpts); const clearAll = (target:string) => `getProto(${target}).data = new List([])`; const clearBtnsOpts:DocumentOptions = { _width: 30, _height: 30, _forceActive: true, _stayInCollection: true, _hideContextMenu: true, title: "Empty", target: recentlyClosed, btnType: ButtonType.ClickButton, buttonText: "Empty", icon: "trash", system: true, toolTip: "Empty recently closed",}; - const clearDocsButton = this.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")}); + const clearDocsButton = DocUtils.AssignDocField(recentlyClosed, "clearDocsBtn", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("self.target")}); if (recentlyClosed.buttonMenuDoc !== clearDocsButton) Doc.GetProto(recentlyClosed).buttonMenuDoc = clearDocsButton; @@ -626,19 +537,6 @@ export class CurrentUserUtils { return recentlyClosed; } - /// creates a new, empty filter doc - static createFilterDoc() { - const clearAll = `getProto(self).data = new List([])`; - const reqdOpts:DocumentOptions = { - _lockedPosition: true, _autoHeight: true, _fitWidth: true, _height: 150, _xPadding: 5, _yPadding: 5, _gridGap: 5, _forceActive: true, - title: "Unnamed Filter", filterBoolean: "AND", boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, - childDropAction: "none", treeViewHideTitle: true, treeViewTruncateTitleWidth: 150, - childContextMenuLabels: new List(["Clear All"]), - childContextMenuScripts: new List([ScriptField.MakeFunction(clearAll)!]), - }; - return Docs.Create.FilterDocument(reqdOpts); - } - /// initializes the left sidebar panel view of the UserDoc static setupUserDocView(doc: Doc, field:string) { const reqdOpts:DocumentOptions = { @@ -646,8 +544,8 @@ export class CurrentUserUtils { boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", ignoreClick: true, system: true, treeViewHideTitle: true, treeViewTruncateTitleWidth: 150 }; - if (!doc[field]) this.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }); - return this.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]); + if (!doc[field]) DocUtils.AssignOpts(doc, {treeViewOpen: true, treeViewExpandedView: "fields" }); + return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, [doc]); } static linearButtonList = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.LinearDocument(docs, { @@ -666,7 +564,7 @@ export class CurrentUserUtils { static setupDockedButtons(doc: Doc, field="myDockedBtns") { const dockedBtns = DocCast(doc[field]); const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string}) => - this.AssignScripts(this.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? + DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? CurrentUserUtils.createToolButton(opts), scripts); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet @@ -680,7 +578,7 @@ export class CurrentUserUtils { }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); - return this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); + return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); } static textTools():Button[] { @@ -768,13 +666,13 @@ export class CurrentUserUtils { ...params.funcs, backgroundColor: params.scripts?.onClick /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally } - return this.AssignScripts(this.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); + return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { const reqdCtxtOpts = { title: "context menu buttons", flexGap: 0, childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; - const ctxtMenuBtnsDoc = this.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); + const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); if (!params.subMenu) { @@ -786,20 +684,20 @@ export class CurrentUserUtils { const items = params.subMenu?.map(sub => this.setupContextMenuButton(sub, DocListCast(menuBtnDoc?.data).find(doc => doc.title === sub.title)) ); - return this.AssignScripts( - this.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), undefined, params.funcs); + return DocUtils.AssignScripts( + DocUtils.AssignDocField(ctxtMenuBtnsDoc, StrCast(params.title), (opts) => this.linearButtonList(opts, items??[]), reqdSubMenuOpts, items), undefined, params.funcs); } }); - return this.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); + return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); } /// collection of documents rendered in the overlay layer above all tabs and other UI static setupOverlays(doc: Doc, field = "myOverlayDocs") { - return this.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", system: true }); + return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.FreeformDocument([], opts), { title: "overlay documents", backgroundColor: "#aca3a6", system: true }); } static setupPublished(doc:Doc, field = "myPublishedDocs") { - return this.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", system: true }); + return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", system: true }); } /// The database of all links on all documents @@ -838,7 +736,7 @@ export class CurrentUserUtils { explainer: "This is where documents or dashboards that other users have shared with you will appear. To share a document or dashboard right click and select 'Share'" }; - this.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); + DocUtils.AssignDocField(doc, "mySharedDocs", opts => Docs.Create.TreeDocument([], opts, sharingDocumentId + "layout", sharingDocumentId), sharedDocOpts, undefined, sharedScripts); } /// Import option on the left side button panel @@ -849,12 +747,12 @@ export class CurrentUserUtils { childDropAction: "copy", _autoHeight: true, _yMargin: 50, _gridGap: 15, boxShadow: "0 0", _lockedPosition: true, system: true, _chromeHidden: true, dontRegisterView: true, explainer: "This is where documents that are Imported into Dash will go." }; - const myImports = this.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts); + const myImports = DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.StackingDocument([], opts), reqdOpts); const reqdBtnOpts:DocumentOptions = { _forceActive: true, toolTip: "Import from computer", _width: 30, _height: 30, _stayInCollection: true, _hideContextMenu: true, title: "Import", btnType: ButtonType.ClickButton, buttonText: "Import", icon: "upload", system: true }; - this.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); + DocUtils.AssignDocField(myImports, "buttonMenuDoc", (opts) => Docs.Create.FontIconDocument(opts), reqdBtnOpts, undefined, { onClick: "importDocument()" }); return myImports; } @@ -909,7 +807,7 @@ export class CurrentUserUtils { /// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field /// whether to revert to "default" values, or to leave them as the user/system last set them. static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) { - this.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {}); + DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {}); reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data-lastModified"]), async () => { const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data); @@ -948,8 +846,8 @@ export class CurrentUserUtils { this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption - this.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {}); - this.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents + DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {}); + DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "header bar", system: true }); // drop down panel at top of dashboard for stashing documents if (doc.activeDashboard instanceof Doc) { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) @@ -962,7 +860,7 @@ export class CurrentUserUtils { } static setupFieldInfos(doc:Doc, field="fieldInfos") { const fieldInfoOpts = { title: "Field Infos", system: true}; // bcz: all possible document options have associated field infos which are stored onn the FieldInfos document **except for title and system which are used as part of the definition of the fieldInfos object - const infos = this.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts); + const infos = DocUtils.AssignDocField(doc, field, opts => Doc.assign(new Doc(), opts as any), fieldInfoOpts); const entries = Object.entries(new DocumentOptions()); entries.forEach(pair => { if (!Array.from(Object.keys(fieldInfoOpts)).includes(pair[0])) { @@ -974,7 +872,7 @@ export class CurrentUserUtils { case Doc.name: opts.fieldValues = new List(options.values as any); break; default: opts.fieldValues = new List(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType } - this.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts); + DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts); } }); } @@ -995,7 +893,6 @@ export class CurrentUserUtils { } public static async loadUserDocument(id: string) { - this.curr_id = id; await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { const { userDocumentId, sharingDocumentId, linkDatabaseId } = JSON.parse(ids); if (userDocumentId !== "guest") { @@ -1012,46 +909,6 @@ export class CurrentUserUtils { }); } - public static _urlState: HistoryUtil.DocUrl; - - /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). - /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) - public static openDashboard = (doc: Doc|undefined, fromHistory = false) => { - if (!doc) return false; - CurrentUserUtils.MainDocId = doc[Id]; - Doc.AddDocToList(CurrentUserUtils.MyDashboards, "data", doc); - - // this has the side-effect of setting the main container since we're assigning the active/guest dashboard - Doc.UserDoc() ? (CurrentUserUtils.ActiveDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); - - const state = CurrentUserUtils._urlState; - if (state.sharing === true && !Doc.UserDoc()) { - DocServer.Control.makeReadOnly(); - } else { - fromHistory || HistoryUtil.pushState({ - type: "doc", - docId: doc[Id], - readonly: state.readonly, - nro: state.nro, - sharing: false, - }); - if (state.readonly === true || state.readonly === null) { - DocServer.Control.makeReadOnly(); - } else if (state.safe) { - if (!state.nro) { - DocServer.Control.makeReadOnly(); - } - CollectionView.SetSafeMode(true); - } else if (state.nro || state.nro === null || state.readonly === false) { - } else if (doc.readOnly) { - DocServer.Control.makeReadOnly(); - } else { - DocServer.Control.makeEditable(); - } - } - - return true; - } public static importDocument = () => { const input = document.createElement("input"); @@ -1068,7 +925,7 @@ export class CurrentUserUtils { // setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => // docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added. // } - const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null); + const list = Cast(Doc.MyImports.data, listSpec(Doc), null); doc instanceof Doc && list?.splice(0, 0, doc); } else if (input.files && input.files.length !== 0) { const disposer = OverlayView.ShowSpinner(); @@ -1076,7 +933,7 @@ export class CurrentUserUtils { if (results.length !== input.files?.length) { alert("Error uploading files - possibly due to unsupported file types"); } - const list = Cast(CurrentUserUtils.MyImports.data, listSpec(Doc), null); + const list = Cast(Doc.MyImports.data, listSpec(Doc), null); list?.splice(0, 0, ...results); disposer(); } else { @@ -1085,104 +942,20 @@ export class CurrentUserUtils { }; input.click(); } - - public static snapshotDashboard() { return CollectionDockingView.TakeSnapshot(CurrentUserUtils.ActiveDashboard); } - - public static closeActiveDashboard = () => { CurrentUserUtils.ActiveDashboard = undefined; } - - public static removeDashboard = async (dashboard:Doc) => { - const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); - if (dashboards?.length) { - if (dashboard === CurrentUserUtils.ActiveDashboard) CurrentUserUtils.openDashboard(dashboards.find(doc => doc !== dashboard)); - Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); - if (!dashboards.length) CurrentUserUtils.ActivePage = "home"; - } - } - public static createNewDashboard = (id?: string, name?: string) => { - const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true); - const dashboards = CurrentUserUtils.MyDashboards; - const dashboardCount = DocListCast(dashboards.data).length + 1; - const freeformOptions: DocumentOptions = { - x: 0, - y: 400, - _width: 1500, - _height: 1000, - _fitWidth: true, - _backgroundGridShow: true, - title: `Untitled Tab 1`, - }; - const title = name ? name : `Dashboard ${dashboardCount}` - const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, "row"); - freeformDoc.context = dashboardDoc; - - // switching the tabs from the datadoc to the regular doc - const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); - dashboardDoc.data = new List(dashboardTabs); - dashboardDoc["pane-count"] = 1; - - CurrentUserUtils.ActivePresentation = presentation; - - Doc.AddDocToList(dashboards, "data", dashboardDoc); - // open this new dashboard - CurrentUserUtils.ActiveDashboard = dashboardDoc; - CurrentUserUtils.ActivePage = "dashboard"; - } - - public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { - const tbox = Docs.Create.TextDocument("", { - _xMargin: noMargins ? 0 : undefined, _yMargin: noMargins ? 0 : undefined, annotationOn, docMaxAutoHeight: maxHeight, backgroundColor: backgroundColor, - _width: width || 200, _height: 35, x: x, y: y, _fitWidth: true, _autoHeight: true, title - }); - const template = Doc.UserDoc().defaultTextLayout; - if (template instanceof Doc) { - tbox._width = NumCast(template._width); - tbox.layoutKey = "layout_" + StrCast(template.title); - Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template; - } - return tbox; - } - - public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } - public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); } - public static get MySearcher() { return DocCast(Doc.UserDoc().mySearcher); } - public static get MyFilesystem() { return DocCast(Doc.UserDoc().myFilesystem); } - public static get MyHeaderBar() { return DocCast(Doc.UserDoc().myHeaderBar); } - public static get MyTools() { return DocCast(Doc.UserDoc().myTools); } - public static get MyDashboards() { return DocCast(Doc.UserDoc().myDashboards); } - public static get MyFileOrphans() { return DocCast(Doc.UserDoc().myFileOrphans); } - public static get MyTemplates() { return DocCast(Doc.UserDoc().myTemplates); } - public static get MyLeftSidebarMenu() { return DocCast(Doc.UserDoc().myLeftSidebarMenu); } - public static get MyLeftSidebarPanel() { return DocCast(Doc.UserDoc().myLeftSidebarPanel); } - public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } - public static get MyTrails() { return DocCast(Doc.UserDoc().myTrails); } - public static get MyImports() { return DocCast(Doc.UserDoc().myImports); } - public static get MyContextMenuBtns() { return DocCast(Doc.UserDoc().myContextMenuBtns); } - public static get MyRecentlyClosed() { return DocCast(Doc.UserDoc().myRecentlyClosed); } - public static get MyOverlayDocs() { return DocCast(Doc.UserDoc().myOverlayDocs); } - public static get MyPublishedDocs() { return DocCast(Doc.UserDoc().myPublishedDocs); } - public static get ActiveDashboard() { return DocCast(Doc.UserDoc().activeDashboard); } - public static set ActiveDashboard(val:Doc|undefined) { Doc.UserDoc().activeDashboard = val; } - public static get ActivePresentation() { return DocCast(Doc.UserDoc().activePresentation); } - public static set ActivePresentation(val) { Doc.UserDoc().activePresentation = val; } - public static get ActivePage() { return StrCast(Doc.UserDoc().activePage); } - public static set ActivePage(val) { Doc.UserDoc().activePage = val; } - public static set ActiveTool(tool: InkTool) { Doc.UserDoc().activeTool = tool; } - public static get ActiveTool(): InkTool { return StrCast(Doc.UserDoc().activeTool, InkTool.None) as InkTool; } } -ScriptingGlobals.add(function MySharedDocs() { return CurrentUserUtils.MySharedDocs; }, "document containing all shared Docs"); +ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); -ScriptingGlobals.add(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(); }, "creates a snapshot copy of a dashboard"); -ScriptingGlobals.add(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(); }, "creates a new dashboard when called"); +ScriptingGlobals.add(function snapshotDashboard() { DashboardView.snapshotDashboard(); }, "creates a snapshot copy of a dashboard"); +ScriptingGlobals.add(function createNewDashboard() { return DashboardView.createNewDashboard(); }, "creates a new dashboard when called"); ScriptingGlobals.add(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); ScriptingGlobals.add(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); ScriptingGlobals.add(function shareDashboard(dashboard: Doc) { SharingManager.Instance.open(undefined, dashboard); }, "opens sharing dialog for Dashboard"); -ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { CurrentUserUtils.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); -ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { CurrentUserUtils.openDashboard( Doc.MakeAlias(dashboard)); }, "adds Dashboard to set of Dashboards"); +ScriptingGlobals.add(function removeDashboard(dashboard: Doc) { DashboardView.removeDashboard(dashboard); }, "Remove Dashboard from Dashboards"); +ScriptingGlobals.add(function addToDashboards(dashboard: Doc) { DashboardView.openDashboard( Doc.MakeAlias(dashboard)); }, "adds Dashboard to set of Dashboards"); ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colType?: CollectionViewType, checkContext?: boolean) { let selected = (sel => checkContext ? DocCast(sel?.context) : sel)(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); return docType ? selected?.type === docType : colType ? selected?.viewType === colType : true; @@ -1190,5 +963,5 @@ ScriptingGlobals.add(function selectedDocumentType(docType?: DocumentType, colTy ScriptingGlobals.add(function makeTopLevelFolder() { TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; const opts = { title: "Untitled folder", _stayInCollection: true, isFolder: true }; - return Doc.AddDocToList(CurrentUserUtils.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); + return Doc.AddDocToList(Doc.MyFilesystem, "data", Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); }); \ No newline at end of file diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 8473ce703..d3ac2f03f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -4,44 +4,43 @@ import { Id } from '../../fields/FieldSymbols'; import { Cast } from '../../fields/Types'; import { returnFalse } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; -import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; -import { CollectionView } from '../views/collections/CollectionView'; import { LightboxView } from '../views/LightboxView'; import { DocumentView, ViewAdjustment } from '../views/nodes/DocumentView'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; -import { CurrentUserUtils } from './CurrentUserUtils'; +import { CollectionDockingView } from '../views/collections/CollectionDockingView'; +import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; +import { CollectionView } from '../views/collections/CollectionView'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; export class DocumentManager { - //global holds all of the nodes (regardless of which collection they're in) @observable public DocumentViews = new Set(); @observable public LinkAnchorBoxViews: DocumentView[] = []; @observable public RecordingEvent = 0; - @observable public LinkedDocumentViews: { a: DocumentView, b: DocumentView, l: Doc }[] = []; + @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = []; private static _instance: DocumentManager; - public static get Instance(): DocumentManager { return this._instance || (this._instance = new this()); } + public static get Instance(): DocumentManager { + return this._instance || (this._instance = new this()); + } //private constructor so no other class can create a nodemanager - private constructor() { } + private constructor() {} @action public AddView = (view: DocumentView) => { //console.log("MOUNT " + view.props.Document.title + "/" + view.props.LayoutTemplateString); if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { - const viewAnchorIndex = view.props.LayoutTemplateString.includes("anchor2") ? "anchor2" : "anchor1"; + const viewAnchorIndex = view.props.LayoutTemplateString.includes('anchor2') ? 'anchor2' : 'anchor1'; DocListCast(view.rootDoc.links).forEach(link => { - this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)). - forEach(otherView => this.LinkedDocumentViews.push( - { - a: viewAnchorIndex === "anchor2" ? otherView : view, - b: viewAnchorIndex === "anchor2" ? view : otherView, - l: link - }) - ); + this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.rootDoc, link) && !dv.props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView => + this.LinkedDocumentViews.push({ + a: viewAnchorIndex === 'anchor2' ? otherView : view, + b: viewAnchorIndex === 'anchor2' ? view : otherView, + l: link, + }) + ); }); this.LinkAnchorBoxViews.push(view); // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " + @@ -49,14 +48,16 @@ export class DocumentManager { } else { this.DocumentViews.add(view); } - } + }; public RemoveView = action((view: DocumentView) => { - this.LinkedDocumentViews.slice().forEach(action(pair => { - if (pair.a === view || pair.b === view) { - const li = this.LinkedDocumentViews.indexOf(pair); - li !== -1 && this.LinkedDocumentViews.splice(li, 1); - } - })); + this.LinkedDocumentViews.slice().forEach( + action(pair => { + if (pair.a === view || pair.b === view) { + const li = this.LinkedDocumentViews.indexOf(pair); + li !== -1 && this.LinkedDocumentViews.splice(li, 1); + } + }) + ); if (view.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { const index = this.LinkAnchorBoxViews.indexOf(view); @@ -125,11 +126,11 @@ export class DocumentManager { const views: DocumentView[] = []; Array.from(DocumentManager.Instance.DocumentViews).map(view => LightboxView.IsLightboxDocView(view.docViewPath) && Doc.AreProtosEqual(view.rootDoc, toFind) && views.push(view)); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); - } + }; public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt = undefined): DocumentView | undefined => { const views = this.getDocumentViews(toFind).filter(view => view.rootDoc !== originatingDoc); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width && view.props.focus !== returnFalse) || views?.find(view => view.props.focus !== returnFalse) || (views.length ? views[0] : undefined); - } + }; public getDocumentViews(toFind: Doc): DocumentView[] { const toReturn: DocumentView[] = []; const docViews = Array.from(DocumentManager.Instance.DocumentViews).filter(view => !LightboxView.IsLightboxDocView(view.docViewPath)); @@ -146,17 +147,16 @@ export class DocumentManager { return toReturn; } - static addView = (doc: Doc, finished?: () => void) => { - CollectionDockingView.AddSplit(doc, "right"); + CollectionDockingView.AddSplit(doc, 'right'); finished?.(); - } + }; public jumpToDocument = async ( - targetDoc: Doc, // document to display - willZoom: boolean, // whether to zoom doc to take up most of screen + targetDoc: Doc, // document to display + willZoom: boolean, // whether to zoom doc to take up most of screen createViewFunc = DocumentManager.addView, // how to create a view of the doc if it doesn't exist - docContext: Doc[], // context to load that should contain the target - linkDoc?: Doc, // link that's being followed + docContext: Doc[], // context to load that should contain the target + linkDoc?: Doc, // link that's being followed closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc finished?: () => void, @@ -172,14 +172,15 @@ export class DocumentManager { var wasHidden = resolvedTarget.hidden; if (wasHidden) { runInAction(() => { - resolvedTarget.hidden = false; // if the target is hidden, un-hide it here. + resolvedTarget.hidden = false; // if the target is hidden, un-hide it here. docView?.props.bringToFront(resolvedTarget); }); } const focusAndFinish = (didFocus: boolean) => { const finalTargetDoc = resolvedTarget; if (originatingDoc?.isPushpin) { - if (!didFocus && !wasHidden) { // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal + if (!didFocus && !wasHidden) { + // don't toggle the hidden state if the doc was already un-hidden as part of this document traversal finalTargetDoc.hidden = !finalTargetDoc.hidden; } } else { @@ -192,77 +193,92 @@ export class DocumentManager { const contextDocs = docContext.length ? await DocListCastAsync(docContext[0].data) : undefined; const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext.lastElement() : undefined; const targetDocContext = contextDoc || annotatedDoc; - const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) || - (wasHidden && annoContainerView);// if we have an annotation container and the target was hidden, then try again because we just un-hid the document above + const targetDocContextView = (targetDocContext && getFirstDocView(targetDocContext)) || (wasHidden && annoContainerView); // if we have an annotation container and the target was hidden, then try again because we just un-hid the document above const focusView = !docView && targetDoc.type === DocumentType.MARKER && annoContainerView ? annoContainerView : docView; if (annoContainerView) { - if (annoContainerView.props.Document.layoutKey === "layout_icon") { - annoContainerView.iconify(() => annoContainerView.focus(targetDoc, { - originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) => - new Promise(res => { - focusAndFinish(true); - res(ViewAdjustment.doNothing); - }) - })); + if (annoContainerView.props.Document.layoutKey === 'layout_icon') { + annoContainerView.iconify(() => + annoContainerView.focus(targetDoc, { + originalTarget, + willZoom, + scale: presZoom, + afterFocus: (didFocus: boolean) => + new Promise(res => { + focusAndFinish(true); + res(ViewAdjustment.doNothing); + }), + }) + ); return; } else if (!docView && targetDoc.type !== DocumentType.MARKER) { - annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below + annoContainerView.focus(targetDoc); // this allows something like a PDF view to remove its doc filters to expose the target so that it can be found in the retry code below } } if (focusView) { !noSelect && Doc.linkFollowHighlight(focusView.rootDoc); //TODO:glr make this a setting in PresBox - const doFocus = (forceDidFocus: boolean) => focusView.focus(originalTarget ?? targetDoc, { - originalTarget, willZoom, scale: presZoom, afterFocus: (didFocus: boolean) => - new Promise(res => { - focusAndFinish(forceDidFocus || didFocus); - res(ViewAdjustment.doNothing); - }) - }); - if (focusView.props.Document.layoutKey === "layout_icon") { + const doFocus = (forceDidFocus: boolean) => + focusView.focus(originalTarget ?? targetDoc, { + originalTarget, + willZoom, + scale: presZoom, + afterFocus: (didFocus: boolean) => + new Promise(res => { + focusAndFinish(forceDidFocus || didFocus); + res(ViewAdjustment.doNothing); + }), + }); + if (focusView.props.Document.layoutKey === 'layout_icon') { focusView.iconify(() => doFocus(true)); } else { doFocus(false); } } else { - if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default + if (!targetDocContext) { + // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); - } else { // otherwise try to get a view of the context of the target - if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. + } else { + // otherwise try to get a view of the context of the target + if (targetDocContextView) { + // we found a context view and aren't forced to create a new one ... focus on the context first.. wasHidden = wasHidden || targetDocContextView.rootDoc.hidden; targetDocContextView.rootDoc.hidden = false; // make sure context isn't hidden - targetDocContext._viewTransition = "transform 500ms"; + targetDocContext._viewTransition = 'transform 500ms'; targetDocContextView.props.focus(targetDocContextView.rootDoc, { - willZoom, afterFocus: async () => { + willZoom, + afterFocus: async () => { targetDocContext._viewTransition = undefined; - if (targetDocContext.layoutKey === "layout_icon") { - targetDocContextView.iconify(() => this.jumpToDocument( - resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, - finished, originalTarget, noSelect, presZoom)); + if (targetDocContext.layoutKey === 'layout_icon') { + targetDocContextView.iconify(() => this.jumpToDocument(resolvedTarget ?? targetDoc, willZoom, createViewFunc, docContext, linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom)); } return ViewAdjustment.doNothing; - } + }, }); // now find the target document within the context - if (targetDoc._timecodeToShow) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; + if (targetDoc._timecodeToShow) { + // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; targetDocContext._currentTimecode = targetDoc.anchorTimecodeToShow; finished?.(); - } else { // no timecode means we need to find the context view and focus on our target + } else { + // no timecode means we need to find the context view and focus on our target const findView = (delay: number) => { - const retryDocView = getFirstDocView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it - if (retryDocView) { // we found the target in the context. + const retryDocView = getFirstDocView(resolvedTarget); // test again for the target view snce we presumably created the context above by focusing on it + if (retryDocView) { + // we found the target in the context. Doc.linkFollowHighlight(retryDocView.rootDoc); retryDocView.focus(targetDoc, { - willZoom, afterFocus: (didFocus: boolean) => + willZoom, + afterFocus: (didFocus: boolean) => new Promise(res => { !noSelect && focusAndFinish(true); res(ViewAdjustment.doNothing); - }) + }), }); // focus on the target in the context } else if (delay > 1000) { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); - if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document + if (targetDoc.layout) { + // there will no layout for a TEXTANCHOR type document createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target } } else { @@ -272,22 +288,24 @@ export class DocumentManager { setTimeout(() => findView(0), 0); } } else { - if (docContext.length && docContext[0]?.layoutKey === "layout_icon") { + if (docContext.length && docContext[0]?.layoutKey === 'layout_icon') { const docContextView = this.getFirstDocumentView(docContext[0]); if (docContextView) { - return docContextView.iconify(() => this.jumpToDocument( - targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, - finished, originalTarget, noSelect, presZoom)); + return docContextView.iconify(() => + this.jumpToDocument(targetDoc, willZoom, createViewFunc, docContext.slice(1, docContext.length), linkDoc, closeContextIfNotFound, originatingDoc, finished, originalTarget, noSelect, presZoom) + ); } } // there's no context view so we need to create one first and try again when that finishes const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget); - createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target - finishFunc); + createViewFunc( + targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target + finishFunc + ); } } } - } + }; } export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { const cv = collectionDoc && DocumentManager.Instance.getDocumentView(collectionDoc); @@ -295,14 +313,12 @@ export function DocFocusOrOpen(doc: Doc, collectionDoc?: Doc) { if (dv && Doc.AreProtosEqual(dv.props.Document, doc)) { dv.props.focus(dv.props.Document, { willZoom: true }); Doc.linkFollowHighlight(dv?.props.Document, false); - } - else { - const context = doc.context !== CurrentUserUtils.MyFilesystem && Cast(doc.context, Doc, null); + } else { + const context = doc.context !== Doc.MyFilesystem && Cast(doc.context, Doc, null); const showDoc = context || doc; const bestAlias = showDoc === Doc.GetProto(showDoc) ? DocListCast(showDoc.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail) : showDoc; - CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), "right") && context && - setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc)); + CollectionDockingView.AddSplit(bestAlias ? bestAlias : Doc.MakeAlias(showDoc), 'right') && context && setTimeout(() => DocumentManager.Instance.getDocumentView(Doc.GetProto(doc))?.focus(doc)); } } -ScriptingGlobals.add(DocFocusOrOpen); \ No newline at end of file +ScriptingGlobals.add(DocFocusOrOpen); diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 7dcff9c56..18aee6444 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -1,8 +1,8 @@ -import { Doc } from "../../fields/Doc"; -import { DocServer } from "../DocServer"; import * as qs from 'query-string'; -import { Utils, OmitKeys } from "../../Utils"; -import { CurrentUserUtils } from "./CurrentUserUtils"; +import { Doc } from '../../fields/Doc'; +import { OmitKeys, Utils } from '../../Utils'; +import { DocServer } from '../DocServer'; +import { DashboardView } from '../views/DashboardView'; export namespace HistoryUtil { export interface DocInitializerList { @@ -10,7 +10,7 @@ export namespace HistoryUtil { } export interface DocUrl { - type: "doc"; + type: 'doc'; docId: string; initializers?: { [docId: string]: DocInitializerList; @@ -25,11 +25,11 @@ export namespace HistoryUtil { // const handlers: ((state: ParsedUrl | null) => void)[] = []; function onHistory(e: PopStateEvent) { - if (window.location.pathname !== "/home") { - const url = e.state as ParsedUrl || parseUrl(window.location); + if (window.location.pathname !== '/home') { + const url = (e.state as ParsedUrl) || parseUrl(window.location); if (url) { switch (url.type) { - case "doc": + case 'doc': onDocUrl(url); break; } @@ -43,13 +43,13 @@ export namespace HistoryUtil { let _lastStatePush = 0; export function pushState(state: ParsedUrl) { if (Date.now() - _lastStatePush > 1000) { - history.pushState(state, "", createUrl(state)); + history.pushState(state, '', createUrl(state)); } _lastStatePush = Date.now(); } export function replaceState(state: ParsedUrl) { - history.replaceState(state, "", createUrl(state)); + history.replaceState(state, '', createUrl(state)); } function copyState(state: ParsedUrl): ParsedUrl { @@ -61,7 +61,7 @@ export namespace HistoryUtil { if (state) { state.initializers = state.initializers || {}; } - return state ?? {initializers:{}}; + return state ?? { initializers: {} }; } // export function addHandler(handler: (state: ParsedUrl | null) => void) { @@ -78,10 +78,10 @@ export namespace HistoryUtil { const parsers: { [type: string]: (pathname: string[], opts: qs.ParsedQuery) => ParsedUrl | undefined } = {}; const stringifiers: { [type: string]: (state: ParsedUrl) => string } = {}; - type ParserValue = true | "none" | "json" | ((value: string) => any); + type ParserValue = true | 'none' | 'json' | ((value: string) => any); type Parser = { - [key: string]: ParserValue + [key: string]: ParserValue; }; function addParser(type: string, requiredFields: Parser, optionalFields: Parser, customParser?: (pathname: string[], opts: qs.ParsedQuery, current: ParsedUrl) => ParsedUrl | null | undefined) { @@ -90,9 +90,9 @@ export namespace HistoryUtil { return value; } if (Array.isArray(value)) { - } else if (parser === true || parser === "json") { + } else if (parser === true || parser === 'json') { value = JSON.parse(value); - } else if (parser === "none") { + } else if (parser === 'none') { } else { value = parser(value); } @@ -142,29 +142,28 @@ export namespace HistoryUtil { } const queryObj = OmitKeys(state, keys).extract; const query: any = {}; - Object.keys(queryObj).forEach(key => query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])); + Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]))); const queryString = qs.stringify(query); - return path + (queryString ? `?${queryString}` : ""); + return path + (queryString ? `?${queryString}` : ''); }; } - addParser("doc", {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => { + addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => { if (pathname.length !== 2) return undefined; current.initializers = current.initializers || {}; const docId = pathname[1]; current.docId = docId; }); - addStringifier("doc", ["initializers", "readonly", "nro"], (state, current) => { + addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => { return `${current}/${state.docId}`; }); - export function parseUrl(location: Location | URL): ParsedUrl | undefined { const pathname = location.pathname.substring(1); const search = location.search; const opts = search.length ? qs.parse(search, { sort: false }) : {}; - const pathnameSplit = pathname.split("/"); + const pathnameSplit = pathname.split('/'); const type = pathnameSplit[0]; @@ -179,7 +178,7 @@ export namespace HistoryUtil { if (params.type in stringifiers) { return stringifiers[params.type](params); } - return ""; + return ''; } export async function initDoc(id: string, initializer: DocInitializerList) { @@ -197,7 +196,7 @@ export namespace HistoryUtil { await Promise.all(Object.keys(init).map(id => initDoc(id, init[id]))); } if (field instanceof Doc) { - CurrentUserUtils.openDashboard(field, true); + DashboardView.openDashboard(field, true); } } diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts new file mode 100644 index 000000000..f75ac24f5 --- /dev/null +++ b/src/client/util/LinkFollower.ts @@ -0,0 +1,112 @@ +import { action, observable, observe } from 'mobx'; +import { computedFn } from 'mobx-utils'; +import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc'; +import { List } from '../../fields/List'; +import { ProxyField } from '../../fields/Proxy'; +import { BoolCast, Cast, StrCast } from '../../fields/Types'; +import { LightboxView } from '../views/LightboxView'; +import { DocumentViewSharedProps, ViewAdjustment } from '../views/nodes/DocumentView'; +import { DocumentManager } from './DocumentManager'; +import { UndoManager } from './UndoManager'; + +type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; +/* + * link doc: + * - anchor1: doc + * - anchor2: doc + * + * group doc: + * - type: string representing the group type/name/category + * - metadata: doc representing the metadata kvps + * + * metadata doc: + * - user defined kvps + */ +export class LinkFollower { + // follows a link - if the target is on screen, it highlights/pans to it. + // if the target isn't onscreen, then it will open up the target in the lightbox, or in place + // depending on the followLinkLocation property of the source (or the link itself as a fallback); + public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => { + const batch = UndoManager.StartBatch('follow link click'); + // open up target if it's not already in view ... + const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { + const createTabForTarget = (didFocus: boolean) => + new Promise(res => { + const where = LightboxView.LightboxDoc ? 'lightbox' : StrCast(sourceDoc.followLinkLocation, followLoc); + docViewProps.addDocTab(doc, where); + setTimeout(() => { + const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; + const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. + if (targDocView) { + targDocView.props.focus(doc, { + willZoom: BoolCast(sourceDoc.followLinkZoom, false), + afterFocus: (didFocus: boolean) => { + finished?.(); + res(ViewAdjustment.resetView); + return new Promise(res2 => res2(ViewAdjustment.doNothing)); + }, + }); + } else { + res(where !== 'inPlace' ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target + } + }); + }); + + if (!sourceDoc.followLinkZoom) { + createTabForTarget(false); + } else { + // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target + docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget }); + } + }; + LinkFollower.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined); + }; + + public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { + const linkDocs = link ? [link] : DocListCast(sourceDoc.links); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 + const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); + const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; + const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs; + const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1); + var count = 0; + const allFinished = () => ++count === followLinks.length && finished?.(); + followLinks.forEach(async linkDoc => { + if (linkDoc) { + const target = ( + sourceDoc === linkDoc.anchor1 + ? linkDoc.anchor2 + : sourceDoc === linkDoc.anchor2 + ? linkDoc.anchor1 + : Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc) + ? linkDoc.anchor2 + : linkDoc.anchor1 + ) as Doc; + if (target) { + if (target.TourMap) { + const fieldKey = Doc.LayoutFieldKey(target); + const tour = DocListCast(target[fieldKey]).reverse(); + LightboxView.SetLightboxDoc(currentContext, undefined, tour); + setTimeout(LightboxView.Next); + allFinished(); + } else { + const containerAnnoDoc = Cast(target.annotationOn, Doc, null); + const containerDoc = containerAnnoDoc || target; + var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : ([] as Doc[]); + while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) { + containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; + } + const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, 'lightbox'), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished); + } + } else { + allFinished(); + } + } else { + allFinished(); + } + }); + } +} diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index d51cd350d..7a12a8580 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -1,35 +1,30 @@ -import { action, observable, observe } from "mobx"; -import { computedFn } from "mobx-utils"; -import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { ProxyField } from "../../fields/Proxy"; -import { BoolCast, Cast, StrCast } from "../../fields/Types"; -import { LightboxView } from "../views/LightboxView"; -import { DocumentViewSharedProps, ViewAdjustment } from "../views/nodes/DocumentView"; -import { DocumentManager } from "./DocumentManager"; -import { UndoManager } from "./UndoManager"; - -type CreateViewFunc = (doc: Doc, followLinkLocation: string, finished?: () => void) => void; -/* - * link doc: - * - anchor1: doc +import { action, observable, observe } from 'mobx'; +import { computedFn } from 'mobx-utils'; +import { DirectLinksSym, Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc'; +import { List } from '../../fields/List'; +import { ProxyField } from '../../fields/Proxy'; +import { Cast, StrCast } from '../../fields/Types'; +/* + * link doc: + * - anchor1: doc * - anchor2: doc - * + * * group doc: * - type: string representing the group type/name/category * - metadata: doc representing the metadata kvps - * + * * metadata doc: - * - user defined kvps + * - user defined kvps */ export class LinkManager { - @observable static _instance: LinkManager; @observable static userLinkDBs: Doc[] = []; public static currentLink: Opt; - public static get Instance() { return LinkManager._instance; } + public static get Instance() { + return LinkManager._instance; + } public static addLinkDB = (linkDb: any) => LinkManager.userLinkDBs.push(linkDb); - public static AutoKeywords = "keywords:Usages"; + public static AutoKeywords = 'keywords:Usages'; static links: Doc[] = []; constructor() { LinkManager._instance = this; @@ -38,50 +33,61 @@ export class LinkManager { const addLinkToDoc = (link: Doc) => { const a1Prom = link?.anchor1; const a2Prom = link?.anchor2; - Promise.all([a1Prom, a2Prom]).then((all) => { + Promise.all([a1Prom, a2Prom]).then(all => { const a1 = all[0]; const a2 = all[1]; const a1ProtoProm = (link?.anchor1 as Doc)?.proto; const a2ProtoProm = (link?.anchor2 as Doc)?.proto; - Promise.all([a1ProtoProm, a2ProtoProm]).then(action((all) => { - if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinksSym].add(link); - Doc.GetProto(a2)[DirectLinksSym].add(link); - Doc.GetProto(link)[DirectLinksSym].add(link); - } - })); + Promise.all([a1ProtoProm, a2ProtoProm]).then( + action(all => { + if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { + Doc.GetProto(a1)[DirectLinksSym].add(link); + Doc.GetProto(a2)[DirectLinksSym].add(link); + Doc.GetProto(link)[DirectLinksSym].add(link); + } + }) + ); }); }; const remLinkFromDoc = (link: Doc) => { const a1 = link?.anchor1; const a2 = link?.anchor2; - Promise.all([a1, a2]).then(action(() => { - if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { - Doc.GetProto(a1)[DirectLinksSym].delete(link); - Doc.GetProto(a2)[DirectLinksSym].delete(link); - Doc.GetProto(link)[DirectLinksSym].delete(link); - } - })); + Promise.all([a1, a2]).then( + action(() => { + if (a1 instanceof Doc && a2 instanceof Doc && ((a1.author !== undefined && a2.author !== undefined) || link.author === Doc.CurrentUserEmail)) { + Doc.GetProto(a1)[DirectLinksSym].delete(link); + Doc.GetProto(a2)[DirectLinksSym].delete(link); + Doc.GetProto(link)[DirectLinksSym].delete(link); + } + }) + ); }; const watchUserLinkDB = (userLinkDBDoc: Doc) => { LinkManager.links.push(...DocListCast(userLinkDBDoc.data)); - const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields - observe(userLinkDBDoc.data as Doc, change => { // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) - switch (change.type as any) { - case "splice": - (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); - (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); - break; - case "update": //let oldValue = change.oldValue; - } - }, true); - observe(userLinkDBDoc, "data", // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link) + const toRealField = (field: Field) => (field instanceof ProxyField ? field.value() : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields + observe( + userLinkDBDoc.data as Doc, change => { + // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) switch (change.type as any) { - case "update": - Promise.all([...(change.oldValue as any as Doc[] || []), ...(change.newValue as any as Doc[] || [])]).then(doclist => { - const oldDocs = doclist.slice(0, (change.oldValue as any as Doc[] || []).length); - const newDocs = doclist.slice((change.oldValue as any as Doc[] || []).length, doclist.length); + case 'splice': + (change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link))); + (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); + break; + case 'update': //let oldValue = change.oldValue; + } + }, + true + ); + observe( + userLinkDBDoc, + 'data', // obsever when a new array of links is assigned as the link DB 'data' field (should happen whenever a remote user adds/removes a link) + change => { + switch (change.type as any) { + case 'update': + Promise.all([...((change.oldValue as any as Doc[]) || []), ...((change.newValue as any as Doc[]) || [])]).then(doclist => { + const oldDocs = doclist.slice(0, ((change.oldValue as any as Doc[]) || []).length); + const newDocs = doclist.slice(((change.oldValue as any as Doc[]) || []).length, doclist.length); const added = newDocs?.filter(link => !(oldDocs || []).includes(link)); const removed = oldDocs?.filter(link => !(newDocs || []).includes(link)); @@ -89,38 +95,54 @@ export class LinkManager { removed?.forEach((link: any) => remLinkFromDoc(toRealField(link))); }); } - }, true); + }, + true + ); }; - observe(LinkManager.userLinkDBs, change => { - switch (change.type as any) { - case "splice": (change as any).added.forEach(watchUserLinkDB); break; - case "update": //let oldValue = change.oldValue; - } - }, true); + observe( + LinkManager.userLinkDBs, + change => { + switch (change.type as any) { + case 'splice': + (change as any).added.forEach(watchUserLinkDB); + break; + case 'update': //let oldValue = change.oldValue; + } + }, + true + ); LinkManager.addLinkDB(Doc.LinkDBDoc()); - DocListCastAsync(Doc.LinkDBDoc()?.data).then(dblist => dblist?.forEach(async link => { // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager - const a1 = await Cast(link?.anchor1, Doc, null); - const a2 = await Cast(link?.anchor2, Doc, null); - })); + DocListCastAsync(Doc.LinkDBDoc()?.data).then(dblist => + dblist?.forEach(async link => { + // make sure anchors are loaded to avoid incremental updates to computedFn's in LinkManager + const a1 = await Cast(link?.anchor1, Doc, null); + const a2 = await Cast(link?.anchor2, Doc, null); + }) + ); } - public createLinkrelationshipLists = () => { //create new lists for link relations and their associated colors if the lists don't already exist !Doc.UserDoc().linkRelationshipList && (Doc.UserDoc().linkRelationshipList = new List()); !Doc.UserDoc().linkColorList && (Doc.UserDoc().linkColorList = new List()); !Doc.UserDoc().linkRelationshipSizes && (Doc.UserDoc().linkRelationshipSizes = new List()); - } + }; public addLink(linkDoc: Doc, checkExists = false) { if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) { - Doc.AddDocToList(Doc.LinkDBDoc(), "data", linkDoc); + Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc); } } - public deleteLink(linkDoc: Doc) { return Doc.RemoveDocFromList(Doc.LinkDBDoc(), "data", linkDoc); } - public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); } + public deleteLink(linkDoc: Doc) { + return Doc.RemoveDocFromList(Doc.LinkDBDoc(), 'data', linkDoc); + } + public deleteAllLinksOnAnchor(anchor: Doc) { + LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); + } - public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor + public getAllRelatedLinks(anchor: Doc) { + return this.relatedLinker(anchor); + } // finds all links that contain the given anchor public getAllDirectLinks(anchor: Doc): Doc[] { // FIXME:glr Why is Doc undefined? if (Doc.GetProto(anchor)[DirectLinksSym]) { @@ -133,18 +155,16 @@ export class LinkManager { relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] { const lfield = Doc.LayoutFieldKey(anchor); if (!anchor || anchor instanceof Promise || Doc.GetProto(anchor) instanceof Promise) { - console.log("WAITING FOR DOC/PROTO IN LINKMANAGER"); + console.log('WAITING FOR DOC/PROTO IN LINKMANAGER'); return []; } const dirLinks = Doc.GetProto(anchor)[DirectLinksSym]; - const annos = DocListCast(anchor[lfield + "-annotations"]); - const timelineAnnos = DocListCast(anchor[lfield + "-annotations-timeline"]); + const annos = DocListCast(anchor[lfield + '-annotations']); + const timelineAnnos = DocListCast(anchor[lfield + '-annotations-timeline']); if (!annos || !timelineAnnos) { debugger; } - const related = [...annos, ...timelineAnnos].reduce((list, anno) => - [...list, ...LinkManager.Instance.relatedLinker(anno)], - Array.from(dirLinks).slice()); + const related = [...annos, ...timelineAnnos].reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks).slice()); return related; }, true); @@ -152,15 +172,15 @@ export class LinkManager { public getRelatedGroupedLinks(anchor: Doc): Map> { const anchorGroups = new Map>(); this.relatedLinker(anchor).forEach(link => { - if (link.linkRelationship && link.linkRelationship !== "-ungrouped-") { + if (link.linkRelationship && link.linkRelationship !== '-ungrouped-') { const relation = StrCast(link.linkRelationship); - const anchorRelation = relation.indexOf(":") !== -1 ? relation.split(":")[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation; + const anchorRelation = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.anchor1, Doc, null), anchor) ? 0 : 1] : relation; const group = anchorGroups.get(anchorRelation); anchorGroups.set(anchorRelation, group ? [...group, link] : [link]); } else { // if link is in no groups then put it in default group - const group = anchorGroups.get("*"); - anchorGroups.set("*", group ? [...group, link] : [link]); + const group = anchorGroups.get('*'); + anchorGroups.set('*', group ? [...group, link] : [link]); } }); return anchorGroups; @@ -178,85 +198,4 @@ export class LinkManager { if (Doc.AreProtosEqual(anchor, a2.annotationOn as Doc)) return a1; if (Doc.AreProtosEqual(anchor, linkDoc)) return linkDoc; } - - - // follows a link - if the target is on screen, it highlights/pans to it. - // if the target isn't onscreen, then it will open up the target in the lightbox, or in place - // depending on the followLinkLocation property of the source (or the link itself as a fallback); - public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, docViewProps: DocumentViewSharedProps, altKey: boolean, zoom: boolean = false) => { - const batch = UndoManager.StartBatch("follow link click"); - // open up target if it's not already in view ... - const createViewFunc = (doc: Doc, followLoc: string, finished?: Opt<() => void>) => { - const createTabForTarget = (didFocus: boolean) => new Promise(res => { - const where = LightboxView.LightboxDoc ? "lightbox" : StrCast(sourceDoc.followLinkLocation, followLoc); - docViewProps.addDocTab(doc, where); - setTimeout(() => { - const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; - const targDocView = getFirstDocView(doc); // get first document view available within the lightbox if that's open, or anywhere otherwise. - if (targDocView) { - targDocView.props.focus(doc, { - willZoom: BoolCast(sourceDoc.followLinkZoom, false), - afterFocus: (didFocus: boolean) => { - finished?.(); - res(ViewAdjustment.resetView); - return new Promise(res2 => res2(ViewAdjustment.doNothing)); - } - }); - } else { - res(where !== "inPlace" ? ViewAdjustment.resetView : ViewAdjustment.doNothing); // for 'inPlace' resetting the initial focus&zoom would negate the zoom into the target - } - }); - }); - - if (!sourceDoc.followLinkZoom) { - createTabForTarget(false); - } else { - // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target - docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget }); - } - }; - LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, zoom), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined); - } - - public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { - const linkDocs = link ? [link] : DocListCast(sourceDoc.links); - const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 - const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 - const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); - const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); - const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; - const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs); - const followLinks = sourceDoc.isPushpin ? linkDocList : linkDocList.slice(0, 1); - var count = 0; - const allFinished = () => ++count === followLinks.length && finished?.(); - followLinks.forEach(async linkDoc => { - if (linkDoc) { - const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 : - (Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; - if (target) { - if (target.TourMap) { - const fieldKey = Doc.LayoutFieldKey(target); - const tour = DocListCast(target[fieldKey]).reverse(); - LightboxView.SetLightboxDoc(currentContext, undefined, tour); - setTimeout(LightboxView.Next); - allFinished(); - } else { - const containerAnnoDoc = Cast(target.annotationOn, Doc, null); - const containerDoc = containerAnnoDoc || target; - var containerDocContext = containerDoc?.context ? [Cast(containerDoc?.context, Doc, null)] : [] as Doc[]; - while (containerDocContext.length && !DocumentManager.Instance.getDocumentView(containerDocContext[0]) && containerDocContext[0].context) { - containerDocContext = [Cast(containerDocContext[0].context, Doc, null), ...containerDocContext]; - } - const targetContexts = LightboxView.LightboxDoc ? [containerAnnoDoc || containerDocContext[0]].filter(a => a) : containerDocContext; - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "lightbox"), finished), targetContexts, linkDoc, undefined, sourceDoc, allFinished); - } - } else { - allFinished(); - } - } else { - allFinished(); - } - }); - } - -} \ No newline at end of file +} diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index b71086561..d67ce6f6a 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,15 +1,11 @@ -import { action, observable, ObservableMap } from "mobx"; -import { computedFn } from "mobx-utils"; -import { Doc, Opt } from "../../fields/Doc"; -import { DocumentType } from "../documents/DocumentTypes"; -import { CollectionViewType } from "../views/collections/CollectionView"; -import { DocumentView } from "../views/nodes/DocumentView"; -import { CurrentUserUtils } from "./CurrentUserUtils"; +import { action, observable, ObservableMap } from 'mobx'; +import { computedFn } from 'mobx-utils'; +import { Doc, Opt } from '../../fields/Doc'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; +import { DocumentView } from '../views/nodes/DocumentView'; export namespace SelectionManager { - class Manager { - @observable IsDragging: boolean = false; SelectedViews: ObservableMap = new ObservableMap(); @observable SelectedSchemaDocument: Doc | undefined; @@ -63,16 +59,21 @@ export namespace SelectionManager { manager.SelectSchemaViewDoc(document); } - const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed + const IsSelectedCache = computedFn(function isSelected(doc: DocumentView) { + // wrapping get() in a computedFn only generates mobx() invalidations when the return value of the function for the specific get parameters has changed return manager.SelectedViews.get(doc) ? true : false; }); // computed functions, such as used in IsSelected generate errors if they're called outside of a // reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature // to avoid unnecessary mobx invalidations when running inside a reaction. export function IsSelected(doc: DocumentView | undefined, outsideReaction?: boolean): boolean { - return !doc ? false : outsideReaction ? - manager.SelectedViews.get(doc) ? true : false : // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get() - IsSelectedCache(doc); + return !doc + ? false + : outsideReaction + ? manager.SelectedViews.get(doc) + ? true + : false // get() accesses a hashtable -- setting anything in the hashtable generates a mobx invalidation for every get() + : IsSelectedCache(doc); } export function DeselectAll(except?: Doc): void { @@ -96,4 +97,4 @@ export namespace SelectionManager { export function Docs(): Doc[] { return Array.from(manager.SelectedViews.values()).filter(doc => doc?._viewType !== CollectionViewType.Docking); } -} \ No newline at end of file +} diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 080237649..12d1793af 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -1,29 +1,28 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { ColorState, SketchPicker } from "react-color"; -import { Doc } from "../../fields/Doc"; -import { BoolCast, StrCast, Cast } from "../../fields/Types"; -import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils"; -import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager"; -import { DocServer } from "../DocServer"; -import { Networking } from "../Network"; -import { MainViewModal } from "../views/MainViewModal"; -import { FontIconBox } from "../views/nodes/button/FontIconBox"; -import { CurrentUserUtils } from "./CurrentUserUtils"; -import { DragManager } from "./DragManager"; -import { GroupManager } from "./GroupManager"; -import "./SettingsManager.scss"; -import { undoBatch } from "./UndoManager"; -const higflyout = require("@hig/flyout"); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { ColorState, SketchPicker } from 'react-color'; +import { Doc } from '../../fields/Doc'; +import { BoolCast, Cast, StrCast } from '../../fields/Types'; +import { addStyleSheet, addStyleSheetRule, Utils } from '../../Utils'; +import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; +import { DocServer } from '../DocServer'; +import { Networking } from '../Network'; +import { MainViewModal } from '../views/MainViewModal'; +import { FontIconBox } from '../views/nodes/button/FontIconBox'; +import { DragManager } from './DragManager'; +import { GroupManager } from './GroupManager'; +import './SettingsManager.scss'; +import { undoBatch } from './UndoManager'; +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; export enum ColorScheme { - Dark = "-Dark", - Light = "-Light", - System = "-MatchSystem" + Dark = '-Dark', + Light = '-Light', + System = '-MatchSystem', } @observer @@ -31,67 +30,77 @@ export class SettingsManager extends React.Component<{}> { public static Instance: SettingsManager; static _settingsStyle = addStyleSheet(); @observable private isOpen = false; - @observable private passwordResultText = ""; + @observable private passwordResultText = ''; @observable private playgroundMode = false; - @observable private curr_password = ""; - @observable private new_password = ""; - @observable private new_confirm = ""; - @observable activeTab = "Accounts"; + @observable private curr_password = ''; + @observable private new_password = ''; + @observable private new_confirm = ''; + @observable activeTab = 'Accounts'; - @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; } - @computed get colorScheme() { return CurrentUserUtils.ActiveDashboard?.colorScheme; } + @observable public static propertiesWidth: number = 0; + @observable public static headerBarHeight: number = 0; + + @computed get backgroundColor() { + return Doc.UserDoc().activeCollectionBackground; + } + @computed get colorScheme() { + return Doc.ActiveDashboard?.colorScheme; + } constructor(props: {}) { super(props); SettingsManager.Instance = this; } - public close = action(() => this.isOpen = false); - public open = action(() => this.isOpen = true); + public close = action(() => (this.isOpen = false)); + public open = action(() => (this.isOpen = true)); private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)); private changePassword = async () => { if (!(this.curr_password && this.new_password && this.new_confirm)) { - runInAction(() => this.passwordResultText = "Error: Hey, we're missing some fields!"); + runInAction(() => (this.passwordResultText = "Error: Hey, we're missing some fields!")); } else { const passwordBundle = { curr_pass: this.curr_password, new_pass: this.new_password, new_confirm: this.new_confirm }; const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle); - runInAction(() => this.passwordResultText = error ? "Error: " + error[0].msg + "..." : "Password successfully updated!"); + runInAction(() => (this.passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!')); } - } - - @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.noviceMode = (e.currentTarget as any)?.value === "Novice"); - @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value ? "title" : undefined); - @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value); - @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value); - @undoBatch switchActiveBackgroundColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex)); - @undoBatch switchUserColor = action((color: ColorState) => { Doc.SharingDoc().userColor = undefined; Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex); }); + }; + + @undoBatch selectUserMode = action((e: React.ChangeEvent) => (Doc.noviceMode = (e.currentTarget as any)?.value === 'Novice')); + @undoBatch changeShowTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().showTitle = (e.currentTarget as any).value ? 'title' : undefined)); + @undoBatch changeFontFamily = action((e: React.ChangeEvent) => (Doc.UserDoc().fontFamily = (e.currentTarget as any).value)); + @undoBatch changeFontSize = action((e: React.ChangeEvent) => (Doc.UserDoc().fontSize = (e.currentTarget as any).value)); + @undoBatch switchActiveBackgroundColor = action((color: ColorState) => (Doc.UserDoc().activeCollectionBackground = String(color.hex))); + @undoBatch switchUserColor = action((color: ColorState) => { + Doc.SharingDoc().userColor = undefined; + Doc.GetProto(Doc.SharingDoc()).userColor = String(color.hex); + }); @undoBatch playgroundModeToggle = action(() => { this.playgroundMode = !this.playgroundMode; if (this.playgroundMode) { DocServer.Control.makeReadOnly(); - addStyleSheetRule(SettingsManager._settingsStyle, "topbar-inner-container", { background: "red !important" }); - } - else DocServer.Control.makeEditable(); + addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' }); + } else DocServer.Control.makeEditable(); }); @undoBatch @action changeColorScheme = action((e: React.ChangeEvent) => { - const activeDashboard= CurrentUserUtils.ActiveDashboard; + const activeDashboard = Doc.ActiveDashboard; if (!activeDashboard) return; const scheme: ColorScheme = (e.currentTarget as any).value; switch (scheme) { case ColorScheme.Light: activeDashboard.colorScheme = undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) - addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "#d3d3d3 !important" }); + addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: '#d3d3d3 !important' }); break; case ColorScheme.Dark: activeDashboard.colorScheme = ColorScheme.Dark; - addStyleSheetRule(SettingsManager._settingsStyle, "lm_header", { background: "black !important" }); + addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: 'black !important' }); break; - case ColorScheme.System: default: + case ColorScheme.System: + default: window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : undefined; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) }); @@ -99,223 +108,285 @@ export class SettingsManager extends React.Component<{}> { } }); - @computed get colorsContent() { - const colorBox = (func: (color: ColorState) => void) => ; - - const colorFlyout =
    - -
    e.stopPropagation()} > - -
    -
    -
    ; - const userColorFlyout =
    - -
    e.stopPropagation()} > - -
    -
    -
    ; + const colorBox = (func: (color: ColorState) => void) => ( + + ); + + const colorFlyout = ( +
    + +
    e.stopPropagation()}> + +
    +
    +
    + ); + const userColorFlyout = ( +
    + +
    e.stopPropagation()}> + +
    +
    +
    + ); const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System]; - const schemeMap = ["Light", "Dark", "Match system"]; + const schemeMap = ['Light', 'Dark', 'Match system']; - return
    -
    -
    Background Color
    - {colorFlyout} -
    -
    -
    Border/Header Color
    - {userColorFlyout} -
    -
    -
    Color Scheme
    -
    - + return ( +
    +
    +
    Background Color
    + {colorFlyout} +
    +
    +
    Border/Header Color
    + {userColorFlyout} +
    +
    +
    Color Scheme
    +
    + +
    -
    ; + ); } @computed get formatsContent() { - return
    -
    - Doc.UserDoc().showTitle = Doc.UserDoc().showTitle ? undefined : "creationDate"} checked={Doc.UserDoc().showTitle !== undefined} /> -
    Show doc header
    -
    -
    - Doc.UserDoc()["documentLinksButton-fullMenu"] = !Doc.UserDoc()["documentLinksButton-fullMenu"]} - checked={BoolCast(Doc.UserDoc()["documentLinksButton-fullMenu"])} /> -
    Show full toolbar
    -
    -
    - DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())} - checked={DragManager.GetRaiseWhenDragged()} /> -
    Raise on drag
    -
    -
    - FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} - checked={FontIconBox.GetShowLabels()} /> -
    Show button labels
    + return ( +
    +
    + (Doc.UserDoc().showTitle = Doc.UserDoc().showTitle ? undefined : 'creationDate')} checked={Doc.UserDoc().showTitle !== undefined} /> +
    Show doc header
    +
    +
    + (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} checked={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} /> +
    Show full toolbar
    +
    +
    + DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged())} checked={DragManager.GetRaiseWhenDragged()} /> +
    Raise on drag
    +
    +
    + FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} checked={FontIconBox.GetShowLabels()} /> +
    Show button labels
    +
    -
    ; + ); } @computed get appearanceContent() { - - return
    -
    -
    Colors
    -
    {this.colorsContent}
    -
    -
    -
    Formats
    -
    {this.formatsContent}
    + return ( +
    +
    +
    Colors
    +
    {this.colorsContent}
    +
    +
    +
    Formats
    +
    {this.formatsContent}
    +
    -
    ; + ); } @computed get textContent() { - - const fontFamilies = ["Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text", "Roboto"]; - const fontSizes = ["7px", "8px", "9px", "10px", "12px", "14px", "16px", "18px", "20px", "24px", "32px", "48px", "72px"]; + const fontFamilies = ['Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text', 'Roboto']; + const fontSizes = ['7px', '8px', '9px', '10px', '12px', '14px', '16px', '18px', '20px', '24px', '32px', '48px', '72px']; return (
    Default Font
    - + {fontSizes.map(size => ( + + ))} - + {fontFamilies.map(font => ( + + ))}
    -
    ); +
    + ); } @action changeVal = (e: React.ChangeEvent, pass: string) => { const value = (e.target as any).value; switch (pass) { - case "curr": this.curr_password = value; break; - case "new": this.new_password = value; break; - case "conf": this.new_confirm = value; break; + case 'curr': + this.curr_password = value; + break; + case 'new': + this.new_password = value; + break; + case 'conf': + this.new_confirm = value; + break; } - } + }; @computed get passwordContent() { - return
    -
    - this.changeVal(e, "curr")} /> - this.changeVal(e, "new")} /> - this.changeVal(e, "conf")} /> -
    -
    - {!this.passwordResultText ? (null) :
    {this.passwordResultText}
    } - forgot password? - + return ( +
    +
    + this.changeVal(e, 'curr')} /> + this.changeVal(e, 'new')} /> + this.changeVal(e, 'conf')} /> +
    +
    + {!this.passwordResultText ? null :
    {this.passwordResultText}
    } + + forgot password? + + +
    -
    ; + ); } @computed get accountOthersContent() { - return
    - -
    ; + return ( +
    + +
    + ); } @computed get accountsContent() { - return
    -
    -
    Password
    -
    {this.passwordContent}
    -
    -
    -
    Others
    -
    {this.accountOthersContent}
    + return ( +
    +
    +
    Password
    +
    {this.passwordContent}
    +
    +
    +
    Others
    +
    {this.accountOthersContent}
    +
    -
    ; + ); } @computed get modesContent() { - return
    -
    -
    Modes
    -
    - -
    - -
    Playground Mode
    + return ( +
    +
    +
    Modes
    +
    + +
    + +
    Playground Mode
    +
    -
    -
    -
    Permissions
    -
    - -
    - Doc.defaultAclPrivate = !Doc.defaultAclPrivate)} /> -
    Default access private
    +
    +
    Permissions
    +
    + +
    + (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} /> +
    Default access private
    +
    - -
    ; + ); } - private get settingsInterface() { // const pairs = [{ title: "Password", ele: this.passwordContent }, { title: "Modes", ele: this.modesContent }, // { title: "Accounts", ele: this.accountsContent }, { title: "Preferences", ele: this.preferencesContent }]; - const tabs = [{ title: "Accounts", ele: this.accountsContent }, { title: "Modes", ele: this.modesContent }, - { title: "Appearance", ele: this.appearanceContent }, { title: "Text", ele: this.textContent }]; + const tabs = [ + { title: 'Accounts', ele: this.accountsContent }, + { title: 'Modes', ele: this.modesContent }, + { title: 'Appearance', ele: this.appearanceContent }, + { title: 'Text', ele: this.textContent }, + ]; - return
    -
    -
    - {tabs.map(tab =>
    this.activeTab = tab.title)}>{tab.title}
    )} -
    + return ( +
    +
    +
    + {tabs.map(tab => ( +
    (this.activeTab = tab.title))}> + {tab.title} +
    + ))} +
    -
    -
    {Doc.CurrentUserEmail}
    - +
    +
    {Doc.CurrentUserEmail}
    + +
    -
    -
    - -
    +
    + +
    -
    - {tabs.map(tab =>
    {tab.ele}
    )} +
    + {tabs.map(tab => ( +
    + {tab.ele} +
    + ))} +
    -
    ; - + ); } render() { - return ; + return ( + + ); } -} \ No newline at end of file +} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 1acb3ab38..793027ea1 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -17,7 +17,6 @@ import { MainViewModal } from '../views/MainViewModal'; import { DocumentView } from '../views/nodes/DocumentView'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import { SearchBox } from '../views/search/SearchBox'; -import { CurrentUserUtils } from './CurrentUserUtils'; import { DocumentManager } from './DocumentManager'; import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; @@ -180,7 +179,7 @@ export class SharingManager extends React.Component<{}> { const target = targetDoc || this.targetDoc!; const acl = `acl-${normalizeEmail(user.email)}`; const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; - const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1; + const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); return !docs @@ -211,7 +210,7 @@ export class SharingManager extends React.Component<{}> { const target = targetDoc || this.targetDoc!; const key = normalizeEmail(StrCast(group.title)); const acl = `acl-${key}`; - const isDashboard = DocListCast(CurrentUserUtils.MyDashboards.data).indexOf(target) !== -1; + const isDashboard = DocListCast(Doc.MyDashboards.data).indexOf(target) !== -1; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); @@ -279,7 +278,7 @@ export class SharingManager extends React.Component<{}> { else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc); }); } else { - const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data); + const dashboards = DocListCast(Doc.MyDashboards.data); docs.forEach(doc => { const isDashboard = dashboards.indexOf(doc) !== -1; if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls(`acl-${shareWith}`, permission, doc, undefined, undefined, isDashboard); @@ -291,7 +290,7 @@ export class SharingManager extends React.Component<{}> { * Sets the background of the Dashboard if it has been shared as a visual indicator */ setDashboardBackground = async (doc: Doc, permission: SharingPermissions) => { - if (Doc.IndexOf(doc, DocListCast(CurrentUserUtils.MyDashboards.data)) !== -1) { + if (Doc.IndexOf(doc, DocListCast(Doc.MyDashboards.data)) !== -1) { if (permission !== SharingPermissions.None) { doc.isShared = true; doc.backgroundColor = 'green'; @@ -329,7 +328,7 @@ export class SharingManager extends React.Component<{}> { */ removeGroup = (group: Doc) => { if (group.docsShared) { - const dashboards = DocListCast(CurrentUserUtils.MyDashboards.data); + const dashboards = DocListCast(Doc.MyDashboards.data); DocListCast(group.docsShared).forEach(doc => { const acl = `acl-${StrCast(group.title)}`; const isDashboard = dashboards.indexOf(doc) !== -1; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 868d63a90..c59c37488 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,164 +1,290 @@ -import { action, computed, observable } from "mobx"; -import { extname } from 'path'; -import { observer } from "mobx-react"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { Cast, ImageCast, StrCast } from "../../fields/Types"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { undoBatch, UndoManager } from "../util/UndoManager"; -import "./DashboardView.scss" -import { MainViewModal } from "./MainViewModal"; -import { ContextMenu } from "./ContextMenu"; -import { DocumentManager } from "../util/DocumentManager"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { ContextMenuProps } from "./ContextMenuItem"; -import { simulateMouseClick } from "../../Utils"; -import { SharingManager } from "../util/SharingManager"; -import { CollectionViewType } from "./collections/CollectionView"; +import { DataSym, Doc, DocListCast, DocListCastAsync } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { List } from '../../fields/List'; +import { Cast, ImageCast, StrCast } from '../../fields/Types'; +import { DocServer } from '../DocServer'; +import { Docs, DocumentOptions } from '../documents/Documents'; +import { CollectionViewType } from '../documents/DocumentTypes'; +import { HistoryUtil } from '../util/History'; +import { SharingManager } from '../util/SharingManager'; +import { undoBatch } from '../util/UndoManager'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +import { CollectionView } from './collections/CollectionView'; +import { ContextMenu } from './ContextMenu'; +import './DashboardView.scss'; +import { MainViewModal } from './MainViewModal'; enum DashboardGroup { - MyDashboards, SharedDashboards + MyDashboards, + SharedDashboards, } // DashboardView is the view with the dashboard previews, rendered when the app first loads @observer export class DashboardView extends React.Component { + //TODO: delete dashboard, share dashboard, etc. - //TODO: delete dashboard, share dashboard, etc. + public static _urlState: HistoryUtil.DocUrl; - @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; + @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; - @observable private newDashboardName: string | undefined = undefined; - @action abortCreateNewDashboard = () => { this.newDashboardName = undefined } - @action setNewDashboardName(name: string) { this.newDashboardName = name } + @observable private newDashboardName: string | undefined = undefined; + @action abortCreateNewDashboard = () => { + this.newDashboardName = undefined; + }; + @action setNewDashboardName(name: string) { + this.newDashboardName = name; + } - @action - selectDashboardGroup = (group: DashboardGroup) => { - this.selectedDashboardGroup = group - } + @action + selectDashboardGroup = (group: DashboardGroup) => { + this.selectedDashboardGroup = group; + }; - clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => { - if (e.detail === 2) { - Doc.AddDocToList(CurrentUserUtils.MySharedDocs, "viewed", dashboard) - CurrentUserUtils.ActiveDashboard = dashboard; - CurrentUserUtils.ActivePage = "dashboard"; + clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => { + if (e.detail === 2) { + Doc.AddDocToList(Doc.MySharedDocs, 'viewed', dashboard); + Doc.ActiveDashboard = dashboard; + Doc.ActivePage = 'dashboard'; + } + }; + + getDashboards = () => { + const allDashboards = DocListCast(Doc.MyDashboards.data); + if (this.selectedDashboardGroup === DashboardGroup.MyDashboards) { + return allDashboards.filter(dashboard => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail); + } else { + const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); + return sharedDashboards; + } + }; + + isUnviewedSharedDashboard = (dashboard: Doc): boolean => { + // const sharedDashboards = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); + return !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard); + }; + + getSharedDashboards = () => { + const sharedDashs = DocListCast(Doc.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); + return sharedDashs.filter(dashboard => !DocListCast(Doc.MySharedDocs.viewed).includes(dashboard)); + }; + + @undoBatch + createNewDashboard = async (name: string) => { + DashboardView.createNewDashboard(undefined, name); + this.abortCreateNewDashboard(); + }; + + @computed + get namingInterface() { + return ( +
    + this.setNewDashboardName((e.target as any).value)} /> + + +
    + ); + } + + _downX: number = 0; + _downY: number = 0; + @action + onContextMenu = (dashboard: Doc, e?: React.MouseEvent, pageX?: number, pageY?: number) => { + // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 + if (e) { + e.preventDefault(); + e.stopPropagation(); + e.persist(); + + if (!navigator.userAgent.includes('Mozilla') && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) { + return; } - } + const cm = ContextMenu.Instance; + cm.addItem({ + description: 'Share Dashboard', + event: async () => { + SharingManager.Instance.open(undefined, dashboard); + }, + icon: 'edit', + }); + cm.addItem({ + description: 'Delete Dashboard', + event: async () => { + DashboardView.removeDashboard(dashboard); + }, + icon: 'trash', + }); + cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); + } + }; + + render() { + return ( + <> +
    +
    +
    { + this.setNewDashboardName(''); + }}> + New +
    +
    this.selectDashboardGroup(DashboardGroup.MyDashboards)}> + My Dashboards +
    +
    this.selectDashboardGroup(DashboardGroup.SharedDashboards)}> + Shared Dashboards +
    +
    +
    + {this.getDashboards().map(dashboard => { + const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; + return ( +
    { + this.onContextMenu(dashboard, e); + }} + onClick={e => this.clickDashboard(e, dashboard)}> + +
    +
    {StrCast(dashboard.title)}
    + {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
    unviewed
    :
    } +
    { + this._downX = e.clientX; + this._downY = e.clientY; + }} + onClick={e => { + this.onContextMenu(dashboard, e); + }}> + +
    +
    +
    + ); + })} +
    +
    + + ; + + ); + } + + public static closeActiveDashboard() { + Doc.ActiveDashboard = undefined; + } + public static snapshotDashboard() { + return CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard); + } - getDashboards = () => { - const allDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); - if (this.selectedDashboardGroup === DashboardGroup.MyDashboards) { - return allDashboards.filter((dashboard) => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail) + /// opens a dashboard as the ActiveDashboard (and adds the dashboard to the users list of dashboards if it's not already there). + /// this also sets the readonly state of the dashboard based on the current mode of dash (from its url) + public static openDashboard = (doc: Doc | undefined, fromHistory = false) => { + if (!doc) return false; + Doc.MainDocId = doc[Id]; + Doc.AddDocToList(Doc.MyDashboards, 'data', doc); + + // this has the side-effect of setting the main container since we're assigning the active/guest dashboard + Doc.UserDoc() ? (Doc.ActiveDashboard = doc) : (Doc.GuestDashboard = doc); + + const state = DashboardView._urlState; + if (state.sharing === true && !Doc.UserDoc()) { + DocServer.Control.makeReadOnly(); + } else { + fromHistory || + HistoryUtil.pushState({ + type: 'doc', + docId: doc[Id], + readonly: state.readonly, + nro: state.nro, + sharing: false, + }); + if (state.readonly === true || state.readonly === null) { + DocServer.Control.makeReadOnly(); + } else if (state.safe) { + if (!state.nro) { + DocServer.Control.makeReadOnly(); + } + CollectionView.SetSafeMode(true); + } else if (state.nro || state.nro === null || state.readonly === false) { + } else if (doc.readOnly) { + DocServer.Control.makeReadOnly(); } else { - const sharedDashboards = DocListCast(CurrentUserUtils.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); - return sharedDashboards + DocServer.Control.makeEditable(); } - } - - isUnviewedSharedDashboard = (dashboard: Doc): boolean => { - // const sharedDashboards = DocListCast(CurrentUserUtils.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); - return !DocListCast(CurrentUserUtils.MySharedDocs.viewed).includes(dashboard) - } - - getSharedDashboards = () => { - const sharedDashs = DocListCast(CurrentUserUtils.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); - return sharedDashs.filter((dashboard) => !DocListCast(CurrentUserUtils.MySharedDocs.viewed).includes(dashboard)) - } - - @undoBatch - createNewDashboard = async (name: string) => { - CurrentUserUtils.createNewDashboard(undefined, name); - this.abortCreateNewDashboard(); - } - - @computed - get namingInterface() { - return
    - this.setNewDashboardName((e.target as any).value)} /> - - -
    ; - } - - _downX: number = 0; - _downY: number = 0; - @action - onContextMenu = (dashboard: Doc, e?: React.MouseEvent, pageX?: number, pageY?: number) => { - // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 - if (e) { - e.preventDefault(); - e.stopPropagation(); - e.persist(); - - if (!navigator.userAgent.includes("Mozilla") && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) { - return; - } - const cm = ContextMenu.Instance; - cm.addItem({ - description: "Share Dashboard", event: async () => { - SharingManager.Instance.open(undefined, dashboard) - }, icon: "edit" - }); - cm.addItem({ - description: "Delete Dashboard", event: async () => { - CurrentUserUtils.removeDashboard(dashboard) - }, icon: "trash" - }); - cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); - } } - - - render() { - return <> -
    -
    -
    { this.setNewDashboardName("") }}>New
    -
    this.selectDashboardGroup(DashboardGroup.MyDashboards)}>My Dashboards
    -
    this.selectDashboardGroup(DashboardGroup.SharedDashboards)}>Shared Dashboards
    -
    -
    - {this.getDashboards().map((dashboard) => { - const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; - return
    {this.onContextMenu(dashboard, e)}} - onClick={e => this.clickDashboard(e, dashboard)}> - -
    -
    {StrCast(dashboard.title)}
    - {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? -
    unviewed
    :
    - } -
    { - this._downX = e.clientX; - this._downY = e.clientY; - }} - onClick={(e) => {this.onContextMenu(dashboard, e)}} - > - -
    -
    -
    + return true; + }; - })} -
    + public static removeDashboard = async (dashboard: Doc) => { + const dashboards = await DocListCastAsync(Doc.MyDashboards.data); + if (dashboards?.length) { + if (dashboard === Doc.ActiveDashboard) DashboardView.openDashboard(dashboards.find(doc => doc !== dashboard)); + Doc.RemoveDocFromList(Doc.MyDashboards, 'data', dashboard); + if (!dashboards.length) Doc.ActivePage = 'home'; + } + }; -
    - ; - + public static createNewDashboard = (id?: string, name?: string) => { + const presentation = Doc.MakeCopy(Doc.UserDoc().emptyPresentation as Doc, true); + const dashboards = Doc.MyDashboards; + const dashboardCount = DocListCast(dashboards.data).length + 1; + const freeformOptions: DocumentOptions = { + x: 0, + y: 400, + _width: 1500, + _height: 1000, + _fitWidth: true, + _backgroundGridShow: true, + title: `Untitled Tab 1`, + }; + const title = name ? name : `Dashboard ${dashboardCount}`; + const freeformDoc = Doc.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); + const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row'); + freeformDoc.context = dashboardDoc; - } + // switching the tabs from the datadoc to the regular doc + const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); + dashboardDoc.data = new List(dashboardTabs); + dashboardDoc['pane-count'] = 1; + + Doc.ActivePresentation = presentation; + + Doc.AddDocToList(dashboards, 'data', dashboardDoc); + // open this new dashboard + Doc.ActiveDashboard = dashboardDoc; + Doc.ActivePage = 'dashboard'; + }; } export function AddToList(MySharedDocs: Doc, arg1: string, dash: any) { - throw new Error("Function not implemented."); + throw new Error('Function not implemented.'); } - diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 169bd3873..280ca8a8c 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -8,13 +8,11 @@ import { Cast, ScriptCast } from '../../fields/Types'; import { denormalizeEmail, distributeAcls, GetEffectiveAcl, inheritParentAcls, SharingPermissions } from '../../fields/util'; import { returnFalse } from '../../Utils'; import { DocUtils } from '../documents/Documents'; -import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { InteractionUtils } from '../util/InteractionUtils'; import { UndoManager } from '../util/UndoManager'; import { DocumentView } from './nodes/DocumentView'; import { Touchable } from './Touchable'; - /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) export interface DocComponentProps { Document: Doc; @@ -24,13 +22,21 @@ export interface DocComponentProps { export function DocComponent

    () { class Component extends Touchable

    { //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed get Document() { return this.props.Document; } + @computed get Document() { + return this.props.Document; + } // This is the "The Document" -- it encapsulates, data, layout, and any templates - @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; } + @computed get rootDoc() { + return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; + } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info - @computed get layoutDoc() { return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); } + @computed get layoutDoc() { + return this.props.LayoutTemplateString ? this.props.Document : Doc.Layout(this.props.Document, this.props.LayoutTemplate?.()); + } // This is the data part of a document -- ie, the data that is constant across all views of the document - @computed get dataDoc() { return this.props.Document[DataSym] as Doc; } + @computed get dataDoc() { + return this.props.Document[DataSym] as Doc; + } protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } @@ -55,32 +61,39 @@ export function ViewBoxBaseComponent

    () { //@computed get Document(): T { return schemaCtor(this.props.Document); } // This is the "The Document" -- it encapsulates, data, layout, and any templates - @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; } + @computed get rootDoc() { + return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; + } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info - @computed get layoutDoc() { return Doc.Layout(this.props.Document); } + @computed get layoutDoc() { + return Doc.Layout(this.props.Document); + } // This is the data part of a document -- ie, the data that is constant across all views of the document - @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; } + @computed get dataDoc() { + return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; + } // key where data is stored - @computed get fieldKey() { return this.props.fieldKey; } - - isContentActive = (outsideReaction?: boolean) => ( - this.props.isContentActive?.() === false ? false : - (CurrentUserUtils.ActiveTool !== InkTool.None || - (this.props.isContentActive?.() || this.props.Document.forceActive || - this.props.isSelected(outsideReaction) || - this.props.rootSelected(outsideReaction)) ? true : undefined)) + @computed get fieldKey() { + return this.props.fieldKey; + } + + isContentActive = (outsideReaction?: boolean) => + this.props.isContentActive?.() === false + ? false + : Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this.props.rootSelected(outsideReaction) + ? true + : undefined; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } return Component; } - /// DocAnnotatbleComponent -return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image) export interface ViewBoxAnnotatableProps { Document: Doc; DataDoc?: Doc; fieldKey: string; - filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) + filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) isContentActive: () => boolean | undefined; select: (isCtrlPressed: boolean) => void; whenChildContentsActiveChanged: (isActive: boolean) => void; @@ -91,19 +104,29 @@ export interface ViewBoxAnnotatableProps { } export function ViewBoxAnnotatableComponent

    () { class Component extends Touchable

    { - @observable _annotationKeySuffix = () => "annotations"; + @observable _annotationKeySuffix = () => 'annotations'; @observable _isAnyChildContentActive = false; //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then - @computed get Document() { return this.props.Document; } + @computed get Document() { + return this.props.Document; + } // This is the "The Document" -- it encapsulates, data, layout, and any templates - @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; } + @computed get rootDoc() { + return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; + } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info - @computed get layoutDoc() { return Doc.Layout(this.props.Document); } + @computed get layoutDoc() { + return Doc.Layout(this.props.Document); + } // This is the data part of a document -- ie, the data that is constant across all views of the document - @computed get dataDoc() { return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; } + @computed get dataDoc() { + return this.props.DataDoc && (this.props.Document.isTemplateForField || this.props.Document.isTemplateDoc) ? this.props.DataDoc : this.props.Document[DataSym]; + } // key where data is stored - @computed get fieldKey() { return this.props.fieldKey; } + @computed get fieldKey() { + return this.props.fieldKey; + } isAnyChildContentActive = () => this._isAnyChildContentActive; @@ -111,20 +134,23 @@ export function ViewBoxAnnotatableComponent

    () styleFromLayoutString = (scale: number) => { const style: { [key: string]: any } = {}; - const divKeys = ["width", "height", "fontSize", "transform", "left", "background", "left", "right", "top", "bottom", "pointerEvents", "position"]; - const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a property expression string: { script } into a value - return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: "number" })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? ""; + const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'background', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position']; + const replacer = (match: any, expr: string, offset: any, string: any) => { + // bcz: this executes a script to convert a property expression string: { script } into a value + return ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.rootDoc, this: this.layoutDoc, scale }).result?.toString() ?? ''; }; divKeys.map((prop: string) => { const p = (this.props as any)[prop]; - typeof p === "string" && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer)); + typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer)); }); return style; - } + }; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; - @computed public get annotationKey() { return this.fieldKey + (this._annotationKeySuffix() ? "-" + this._annotationKeySuffix() : ""); } + @computed public get annotationKey() { + return this.fieldKey + (this._annotationKeySuffix() ? '-' + this._annotationKeySuffix() : ''); + } @action.bound removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean { @@ -132,23 +158,26 @@ export function ViewBoxAnnotatableComponent

    () const indocs = doc instanceof Doc ? [doc] : doc; const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin); if (docs.length) { - setTimeout(() => docs.map(doc => { // this allows 'addDocument' to see the annotationOn field in order to create a pushin - Doc.SetInPlace(doc, "isPushpin", undefined, true); - doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, "annotationOn", undefined, true); - })); + setTimeout(() => + docs.map(doc => { + // this allows 'addDocument' to see the annotationOn field in order to create a pushin + Doc.SetInPlace(doc, 'isPushpin', undefined, true); + doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true); + }) + ); const targetDataDoc = this.dataDoc; const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { - const recent = CurrentUserUtils.MyRecentlyClosed; + const recent = Doc.MyRecentlyClosed; toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); doc.context = undefined; if (recent) { - Doc.RemoveDocFromList(recent, "data", doc); - Doc.AddDocToList(recent, "data", doc, undefined, true, true); + Doc.RemoveDocFromList(recent, 'data', doc); + Doc.AddDocToList(recent, 'data', doc, undefined, true, true); } }); this.isAnyChildContentActive() && this.props.select(false); @@ -172,12 +201,11 @@ export function ViewBoxAnnotatableComponent

    () return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc, annotationKey)); } return false; - } + }; @action.bound addDocument = (doc: Doc | Doc[], annotationKey?: string): boolean => { const docs = doc instanceof Doc ? [doc] : doc; - if (this.props.filterAddDocument?.(docs) === false || - docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) { + if (this.props.filterAddDocument?.(docs) === false || docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document) && Doc.LayoutField(doc) === Doc.LayoutField(this.props.Document))) { return false; } const targetDataDoc = this.props.Document[DataSym]; @@ -188,8 +216,7 @@ export function ViewBoxAnnotatableComponent

    () if (added.length) { if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { return false; - } - else { + } else { if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym]).length) { added.forEach(d => { for (const [key, value] of Object.entries(this.props.Document[AclSym])) { @@ -200,32 +227,34 @@ export function ViewBoxAnnotatableComponent

    () if (effectiveAcl === AclAugment) { added.map(doc => { - if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && CurrentUserUtils.ActiveDashboard) inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); + if ([AclAdmin, AclEdit].includes(GetEffectiveAcl(doc)) && Doc.ActiveDashboard) inheritParentAcls(Doc.ActiveDashboard, doc); doc.context = this.props.Document; if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); }); - } - else { - added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document - //DocUtils.LeavePushpin(doc); - doc._stayInCollection = undefined; - doc.context = this.props.Document; - if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; + } else { + added + .filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))) + .map(doc => { + // only make a pushpin if we have acl's to edit the document + //DocUtils.LeavePushpin(doc); + doc._stayInCollection = undefined; + doc.context = this.props.Document; + if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.props.Document; - CurrentUserUtils.ActiveDashboard && inheritParentAcls(CurrentUserUtils.ActiveDashboard, doc); - }); + Doc.ActiveDashboard && inheritParentAcls(Doc.ActiveDashboard, doc); + }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; if (annoDocs instanceof List) annoDocs.push(...added); else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); - targetDataDoc[(annotationKey ?? this.annotationKey) + "-lastModified"] = new DateField(new Date(Date.now())); + targetDataDoc[(annotationKey ?? this.annotationKey) + '-lastModified'] = new DateField(new Date(Date.now())); } } } return true; - } + }; - whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); + whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); } return Component; -} \ No newline at end of file +} diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 9b8f7238d..bac51a11d 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -1,57 +1,54 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, DocCastAsync } from "../../fields/Doc"; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../fields/Doc'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from "../../Utils"; +import { Cast, NumCast } from '../../fields/Types'; +import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { Docs } from '../documents/Documents'; -import { DocumentType } from '../documents/DocumentTypes'; -import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { DragManager } from '../util/DragManager'; import { SelectionManager } from '../util/SelectionManager'; +import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; +import { undoBatch } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { TabDocView } from './collections/TabDocView'; import './DocumentButtonBar.scss'; +import { Colors } from './global/globalEnums'; import { MetadataEntryMenu } from './MetadataEntryMenu'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; -import { GoogleRef } from "./nodes/formattedText/FormattedTextBox"; -import { TemplateMenu } from "./TemplateMenu"; -import React = require("react"); -import { PresBox } from './nodes/trails/PresBox'; -import { undoBatch } from '../util/UndoManager'; -import { CollectionViewType } from './collections/CollectionView'; -import { Colors } from './global/globalEnums'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; -const higflyout = require("@hig/flyout"); +import { GoogleRef } from './nodes/formattedText/FormattedTextBox'; +import { TemplateMenu } from './TemplateMenu'; +import React = require('react'); +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -const cloud: IconProp = "cloud-upload-alt"; -const fetch: IconProp = "sync-alt"; +const cloud: IconProp = 'cloud-upload-alt'; +const fetch: IconProp = 'sync-alt'; enum UtilityButtonState { Default, OpenRight, - OpenExternally + OpenExternally, } @observer -export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[], stack?: any }, {}> { +export class DocumentButtonBar extends React.Component<{ views: () => (DocumentView | undefined)[]; stack?: any }, {}> { private _dragRef = React.createRef(); private _pullAnimating = false; private _pushAnimating = false; private _pullColorAnimating = false; - @observable private pushIcon: IconProp = "arrow-alt-circle-up"; - @observable private pullIcon: IconProp = "arrow-alt-circle-down"; - @observable private pullColor: string = "white"; + @observable private pushIcon: IconProp = 'arrow-alt-circle-up'; + @observable private pullIcon: IconProp = 'arrow-alt-circle-down'; + @observable private pullColor: string = 'white'; @observable public isAnimatingFetch = false; @observable public isAnimatingPulse = false; @@ -63,17 +60,21 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV constructor(props: { views: () => (DocumentView | undefined)[] }) { super(props); - runInAction(() => DocumentButtonBar.Instance = this); + runInAction(() => (DocumentButtonBar.Instance = this)); } public startPullOutcome = action((success: boolean) => { if (!this._pullAnimating) { this._pullAnimating = true; - this.pullIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pullIcon = "arrow-alt-circle-down"; - this._pullAnimating = false; - }), 1000); + this.pullIcon = success ? 'check-circle' : 'stop-circle'; + setTimeout( + () => + runInAction(() => { + this.pullIcon = 'arrow-alt-circle-down'; + this._pullAnimating = false; + }), + 1000 + ); } }); @@ -81,11 +82,15 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV this.isAnimatingPulse = false; if (!this._pushAnimating) { this._pushAnimating = true; - this.pushIcon = success ? "check-circle" : "stop-circle"; - setTimeout(() => runInAction(() => { - this.pushIcon = "arrow-alt-circle-up"; - this._pushAnimating = false; - }), 1000); + this.pushIcon = success ? 'check-circle' : 'stop-circle'; + setTimeout( + () => + runInAction(() => { + this.pushIcon = 'arrow-alt-circle-up'; + this._pushAnimating = false; + }), + 1000 + ); } }); @@ -93,165 +98,241 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV this.isAnimatingFetch = false; if (!this._pullColorAnimating) { this._pullColorAnimating = true; - this.pullColor = unchanged ? "lawngreen" : "red"; + this.pullColor = unchanged ? 'lawngreen' : 'red'; setTimeout(this.clearPullColor, 1000); } }); private clearPullColor = action(() => { - this.pullColor = "white"; + this.pullColor = 'white'; this._pullColorAnimating = false; }); - get view0() { return this.props.views()?.[0]; } + get view0() { + return this.props.views()?.[0]; + } @computed get considerGoogleDocsPush() { const targetDoc = this.view0?.props.Document; const published = targetDoc && Doc.GetProto(targetDoc)[GoogleRef] !== undefined; - const animation = this.isAnimatingPulse ? "shadow-pulse 1s linear infinite" : "none"; - return !targetDoc ? (null) :

    {`${published ? "Push" : "Publish"} to Google Docs`}
    }> -
    { - await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); - !published && runInAction(() => this.isAnimatingPulse = true); - DocumentButtonBar.hasPushedHack = false; - targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; - }}> - -
    ; + const animation = this.isAnimatingPulse ? 'shadow-pulse 1s linear infinite' : 'none'; + return !targetDoc ? null : ( + +
    {`${published ? 'Push' : 'Publish'} to Google Docs`}
    + + }> +
    { + await GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(); + !published && runInAction(() => (this.isAnimatingPulse = true)); + DocumentButtonBar.hasPushedHack = false; + targetDoc[Pushes] = NumCast(targetDoc[Pushes]) + 1; + }}> + +
    +
    + ); } @computed get considerGoogleDocsPull() { const targetDoc = this.view0?.props.Document; const dataDoc = targetDoc && Doc.GetProto(targetDoc); - const animation = this.isAnimatingFetch ? "spin 0.5s linear infinite" : "none"; + const animation = this.isAnimatingFetch ? 'spin 0.5s linear infinite' : 'none'; const title = (() => { switch (this.openHover) { default: - case UtilityButtonState.Default: return `${!dataDoc?.googleDocUnchanged ? "Pull from" : "Fetch"} Google Docs`; - case UtilityButtonState.OpenRight: return "Open in Right Split"; - case UtilityButtonState.OpenExternally: return "Open in new Browser Tab"; + case UtilityButtonState.Default: + return `${!dataDoc?.googleDocUnchanged ? 'Pull from' : 'Fetch'} Google Docs`; + case UtilityButtonState.OpenRight: + return 'Open in Right Split'; + case UtilityButtonState.OpenExternally: + return 'Open in new Browser Tab'; } })(); - return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? (null) :
    {title}
    }> -
    { - if (e.altKey) { - this.openHover = UtilityButtonState.OpenExternally; - } else if (e.shiftKey) { - this.openHover = UtilityButtonState.OpenRight; - } - })} - onPointerLeave={action(() => this.openHover = UtilityButtonState.Default)} - onClick={async e => { - const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`; - if (e.shiftKey) { - e.preventDefault(); - let googleDoc = await Cast(dataDoc.googleDoc, Doc); - if (!googleDoc) { - const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false }; - googleDoc = Docs.Create.WebDocument(googleDocUrl, options); - dataDoc.googleDoc = googleDoc; + return !targetDoc || !dataDoc || !dataDoc[GoogleRef] ? null : ( + +
    {title}
    + + }> +
    { + if (e.altKey) { + this.openHover = UtilityButtonState.OpenExternally; + } else if (e.shiftKey) { + this.openHover = UtilityButtonState.OpenRight; } - CollectionDockingView.AddSplit(googleDoc, "right"); - } else if (e.altKey) { - e.preventDefault(); - window.open(googleDocUrl); - } else { - this.clearPullColor(); - DocumentButtonBar.hasPulledHack = false; - targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; - dataDoc.googleDocUnchanged && runInAction(() => this.isAnimatingFetch = true); - } - }}> - { - switch (this.openHover) { - default: - case UtilityButtonState.Default: return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; - case UtilityButtonState.OpenRight: return "arrow-alt-circle-right"; - case UtilityButtonState.OpenExternally: return "share"; + })} + onPointerLeave={action(() => (this.openHover = UtilityButtonState.Default))} + onClick={async e => { + const googleDocUrl = `https://docs.google.com/document/d/${dataDoc[GoogleRef]}/edit`; + if (e.shiftKey) { + e.preventDefault(); + let googleDoc = await Cast(dataDoc.googleDoc, Doc); + if (!googleDoc) { + const options = { _width: 600, _nativeWidth: 960, _nativeHeight: 800, useCors: false }; + googleDoc = Docs.Create.WebDocument(googleDocUrl, options); + dataDoc.googleDoc = googleDoc; + } + CollectionDockingView.AddSplit(googleDoc, 'right'); + } else if (e.altKey) { + e.preventDefault(); + window.open(googleDocUrl); + } else { + this.clearPullColor(); + DocumentButtonBar.hasPulledHack = false; + targetDoc[Pulls] = NumCast(targetDoc[Pulls]) + 1; + dataDoc.googleDocUnchanged && runInAction(() => (this.isAnimatingFetch = true)); } - })()} - /> -
    ; + }}> + { + switch (this.openHover) { + default: + case UtilityButtonState.Default: + return dataDoc.googleDocUnchanged === false ? (this.pullIcon as any) : fetch; + case UtilityButtonState.OpenRight: + return 'arrow-alt-circle-right'; + case UtilityButtonState.OpenExternally: + return 'share'; + } + })()} + /> +
    +
    + ); } @computed get followLinkButton() { const targetDoc = this.view0?.props.Document; - return !targetDoc ? (null) : {"Set onClick to follow primary link"}
    }> -
    this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}> - -
    - ; + return !targetDoc ? null : ( + {'Set onClick to follow primary link'}
    }> +
    this.props.views().map(view => view?.docView?.toggleFollowLink(undefined, undefined, false)))}> + +
    + + ); } @computed get pinButton() { const targetDoc = this.view0?.props.Document; - return !targetDoc ? (null) : {SelectionManager.Views().length > 1 ? "Pin multiple documents to presentation" : "Pin to presentation"}
    }> -
    TabDocView.PinDoc(this.props.views().filter(v => v).map(dv => dv!.rootDoc), {pinDocView: true}))} - > - -
    - ; + return !targetDoc ? null : ( + {SelectionManager.Views().length > 1 ? 'Pin multiple documents to presentation' : 'Pin to presentation'}
    }> +
    + TabDocView.PinDoc( + this.props + .views() + .filter(v => v) + .map(dv => dv!.rootDoc), + { pinDocView: true } + ) + }> + +
    + + ); } @computed get shareButton() { const targetDoc = this.view0?.props.Document; - return !targetDoc ? (null) :
    {"Open Sharing Manager"}
    }> -
    SharingManager.Instance.open(this.view0, targetDoc)}> - -
    ; + return !targetDoc ? null : ( + +
    {'Open Sharing Manager'}
    + + }> +
    SharingManager.Instance.open(this.view0, targetDoc)}> + +
    +
    + ); } @computed get menuButton() { const targetDoc = this.view0?.props.Document; - return !targetDoc ? (null) :
    {`Open Context Menu`}
    }> -
    this.openContextMenu(e)}> - -
    ; + return !targetDoc ? null : ( + +
    {`Open Context Menu`}
    + + }> +
    this.openContextMenu(e)}> + +
    +
    + ); } @computed get moreButton() { const targetDoc = this.view0?.props.Document; - return !targetDoc ? (null) :
    {`${CurrentUserUtils.propertiesWidth > 0 ? "Close" : "Open"} Properties Panel`}
    }> -
    - CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}> - -
    ; + return !targetDoc ? null : ( + +
    {`${SettingsManager.propertiesWidth > 0 ? 'Close' : 'Open'} Properties Panel`}
    + + }> +
    (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 250))}> + +
    +
    + ); } @computed get metadataButton() { const view0 = this.view0; - return !view0 ? (null) :
    Show metadata panel
    }> -
    - dv).map(dv => dv!.props.Document)} suggestWithFunction /> /* tfs: @bcz This might need to be the data document? */}> -
    e.stopPropagation()} > - {} -
    -
    -
    ; + return !view0 ? null : ( + +
    Show metadata panel
    + + }> +
    + dv) + .map(dv => dv!.props.Document)} + suggestWithFunction + /> /* tfs: @bcz This might need to be the data document? */ + }> +
    e.stopPropagation()}> + {} +
    +
    +
    +
    + ); } @observable _aliasDown = false; onAliasButtonDown = action((e: React.PointerEvent): void => { @@ -264,13 +345,13 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV const dragDocView = this.view0!; const dragData = new DragManager.DocumentDragData([dragDocView.props.Document]); const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); - dragData.defaultDropAction = "alias"; + dragData.defaultDropAction = 'alias'; dragData.canEmbed = true; DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, left, top, { hideSource: false }); return true; } return false; - } + }; _ref = React.createRef(); @observable _tooltipOpen: boolean = false; @@ -278,49 +359,60 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV get templateButton() { const view0 = this.view0; const views = this.props.views(); - return !view0 ? (null) : - Tap to Customize Layout. Drag an embeddable alias
    } open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom"> -
    !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} > - - this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} - content={!this._aliasDown ? (null) : -
    v).map(v => v as DocumentView)} />
    }> -
    + return !view0 ? null : ( + Tap to Customize Layout. Drag an embeddable alias
    } open={this._tooltipOpen} onClose={action(() => (this._tooltipOpen = false))} placement="bottom"> +
    !this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))}> + (this._aliasDown = true))} + onClose={action(() => (this._aliasDown = false))} + content={ + !this._aliasDown ? null : ( +
    + {' '} + v).map(v => v as DocumentView)} /> +
    + ) + }> +
    {}
    - ; + + ); } openContextMenu = (e: React.MouseEvent) => { let child = SelectionManager.Views()[0].ContentDiv!.children[0]; while (child.children.length) { - const next = Array.from(child.children).find(c => c.className?.toString().includes("SVGAnimatedString") || typeof (c.className) === "string"); + const next = Array.from(child.children).find(c => c.className?.toString().includes('SVGAnimatedString') || typeof c.className === 'string'); if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; if (next?.className?.toString().includes(DashFieldView.name)) break; if (next) child = next; else break; } simulateMouseClick(child, e.clientX, e.clientY - 30, e.screenX, e.screenY - 30); - } + }; render() { - if (!this.view0) return (null); + if (!this.view0) return null; const isText = this.view0.props.Document[this.view0.LayoutFieldKey] instanceof RichTextField; const doc = this.view0?.props.Document; const considerPull = isText && this.considerGoogleDocsPull; const considerPush = isText && this.considerGoogleDocsPush; - return
    -
    - -
    - {(DocumentLinksButton.StartLink || Doc.UserDoc()["documentLinksButton-fullMenu"]) && DocumentLinksButton.StartLink !== doc ?
    - -
    : (null)} - {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
    + return ( +
    +
    + +
    + {(DocumentLinksButton.StartLink || Doc.UserDoc()['documentLinksButton-fullMenu']) && DocumentLinksButton.StartLink !== doc ? ( +
    + +
    + ) : null} + {/*!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
    {this.templateButton}
    /*
    @@ -329,27 +421,22 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
    {this.contextButton}
    */} - {!SelectionManager.Views()?.some(v => v.allLinks.length) ? (null) :
    - {this.followLinkButton} -
    } -
    - {this.pinButton} -
    - {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
    - {this.shareButton} -
    } - {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) :
    - {this.considerGoogleDocsPush} -
    } -
    - {this.considerGoogleDocsPull} -
    -
    - {this.menuButton} -
    - {/* {Doc.noviceMode ? (null) :
    + {!SelectionManager.Views()?.some(v => v.allLinks.length) ? null :
    {this.followLinkButton}
    } +
    {this.pinButton}
    + {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null :
    {this.shareButton}
    } + {!Doc.UserDoc()['documentLinksButton-fullMenu'] ? null : ( +
    + {this.considerGoogleDocsPush} +
    + )} +
    + {this.considerGoogleDocsPull} +
    +
    {this.menuButton}
    + {/* {Doc.noviceMode ? (null) :
    {this.moreButton}
    } */} -
    ; +
    + ); } -} \ No newline at end of file +} diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9544c588b..c55daca3f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -7,13 +7,12 @@ import { DateField } from '../../fields/DateField'; import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc'; import { Document } from '../../fields/documentSchemas'; import { InkField } from '../../fields/InkField'; -import { ComputedField, ScriptField } from '../../fields/ScriptField'; -import { Cast, FieldValue, NumCast, StrCast } from '../../fields/Types'; +import { ScriptField } from '../../fields/ScriptField'; +import { Cast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; -import { emptyFunction, returnFalse, setupMoveUpEvents, numberValue, numbersAlmostEqual } from '../../Utils'; +import { emptyFunction, numberValue, returnFalse, setupMoveUpEvents } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; -import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { DragManager } from '../util/DragManager'; import { SelectionManager } from '../util/SelectionManager'; import { SnappingManager } from '../util/SnappingManager'; @@ -22,7 +21,7 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; -import { KeyManager } from './GlobalKeyHandler'; +import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; @@ -30,7 +29,6 @@ import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); -import { Colors } from './global/globalEnums'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -105,10 +103,10 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P if (titleFieldKey === 'title') { d.dataDoc['title-custom'] = !this._accumulatedTitle.startsWith('-'); if (StrCast(d.rootDoc.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) { - Doc.RemoveDocFromList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); + Doc.RemoveDocFromList(Doc.MyPublishedDocs, undefined, d.rootDoc); } if (!StrCast(d.rootDoc.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) { - Doc.AddDocToList(CurrentUserUtils.MyPublishedDocs, undefined, d.rootDoc); + Doc.AddDocToList(Doc.MyPublishedDocs, undefined, d.rootDoc); } } //@ts-ignore @@ -266,7 +264,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P }; onSelectorClick = () => SelectionManager.Views()?.[0]?.props.ContainingCollectionView?.props.select(false); - + /** * Handles setting up events when user clicks on the border radius editor * @param e PointerEvent @@ -284,21 +282,20 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const y = this.Bounds.y + 3; const maxDist = Math.min((this.Bounds.r - this.Bounds.x) / 2, (this.Bounds.b - this.Bounds.y) / 2); let dist = Math.sqrt((e.clientX - x) * (e.clientX - x) + (e.clientY - y) * (e.clientY - y)); - if (e.clientX < x && e.clientY < y) dist = 0 + if (e.clientX < x && e.clientY < y) dist = 0; SelectionManager.Views() .map(dv => dv.props.Document) .map(doc => { - const docMax = Math.min(NumCast(doc.width)/2, NumCast(doc.height)/2); - const ratio = dist/maxDist; + const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2); + const ratio = dist / maxDist; const radius = Math.min(1, ratio) * docMax; doc.borderRounding = `${radius}px`; - } - ); + }); return false; }, // moveEvent action(e => { this._isRounding = false; - this._resizeUndo?.end() + this._resizeUndo?.end(); }), // upEvent e => {} // clickEvent ); @@ -325,8 +322,8 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // Rotation between -360 and 360 let newRotation = (oldRotation - (angle * 180) / Math.PI) % 360; - const diff = Math.round(newRotation / 45) - newRotation / 45 - if (diff < .05) { + const diff = Math.round(newRotation / 45) - newRotation / 45; + if (diff < 0.05) { console.log('show lines'); } dv.rootDoc._jitterRotation = newRotation; @@ -337,12 +334,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P action(() => { SelectionManager.Views().forEach(dv => { const oldRotation = NumCast(dv.rootDoc._jitterRotation); - const diff = Math.round(oldRotation / 45) - oldRotation / 45 - if (diff < .05) { + const diff = Math.round(oldRotation / 45) - oldRotation / 45; + if (diff < 0.05) { let newRotation = Math.round(oldRotation / 45) * 45; dv.rootDoc._jitterRotation = newRotation; } - }) + }); this._isRotating = false; rotateUndo?.end(); UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); @@ -599,13 +596,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P // hide the decorations if the parent chooses to hide it or if the document itself hides it const hideResizers = seldoc.props.hideResizeHandles || seldoc.rootDoc.hideResizeHandles || seldoc.rootDoc._isGroup || this._isRounding || this._isRotating; const hideTitle = seldoc.props.hideDecorationTitle || seldoc.rootDoc.hideDecorationTitle || this._isRounding || this._isRotating; - const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar || this._isRounding || - this._isRotating; + const hideDocumentButtonBar = seldoc.props.hideDocumentButtonBar || seldoc.rootDoc.hideDocumentButtonBar || this._isRounding || this._isRotating; // if multiple documents have been opened at the same time, then don't show open button const hideOpenButton = - seldoc.props.hideOpenButton || seldoc.rootDoc.hideOpenButton || SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || this._isRounding || this._isRotating; + seldoc.props.hideOpenButton || + seldoc.rootDoc.hideOpenButton || + SelectionManager.Views().some(docView => docView.props.Document._stayInCollection || docView.props.Document.isGroup || docView.props.Document.hideOpenButton) || + this._isRounding || + this._isRotating; const hideDeleteButton = - this._isRounding || + this._isRounding || this._isRotating || seldoc.props.hideDeleteButton || seldoc.rootDoc.hideDeleteButton || @@ -635,7 +635,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P ); - const colorScheme = StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); + const colorScheme = StrCast(Doc.ActiveDashboard?.colorScheme); const titleArea = hideTitle ? null : this._editingTitle ? ( e.preventDefault()} />
    e.preventDefault()} /> - {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} + {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? null : topBtn('selector', 'arrow-alt-circle-up', undefined, this.onSelectorClick, 'tap to select containing document')} )} @@ -733,11 +732,16 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P )} {useRounding && ( -
    e.preventDefault()} + left: `${radiusHandleLocation + 3}`, + top: `${radiusHandleLocation + 23}`, + }} + className={`documentDecorations-borderRadius`} + onPointerDown={this.onRadiusDown} + onContextMenu={e => e.preventDefault()} /> )} diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index e960f5cca..5a68e9091 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -1,37 +1,52 @@ -import React = require("react"); +import React = require('react'); import * as fitCurve from 'fit-curve'; -import { action, computed, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc } from "../../fields/Doc"; -import { InkData, InkTool } from "../../fields/InkField"; -import { Cast, FieldValue, NumCast } from "../../fields/Types"; -import MobileInkOverlay from "../../mobile/MobileInkOverlay"; -import { GestureUtils } from "../../pen-gestures/GestureUtils"; -import { MobileInkOverlayContent } from "../../server/Message"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils"; -import { CognitiveServices } from "../cognitive_services/CognitiveServices"; -import { DocUtils } from "../documents/Documents"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { InteractionUtils } from "../util/InteractionUtils"; -import { ScriptingGlobals } from "../util/ScriptingGlobals"; -import { SelectionManager } from "../util/SelectionManager"; -import { Transform } from "../util/Transform"; -import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu"; -import "./GestureOverlay.scss"; -import { ActiveArrowEnd, ActiveArrowScale, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, SetActiveArrowStart, SetActiveDash, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "./InkingStroke"; -import { checkInksToGroup, createInkGroup } from "./nodes/button/FontIconBox"; -import { DocumentView } from "./nodes/DocumentView"; -import { RadialMenu } from "./nodes/RadialMenu"; -import HorizontalPalette from "./Palette"; -import { Touchable } from "./Touchable"; -import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; -import { InkTranscription } from "./InkTranscription"; +import { action, computed, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../fields/Doc'; +import { InkData, InkTool } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { ScriptField } from '../../fields/ScriptField'; +import { Cast, FieldValue, NumCast } from '../../fields/Types'; +import MobileInkOverlay from '../../mobile/MobileInkOverlay'; +import { GestureUtils } from '../../pen-gestures/GestureUtils'; +import { MobileInkOverlayContent } from '../../server/Message'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; +import { CognitiveServices } from '../cognitive_services/CognitiveServices'; +import { Docs, DocUtils } from '../documents/Documents'; +import { InteractionUtils } from '../util/InteractionUtils'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { SelectionManager } from '../util/SelectionManager'; +import { Transform } from '../util/Transform'; +import { CollectionFreeFormViewChrome } from './collections/CollectionMenu'; +import './GestureOverlay.scss'; +import { + ActiveArrowEnd, + ActiveArrowScale, + ActiveArrowStart, + ActiveDash, + ActiveFillColor, + ActiveInkBezierApprox, + ActiveInkColor, + ActiveInkWidth, + SetActiveArrowStart, + SetActiveDash, + SetActiveFillColor, + SetActiveInkColor, + SetActiveInkWidth, +} from './InkingStroke'; +import { InkTranscription } from './InkTranscription'; +import { checkInksToGroup } from './nodes/button/FontIconBox'; +import { DocumentView } from './nodes/DocumentView'; +import { RadialMenu } from './nodes/RadialMenu'; +import HorizontalPalette from './Palette'; +import { Touchable } from './Touchable'; +import TouchScrollableMenu, { TouchScrollableMenuItem } from './TouchScrollableMenu'; @observer export class GestureOverlay extends Touchable { static Instance: GestureOverlay; - @observable public InkShape: string = ""; + @observable public InkShape: string = ''; @observable public SavedColor?: string; @observable public SavedWidth?: number; @observable public Tool: ToolglassTools = ToolglassTools.None; @@ -42,14 +57,18 @@ export class GestureOverlay extends Touchable { @observable private _menuX: number = -300; @observable private _menuY: number = -300; @observable private _pointerY?: number; - @observable private _points: { X: number, Y: number }[] = []; + @observable private _points: { X: number; Y: number }[] = []; @observable private _strokes: InkData[] = []; @observable private _palette?: JSX.Element; @observable private _clipboardDoc?: JSX.Element; @observable private _possibilities: JSX.Element[] = []; - @computed private get height(): number { return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100); } - @computed private get showBounds() { return this.Tool !== ToolglassTools.None; } + @computed private get height(): number { + return 2 * Math.max(this._pointerY && this._thumbY ? this._thumbY - this._pointerY : 100, 100); + } + @computed private get showBounds() { + return this.Tool !== ToolglassTools.None; + } @observable private showMobileInkOverlay: boolean = false; @@ -70,10 +89,73 @@ export class GestureOverlay extends Touchable { GestureOverlay.Instance = this; } + static setupThumbButtons(doc: Doc) { + const docProtoData: { title: string; icon: string; drag?: string; ignoreClick?: boolean; pointerDown?: string; pointerUp?: string; clipboard?: Doc; backgroundColor?: string; dragFactory?: Doc }[] = [ + { title: 'use pen', icon: 'pen-nib', pointerUp: 'resetPen()', pointerDown: 'setPen(2, this.backgroundColor)', backgroundColor: 'blue' }, + { title: 'use highlighter', icon: 'highlighter', pointerUp: 'resetPen()', pointerDown: 'setPen(20, this.backgroundColor)', backgroundColor: 'yellow' }, + { + title: 'notepad', + icon: 'clipboard', + pointerUp: 'GestureOverlay.Instance.closeFloatingDoc()', + pointerDown: 'GestureOverlay.Instance.openFloatingDoc(this.clipboard)', + clipboard: Docs.Create.FreeformDocument([], { _width: 300, _height: 300, system: true }), + backgroundColor: 'orange', + }, + { title: 'interpret text', icon: 'font', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('inktotext')", backgroundColor: 'orange' }, + { title: 'ignore gestures', icon: 'signature', pointerUp: "setToolglass('none')", pointerDown: "setToolglass('ignoregesture')", backgroundColor: 'green' }, + ]; + return docProtoData.map(data => + Docs.Create.FontIconDocument({ + _nativeWidth: 10, + _nativeHeight: 10, + _width: 10, + _height: 10, + title: data.title, + icon: data.icon, + _dropAction: data.pointerDown ? 'copy' : undefined, + ignoreClick: data.ignoreClick, + onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, + clipboard: data.clipboard, + onPointerUp: data.pointerUp ? ScriptField.MakeScript(data.pointerUp) : undefined, + onPointerDown: data.pointerDown ? ScriptField.MakeScript(data.pointerDown) : undefined, + backgroundColor: data.backgroundColor, + _removeDropProperties: new List(['dropAction']), + dragFactory: data.dragFactory, + system: true, + }) + ); + } + + static setupThumbDoc(userDoc: Doc) { + if (!userDoc.thumbDoc) { + const thumbDoc = Docs.Create.LinearDocument(GestureOverlay.setupThumbButtons(userDoc), { + _width: 100, + _height: 50, + ignoreClick: true, + _lockedPosition: true, + title: 'buttons', + _autoHeight: true, + _yMargin: 5, + linearViewIsExpanded: true, + backgroundColor: 'white', + system: true, + }); + thumbDoc.inkToTextDoc = Docs.Create.LinearDocument([], { + _width: 300, + _height: 25, + _autoHeight: true, + linearViewIsExpanded: true, + flexDirection: 'column', + system: true, + }); + userDoc.thumbDoc = thumbDoc; + } + return Cast(userDoc.thumbDoc, Doc); + } componentDidMount = () => { - this._thumbDoc = FieldValue(Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc)); + this._thumbDoc = FieldValue(Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc)); this._inkToTextDoc = FieldValue(Cast(this._thumbDoc?.inkToTextDoc, Doc)); - } + }; // TODO: nda - add dragging groups with one finger drag and have to click into group to scroll within the group @@ -84,24 +166,24 @@ export class GestureOverlay extends Touchable { const ntt: (React.Touch | Touch)[] = Array.from(e.targetTouches); const nct: (React.Touch | Touch)[] = Array.from(e.changedTouches); const nt: (React.Touch | Touch)[] = Array.from(e.touches); - this._hands.forEach((hand) => { + this._hands.forEach(hand => { for (let i = 0; i < e.targetTouches.length; i++) { const pt = e.targetTouches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { + if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { ntt.splice(ntt.indexOf(pt), 1); } } for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { + if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { nct.splice(nct.indexOf(pt), 1); } } for (let i = 0; i < e.touches.length; i++) { const pt = e.touches.item(i); - if (pt && hand.some((finger) => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { + if (pt && hand.some(finger => finger.screenX === pt.screenX && finger.screenY === pt.screenY)) { nt.splice(nt.indexOf(pt), 1); } } @@ -110,8 +192,8 @@ export class GestureOverlay extends Touchable { } onReactTouchStart = (te: React.TouchEvent) => { - document.removeEventListener("touchmove", this.onReactHoldTouchMove); - document.removeEventListener("touchend", this.onReactHoldTouchEnd); + document.removeEventListener('touchmove', this.onReactHoldTouchMove); + document.removeEventListener('touchend', this.onReactHoldTouchEnd); if (RadialMenu.Instance?._display === true) { te.preventDefault(); te.stopPropagation(); @@ -129,7 +211,7 @@ export class GestureOverlay extends Touchable { // and this seems to be the only way of differentiating pen and touch on touch events if (pt.radiusX > 1 && pt.radiusY > 1) { InkTranscription.Instance.createInkGroup(); - CurrentUserUtils.ActiveTool = InkTool.None; + Doc.ActiveTool = InkTool.None; this.prevPoints.set(pt.identifier, pt); } } @@ -147,18 +229,16 @@ export class GestureOverlay extends Touchable { if (nts.nt.length < 5) { const target = document.elementFromPoint(te.changedTouches.item(0).clientX, te.changedTouches.item(0).clientY); target?.dispatchEvent( - new CustomEvent>("dashOnTouchStart", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: te - } - } - ) + new CustomEvent>('dashOnTouchStart', { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: te, + }, + }) ); if (nts.nt.length === 1) { // -- radial menu code -- @@ -167,45 +247,41 @@ export class GestureOverlay extends Touchable { const pt: any = te.touches[te.touches?.length - 1]; if (nts.nt.length === 1 && pt.radiusX > 1 && pt.radiusY > 1) { target?.dispatchEvent( - new CustomEvent>("dashOnTouchHoldStart", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: te - } - } - ) + new CustomEvent>('dashOnTouchHoldStart', { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: te, + }, + }) ); this._holdTimer = undefined; - document.removeEventListener("touchmove", this.onReactTouchMove); - document.removeEventListener("touchend", this.onReactTouchEnd); - document.removeEventListener("touchmove", this.onReactHoldTouchMove); - document.removeEventListener("touchend", this.onReactHoldTouchEnd); - document.addEventListener("touchmove", this.onReactHoldTouchMove); - document.addEventListener("touchend", this.onReactHoldTouchEnd); + document.removeEventListener('touchmove', this.onReactTouchMove); + document.removeEventListener('touchend', this.onReactTouchEnd); + document.removeEventListener('touchmove', this.onReactHoldTouchMove); + document.removeEventListener('touchend', this.onReactHoldTouchEnd); + document.addEventListener('touchmove', this.onReactHoldTouchMove); + document.addEventListener('touchend', this.onReactHoldTouchEnd); } - - }, (500)); - } - else { + }, 500); + } else { this._holdTimer && clearTimeout(this._holdTimer); } - document.removeEventListener("touchmove", this.onReactTouchMove); - document.removeEventListener("touchend", this.onReactTouchEnd); - document.addEventListener("touchmove", this.onReactTouchMove); - document.addEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener('touchmove', this.onReactTouchMove); + document.removeEventListener('touchend', this.onReactTouchEnd); + document.addEventListener('touchmove', this.onReactTouchMove); + document.addEventListener('touchend', this.onReactTouchEnd); } // otherwise, handle as a hand event else { this.handleHandDown(te); - document.removeEventListener("touchmove", this.onReactTouchMove); - document.removeEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener('touchmove', this.onReactTouchMove); + document.removeEventListener('touchend', this.onReactTouchEnd); } - } + }; onReactTouchMove = (e: TouchEvent) => { const nts: any = this.getNewTouches(e); @@ -213,19 +289,18 @@ export class GestureOverlay extends Touchable { this._holdTimer = undefined; document.dispatchEvent( - new CustomEvent>("dashOnTouchMove", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: e - } - }) + new CustomEvent>('dashOnTouchMove', { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e, + }, + }) ); - } + }; onReactTouchEnd = (e: TouchEvent) => { const nts: any = this.getNewTouches(e); @@ -233,17 +308,16 @@ export class GestureOverlay extends Touchable { this._holdTimer = undefined; document.dispatchEvent( - new CustomEvent>("dashOnTouchEnd", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: e - } - }) + new CustomEvent>('dashOnTouchEnd', { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e, + }, + }) ); // cleanup any lingering pointers @@ -257,11 +331,11 @@ export class GestureOverlay extends Touchable { } if (this.prevPoints.size === 0) { - document.removeEventListener("touchmove", this.onReactTouchMove); - document.removeEventListener("touchend", this.onReactTouchEnd); + document.removeEventListener('touchmove', this.onReactTouchMove); + document.removeEventListener('touchend', this.onReactTouchEnd); } e.stopPropagation(); - } + }; handleHandDown = async (e: React.TouchEvent) => { this._holdTimer && clearTimeout(this._holdTimer); @@ -285,17 +359,17 @@ export class GestureOverlay extends Touchable { } // this chunk of code determines whether this is a left hand or a right hand, as well as which pointer is the thumb and pointer - const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); + const thumb = fingers.reduce((a, v) => (a.clientY > v.clientY ? a : v), fingers[0]); const rightMost = Math.max(...fingers.map(f => f.clientX)); const leftMost = Math.min(...fingers.map(f => f.clientX)); let pointer: React.Touch | undefined; // left hand if (thumb.clientX === rightMost) { - pointer = fingers.reduce((a, v) => a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v); + pointer = fingers.reduce((a, v) => (a.clientX > v.clientX || v.identifier === thumb.identifier ? a : v)); } // right hand else if (thumb.clientX === leftMost) { - pointer = fingers.reduce((a, v) => a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v); + pointer = fingers.reduce((a, v) => (a.clientX < v.clientX || v.identifier === thumb.identifier ? a : v)); } this.pointerIdentifier = pointer?.identifier; @@ -316,7 +390,7 @@ export class GestureOverlay extends Touchable { const minY = Math.min(...others.map(f => f.clientY)); // load up the palette collection around the thumb - const thumbDoc = await Cast(CurrentUserUtils.setupThumbDoc(CurrentUserUtils.UserDocument), Doc); + const thumbDoc = await Cast(GestureOverlay.setupThumbDoc(Doc.UserDoc()), Doc); if (thumbDoc) { runInAction(() => { RadialMenu.Instance._display = false; @@ -331,11 +405,11 @@ export class GestureOverlay extends Touchable { } this.removeMoveListeners(); - document.removeEventListener("touchmove", this.handleHandMove); - document.addEventListener("touchmove", this.handleHandMove); - document.removeEventListener("touchend", this.handleHandUp); - document.addEventListener("touchend", this.handleHandUp); - } + document.removeEventListener('touchmove', this.handleHandMove); + document.addEventListener('touchmove', this.handleHandMove); + document.removeEventListener('touchend', this.handleHandUp); + document.addEventListener('touchend', this.handleHandUp); + }; @action handleHandMove = (e: TouchEvent) => { @@ -348,18 +422,20 @@ export class GestureOverlay extends Touchable { const tPt = e.targetTouches.item(j); if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) { if (pt && this.prevPoints.has(pt.identifier)) { - this._hands.forEach(hand => hand.some(f => { - if (f.identifier === pt.identifier) { - fingers.push(pt); - } - })); + this._hands.forEach(hand => + hand.some(f => { + if (f.identifier === pt.identifier) { + fingers.push(pt); + } + }) + ); } } } } } // update hand trackers - const thumb = fingers.reduce((a, v) => a.clientY > v.clientY ? a : v, fingers[0]); + const thumb = fingers.reduce((a, v) => (a.clientY > v.clientY ? a : v), fingers[0]); if (thumb?.identifier && thumb?.identifier === this.thumbIdentifier) { this._hands.set(thumb.identifier, fingers); } @@ -373,12 +449,11 @@ export class GestureOverlay extends Touchable { // moving a thumb horiz. changes the palette collection selection, moving vert. changes the selection of any menus on the current palette item const yOverX = Math.abs(pt.clientX - this._thumbX) < Math.abs(pt.clientY - this._thumbY); if ((yOverX && this._inkToTextDoc) || this._selectedIndex > -1) { - if (Math.abs(pt.clientY - this._thumbY) > (10 * window.devicePixelRatio)) { - this._selectedIndex = Math.min(Math.max(-1, (-Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1)), this._possibilities.length - 1); + if (Math.abs(pt.clientY - this._thumbY) > 10 * window.devicePixelRatio) { + this._selectedIndex = Math.min(Math.max(-1, -Math.ceil((pt.clientY - this._thumbY) / (10 * window.devicePixelRatio)) - 1), this._possibilities.length - 1); } - } - else if (this._thumbDoc) { - if (Math.abs(pt.clientX - this._thumbX) > (15 * window.devicePixelRatio)) { + } else if (this._thumbDoc) { + if (Math.abs(pt.clientX - this._thumbX) > 15 * window.devicePixelRatio) { this._thumbDoc.selectedIndex = Math.max(-1, NumCast(this._thumbDoc.selectedIndex) - Math.sign(pt.clientX - this._thumbX)); this._thumbX = pt.clientX; } @@ -390,7 +465,7 @@ export class GestureOverlay extends Touchable { this._pointerY = pt.clientY; } } - } + }; @action handleHandUp = (e: TouchEvent) => { @@ -422,38 +497,37 @@ export class GestureOverlay extends Touchable { this._strokes = []; this._points = []; this._possibilities = []; - document.removeEventListener("touchend", this.handleHandUp); + document.removeEventListener('touchend', this.handleHandUp); } - } + }; /** * Code for radial menu */ onReactHoldTouchMove = (e: TouchEvent) => { - document.removeEventListener("touchmove", this.onReactTouchMove); - document.removeEventListener("touchend", this.onReactTouchEnd); - document.removeEventListener("touchmove", this.onReactHoldTouchMove); - document.removeEventListener("touchend", this.onReactHoldTouchEnd); - document.addEventListener("touchmove", this.onReactHoldTouchMove); - document.addEventListener("touchend", this.onReactHoldTouchEnd); + document.removeEventListener('touchmove', this.onReactTouchMove); + document.removeEventListener('touchend', this.onReactTouchEnd); + document.removeEventListener('touchmove', this.onReactHoldTouchMove); + document.removeEventListener('touchend', this.onReactHoldTouchEnd); + document.addEventListener('touchmove', this.onReactHoldTouchMove); + document.addEventListener('touchend', this.onReactHoldTouchEnd); const nts: any = this.getNewTouches(e); if (this.prevPoints.size === 1 && this._holdTimer) { clearTimeout(this._holdTimer); } document.dispatchEvent( - new CustomEvent>("dashOnTouchHoldMove", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: e - } - }) + new CustomEvent>('dashOnTouchHoldMove', { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e, + }, + }) ); - } + }; /** * Code for radial menu @@ -465,17 +539,16 @@ export class GestureOverlay extends Touchable { this._holdTimer = undefined; } document.dispatchEvent( - new CustomEvent>("dashOnTouchHoldEnd", - { - bubbles: true, - detail: { - fingers: this.prevPoints.size, - targetTouches: nts.ntt, - touches: nts.nt, - changedTouches: nts.nct, - touchEvent: e - } - }) + new CustomEvent>('dashOnTouchHoldEnd', { + bubbles: true, + detail: { + fingers: this.prevPoints.size, + targetTouches: nts.ntt, + touches: nts.nt, + changedTouches: nts.nct, + touchEvent: e, + }, + }) ); for (let i = 0; i < e.changedTouches.length; i++) { const pt = e.changedTouches.item(i); @@ -486,32 +559,38 @@ export class GestureOverlay extends Touchable { } } - document.removeEventListener("touchmove", this.onReactHoldTouchMove); - document.removeEventListener("touchend", this.onReactHoldTouchEnd); + document.removeEventListener('touchmove', this.onReactHoldTouchMove); + document.removeEventListener('touchend', this.onReactHoldTouchEnd); e.stopPropagation(); - } + }; @action onPointerDown = (e: React.PointerEvent) => { if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => { - if (doubleTap) { - InkTranscription.Instance.createInkGroup(); - CurrentUserUtils.ActiveTool = InkTool.None; - return; - } - })); + setupMoveUpEvents( + this, + e, + returnFalse, + returnFalse, + action((e: PointerEvent, doubleTap?: boolean) => { + if (doubleTap) { + InkTranscription.Instance.createInkGroup(); + Doc.ActiveTool = InkTool.None; + return; + } + }) + ); } - if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { + if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { - CurrentUserUtils.ActiveTool = InkTool.Write; + Doc.ActiveTool = InkTool.Write; } this._points.push({ X: e.clientX, Y: e.clientY }); setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); - // if (CurrentUserUtils.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); + // if (Doc.ActiveTool === InkTool.Highlighter) SetActiveInkColor("rgba(245, 230, 95, 0.75)"); } - } + }; @action onPointerMove = (e: PointerEvent) => { @@ -519,17 +598,18 @@ export class GestureOverlay extends Touchable { if (this._points.length > 1) { const B = this.svgBounds; - const initialPoint = this._points[0.]; + const initialPoint = this._points[0]; const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { switch (this.Tool) { - case ToolglassTools.RadialMenu: return true; + case ToolglassTools.RadialMenu: + return true; } } } return false; - } + }; handleLineGesture = (): boolean => { const actionPerformed = false; @@ -541,19 +621,18 @@ export class GestureOverlay extends Touchable { const target1 = document.elementFromPoint(ep1.X, ep1.Y); const target2 = document.elementFromPoint(ep2.X, ep2.Y); - const ge = new CustomEvent("dashOnGesture", - { - bubbles: true, - detail: { - points: this._points, - gesture: GestureUtils.Gestures.Line, - bounds: B - } - }); + const ge = new CustomEvent('dashOnGesture', { + bubbles: true, + detail: { + points: this._points, + gesture: GestureUtils.Gestures.Line, + bounds: B, + }, + }); target1?.dispatchEvent(ge); target2?.dispatchEvent(ge); return actionPerformed; - } + }; @action onPointerUp = (e: PointerEvent) => { @@ -563,9 +642,9 @@ export class GestureOverlay extends Touchable { //push first points to so interactionUtil knows pointer is up this._points.push({ X: this._points[0].X, Y: this._points[0].Y }); - const initialPoint = this._points[0.]; - const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + (this.height); - const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - (this.height) && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); + const initialPoint = this._points[0]; + const xInGlass = initialPoint.X > (this._thumbX ?? Number.MAX_SAFE_INTEGER) && initialPoint.X < (this._thumbX ?? Number.MAX_SAFE_INTEGER) + this.height; + const yInGlass = initialPoint.Y > (this._thumbY ?? Number.MAX_SAFE_INTEGER) - this.height && initialPoint.Y < (this._thumbY ?? Number.MAX_SAFE_INTEGER); // if a toolglass is selected and the stroke starts within the toolglass boundaries if (this.Tool !== ToolglassTools.None && xInGlass && yInGlass) { @@ -573,8 +652,8 @@ export class GestureOverlay extends Touchable { case ToolglassTools.InkToText: this._strokes.push(new Array(...this._points)); this._points = []; - CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then((results) => { - const wordResults = results.filter((r: any) => r.category === "line"); + CognitiveServices.Inking.Appliers.InterpretStrokes(this._strokes).then(results => { + const wordResults = results.filter((r: any) => r.category === 'line'); const possibilities: string[] = []; for (const wR of wordResults) { if (wR?.recognizedText) { @@ -588,8 +667,7 @@ export class GestureOverlay extends Touchable { // if we receive any word results from cognitive services, display them runInAction(() => { - this._possibilities = possibilities.map(p => - GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />); + this._possibilities = possibilities.map(p => GestureOverlay.Instance.dispatchGesture(GestureUtils.Gestures.Text, [{ X: l, Y: t }], p)} />); }); }); break; @@ -605,47 +683,66 @@ export class GestureOverlay extends Touchable { this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; if (!CollectionFreeFormViewChrome.Instance?._keepPrimitiveMode) { - this.InkShape = ""; - CurrentUserUtils.ActiveTool = InkTool.None; + this.InkShape = ''; + Doc.ActiveTool = InkTool.None; } } // if we're not drawing in a toolglass try to recognize as gesture - else { // need to decide when to turn gestures back on + else { + // need to decide when to turn gestures back on const result = points.length > 2 && GestureUtils.GestureRecognizer.Recognize(new Array(points)); let actionPerformed = false; if (Doc.UserDoc().recognizeGestures && result && result.Score > 0.7) { switch (result.Name) { - case GestureUtils.Gestures.Box: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box); break; - case GestureUtils.Gestures.StartBracket: actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket); break; - case GestureUtils.Gestures.EndBracket: actionPerformed = this.dispatchGesture("endbracket"); break; - case GestureUtils.Gestures.Line: actionPerformed = this.handleLineGesture(); break; - case GestureUtils.Gestures.Triangle: actionPerformed = this.makePolygon("triangle", true); break; - case GestureUtils.Gestures.Circle: actionPerformed = this.makePolygon("circle", true); break; - case GestureUtils.Gestures.Rectangle: actionPerformed = this.makePolygon("rectangle", true); break; - case GestureUtils.Gestures.Scribble: console.log("scribble"); break; + case GestureUtils.Gestures.Box: + actionPerformed = this.dispatchGesture(GestureUtils.Gestures.Box); + break; + case GestureUtils.Gestures.StartBracket: + actionPerformed = this.dispatchGesture(GestureUtils.Gestures.StartBracket); + break; + case GestureUtils.Gestures.EndBracket: + actionPerformed = this.dispatchGesture('endbracket'); + break; + case GestureUtils.Gestures.Line: + actionPerformed = this.handleLineGesture(); + break; + case GestureUtils.Gestures.Triangle: + actionPerformed = this.makePolygon('triangle', true); + break; + case GestureUtils.Gestures.Circle: + actionPerformed = this.makePolygon('circle', true); + break; + case GestureUtils.Gestures.Rectangle: + actionPerformed = this.makePolygon('rectangle', true); + break; + case GestureUtils.Gestures.Scribble: + console.log('scribble'); + break; } } // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document if (!actionPerformed) { - const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); + const newPoints = this._points.reduce((p, pts) => { + p.push([pts.X, pts.Y]); + return p; + }, [] as number[][]); newPoints.pop(); - const controlPoints: { X: number, Y: number }[] = []; + const controlPoints: { X: number; Y: number }[] = []; const bezierCurves = fitCurve(newPoints, 10); for (const curve of bezierCurves) { - controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); - } - const dist = Math.sqrt((controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + - (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y)); + const dist = Math.sqrt( + (controlPoints[0].X - controlPoints.lastElement().X) * (controlPoints[0].X - controlPoints.lastElement().X) + (controlPoints[0].Y - controlPoints.lastElement().Y) * (controlPoints[0].Y - controlPoints.lastElement().Y) + ); if (controlPoints.length > 4 && dist < 10) controlPoints[controlPoints.length - 1] = controlPoints[0]; this._points = controlPoints; - this.dispatchGesture(GestureUtils.Gestures.Stroke); + this.dispatchGesture(GestureUtils.Gestures.Stroke); // TODO: nda - check inks to group here checkInksToGroup(); } @@ -655,7 +752,7 @@ export class GestureOverlay extends Touchable { this._points = []; } CollectionFreeFormViewChrome.Instance?.primCreated(); - } + }; makePolygon = (shape: string, gesture: boolean) => { //take off gesture recognition for now @@ -673,11 +770,15 @@ export class GestureOverlay extends Touchable { var lastx = this._points[this._points.length - 2].X; var lasty = this._points[this._points.length - 2].Y; var fourth = (lastx - firstx) / 4; - if (isNaN(fourth) || fourth === 0) { fourth = 0.01; } + if (isNaN(fourth) || fourth === 0) { + fourth = 0.01; + } var m = (lasty - firsty) / (lastx - firstx); - if (isNaN(m) || m === 0) { m = 0.01; } + if (isNaN(m) || m === 0) { + m = 0.01; + } const b = firsty - m * firstx; - if (shape === "noRec") { + if (shape === 'noRec') { return false; } if (!gesture) { @@ -687,7 +788,7 @@ export class GestureOverlay extends Touchable { left = this._points[0].X; bottom = this._points[this._points.length - 2].Y; top = this._points[0].Y; - if (shape !== "arrow" && shape !== "line" && shape !== "circle") { + if (shape !== 'arrow' && shape !== 'line' && shape !== 'circle') { if (left > right) { const temp = right; right = left; @@ -704,7 +805,7 @@ export class GestureOverlay extends Touchable { switch (shape) { //must push an extra point in the end so InteractionUtils knows pointer is up. //must be (points[0].X,points[0]-1) - case "rectangle": + case 'rectangle': this._points.push({ X: left, Y: top }); this._points.push({ X: left, Y: top }); this._points.push({ X: right, Y: top }); @@ -727,7 +828,7 @@ export class GestureOverlay extends Touchable { break; - case "triangle": + case 'triangle': this._points.push({ X: left, Y: bottom }); this._points.push({ X: left, Y: bottom }); @@ -744,9 +845,8 @@ export class GestureOverlay extends Touchable { this._points.push({ X: left, Y: bottom }); this._points.push({ X: left, Y: bottom }); - break; - case "circle": + case 'circle': // Approximation of a circle using 4 Bézier curves in which the constant "c" reduces the maximum radial drift to 0.019608%, // making the curves indistinguishable from a circle. // Source: https://spencermortensen.com/articles/bezier-circle/ @@ -755,30 +855,30 @@ export class GestureOverlay extends Touchable { const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2; const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom)); - // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve. + // Dividing the circle into four equal sections, and fitting each section to a cubic Bézier curve. this._points.push({ X: centerX, Y: centerY + radius }); - this._points.push({ X: centerX + (c * radius), Y: centerY + radius }); - this._points.push({ X: centerX + radius, Y: centerY + (c * radius) }); + this._points.push({ X: centerX + c * radius, Y: centerY + radius }); + this._points.push({ X: centerX + radius, Y: centerY + c * radius }); this._points.push({ X: centerX + radius, Y: centerY }); this._points.push({ X: centerX + radius, Y: centerY }); - this._points.push({ X: centerX + radius, Y: centerY - (c * radius) }); - this._points.push({ X: centerX + (c * radius), Y: centerY - radius }); + this._points.push({ X: centerX + radius, Y: centerY - c * radius }); + this._points.push({ X: centerX + c * radius, Y: centerY - radius }); this._points.push({ X: centerX, Y: centerY - radius }); this._points.push({ X: centerX, Y: centerY - radius }); - this._points.push({ X: centerX - (c * radius), Y: centerY - radius }); - this._points.push({ X: centerX - radius, Y: centerY - (c * radius) }); + this._points.push({ X: centerX - c * radius, Y: centerY - radius }); + this._points.push({ X: centerX - radius, Y: centerY - c * radius }); this._points.push({ X: centerX - radius, Y: centerY }); this._points.push({ X: centerX - radius, Y: centerY }); - this._points.push({ X: centerX - radius, Y: centerY + (c * radius) }); - this._points.push({ X: centerX - (c * radius), Y: centerY + radius }); + this._points.push({ X: centerX - radius, Y: centerY + c * radius }); + this._points.push({ X: centerX - c * radius, Y: centerY + radius }); this._points.push({ X: centerX, Y: centerY + radius }); break; - case "line": + case 'line': if (Math.abs(firstx - lastx) < 10 && Math.abs(firsty - lasty) > 10) { lastx = firstx; } @@ -791,12 +891,12 @@ export class GestureOverlay extends Touchable { this._points.push({ X: lastx, Y: lasty }); this._points.push({ X: lastx, Y: lasty }); break; - case "arrow": + case 'arrow': const x1 = left; const y1 = top; const x2 = right; const y2 = bottom; - const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + (Math.pow(Math.abs(y1 - y2), 2))); + const L1 = Math.sqrt(Math.pow(Math.abs(x1 - x2), 2) + Math.pow(Math.abs(y1 - y2), 2)); const L2 = L1 / 5; const angle = 0.785398; const x3 = x2 + (L2 / L1) * ((x1 - x2) * Math.cos(angle) + (y1 - y2) * Math.sin(angle)); @@ -811,24 +911,24 @@ export class GestureOverlay extends Touchable { // this._points.push({ X: x1, Y: y1 - 1 }); } return true; - } + }; - dispatchGesture = (gesture: "box" | "line" | "startbracket" | "endbracket" | "stroke" | "scribble" | "text", stroke?: InkData, data?: any) => { + dispatchGesture = (gesture: 'box' | 'line' | 'startbracket' | 'endbracket' | 'stroke' | 'scribble' | 'text', stroke?: InkData, data?: any) => { const target = document.elementFromPoint((stroke ?? this._points)[0].X, (stroke ?? this._points)[0].Y); - return target?.dispatchEvent( - new CustomEvent("dashOnGesture", - { + return ( + target?.dispatchEvent( + new CustomEvent('dashOnGesture', { bubbles: true, detail: { points: stroke ?? this._points, gesture: gesture as any, bounds: this.getBounds(stroke ?? this._points), text: data, - } - } - ) - ) || false; - } + }, + }) + ) || false + ); + }; getBounds = (stroke: InkData, pad?: boolean) => { const padding = pad ? [-20000, 20000] : []; @@ -839,7 +939,7 @@ export class GestureOverlay extends Touchable { const bottom = Math.max(...ys); const top = Math.min(...ys); return { right, left, bottom, top, width: right - left, height: bottom - top }; - } + }; @computed get svgBounds() { return this.getBounds(this._points); @@ -847,7 +947,7 @@ export class GestureOverlay extends Touchable { @computed get elements() { const selView = SelectionManager.Views().lastElement(); - const width = Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1) / (selView?.props.ScreenToLocalTransform().Scale || 1); + const width = (Number(ActiveInkWidth()) * NumCast(selView?.rootDoc._viewScale, 1)) / (selView?.props.ScreenToLocalTransform().Scale || 1); const rect = this._overlayRef.current?.getBoundingClientRect(); const B = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(this._points, true); B.left = B.left - width / 2; @@ -857,30 +957,74 @@ export class GestureOverlay extends Touchable { B.width += width; B.height += width; const fillColor = ActiveFillColor(); - const strokeColor = fillColor && fillColor !== "transparent" ? fillColor : ActiveInkColor(); + const strokeColor = fillColor && fillColor !== 'transparent' ? fillColor : ActiveInkColor(); return [ this.props.children, this._palette, - [this._strokes.map((l, i) => { - const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 };//this.getBounds(l, true); - return - {InteractionUtils.CreatePolyline(l, b.left, b.top, strokeColor, width, width, "miter", "round", - ActiveInkBezierApprox(), "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveArrowScale(), - ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} - ; - }), - this._points.length <= 1 ? (null) : - {InteractionUtils.CreatePolyline(this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), B.left, B.top, ActiveInkColor(), width, width, "miter", "round", "", - "none" /*ActiveFillColor()*/, ActiveArrowStart(), ActiveArrowEnd(), ActiveArrowScale(), ActiveDash(), 1, 1, this.InkShape, "none", 1.0, false)} - ] + [ + this._strokes.map((l, i) => { + const b = { left: -20000, right: 20000, top: -20000, bottom: 20000, width: 40000, height: 40000 }; //this.getBounds(l, true); + return ( + + {InteractionUtils.CreatePolyline( + l, + b.left, + b.top, + strokeColor, + width, + width, + 'miter', + 'round', + ActiveInkBezierApprox(), + 'none' /*ActiveFillColor()*/, + ActiveArrowStart(), + ActiveArrowEnd(), + ActiveArrowScale(), + ActiveDash(), + 1, + 1, + this.InkShape, + 'none', + 1.0, + false + )} + + ); + }), + this._points.length <= 1 ? null : ( + + {InteractionUtils.CreatePolyline( + this._points.map(p => ({ X: p.X, Y: p.Y - (rect?.y || 0) })), + B.left, + B.top, + ActiveInkColor(), + width, + width, + 'miter', + 'round', + '', + 'none' /*ActiveFillColor()*/, + ActiveArrowStart(), + ActiveArrowEnd(), + ActiveArrowScale(), + ActiveDash(), + 1, + 1, + this.InkShape, + 'none', + 1.0, + false + )} + + ), + ], ]; } screenToLocalTransform = () => new Transform(-(this._thumbX ?? 0), -(this._thumbY ?? 0) + this.height, 1); return300 = () => 300; @action public openFloatingDoc = (doc: Doc) => { - this._clipboardDoc = + this._clipboardDoc = ( ; - } + /> + ); + }; @action public closeFloatingDoc = () => { this._clipboardDoc = undefined; - } + }; @action enableMobileInkOverlay = (content: MobileInkOverlayContent) => { this.showMobileInkOverlay = content.enableOverlay; - } + }; render() { return ( -
    +
    {this.showMobileInkOverlay ? : <>} {this.elements} -
    +
    {this._clipboardDoc}
    -
    -
    +
    -
    ); +
    + ); } } // export class export enum ToolglassTools { - InkToText = "inktotext", - IgnoreGesture = "ignoregesture", - RadialMenu = "radialmenu", - None = "none", + InkToText = 'inktotext', + IgnoreGesture = 'ignoregesture', + RadialMenu = 'radialmenu', + None = 'none', } -ScriptingGlobals.add("GestureOverlay", GestureOverlay); +ScriptingGlobals.add('GestureOverlay', GestureOverlay); ScriptingGlobals.add(function setToolglass(tool: any) { - runInAction(() => GestureOverlay.Instance.Tool = tool); + runInAction(() => (GestureOverlay.Instance.Tool = tool)); }); ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowStart: any, arrowEnd: any, dash: any) { runInAction(() => { @@ -975,10 +1123,14 @@ ScriptingGlobals.add(function setPen(width: any, color: any, fill: any, arrowSta }); ScriptingGlobals.add(function resetPen() { runInAction(() => { - SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? "rgb(0, 0, 0)"); - SetActiveInkWidth(GestureOverlay.Instance.SavedWidth?.toString() ?? "2"); + SetActiveInkColor(GestureOverlay.Instance.SavedColor ?? 'rgb(0, 0, 0)'); + SetActiveInkWidth(GestureOverlay.Instance.SavedWidth?.toString() ?? '2'); }); -}, "resets the pen tool"); -ScriptingGlobals.add(function createText(text: any, x: any, y: any) { - GestureOverlay.Instance.dispatchGesture("text", [{ X: x, Y: y }], text); -}, "creates a text document with inputted text and coordinates", "(text: any, x: any, y: any)"); +}, 'resets the pen tool'); +ScriptingGlobals.add( + function createText(text: any, x: any, y: any) { + GestureOverlay.Instance.dispatchGesture('text', [{ X: x, Y: y }], text); + }, + 'creates a text document with inputted text and coordinates', + '(text: any, x: any, y: any)' +); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index f5122df3f..73e0c9933 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -1,39 +1,38 @@ -import { random } from "lodash"; -import { action, observable, runInAction } from "mobx"; -import { DateField } from "../../fields/DateField"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { InkTool } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { ScriptField } from "../../fields/ScriptField"; -import { Cast, PromiseValue } from "../../fields/Types"; -import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager"; -import { DocServer } from "../DocServer"; -import { DocumentType } from "../documents/DocumentTypes"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { DragManager } from "../util/DragManager"; -import { GroupManager } from "../util/GroupManager"; -import { SelectionManager } from "../util/SelectionManager"; -import { SettingsManager } from "../util/SettingsManager"; -import { SharingManager } from "../util/SharingManager"; -import { SnappingManager } from "../util/SnappingManager"; -import { undoBatch, UndoManager } from "../util/UndoManager"; -import { CollectionDockingView } from "./collections/CollectionDockingView"; -import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu"; -import { CollectionStackedTimeline } from "./collections/CollectionStackedTimeline"; -import { ContextMenu } from "./ContextMenu"; -import { DocumentDecorations } from "./DocumentDecorations"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { LightboxView } from "./LightboxView"; -import { MainView } from "./MainView"; -import { DocumentLinksButton } from "./nodes/DocumentLinksButton"; -import { AnchorMenu } from "./pdf/AnchorMenu"; +import { random } from 'lodash'; +import { action, observable, runInAction } from 'mobx'; +import { DateField } from '../../fields/DateField'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { InkTool } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { ScriptField } from '../../fields/ScriptField'; +import { Cast, PromiseValue } from '../../fields/Types'; +import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; +import { DocServer } from '../DocServer'; +import { DocumentType } from '../documents/DocumentTypes'; +import { DragManager } from '../util/DragManager'; +import { GroupManager } from '../util/GroupManager'; +import { SelectionManager } from '../util/SelectionManager'; +import { SettingsManager } from '../util/SettingsManager'; +import { SharingManager } from '../util/SharingManager'; +import { SnappingManager } from '../util/SnappingManager'; +import { undoBatch, UndoManager } from '../util/UndoManager'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +import { CollectionFreeFormViewChrome } from './collections/CollectionMenu'; +import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; +import { ContextMenu } from './ContextMenu'; +import { DocumentDecorations } from './DocumentDecorations'; +import { InkStrokeProperties } from './InkStrokeProperties'; +import { LightboxView } from './LightboxView'; +import { MainView } from './MainView'; +import { DocumentLinksButton } from './nodes/DocumentLinksButton'; +import { AnchorMenu } from './pdf/AnchorMenu'; -const modifiers = ["control", "meta", "shift", "alt"]; +const modifiers = ['control', 'meta', 'shift', 'alt']; type KeyHandler = (keycode: string, e: KeyboardEvent) => KeyControlInfo; type KeyControlInfo = { - preventDefault: boolean, - stopPropagation: boolean + preventDefault: boolean; + stopPropagation: boolean; }; export class KeyManager { @@ -41,22 +40,22 @@ export class KeyManager { private router = new Map(); constructor() { - const isMac = navigator.platform.toLowerCase().indexOf("mac") >= 0; + const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; // SHIFT CONTROL ALT META - this.router.set("0000", this.unmodified); - this.router.set(isMac ? "0001" : "0100", this.ctrl); - this.router.set(isMac ? "0100" : "0010", this.alt); - this.router.set(isMac ? "1001" : "1100", this.ctrl_shift); - this.router.set("1000", this.shift); + this.router.set('0000', this.unmodified); + this.router.set(isMac ? '0001' : '0100', this.ctrl); + this.router.set(isMac ? '0100' : '0010', this.alt); + this.router.set(isMac ? '1001' : '1100', this.ctrl_shift); + this.router.set('1000', this.shift); } public unhandle = action((e: KeyboardEvent) => { - if (e.key?.toLowerCase() === "shift") runInAction(() => DocumentDecorations.Instance.AddToSelection = false); + if (e.key?.toLowerCase() === 'shift') runInAction(() => (DocumentDecorations.Instance.AddToSelection = false)); }); public handle = action((e: KeyboardEvent) => { - if (e.key?.toLowerCase() === "shift") DocumentDecorations.Instance.AddToSelection = true; + if (e.key?.toLowerCase() === 'shift') DocumentDecorations.Instance.AddToSelection = true; //if (!Doc.noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.UPDATE_SERVER_CACHE(true); const keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); @@ -65,7 +64,7 @@ export class KeyManager { return; } - const bit = (value: boolean) => value ? "1" : "0"; + const bit = (value: boolean) => (value ? '1' : '0'); const modifierIndex = bit(e.shiftKey) + bit(e.ctrlKey) + bit(e.altKey) + bit(e.metaKey); const handleConstrained = this.router.get(modifierIndex); @@ -86,33 +85,33 @@ export class KeyManager { private unmodified = action((keyname: string, e: KeyboardEvent) => { switch (keyname) { - case "u": - if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") { + case 'u': + if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { return { stopPropagation: false, preventDefault: false }; } const ungroupings = SelectionManager.Views().slice(); - UndoManager.RunInBatch(() => ungroupings.map(dv => dv.layoutDoc.group = undefined), "ungroup"); + UndoManager.RunInBatch(() => ungroupings.map(dv => (dv.layoutDoc.group = undefined)), 'ungroup'); SelectionManager.DeselectAll(); break; - case "g": - if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") { + case 'g': + if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { return { stopPropagation: false, preventDefault: false }; } const groupings = SelectionManager.Views().slice(); const randomGroup = random(0, 1000); - UndoManager.RunInBatch(() => groupings.map(dv => dv.layoutDoc.group = randomGroup), "group"); + UndoManager.RunInBatch(() => groupings.map(dv => (dv.layoutDoc.group = randomGroup)), 'group'); SelectionManager.DeselectAll(); break; - case " ": + case ' ': // MarqueeView.DragMarquee = !MarqueeView.DragMarquee; // bcz: this needs a better disclosure UI break; - case "escape": + case 'escape': DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; InkStrokeProperties.Instance._controlButton = false; - CurrentUserUtils.ActiveTool = InkTool.None; + Doc.ActiveTool = InkTool.None; DragManager.CompleteWindowDrag?.(true); var doDeselect = true; if (SnappingManager.GetIsDragging()) { @@ -138,20 +137,19 @@ export class KeyManager { window.getSelection()?.empty(); document.body.focus(); break; - case "enter": { + case 'enter': { DocumentDecorations.Instance.onCloseClick(false); break; } - case "delete": - case "backspace": - if (document.activeElement?.tagName !== "INPUT" && document.activeElement?.tagName !== "TEXTAREA") { + case 'delete': + case 'backspace': + if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { UndoManager.RunInBatch(() => { if (LightboxView.LightboxDoc) { LightboxView.SetLightboxDoc(undefined); SelectionManager.DeselectAll(); - } - else DocumentDecorations.Instance.onCloseClick(true); - }, "backspace"); + } else DocumentDecorations.Instance.onCloseClick(true); + }, 'backspace'); // const selected = SelectionManager.Views().filter(dv => !dv.topMost); // UndoManager.RunInBatch(() => { // SelectionManager.DeselectAll(); @@ -160,15 +158,23 @@ export class KeyManager { return { stopPropagation: true, preventDefault: true }; } break; - case "arrowleft": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge(-1, 0)), "nudge left"); break; - case "arrowright": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(1, 0)), "nudge right"); break; - case "arrowup": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -1)), "nudge up"); break; - case "arrowdown": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 1)), "nudge down"); break; + case 'arrowleft': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge(-1, 0)), 'nudge left'); + break; + case 'arrowright': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(1, 0)), 'nudge right'); + break; + case 'arrowup': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -1)), 'nudge up'); + break; + case 'arrowdown': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 1)), 'nudge down'); + break; } return { stopPropagation: false, - preventDefault: false + preventDefault: false, }; }); @@ -177,15 +183,23 @@ export class KeyManager { const preventDefault = false; switch (keyname) { - case "arrowleft": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(-10, 0)), "nudge left"); break; - case "arrowright": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(10, 0)), "nudge right"); break; - case "arrowup": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -10)), "nudge up"); break; - case "arrowdown": UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), "nudge down"); break; + case 'arrowleft': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(-10, 0)), 'nudge left'); + break; + case 'arrowright': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(10, 0)), 'nudge right'); + break; + case 'arrowup': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, -10)), 'nudge up'); + break; + case 'arrowdown': + UndoManager.RunInBatch(() => SelectionManager.Views().map(dv => dv.props.CollectionFreeFormDocumentView?.().nudge?.(0, 10)), 'nudge down'); + break; } return { stopPropagation: stopPropagation, - preventDefault: preventDefault + preventDefault: preventDefault, }; }); @@ -194,15 +208,15 @@ export class KeyManager { const preventDefault = true; switch (keyname) { - case "ƒ": - case "f": + case 'ƒ': + case 'f': const dv = SelectionManager.Views()?.[0]; - UndoManager.RunInBatch(() => dv.props.CollectionFreeFormDocumentView?.().float(), "float"); + UndoManager.RunInBatch(() => dv.props.CollectionFreeFormDocumentView?.().float(), 'float'); } return { stopPropagation: stopPropagation, - preventDefault: preventDefault + preventDefault: preventDefault, }; }); @@ -211,83 +225,97 @@ export class KeyManager { let preventDefault = true; switch (keyname) { - case "arrowright": - if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") { + case 'arrowright': + if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { return { stopPropagation: false, preventDefault: false }; } - MainView.Instance.mainFreeform && CollectionDockingView.AddSplit(MainView.Instance.mainFreeform, "right"); + MainView.Instance.mainFreeform && CollectionDockingView.AddSplit(MainView.Instance.mainFreeform, 'right'); break; - case "arrowleft": - if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") { + case 'arrowleft': + if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { return { stopPropagation: false, preventDefault: false }; } MainView.Instance.mainFreeform && CollectionDockingView.CloseSplit(MainView.Instance.mainFreeform); break; - case "backspace": - if (document.activeElement?.tagName === "INPUT" || document.activeElement?.tagName === "TEXTAREA") { + case 'backspace': + if (document.activeElement?.tagName === 'INPUT' || document.activeElement?.tagName === 'TEXTAREA') { return { stopPropagation: false, preventDefault: false }; } break; - case "t": - PromiseValue(Cast(Doc.UserDoc()["tabs-button-tools"], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv })); + case 't': + PromiseValue(Cast(Doc.UserDoc()['tabs-button-tools'], Doc)).then(pv => pv && (pv.onClick as ScriptField).script.run({ this: pv })); break; - case "f": + case 'f': if (SelectionManager.Views().length === 1 && SelectionManager.Views()[0].ComponentView?.search) { - SelectionManager.Views()[0].ComponentView?.search?.("", false, false); + SelectionManager.Views()[0].ComponentView?.search?.('', false, false); } else { - const searchBtn = CurrentUserUtils.MySearcher; + const searchBtn = Doc.MySearcher; if (searchBtn) { MainView.Instance.selectMenu(searchBtn); } } break; - case "e": CurrentUserUtils.ActiveTool = InkTool.Eraser; + case 'e': + Doc.ActiveTool = InkTool.Eraser; break; - case "p": CurrentUserUtils.ActiveTool = InkTool.Pen; + case 'p': + Doc.ActiveTool = InkTool.Pen; break; - case "o": + case 'o': const target = SelectionManager.Docs().lastElement(); target && CollectionDockingView.OpenFullScreen(target); break; - case "r": + case 'r': preventDefault = false; break; - case "y": + case 'y': SelectionManager.DeselectAll(); UndoManager.Redo(); stopPropagation = false; break; - case "z": + case 'z': SelectionManager.DeselectAll(); UndoManager.Undo(); stopPropagation = false; break; - case "a": + case 'a': if (e.target !== document.body) { stopPropagation = false; preventDefault = false; } break; - case "v": + case 'v': stopPropagation = false; preventDefault = false; break; - case "x": + case 'x': if (SelectionManager.Views().length) { const bds = DocumentDecorations.Instance.Bounds; - const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); - const text = `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":"); + const pt = SelectionManager.Views()[0] + .props.ScreenToLocalTransform() + .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); + const text = + `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + + SelectionManager.Views() + .map(dv => dv.Document[Id]) + .join(':'); SelectionManager.Views().length && navigator.clipboard.writeText(text); DocumentDecorations.Instance.onCloseClick(true); stopPropagation = false; preventDefault = false; } break; - case "c": + case 'c': if (!AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) { const bds = DocumentDecorations.Instance.Bounds; - const pt = SelectionManager.Views()[0].props.ScreenToLocalTransform().transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); - const text = `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + SelectionManager.Views().map(dv => dv.Document[Id]).join(":"); + const pt = SelectionManager.Views()[0] + .props.ScreenToLocalTransform() + .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); + const text = + `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + + SelectionManager.Views() + .map(dv => dv.Document[Id]) + .join(':'); SelectionManager.Views().length && navigator.clipboard.writeText(text); stopPropagation = false; } @@ -297,58 +325,63 @@ export class KeyManager { return { stopPropagation: stopPropagation, - preventDefault: preventDefault + preventDefault: preventDefault, }; }); public paste(e: ClipboardEvent) { - const plain = e.clipboardData?.getData("text/plain"); - const clone = plain?.startsWith("__DashCloneId("); - if (plain && (plain.startsWith("__DashDocId(") || clone)) { + const plain = e.clipboardData?.getData('text/plain'); + const clone = plain?.startsWith('__DashCloneId('); + if (plain && (plain.startsWith('__DashDocId(') || clone)) { const first = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; if (first?.props.Document.type === DocumentType.COL) { - const docids = plain.split(":"); + const docids = plain.split(':'); let count = 1; const list: Doc[] = []; const targetDataDoc = Doc.GetProto(first.props.Document); const fieldKey = first.LayoutFieldKey; const docList = DocListCast(targetDataDoc[fieldKey]); - docids.map((did, i) => i && DocServer.GetRefField(did).then(async doc => { - count++; - if (doc instanceof Doc) { - list.push(doc); - } - if (count === docids.length) { - const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => clone ? (await Doc.MakeClone(d)).clone : d)); - if (added.length) { - added.map(doc => doc.context = targetDataDoc); - undoBatch(() => { - targetDataDoc[fieldKey] = new List([...docList, ...added]); - targetDataDoc[fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); - })(); - } - } - })); + docids.map( + (did, i) => + i && + DocServer.GetRefField(did).then(async doc => { + count++; + if (doc instanceof Doc) { + list.push(doc); + } + if (count === docids.length) { + const added = await Promise.all(list.filter(d => !docList.includes(d)).map(async d => (clone ? (await Doc.MakeClone(d)).clone : d))); + if (added.length) { + added.map(doc => (doc.context = targetDataDoc)); + undoBatch(() => { + targetDataDoc[fieldKey] = new List([...docList, ...added]); + targetDataDoc[fieldKey + '-lastModified'] = new DateField(new Date(Date.now())); + })(); + } + } + }) + ); } } } - getClipboard() { return navigator.clipboard.readText(); } + getClipboard() { + return navigator.clipboard.readText(); + } private ctrl_shift = action((keyname: string) => { const stopPropagation = true; const preventDefault = true; switch (keyname) { - case "z": + case 'z': UndoManager.Redo(); break; } return { stopPropagation: stopPropagation, - preventDefault: preventDefault + preventDefault: preventDefault, }; }); - } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 471ad09e9..821e2f739 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -1,23 +1,23 @@ -import { Bezier } from "bezier-js"; -import { Normalize, Distance } from "../util/bezierFit"; -import { action, observable, reaction } from "mobx"; -import { Doc, NumListCast, Opt } from "../../fields/Doc"; -import { InkData, InkField, InkTool, PointData } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { listSpec } from "../../fields/Schema"; -import { Cast, NumCast } from "../../fields/Types"; -import { Point } from "../../pen-gestures/ndollar"; -import { DocumentType } from "../documents/DocumentTypes"; -import { FitOneCurve } from "../util/bezierFit"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { DocumentManager } from "../util/DocumentManager"; -import { undoBatch } from "../util/UndoManager"; -import { InkingStroke } from "./InkingStroke"; -import { DocumentView } from "./nodes/DocumentView"; +import { Bezier } from 'bezier-js'; +import { action, observable, reaction } from 'mobx'; +import { Doc, NumListCast, Opt } from '../../fields/Doc'; +import { InkData, InkField, InkTool, PointData } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { listSpec } from '../../fields/Schema'; +import { Cast, NumCast } from '../../fields/Types'; +import { Point } from '../../pen-gestures/ndollar'; +import { DocumentType } from '../documents/DocumentTypes'; +import { FitOneCurve } from '../util/bezierFit'; +import { DocumentManager } from '../util/DocumentManager'; +import { undoBatch } from '../util/UndoManager'; +import { InkingStroke } from './InkingStroke'; +import { DocumentView } from './nodes/DocumentView'; export class InkStrokeProperties { static _Instance: InkStrokeProperties | undefined; - public static get Instance() { return this._Instance || new InkStrokeProperties(); } + public static get Instance() { + return this._Instance || new InkStrokeProperties(); + } @observable _lock = false; @observable _controlButton = false; @@ -25,8 +25,14 @@ export class InkStrokeProperties { constructor() { InkStrokeProperties._Instance = this; - reaction(() => this._controlButton, button => button && (CurrentUserUtils.ActiveTool = InkTool.None)); - reaction(() => CurrentUserUtils.ActiveTool, tool => (tool !== InkTool.None) && (this._controlButton = false)); + reaction( + () => this._controlButton, + button => button && (Doc.ActiveTool = InkTool.None) + ); + reaction( + () => Doc.ActiveTool, + tool => tool !== InkTool.None && (this._controlButton = false) + ); } /** @@ -34,35 +40,41 @@ export class InkStrokeProperties { * @param func The inputted function. * @param requireCurrPoint Indicates whether the current selected point is needed. */ - applyFunction = (strokes: Opt, func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number, Y: number }[] | undefined, requireCurrPoint: boolean = false) => { + applyFunction = ( + strokes: Opt, + func: (view: DocumentView, ink: InkData, ptsXscale: number, ptsYscale: number, inkStrokeWidth: number) => { X: number; Y: number }[] | undefined, + requireCurrPoint: boolean = false + ) => { var appliedFunc = false; - (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach(action(inkView => { - if (!requireCurrPoint || this._currentPoint !== -1) { - const doc = inkView.rootDoc; - if (doc.type === DocumentType.INK && doc.width && doc.height) { - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); - const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); - const ptsXscale = ((NumCast(doc._width) - NumCast(doc.strokeWidth)) / ((oldXrange.max - oldXrange.min) || 1)) || 1; - const ptsYscale = ((NumCast(doc._height) - NumCast(doc.strokeWidth)) / ((oldYrange.max - oldYrange.min) || 1)) || 1; - const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth)); - if (newPoints) { - const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); - const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y)); - doc._width = (newXrange.max - newXrange.min) * ptsXscale + NumCast(doc.strokeWidth); - doc._height = (newYrange.max - newYrange.min) * ptsYscale + NumCast(doc.strokeWidth); - doc.x = (oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale); - doc.y = (oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale); - Doc.GetProto(doc).data = new InkField(newPoints); - appliedFunc = true; + (strokes instanceof DocumentView ? [strokes] : strokes)?.forEach( + action(inkView => { + if (!requireCurrPoint || this._currentPoint !== -1) { + const doc = inkView.rootDoc; + if (doc.type === DocumentType.INK && doc.width && doc.height) { + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { + const oldXrange = (xs => ({ coord: NumCast(doc.x), min: Math.min(...xs), max: Math.max(...xs) }))(ink.map(p => p.X)); + const oldYrange = (ys => ({ coord: NumCast(doc.y), min: Math.min(...ys), max: Math.max(...ys) }))(ink.map(p => p.Y)); + const ptsXscale = (NumCast(doc._width) - NumCast(doc.strokeWidth)) / (oldXrange.max - oldXrange.min || 1) || 1; + const ptsYscale = (NumCast(doc._height) - NumCast(doc.strokeWidth)) / (oldYrange.max - oldYrange.min || 1) || 1; + const newPoints = func(inkView, ink, ptsXscale, ptsYscale, NumCast(doc.strokeWidth)); + if (newPoints) { + const newXrange = (xs => ({ min: Math.min(...xs), max: Math.max(...xs) }))(newPoints.map(p => p.X)); + const newYrange = (ys => ({ min: Math.min(...ys), max: Math.max(...ys) }))(newPoints.map(p => p.Y)); + doc._width = (newXrange.max - newXrange.min) * ptsXscale + NumCast(doc.strokeWidth); + doc._height = (newYrange.max - newYrange.min) * ptsYscale + NumCast(doc.strokeWidth); + doc.x = oldXrange.coord + (newXrange.min - oldXrange.min) * ptsXscale; + doc.y = oldYrange.coord + (newYrange.min - oldYrange.min) * ptsYscale; + Doc.GetProto(doc).data = new InkField(newPoints); + appliedFunc = true; + } } } } - } - })); + }) + ); return appliedFunc; - } + }; /** * Adds a new control point to the ink instance when editing its format. @@ -72,7 +84,7 @@ export class InkStrokeProperties { */ @undoBatch @action - addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number, Y: number }[]) => { + addPoints = (inkView: DocumentView, t: number, i: number, controls: { X: number; Y: number }[]) => { this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { const doc = view.rootDoc; const array = [controls[i], controls[i + 1], controls[i + 2], controls[i + 3]]; @@ -81,12 +93,12 @@ export class InkStrokeProperties { controls.splice(i, 4, ...splicepts.map(p => ({ X: p.x, Y: p.y }))); // Updating the indices of the control points whose handle tangency has been broken. - doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec("number"), []).map(control => control > i ? control + 4 : control)); + doc.brokenInkIndices = new List(Cast(doc.brokenInkIndices, listSpec('number'), []).map(control => (control > i ? control + 4 : control))); this._currentPoint = -1; return controls; }); - } + }; /** * Scales a handle point of a control point that is adjacent to a newly added one. @@ -107,15 +119,15 @@ export class InkStrokeProperties { * the tangent vector to a control point is equivalent to the first/last (depending on the direction * of the curve) leg of the Bézier curve's derivative. * (Source: https://pages.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html) - * + * * @param C The curve represented by all points from the previous control until the newly added point. * @param D The curve represented by all points from the newly added point to the next control. * @param newControl The newly added control point. */ getNewHandlePoints = (C: PointData[], D: PointData[], newControl: PointData) => { const [m, n] = [C.length, D.length]; - let handleSizeA = Math.sqrt((Math.pow(newControl.X - C[0].X, 2)) + (Math.pow(newControl.Y - C[0].Y, 2))); - let handleSizeB = Math.sqrt((Math.pow(D[n - 1].X - newControl.X, 2)) + (Math.pow(D[n - 1].Y - newControl.Y, 2))); + let handleSizeA = Math.sqrt(Math.pow(newControl.X - C[0].X, 2) + Math.pow(newControl.Y - C[0].Y, 2)); + let handleSizeB = Math.sqrt(Math.pow(D[n - 1].X - newControl.X, 2) + Math.pow(D[n - 1].Y - newControl.Y, 2)); // Scaling adjustments to improve the ratio between the magnitudes of the two handle lines. // (Ensures that the new point added doesn't augment the inital shape of the curve much). if (handleSizeA < 75 && handleSizeB < 75) { @@ -131,50 +143,55 @@ export class InkStrokeProperties { } // Finding the last leg of the derivative curve of C. const dC = { X: (handleSizeA / n) * (C[m - 1].X - C[m - 2].X), Y: (handleSizeA / n) * (C[m - 1].Y - C[m - 2].Y) }; - // Finding the first leg of the derivative curve of D. + // Finding the first leg of the derivative curve of D. const dD = { X: (handleSizeB / m) * (D[1].X - D[0].X), Y: (handleSizeB / m) * (D[1].Y - D[0].Y) }; const handleA = { X: newControl.X - dC.X, Y: newControl.Y - dC.Y }; const handleB = { X: newControl.X + dD.X, Y: newControl.Y + dD.Y }; return [handleA, handleB]; - } + }; /** * Deletes the current control point of the selected ink instance. */ @undoBatch @action - deletePoints = (inkView: DocumentView, preserve: boolean) => this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { - const doc = view.rootDoc; - const newPoints = ink.slice(); - const brokenIndices = NumListCast(doc.brokenInkIndices); - if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) { - newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4); - } else { - const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; - const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); - const samples: Point[] = []; - var startDir = { x: 0, y: 0 }; - var endDir = { x: 0, y: 0 }; - for (var i = 0; i < splicedPoints.length / 4; i++) { - const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); - if (i === 0) startDir = bez.derivative(0); - if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1); - for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) { - const pt = bez.compute(t); - samples.push(new Point(pt.x, pt.y)); + deletePoints = (inkView: DocumentView, preserve: boolean) => + this.applyFunction( + inkView, + (view: DocumentView, ink: InkData) => { + const doc = view.rootDoc; + const newPoints = ink.slice(); + const brokenIndices = NumListCast(doc.brokenInkIndices); + if (preserve || this._currentPoint === 0 || this._currentPoint === ink.length - 1 || brokenIndices.includes(this._currentPoint)) { + newPoints.splice(this._currentPoint === 0 ? 0 : this._currentPoint === ink.length - 1 ? this._currentPoint - 3 : this._currentPoint - 2, 4); + } else { + const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; + const splicedPoints = ink.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); + const samples: Point[] = []; + var startDir = { x: 0, y: 0 }; + var endDir = { x: 0, y: 0 }; + for (var i = 0; i < splicedPoints.length / 4; i++) { + const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); + if (i === 0) startDir = bez.derivative(0); + if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1); + for (var t = 0; t < (i === splicedPoints.length / 4 - 1 ? 1 + 1e-7 : 1); t += 0.05) { + const pt = bez.compute(t); + samples.push(new Point(pt.x, pt.y)); + } + } + const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); + if (error < 100) { + newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls); + } else { + newPoints.splice(this._currentPoint - 2, 4); + } } - } - const { finalCtrls, error } = FitOneCurve(samples, { X: startDir.x, Y: startDir.y }, { X: endDir.x, Y: endDir.y }); - if (error < 100) { - newPoints.splice(this._currentPoint - 4, 8, ...finalCtrls); - } else { - newPoints.splice(this._currentPoint - 2, 4); - } - } - doc.brokenInkIndices = new List(brokenIndices.map(control => control >= this._currentPoint ? control - 4 : control)); - this._currentPoint = -1; - return newPoints.length < 4 ? undefined : newPoints; - }, true) + doc.brokenInkIndices = new List(brokenIndices.map(control => (control >= this._currentPoint ? control - 4 : control))); + this._currentPoint = -1; + return newPoints.length < 4 ? undefined : newPoints; + }, + true + ); /** * Rotates ink stroke(s) about a point @@ -188,15 +205,16 @@ export class InkStrokeProperties { this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData, xScale: number, yScale: number, inkStrokeWidth: number) => { view.rootDoc.rotation = NumCast(view.rootDoc.rotation) + angle; const inkCenterPt = view.ComponentView?.ptFromScreen?.(scrpt); - return !inkCenterPt ? ink : - ink.map(i => { - const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y }; - const newX = Math.cos(angle) * pt.X - Math.sin(angle) * pt.Y * yScale / xScale; - const newY = Math.sin(angle) * pt.X * xScale / yScale + Math.cos(angle) * pt.Y; - return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; - }); + return !inkCenterPt + ? ink + : ink.map(i => { + const pt = { X: i.X - inkCenterPt.X, Y: i.Y - inkCenterPt.Y }; + const newX = Math.cos(angle) * pt.X - (Math.sin(angle) * pt.Y * yScale) / xScale; + const newY = (Math.sin(angle) * pt.X * xScale) / yScale + Math.cos(angle) * pt.Y; + return { X: newX + inkCenterPt.X, Y: newY + inkCenterPt.Y }; + }); }); - } + }; /** * Rotates ink stroke(s) about a point @@ -210,16 +228,17 @@ export class InkStrokeProperties { this.applyFunction(inkStrokes, (view: DocumentView, ink: InkData) => { const ptFromScreen = view.ComponentView?.ptFromScreen; const ptToScreen = view.ComponentView?.ptToScreen; - return !ptToScreen || !ptFromScreen ? ink : - ink.map(ptToScreen).map(i => { - const pvec = { X: i.X - scrpt.X, Y: i.Y - scrpt.Y }; - const svec = pvec.X * scrVec.X * scaling + pvec.Y * scrVec.Y * scaling; - const ovec = -pvec.X * scrVec.Y * (scaleUniformly ? scaling : 1) + pvec.Y * scrVec.X * (scaleUniformly ? scaling : 1); - const newscrpt = { X: scrpt.X + svec * scrVec.X - ovec * scrVec.Y, Y: scrpt.Y + svec * scrVec.Y + ovec * scrVec.X }; - return ptFromScreen(newscrpt); - }); + return !ptToScreen || !ptFromScreen + ? ink + : ink.map(ptToScreen).map(i => { + const pvec = { X: i.X - scrpt.X, Y: i.Y - scrpt.Y }; + const svec = pvec.X * scrVec.X * scaling + pvec.Y * scrVec.Y * scaling; + const ovec = -pvec.X * scrVec.Y * (scaleUniformly ? scaling : 1) + pvec.Y * scrVec.X * (scaleUniformly ? scaling : 1); + const newscrpt = { X: scrpt.X + svec * scrVec.X - ovec * scrVec.Y, Y: scrpt.Y + svec * scrVec.Y + ovec * scrVec.X }; + return ptFromScreen(newscrpt); + }); }); - } + }; /** * Handles the movement/scaling of a control point. @@ -230,7 +249,7 @@ export class InkStrokeProperties { this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { const order = controlIndex % 4; const closed = InkingStroke.IsClosed(ink); - const brokenIndices = Cast(inkView.props.Document.brokenInkIndices, listSpec("number"), []); + const brokenIndices = Cast(inkView.props.Document.brokenInkIndices, listSpec('number'), []); if (origInk && this._currentPoint > 0 && this._currentPoint < ink.length - 1 && brokenIndices.findIndex(value => value === controlIndex) === -1) { const cpt_before = ink[controlIndex]; const cpt = { X: cpt_before.X + deltaX, Y: cpt_before.Y + deltaY }; @@ -238,7 +257,7 @@ export class InkStrokeProperties { const start = this._currentPoint === 0 ? 0 : this._currentPoint - 4; const splicedPoints = origInk.slice(start, start + (this._currentPoint === 0 || this._currentPoint === ink.length - 1 ? 4 : 8)); const { nearestT, nearestSeg } = InkStrokeProperties.nearestPtToStroke(splicedPoints, cpt); - if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && (1 - nearestT) < 1e-1)) return ink.slice(); + if ((nearestSeg === 0 && nearestT < 1e-1) || (nearestSeg === 4 && 1 - nearestT < 1e-1)) return ink.slice(); const samplesLeft: Point[] = []; const samplesRight: Point[] = []; var startDir = { x: 0, y: 0 }; @@ -247,7 +266,7 @@ export class InkStrokeProperties { const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); if (i === 0) startDir = bez.derivative(0); if (i === nearestSeg / 4) endDir = bez.derivative(nearestT); - for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + .05 : 1); t += 0.05) { + for (var t = 0; t < (i === nearestSeg / 4 ? nearestT + 0.05 : 1); t += 0.05) { const pt = bez.compute(i !== nearestSeg / 4 ? t : Math.min(nearestT, t)); samplesLeft.push(new Point(pt.x, pt.y)); } @@ -257,7 +276,7 @@ export class InkStrokeProperties { const bez = new Bezier(splicedPoints.slice(i * 4, i * 4 + 4).map(p => ({ x: p.X, y: p.Y }))); if (i === nearestSeg / 4) startDir = bez.derivative(nearestT); if (i === splicedPoints.length / 4 - 1) endDir = bez.derivative(1); - for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + .05 + 1e-7 : 1 + 1e-7); t += 0.05) { + for (var t = i === nearestSeg / 4 ? nearestT : 0; t < (i === nearestSeg / 4 ? 1 + 0.05 + 1e-7 : 1 + 1e-7); t += 0.05) { const pt = bez.compute(Math.min(1, t)); samplesRight.push(new Point(pt.x, pt.y)); } @@ -271,12 +290,11 @@ export class InkStrokeProperties { return ink.map((pt, i) => { const leftHandlePoint = order === 0 && i === controlIndex + 1; const rightHandlePoint = order === 0 && controlIndex !== 0 && i === controlIndex - 2; - if (controlIndex === i || - (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || - (order === 3 && i === controlIndex - 1)) { - return ({ X: pt.X + deltaX, Y: pt.Y + deltaY }); + if (controlIndex === i || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || (order === 3 && i === controlIndex - 1)) { + return { X: pt.X + deltaX, Y: pt.Y + deltaY }; } - if (controlIndex === i || + if ( + controlIndex === i || leftHandlePoint || rightHandlePoint || (order === 0 && controlIndex !== 0 && i === controlIndex - 1) || @@ -284,15 +302,15 @@ export class InkStrokeProperties { (order === 3 && i === controlIndex - 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 1) || (order === 3 && controlIndex !== ink.length - 1 && i === controlIndex + 2) || - ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1))) { - return ({ X: pt.X + deltaX, Y: pt.Y + deltaY }); + (ink[0].X === ink[ink.length - 1].X && ink[0].Y === ink[ink.length - 1].Y && (i === 0 || i === ink.length - 1) && (controlIndex === 0 || controlIndex === ink.length - 1)) + ) { + return { X: pt.X + deltaX, Y: pt.Y + deltaY }; } return pt; }); - }) - + }); - public static nearestPtToStroke(ctrlPoints: { X: number, Y: number }[], refInkSpacePt: { X: number, Y: number }, excludeSegs?: number[]) { + public static nearestPtToStroke(ctrlPoints: { X: number; Y: number }[], refInkSpacePt: { X: number; Y: number }, excludeSegs?: number[]) { var distance = Number.MAX_SAFE_INTEGER; var nearestT = -1; var nearestSeg = -1; @@ -326,16 +344,16 @@ export class InkStrokeProperties { if (screenDragPt) { const snapData = this.snapToAllCurves(screenDragPt, inkView, { nearestPt: { X: 0, Y: 0 }, distance: 10 }, ink, controlIndex); if (snapData.distance < 10) { - const deltaX = (snapData.nearestPt.X - ink[controlIndex].X); - const deltaY = (snapData.nearestPt.Y - ink[controlIndex].Y); + const deltaX = snapData.nearestPt.X - ink[controlIndex].X; + const deltaY = snapData.nearestPt.Y - ink[controlIndex].Y; const res = this.moveControlPtHandle(inkView, deltaX, deltaY, controlIndex, ink.slice()); - console.log("X = " + snapData.nearestPt.X + " " + snapData.nearestPt.Y); + console.log('X = ' + snapData.nearestPt.X + ' ' + snapData.nearestPt.Y); return res; } } } return false; - } + }; excludeSelfSnapSegs = (ink: InkData, controlIndex: number) => { const closed = InkingStroke.IsClosed(ink); @@ -346,9 +364,9 @@ export class InkStrokeProperties { const nextseg = which > 1 && (closed || controlIndex < ink.length - 1) ? (thisseg + 4) % ink.length : -1; const prevseg = which < 2 && (closed || controlIndex > 0) ? (thisseg - 4 + ink.length) % ink.length : -1; return [thisseg, prevseg, nextseg]; - } + }; - snapToAllCurves = (screenDragPt: { X: number, Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number, Y: number }, distance: number }, ink: InkData, controlIndex: number) => { + snapToAllCurves = (screenDragPt: { X: number; Y: number }, inkView: DocumentView, snapData: { nearestPt: { X: number; Y: number }; distance: number }, ink: InkData, controlIndex: number) => { const containingCollection = inkView.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; containingCollection?.childDocs .filter(doc => doc.type === DocumentType.INK) @@ -356,8 +374,7 @@ export class InkStrokeProperties { const testInkView = DocumentManager.Instance.getDocumentView(doc, containingCollection?.props.CollectionView); const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.rootDoc ? this.excludeSelfSnapSegs(ink, controlIndex) : []); if (snapped && snapped.distance < snapData.distance) { - const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : - inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space + const snappedInkPt = doc === inkView.rootDoc ? snapped.nearestPt : inkView.ComponentView?.ptFromScreen?.(testInkView?.ComponentView?.ptToScreen?.(snapped.nearestPt) ?? { X: 0, Y: 0 }); // convert from snapped ink coordinate system to dragged ink coordinate system by converting to/from screen space if (snappedInkPt) { snapData = { nearestPt: snappedInkPt, distance: snapped.distance }; @@ -365,7 +382,7 @@ export class InkStrokeProperties { } }); return snapData; - } + }; /** * Snaps a control point with broken tangency back to synced rotation. @@ -375,7 +392,7 @@ export class InkStrokeProperties { snapHandleTangent = (inkView: DocumentView, controlIndex: number, handleIndexA: number, handleIndexB: number) => { this.applyFunction(inkView, (view: DocumentView, ink: InkData) => { const doc = view.rootDoc; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number"), []); + const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number'), []); const ind = brokenIndices.findIndex(value => value === controlIndex); if (ind !== -1) { brokenIndices.splice(ind, 1); @@ -387,7 +404,7 @@ export class InkStrokeProperties { return inkCopy; } }); - } + }; /** * Rotates the target point about the origin point for a given angle (radians). @@ -398,11 +415,11 @@ export class InkStrokeProperties { const newX = Math.cos(angle) * rotatedTarget.X - Math.sin(angle) * rotatedTarget.Y; const newY = Math.sin(angle) * rotatedTarget.X + Math.cos(angle) * rotatedTarget.Y; return { X: newX + origin.X, Y: newY + origin.Y }; - } + }; /** * Finds the angle (in radians) between two inputted vectors. - * + * * α = arccos(a·b / |a|·|b|), where a and b are both vectors. */ public static angleBetweenTwoVectors(vectorA: PointData, vectorB: PointData) { @@ -444,14 +461,13 @@ export class InkStrokeProperties { const newHandlePoint = { X: ink[handleIndex].X - deltaX, Y: ink[handleIndex].Y - deltaY }; const inkCopy = ink.slice(); inkCopy[handleIndex] = newHandlePoint; - const brokenIndices = Cast(doc.brokenInkIndices, listSpec("number")); + const brokenIndices = Cast(doc.brokenInkIndices, listSpec('number')); const equivIndex = closed ? (controlIndex === 0 ? ink.length - 1 : controlIndex === ink.length - 1 ? 0 : -1) : -1; // Rotate opposite handle if user hasn't held 'Alt' key or not first/final control (which have only 1 handle). - if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && - (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { + if ((!brokenIndices || (!brokenIndices?.includes(controlIndex) && !brokenIndices?.includes(equivIndex))) && (closed || (handleIndex !== 1 && handleIndex !== ink.length - 2))) { const angle = InkStrokeProperties.angleChange(oldHandlePoint, newHandlePoint, controlPoint); inkCopy[oppositeHandleIndex] = this.rotatePoint(oppositeHandlePoint, controlPoint, angle); } return inkCopy; - }) -} \ No newline at end of file + }); +} diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 5936ea32d..bf0e8081d 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -9,7 +9,6 @@ import { DocumentType } from '../documents/DocumentTypes'; import { DocumentManager } from '../util/DocumentManager'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { InkingStroke } from './InkingStroke'; -import { CurrentUserUtils } from '../util/CurrentUserUtils'; import './InkTranscription.scss'; /** @@ -256,7 +255,7 @@ export class InkTranscription extends React.Component { */ createInkGroup() { // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (CurrentUserUtils.ActiveTool === InkTool.Write) { + if (Doc.ActiveTool === InkTool.Write) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those const selected = ffView.unprocessedDocs; diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index a1e71b5f4..99d50b4a2 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -1,7 +1,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; -import "normalize.css"; +import 'normalize.css'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../fields/Types'; @@ -13,7 +13,7 @@ import { SelectionManager } from '../util/SelectionManager'; import { Transform } from '../util/Transform'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { TabDocView } from './collections/TabDocView'; -import "./LightboxView.scss"; +import './LightboxView.scss'; import { DocumentView } from './nodes/DocumentView'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; @@ -25,20 +25,21 @@ interface LightboxViewProps { @observer export class LightboxView extends React.Component { - - @computed public static get LightboxDoc() { return this._doc; } + @computed public static get LightboxDoc() { + return this._doc; + } private static LightboxDocTemplate = () => LightboxView._layoutTemplate; @observable private static _layoutTemplate: Opt; @observable private static _doc: Opt; @observable private static _docTarget: Opt; @observable private static _docFilters: string[] = []; // filters - @observable private static _tourMap: Opt = []; // list of all tours available from the current target - private static _savedState: Opt<{ panX: Opt, panY: Opt, scale: Opt, scrollTop: Opt }>; - private static _history: Opt<{ doc: Doc, target?: Doc }[]> = []; + @observable private static _tourMap: Opt = []; // list of all tours available from the current target + private static _savedState: Opt<{ panX: Opt; panY: Opt; scale: Opt; scrollTop: Opt }>; + private static _history: Opt<{ doc: Doc; target?: Doc }[]> = []; @observable private static _future: Opt = []; private static _docView: Opt; private static openInTabFunc: any; - static path: { doc: Opt, target: Opt, history: Opt<{ doc: Doc, target?: Doc }[]>, future: Opt, saved: Opt<{ panX: Opt, panY: Opt, scale: Opt, scrollTop: Opt }> }[] = []; + static path: { doc: Opt; target: Opt; history: Opt<{ doc: Doc; target?: Doc }[]>; future: Opt; saved: Opt<{ panX: Opt; panY: Opt; scale: Opt; scrollTop: Opt }> }[] = []; @action public static SetLightboxDoc(doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc) { if (this.LightboxDoc && this.LightboxDoc !== doc && this._savedState) { this.LightboxDoc._panX = this._savedState.panX; @@ -53,52 +54,71 @@ export class LightboxView extends React.Component { } else { if (doc) { const l = DocUtils.MakeLinkToActiveAudio(() => doc).lastElement(); - l && (Cast(l.anchor2, Doc, null).backgroundColor = "lightgreen"); + l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); } //TabDocView.PinDoc(doc, { hidePresBox: true }); - this._history ? this._history.push({ doc, target }) : this._history = [{ doc, target }]; + this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); if (doc !== LightboxView.LightboxDoc) { this._savedState = { - panX: Cast(doc._panX, "number", null), - panY: Cast(doc._panY, "number", null), - scale: Cast(doc._viewScale, "number", null), - scrollTop: Cast(doc._scrollTop, "number", null), + panX: Cast(doc._panX, 'number', null), + panY: Cast(doc._panY, 'number', null), + scale: Cast(doc._viewScale, 'number', null), + scrollTop: Cast(doc._scrollTop, 'number', null), }; } } if (future) { - this._future = [...(this._future ?? []), ...(this.LightboxDoc ? [this.LightboxDoc] : []), ...future.slice().sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)).sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length),]; + this._future = [ + ...(this._future ?? []), + ...(this.LightboxDoc ? [this.LightboxDoc] : []), + ...future + .slice() + .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)) + .sort((a, b) => DocListCast(a.links).length - DocListCast(b.links).length), + ]; } this._doc = doc; this._layoutTemplate = layoutTemplate; this._docTarget = target || doc; - this._tourMap = DocListCast(doc?.links).map(link => { - const opp = LinkManager.getOppositeAnchor(link, doc!); - return opp?.TourMap ? opp : undefined; - }).filter(m => m).map(m => m!); + this._tourMap = DocListCast(doc?.links) + .map(link => { + const opp = LinkManager.getOppositeAnchor(link, doc!); + return opp?.TourMap ? opp : undefined; + }) + .filter(m => m) + .map(m => m!); return true; } - public static IsLightboxDocView(path: DocumentView[]) { return path.includes(this._docView!); } - @computed get leftBorder() { return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); } - @computed get topBorder() { return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); } + public static IsLightboxDocView(path: DocumentView[]) { + return path.includes(this._docView!); + } + @computed get leftBorder() { + return Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]); + } + @computed get topBorder() { + return Math.min(this.props.PanelHeight / 4, this.props.maxBorder[1]); + } lightboxWidth = () => this.props.PanelWidth - this.leftBorder * 2; lightboxHeight = () => this.props.PanelHeight - this.topBorder * 2; lightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1); navBtn = (left: Opt, bottom: Opt, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => { - return
    -
    -
    {color}
    - + return ( +
    +
    +
    {color}
    + +
    -
    ; - } + ); + }; public static GetSavedState(doc: Doc) { return this.LightboxDoc === doc && this._savedState ? this._savedState : undefined; } @@ -107,27 +127,28 @@ export class LightboxView extends React.Component { @action public static SetCookie(cookie: string) { if (this.LightboxDoc && cookie) { - this._docFilters = (f => this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f])(`cookies:${cookie}:provide`); + this._docFilters = (f => (this._docFilters ? [this._docFilters.push(f) as any, this._docFilters][1] : [f]))(`cookies:${cookie}:provide`); } } public static AddDocTab = (doc: Doc, location: string, layoutTemplate?: Doc, openInTabFunc?: any) => { LightboxView.openInTabFunc = openInTabFunc; SelectionManager.DeselectAll(); - return LightboxView.SetLightboxDoc(doc, undefined, - [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), - ...DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]), - ...(LightboxView._future ?? []) - ].sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)), layoutTemplate); - } + return LightboxView.SetLightboxDoc( + doc, + undefined, + [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '-annotations']), ...(LightboxView._future ?? [])].sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)), + layoutTemplate + ); + }; docFilters = () => LightboxView._docFilters || []; addDocTab = LightboxView.AddDocTab; @action public static Next() { const doc = LightboxView._doc!; - const target = LightboxView._docTarget = this._future?.pop(); + const target = (LightboxView._docTarget = this._future?.pop()); const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (targetDocView && target) { const l = DocUtils.MakeLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.() || target).lastElement(); - l && (Cast(l.anchor2, Doc, null).backgroundColor = "lightgreen"); + l && (Cast(l.anchor2, Doc, null).backgroundColor = 'lightgreen'); targetDocView.focus(target, { originalTarget: target, willZoom: true, scale: 0.9 }); if (LightboxView._history?.lastElement().target !== target) LightboxView._history?.push({ doc, target }); } else { @@ -152,10 +173,13 @@ export class LightboxView extends React.Component { LightboxView.SetLightboxDoc(target); } } - LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => { - const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); - return opp?.TourMap ? opp : undefined; - }).filter(m => m).map(m => m!); + LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links) + .map(link => { + const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); + return opp?.TourMap ? opp : undefined; + }) + .filter(m => m) + .map(m => m!); } @action public static Previous() { @@ -170,15 +194,17 @@ export class LightboxView extends React.Component { LightboxView._docTarget = target; if (!target) docView.ComponentView?.shrinkWrap?.(); else docView.focus(target, { willZoom: true, scale: 0.9 }); - } - else { + } else { LightboxView.SetLightboxDoc(doc, target); } if (LightboxView._future?.lastElement() !== previous.target || previous.doc) LightboxView._future?.push(previous.target || previous.doc); - LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links).map(link => { - const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); - return opp?.TourMap ? opp : undefined; - }).filter(m => m).map(m => m!); + LightboxView._tourMap = DocListCast(LightboxView._docTarget?.links) + .map(link => { + const opp = LinkManager.getOppositeAnchor(link, LightboxView._docTarget!); + return opp?.TourMap ? opp : undefined; + }) + .filter(m => m) + .map(m => m!); } @action stepInto = () => { @@ -187,7 +213,7 @@ export class LightboxView extends React.Component { target: LightboxView._docTarget, future: LightboxView._future, history: LightboxView._history, - saved: LightboxView._savedState + saved: LightboxView._savedState, }); const tours = LightboxView._tourMap; if (tours && tours.length) { @@ -197,45 +223,58 @@ export class LightboxView extends React.Component { const coll = LightboxView._docTarget; if (coll) { const fieldKey = Doc.LayoutFieldKey(coll); - const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]; - const links = DocListCast(coll.links).map(link => LinkManager.getOppositeAnchor(link, coll)).filter(doc => doc).map(doc => doc!); + const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '-annotations'])]; + const links = DocListCast(coll.links) + .map(link => LinkManager.getOppositeAnchor(link, coll)) + .filter(doc => doc) + .map(doc => doc!); LightboxView.SetLightboxDoc(coll, undefined, contents.length ? contents : links); TabDocView.PinDoc(coll, { hidePresBox: true }); } } - } + }; future = () => LightboxView._future; tourMap = () => LightboxView._tourMap; render() { - let downx = 0, downy = 0; - return !LightboxView.LightboxDoc ? (null) : -
    { downx = e.clientX; downy = e.clientY; }} + let downx = 0, + downy = 0; + return !LightboxView.LightboxDoc ? null : ( +
    { + downx = e.clientX; + downy = e.clientY; + }} onClick={e => { if (Math.abs(downx - e.clientX) < 4 && Math.abs(downy - e.clientY) < 4) { LightboxView.SetLightboxDoc(undefined); } - }} > - -
    +
    {/* TODO:glr This is where it would go*/} - { - LightboxView._docView = r !== null ? r : undefined; - r && setTimeout(action(() => { - const target = LightboxView._docTarget; - const doc = LightboxView._doc; - const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); - if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); - //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button - })); - })} + { + LightboxView._docView = r !== null ? r : undefined; + r && + setTimeout( + action(() => { + const target = LightboxView._docTarget; + const doc = LightboxView._doc; + const targetView = target && DocumentManager.Instance.getLightboxDocumentView(target); + if (doc === r.props.Document && (!target || target === doc)) r.ComponentView?.shrinkWrap?.(); + //else target?.focus(target, { willZoom: true, scale: 0.9, instant: true }); // bcz: why was this here? it breaks smooth navigation in lightbox using 'next' button + }) + ); + })} Document={LightboxView.LightboxDoc} DataDoc={undefined} LayoutTemplate={LightboxView.LightboxDocTemplate} @@ -259,35 +298,57 @@ export class LightboxView extends React.Component { searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} - renderDepth={0} /> + renderDepth={0} + />
    - {this.navBtn(0, undefined, this.props.PanelHeight / 2 - 12.50, "chevron-left", - () => LightboxView.LightboxDoc && LightboxView._history?.length ? "" : "none", e => { + {this.navBtn( + 0, + undefined, + this.props.PanelHeight / 2 - 12.5, + 'chevron-left', + () => (LightboxView.LightboxDoc && LightboxView._history?.length ? '' : 'none'), + e => { e.stopPropagation(); LightboxView.Previous(); - })} - {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), undefined, this.props.PanelHeight / 2 - 12.50, "chevron-right", - () => LightboxView.LightboxDoc && LightboxView._future?.length ? "" : "none", e => { + } + )} + {this.navBtn( + this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), + undefined, + this.props.PanelHeight / 2 - 12.5, + 'chevron-right', + () => (LightboxView.LightboxDoc && LightboxView._future?.length ? '' : 'none'), + e => { e.stopPropagation(); LightboxView.Next(); - }, this.future()?.length.toString())} + }, + this.future()?.length.toString() + )} -
    { e.stopPropagation(); - CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, ""); + CollectionDockingView.AddSplit(LightboxView._docTarget || LightboxView._doc!, ''); //LightboxView.openInTabFunc(LightboxView._docTarget || LightboxView._doc!, "inPlace"); SelectionManager.DeselectAll(); LightboxView.SetLightboxDoc(undefined); }}> - +
    -
    { e.stopPropagation(); LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; }}> - +
    { + e.stopPropagation(); + LightboxView.LightboxDoc!._fitWidth = !LightboxView.LightboxDoc!._fitWidth; + }}> +
    -
    ; +
    + ); } } interface LightboxTourBtnProps { @@ -299,12 +360,17 @@ interface LightboxTourBtnProps { @observer export class LightboxTourBtn extends React.Component { render() { - return this.props.navBtn("50%", 0, 0, "chevron-down", - () => LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? "" : "none", e => { + return this.props.navBtn( + '50%', + 0, + 0, + 'chevron-down', + () => (LightboxView.LightboxDoc /*&& this.props.future()?.length*/ ? '' : 'none'), + e => { e.stopPropagation(); this.props.stepInto(); }, StrCast(this.props.tourMap()?.lastElement()?.TourMap) ); } -} \ No newline at end of file +} diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 542f85228..e998f1fb9 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -7,7 +7,7 @@ import * as ReactDOM from 'react-dom'; import { AssignAllExtensions } from '../../extensions/General/Extensions'; import { Docs } from '../documents/Documents'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; -import { LinkManager } from '../util/LinkManager'; +import { LinkManager } from '../util/LinkManager'; // this must come before importing Docs and CurrentUserUtils import { ReplayMovements } from '../util/ReplayMovements'; import { TrackMovements } from '../util/TrackMovements'; import { CollectionView } from './collections/CollectionView'; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6c0a67de2..edc16d9a6 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -9,13 +9,13 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { ScriptField } from '../../fields/ScriptField'; -import { PromiseValue, StrCast } from '../../fields/Types'; +import { StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; import { Docs, DocUtils } from '../documents/Documents'; +import { CollectionViewType } from '../documents/DocumentTypes'; import { CaptureManager } from '../util/CaptureManager'; -import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { DocumentManager } from '../util/DocumentManager'; import { GroupManager } from '../util/GroupManager'; import { HistoryUtil } from '../util/History'; @@ -31,7 +31,6 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; -import { CollectionViewType } from './collections/CollectionView'; import './collections/TreeView.scss'; import { ComponentDecorations } from './ComponentDecorations'; import { ContextMenu } from './ContextMenu'; @@ -76,7 +75,7 @@ export class MainView extends React.Component { @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons @observable private _panelContent: string = 'none'; - @observable private _sidebarContent: any = CurrentUserUtils.MyLeftSidebarPanel; + @observable private _sidebarContent: any = Doc.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; @computed private get dashboardTabHeight() { @@ -104,27 +103,27 @@ export class MainView extends React.Component { return Doc.UserDoc(); } @computed private get colorScheme() { - return StrCast(CurrentUserUtils.ActiveDashboard?.colorScheme); + return StrCast(Doc.ActiveDashboard?.colorScheme); } @computed private get mainContainer() { - return this.userDoc ? CurrentUserUtils.ActiveDashboard : CurrentUserUtils.GuestDashboard; + return this.userDoc ? Doc.ActiveDashboard : Doc.GuestDashboard; } @computed private get headerBarDoc() { - return CurrentUserUtils.MyHeaderBar; + return Doc.MyHeaderBar; } @computed public get mainFreeform(): Opt { return (docs => (docs?.length > 1 ? docs[1] : undefined))(DocListCast(this.mainContainer!.data)); } headerBarDocWidth = () => this.mainDocViewWidth(); - headerBarDocHeight = () => CurrentUserUtils.headerBarHeight ?? 0; + headerBarDocHeight = () => SettingsManager.headerBarHeight ?? 0; topMenuHeight = () => 35; topMenuWidth = returnZero; // value is ignored ... leftMenuWidth = () => Number(LEFT_MENU_WIDTH.replace('px', '')); leftMenuHeight = () => this._dashUIHeight; leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth; leftMenuFlyoutHeight = () => this._dashUIHeight; - propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, CurrentUserUtils.propertiesWidth || 0)); + propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.propertiesWidth || 0)); propertiesHeight = () => this._dashUIHeight; mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth(); mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); @@ -204,7 +203,7 @@ export class MainView extends React.Component { constructor(props: Readonly<{}>) { super(props); MainView.Instance = this; - CurrentUserUtils._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); + DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); // causes errors to be generated when modifying an observable outside of an action configure({ enforceActions: 'observed' }); @@ -212,8 +211,8 @@ export class MainView extends React.Component { if (window.location.pathname !== '/home') { const pathname = window.location.pathname.substr(1).split('/'); if (pathname.length > 1 && pathname[0] === 'doc') { - CurrentUserUtils.MainDocId = pathname[1]; - !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (CurrentUserUtils.GuestTarget = field))); + Doc.MainDocId = pathname[1]; + !this.userDoc && DocServer.GetRefField(pathname[1]).then(action(field => field instanceof Doc && (Doc.GuestTarget = field))); } } @@ -483,18 +482,18 @@ export class MainView extends React.Component { }; initAuthenticationRouters = async () => { - const received = CurrentUserUtils.MainDocId; + const received = Doc.MainDocId; if (received && !this.userDoc) { reaction( - () => CurrentUserUtils.GuestTarget, - target => target && CurrentUserUtils.createNewDashboard(), + () => Doc.GuestTarget, + target => target && DashboardView.createNewDashboard(), { fireImmediately: true } ); } // else { // PromiseValue(this.userDoc.activeDashboard).then(dash => { - // if (dash instanceof Doc) CurrentUserUtils.openDashboard(dash); - // else CurrentUserUtils.createNewDashboard(); + // if (dash instanceof Doc) DashboardView.openDashboard(dash); + // else Doc.createNewDashboard(); // }); // } }; @@ -503,14 +502,14 @@ export class MainView extends React.Component { createNewPresentation = async () => { const pres = Docs.Create.PresDocument({ title: 'Untitled Trail', _viewType: CollectionViewType.Stacking, _fitWidth: true, _width: 400, _height: 500, targetDropAction: 'alias', _chromeHidden: true, boxShadow: '0 0' }); CollectionDockingView.AddSplit(pres, 'left'); - CurrentUserUtils.ActivePresentation = pres; - Doc.AddDocToList(CurrentUserUtils.MyTrails, 'data', pres); + Doc.ActivePresentation = pres; + Doc.AddDocToList(Doc.MyTrails, 'data', pres); }; @action createNewFolder = async () => { const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true }); - Doc.AddDocToList(CurrentUserUtils.MyFilesystem, 'data', folder); + Doc.AddDocToList(Doc.MyFilesystem, 'data', folder); }; @observable _exploreMode = false; @@ -612,9 +611,9 @@ export class MainView extends React.Component { setupMoveUpEvents( this, e, - action(e => ((CurrentUserUtils.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)), - action(() => CurrentUserUtils.propertiesWidth < 5 && (CurrentUserUtils.propertiesWidth = 0)), - action(() => (CurrentUserUtils.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)), + action(e => ((SettingsManager.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX)) ? false : false)), + action(() => SettingsManager.propertiesWidth < 5 && (SettingsManager.propertiesWidth = 0)), + action(() => (SettingsManager.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0)), false ); }; @@ -635,10 +634,10 @@ export class MainView extends React.Component { addDocTabFunc = (doc: Doc, location: string): boolean => { const locationFields = doc._viewType === CollectionViewType.Docking ? ['dashboard'] : location.split(':'); const locationParams = locationFields.length > 1 ? locationFields[1] : ''; - if (doc.dockingConfig) return CurrentUserUtils.openDashboard(doc); + if (doc.dockingConfig) return DashboardView.openDashboard(doc); switch (locationFields[0]) { case 'dashboard': - return CurrentUserUtils.openDashboard(doc); + return DashboardView.openDashboard(doc); case 'close': return CollectionDockingView.CloseSplit(doc, locationParams); case 'fullScreen': @@ -669,7 +668,7 @@ export class MainView extends React.Component { addDocTab={this.addDocTabFunc} pinToPres={emptyFunction} docViewPath={returnEmptyDoclist} - styleProvider={this._sidebarContent.proto === CurrentUserUtils.MyDashboards || this._sidebarContent.proto === CurrentUserUtils.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} + styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem ? DashboardStyleProvider : DefaultStyleProvider} rootSelected={returnTrue} removeDocument={returnFalse} ScreenToLocalTransform={this.mainContainerXf} @@ -697,7 +696,7 @@ export class MainView extends React.Component { return (
    (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(CurrentUserUtils.MyDockedBtns, 'data', doc), true); + remButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true); moveButtonDoc = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(doc) && addDocument(doc); - addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(CurrentUserUtils.MyDockedBtns, 'data', doc), true); + addButtonDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); @@ -826,10 +825,10 @@ export class MainView extends React.Component { }; @computed get docButtons() { - return !CurrentUserUtils.MyDockedBtns ? null : ( -
    + return !Doc.MyDockedBtns ? null : ( +
    ; } - })(CurrentUserUtils.ActivePage)} + })(Doc.ActivePage)} diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 20b99788c..b01ee5f42 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -1,21 +1,20 @@ -import { action, observable, ObservableMap, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { AclAugment, AclAdmin, AclEdit, DataSym, Doc, Opt, AclSelfEdit } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { List } from "../../fields/List"; -import { NumCast } from "../../fields/Types"; -import { GetEffectiveAcl } from "../../fields/util"; -import { unimplementedFunction, Utils } from "../../Utils"; -import { Docs } from "../documents/Documents"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { DragManager } from "../util/DragManager"; -import { undoBatch } from "../util/UndoManager"; -import "./MarqueeAnnotator.scss"; -import { DocumentView } from "./nodes/DocumentView"; -import { FormattedTextBox } from "./nodes/formattedText/FormattedTextBox"; -import { AnchorMenu } from "./pdf/AnchorMenu"; -import React = require("react"); -const _global = (window /* browser */ || global /* node */) as any; +import { action, observable, ObservableMap, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, Opt } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { List } from '../../fields/List'; +import { NumCast } from '../../fields/Types'; +import { GetEffectiveAcl } from '../../fields/util'; +import { unimplementedFunction, Utils } from '../../Utils'; +import { Docs, DocUtils } from '../documents/Documents'; +import { DragManager } from '../util/DragManager'; +import { undoBatch } from '../util/UndoManager'; +import './MarqueeAnnotator.scss'; +import { DocumentView } from './nodes/DocumentView'; +import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { AnchorMenu } from './pdf/AnchorMenu'; +import React = require('react'); +const _global = (window /* browser */ || global) /* node */ as any; export interface MarqueeAnnotatorProps { rootDoc: Doc; @@ -46,7 +45,7 @@ export class MarqueeAnnotator extends React.Component { @action static clearAnnotations(savedAnnotations: ObservableMap) { - AnchorMenu.Instance.Status = "marquee"; + AnchorMenu.Instance.Status = 'marquee'; AnchorMenu.Instance.fadeOut(true); // clear out old marquees and initialize menu for new selection Array.from(savedAnnotations.values()).forEach(v => v.forEach(a => a.remove())); @@ -60,30 +59,30 @@ export class MarqueeAnnotator extends React.Component { this._startY = this._top = (this.props.down[1] - boundingRect.top) * (this.props.mainCont.offsetHeight / boundingRect.height) + this.props.mainCont.scrollTop; this._height = this._width = 0; - const doc = (this.props.iframe?.()?.contentDocument ?? document); - doc.addEventListener("pointermove", this.onSelectMove); - doc.addEventListener("pointerup", this.onSelectEnd); + const doc = this.props.iframe?.()?.contentDocument ?? document; + doc.addEventListener('pointermove', this.onSelectMove); + doc.addEventListener('pointerup', this.onSelectEnd); - AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight("rgba(173, 216, 230, 0.75)", true), true); - AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight("rgba(173, 216, 230, 0.75)", true)); + AnchorMenu.Instance.OnCrop = (e: PointerEvent) => this.props.anchorMenuCrop?.(this.highlight('rgba(173, 216, 230, 0.75)', true), true); + AnchorMenu.Instance.OnClick = (e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight('rgba(173, 216, 230, 0.75)', true)); AnchorMenu.Instance.Highlight = this.highlight; - AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap) => this.highlight("rgba(173, 216, 230, 0.75)", true, savedAnnotations); + AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations); AnchorMenu.Instance.onMakeAnchor = AnchorMenu.Instance.GetAnchor; /** - * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. + * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. */ AnchorMenu.Instance.StartDrag = action((e: PointerEvent, ele: HTMLElement) => { e.preventDefault(); e.stopPropagation(); const sourceAnchorCreator = () => { - const annoDoc = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color + const annoDoc = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color annoDoc && this.props.addDocument(annoDoc); return annoDoc; }; const targetCreator = (annotationOn: Doc | undefined) => { - const target = CurrentUserUtils.GetNewTextDoc("Note linked to " + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, "yellow"); + const target = DocUtils.GetNewTextDoc('Note linked to ' + this.props.rootDoc.title, 0, 0, 100, 100, undefined, annotationOn, undefined, 'yellow'); FormattedTextBox.SelectOnLoad = target[Id]; return target; }; @@ -92,37 +91,39 @@ export class MarqueeAnnotator extends React.Component { if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { e.annoDragData.linkSourceDoc.isPushpin = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; } - } + }, }); }); /** - * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. + * This function is used by the AnchorMenu to create an anchor highlight and a new linked text annotation. * It also initiates a Drag/Drop interaction to place the text annotation. */ - AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop ? unimplementedFunction : action((e: PointerEvent, ele: HTMLElement) => { - e.preventDefault(); - e.stopPropagation(); - var cropRegion: Doc | undefined; - const sourceAnchorCreator = () => { - cropRegion = this.highlight("rgba(173, 216, 230, 0.75)", true); // hyperlink color - cropRegion && this.props.addDocument(cropRegion); - return cropRegion; - }; - const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!; - DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { - dragComplete: e => { - if (!e.aborted && e.linkDocument) { - Doc.GetProto(e.linkDocument).linkRelationship = "cropped image"; - Doc.GetProto(e.linkDocument).title = "crop: " + this.props.docView.rootDoc.title; - } - } - }); - }); + AnchorMenu.Instance.StartCropDrag = !this.props.anchorMenuCrop + ? unimplementedFunction + : action((e: PointerEvent, ele: HTMLElement) => { + e.preventDefault(); + e.stopPropagation(); + var cropRegion: Doc | undefined; + const sourceAnchorCreator = () => { + cropRegion = this.highlight('rgba(173, 216, 230, 0.75)', true); // hyperlink color + cropRegion && this.props.addDocument(cropRegion); + return cropRegion; + }; + const targetCreator = (annotationOn: Doc | undefined) => this.props.anchorMenuCrop!(cropRegion, false)!; + DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docView, sourceAnchorCreator, targetCreator), e.pageX, e.pageY, { + dragComplete: e => { + if (!e.aborted && e.linkDocument) { + Doc.GetProto(e.linkDocument).linkRelationship = 'cropped image'; + Doc.GetProto(e.linkDocument).title = 'crop: ' + this.props.docView.rootDoc.title; + } + }, + }); + }); } componentWillUnmount() { - const doc = (this.props.iframe?.()?.contentDocument ?? document); - doc.removeEventListener("pointermove", this.onSelectMove); - doc.removeEventListener("pointerup", this.onSelectEnd); + const doc = this.props.iframe?.()?.contentDocument ?? document; + doc.removeEventListener('pointermove', this.onSelectMove); + doc.removeEventListener('pointerup', this.onSelectEnd); } @undoBatch @@ -132,40 +133,42 @@ export class MarqueeAnnotator extends React.Component { if (savedAnnoMap.size === 0) return undefined; const savedAnnos = Array.from(savedAnnoMap.values())[0]; if (savedAnnos.length && (savedAnnos[0] as any).marqueeing) { - const scale = (this.props.scaling?.() || 1); + const scale = this.props.scaling?.() || 1; const anno = savedAnnos[0]; const containerOffset = this.props.containerOffset?.() || [0, 0]; - const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title }); - marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || "0") - containerOffset[0]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1); - marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || "0") - containerOffset[1]) / scale/ NumCast(this.props.docView.props.Document._viewScale,1) + NumCast(this.props.scrollTop); - marqueeAnno._height = parseInt(anno.style.height || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1); - marqueeAnno._width = parseInt(anno.style.width || "0") / scale/ NumCast(this.props.docView.props.Document._viewScale,1); + const marqueeAnno = Docs.Create.FreeformDocument([], { _isLinkButton: isLinkButton, backgroundColor: color, annotationOn: this.props.rootDoc, title: 'Annotation on ' + this.props.rootDoc.title }); + marqueeAnno.x = NumCast(this.props.docView.props.Document.panXMin) + (parseInt(anno.style.left || '0') - containerOffset[0]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1); + marqueeAnno.y = NumCast(this.props.docView.props.Document.panYMin) + (parseInt(anno.style.top || '0') - containerOffset[1]) / scale / NumCast(this.props.docView.props.Document._viewScale, 1) + NumCast(this.props.scrollTop); + marqueeAnno._height = parseInt(anno.style.height || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1); + marqueeAnno._width = parseInt(anno.style.width || '0') / scale / NumCast(this.props.docView.props.Document._viewScale, 1); anno.remove(); savedAnnoMap.clear(); return marqueeAnno; } - const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: "transparent", title: "Selection on " + this.props.rootDoc.title }); + const textRegionAnno = Docs.Create.HTMLAnchorDocument([], { annotationOn: this.props.rootDoc, backgroundColor: 'transparent', title: 'Selection on ' + this.props.rootDoc.title }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; let minY = Number.MAX_VALUE; let maxY = -Number.MIN_VALUE; const annoDocs: Doc[] = []; - savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => value.map(anno => { - const textRegion = new Doc(); - textRegion.x = parseInt(anno.style.left ?? "0"); - textRegion.y = parseInt(anno.style.top ?? "0"); - textRegion._height = parseInt(anno.style.height ?? "0"); - textRegion._width = parseInt(anno.style.width ?? "0"); - textRegion.annoTextRegion = textRegionAnno; - textRegion.backgroundColor = color; - annoDocs.push(textRegion); - anno.remove(); - minY = Math.min(NumCast(textRegion.y), minY); - minX = Math.min(NumCast(textRegion.x), minX); - maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY); - maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX); - })); + savedAnnoMap.forEach((value: HTMLDivElement[], key: number) => + value.map(anno => { + const textRegion = new Doc(); + textRegion.x = parseInt(anno.style.left ?? '0'); + textRegion.y = parseInt(anno.style.top ?? '0'); + textRegion._height = parseInt(anno.style.height ?? '0'); + textRegion._width = parseInt(anno.style.width ?? '0'); + textRegion.annoTextRegion = textRegionAnno; + textRegion.backgroundColor = color; + annoDocs.push(textRegion); + anno.remove(); + minY = Math.min(NumCast(textRegion.y), minY); + minX = Math.min(NumCast(textRegion.x), minX); + maxY = Math.max(NumCast(textRegion.y) + NumCast(textRegion._height), maxY); + maxX = Math.max(NumCast(textRegion.x) + NumCast(textRegion._width), maxX); + }) + ); const textRegionAnnoProto = Doc.GetProto(textRegionAnno); textRegionAnnoProto.y = Math.max(minY, 0); @@ -176,29 +179,29 @@ export class MarqueeAnnotator extends React.Component { textRegionAnnoProto.textInlineAnnotations = new List(annoDocs); savedAnnoMap.clear(); return textRegionAnno; - } + }; @action highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap) => { // creates annotation documents for current highlights const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations); !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc); - return annotationDoc as Doc ?? undefined; - } + return (annotationDoc as Doc) ?? undefined; + }; public static previewNewAnnotation = action((savedAnnotations: ObservableMap, annotationLayer: HTMLDivElement, div: HTMLDivElement, page: number) => { if (div.style.top) { - div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString(); + div.style.top = parseInt(div.style.top) /*+ this.getScrollFromPage(page)*/ + .toString(); } annotationLayer.append(div); - div.style.backgroundColor = "#ACCEF7"; - div.style.opacity = "0.5"; + div.style.backgroundColor = '#ACCEF7'; + div.style.opacity = '0.5'; const savedPage = savedAnnotations.get(page); if (savedPage) { savedPage.push(div); savedAnnotations.set(page, savedPage); - } - else { + } else { savedAnnotations.set(page, [div]); } }); @@ -210,58 +213,65 @@ export class MarqueeAnnotator extends React.Component { const mainRect = this.props.mainCont.getBoundingClientRect(); const cliX = e.clientX * (this.props.iframeScaling?.() || 1) - boundingRect.left; const cliY = e.clientY * (this.props.iframeScaling?.() || 1) - boundingRect.top; - this._width = (cliX * (this.props.mainCont.offsetWidth / mainRect.width)) - this._startX; - this._height = (cliY * (this.props.mainCont.offsetHeight / mainRect.height)) - this._startY + this.props.mainCont.scrollTop; + this._width = cliX * (this.props.mainCont.offsetWidth / mainRect.width) - this._startX; + this._height = cliY * (this.props.mainCont.offsetHeight / mainRect.height) - this._startY + this.props.mainCont.scrollTop; this._left = Math.min(this._startX, this._startX + this._width); this._top = Math.min(this._startY, this._startY + this._height); this._width = Math.abs(this._width); this._height = Math.abs(this._height); e.stopPropagation(); - } + }; onSelectEnd = (e: PointerEvent) => { const mainRect = this.props.mainCont.getBoundingClientRect(); const cliX = e.clientX * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.left : 0); const cliY = e.clientY * (this.props.iframeScaling?.() || 1) + (this.props.iframe ? mainRect.top : 0); - if (this._width > 10 || this._height > 10) { // configure and show the annotation/link menu if a the drag region is big enough - const marquees = this.props.mainCont.getElementsByClassName("marqueeAnnotator-dragBox"); - if (marquees?.length) { // copy the temporary marquee to allow for multiple selections (not currently available though). - const copy = document.createElement("div"); - ["border", "opacity"].forEach(prop => copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any]); + if (this._width > 10 || this._height > 10) { + // configure and show the annotation/link menu if a the drag region is big enough + const marquees = this.props.mainCont.getElementsByClassName('marqueeAnnotator-dragBox'); + if (marquees?.length) { + // copy the temporary marquee to allow for multiple selections (not currently available though). + const copy = document.createElement('div'); + ['border', 'opacity'].forEach(prop => (copy.style[prop as any] = (marquees[0] as HTMLDivElement).style[prop as any])); const bounds = (marquees[0] as HTMLDivElement).getBoundingClientRect(); const uitls = Utils.GetScreenTransform(marquees[0] as HTMLDivElement); - const rbounds = { top: uitls.translateY, left: uitls.translateX, width: (bounds.right - bounds.left), height: (bounds.bottom - bounds.top) }; + const rbounds = { top: uitls.translateY, left: uitls.translateX, width: bounds.right - bounds.left, height: bounds.bottom - bounds.top }; const otls = Utils.GetScreenTransform(this.props.annotationLayer); const fbounds = { top: (rbounds.top - otls.translateY) / otls.scale, left: (rbounds.left - otls.translateX) / otls.scale, width: rbounds.width / otls.scale, height: rbounds.height / otls.scale }; - copy.style.top = fbounds.top.toString() + "px"; - copy.style.left = fbounds.left.toString() + "px"; - copy.style.width = fbounds.width.toString() + "px"; - copy.style.height = fbounds.height.toString() + "px"; - copy.className = "marqueeAnnotator-annotationBox"; + copy.style.top = fbounds.top.toString() + 'px'; + copy.style.left = fbounds.left.toString() + 'px'; + copy.style.width = fbounds.width.toString() + 'px'; + copy.style.height = fbounds.height.toString() + 'px'; + copy.className = 'marqueeAnnotator-annotationBox'; (copy as any).marqueeing = true; MarqueeAnnotator.previewNewAnnotation(this.props.savedAnnotations(), this.props.annotationLayer, copy, this.props.getPageFromScroll?.(this._top) || 0); } AnchorMenu.Instance.jumpTo(cliX, cliY); - if (AnchorMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up - this.highlight("rgba(245, 230, 95, 0.75)", false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) + if (AnchorMenu.Instance.Highlighting) { + // when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up + this.highlight('rgba(245, 230, 95, 0.75)', false); // yellowish highlight color for highlighted text (should match AnchorMenu's highlight color) } this.props.finishMarquee(undefined, undefined, e); } else { - runInAction(() => this._width = this._height = 0); + runInAction(() => (this._width = this._height = 0)); this.props.finishMarquee(cliX, cliY, e); } - } + }; render() { - return
    -
    ; + return ( +
    + ); } } diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 598fff29a..5242fabb8 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -1,23 +1,21 @@ -import { docs } from "googleapis/build/src/apis/docs"; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { computedFn } from "mobx-utils"; -import * as React from "react"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { computedFn } from 'mobx-utils'; +import * as React from 'react'; import ReactLoading from 'react-loading'; -import { Doc, WidthSym, HeightSym, DocListCast } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { Cast, NumCast } from "../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, Utils } from "../../Utils"; -import { DocUtils } from "../documents/Documents"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { DragManager } from "../util/DragManager"; -import { ScriptingGlobals } from "../util/ScriptingGlobals"; -import { Transform } from "../util/Transform"; -import { CollectionFreeFormLinksView } from "./collections/collectionFreeForm/CollectionFreeFormLinksView"; -import { DocumentView } from "./nodes/DocumentView"; +import { Doc, DocListCast, HeightSym, WidthSym } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { NumCast } from '../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../Utils'; +import { DocUtils } from '../documents/Documents'; +import { DragManager } from '../util/DragManager'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { Transform } from '../util/Transform'; +import { CollectionFreeFormLinksView } from './collections/collectionFreeForm/CollectionFreeFormLinksView'; +import { DocumentView } from './nodes/DocumentView'; import './OverlayView.scss'; import { ScriptingRepl } from './ScriptingRepl'; -import { DefaultStyleProvider } from "./StyleProvider"; +import { DefaultStyleProvider } from './StyleProvider'; export type OverlayDisposer = () => void; @@ -52,18 +50,18 @@ export class OverlayWindow extends React.Component { } onPointerDown = (_: React.PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - document.addEventListener("pointermove", this.onPointerMove); - document.addEventListener("pointerup", this.onPointerUp); - } + document.removeEventListener('pointermove', this.onPointerMove); + document.removeEventListener('pointerup', this.onPointerUp); + document.addEventListener('pointermove', this.onPointerMove); + document.addEventListener('pointerup', this.onPointerUp); + }; onResizerPointerDown = (_: React.PointerEvent) => { - document.removeEventListener("pointermove", this.onResizerPointerMove); - document.removeEventListener("pointerup", this.onResizerPointerUp); - document.addEventListener("pointermove", this.onResizerPointerMove); - document.addEventListener("pointerup", this.onResizerPointerUp); - } + document.removeEventListener('pointermove', this.onResizerPointerMove); + document.removeEventListener('pointerup', this.onResizerPointerUp); + document.addEventListener('pointermove', this.onResizerPointerMove); + document.addEventListener('pointerup', this.onResizerPointerUp); + }; @action onPointerMove = (e: PointerEvent) => { @@ -71,7 +69,7 @@ export class OverlayWindow extends React.Component { this.x = Math.max(Math.min(this.x, window.innerWidth - this.width), 0); this.y += e.movementY; this.y = Math.max(Math.min(this.y, window.innerHeight - this.height), 0); - } + }; @action onResizerPointerMove = (e: PointerEvent) => { @@ -79,28 +77,28 @@ export class OverlayWindow extends React.Component { this.width = Math.max(this.width, 30); this.height += e.movementY; this.height = Math.max(this.height, 30); - } + }; onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMove); - document.removeEventListener("pointerup", this.onPointerUp); - } + document.removeEventListener('pointermove', this.onPointerMove); + document.removeEventListener('pointerup', this.onPointerUp); + }; onResizerPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onResizerPointerMove); - document.removeEventListener("pointerup", this.onResizerPointerUp); - } + document.removeEventListener('pointermove', this.onResizerPointerMove); + document.removeEventListener('pointerup', this.onResizerPointerUp); + }; render() { return (
    -
    - {this.props.overlayOptions.title || "Untitled"} - -
    -
    - {this.props.children} +
    + {this.props.overlayOptions.title || 'Untitled'} +
    +
    {this.props.children}
    ); @@ -126,13 +124,20 @@ export class OverlayView extends React.Component { const index = this._elements.indexOf(ele); if (index !== -1) this._elements.splice(index, 1); }); - ele =
    {ele}
    ; + ele = ( +
    + {ele} +
    + ); this._elements.push(ele); return remove; } @@ -143,23 +148,30 @@ export class OverlayView extends React.Component { const index = this._elements.indexOf(contents); if (index !== -1) this._elements.splice(index, 1); }); - contents = {contents}; + contents = ( + + {contents} + + ); this._elements.push(contents); return remove; } removeOverlayDoc = (doc: Doc | Doc[]) => { - (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc)); + (doc instanceof Doc ? [doc] : doc).forEach(doc => Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc)); return true; - } - - docScreenToLocalXf = computedFn(function docScreenToLocalXf(this: any, doc: Doc) { - return () => new Transform(-NumCast(doc.x), -NumCast(doc.y), 1); - }.bind(this)); + }; + + docScreenToLocalXf = computedFn( + function docScreenToLocalXf(this: any, doc: Doc) { + return () => new Transform(-NumCast(doc.x), -NumCast(doc.y), 1); + }.bind(this) + ); @computed get overlayDocs() { - return DocListCast(CurrentUserUtils.MyOverlayDocs?.data).map(d => { - let offsetx = 0, offsety = 0; + return DocListCast(Doc.MyOverlayDocs?.data).map(d => { + let offsetx = 0, + offsety = 0; const dref = React.createRef(); const onPointerMove = action((e: PointerEvent, down: number[]) => { if (e.buttons === 1) { @@ -169,10 +181,10 @@ export class OverlayView extends React.Component { if (e.metaKey) { const dragData = new DragManager.DocumentDragData([d]); dragData.offset = [-offsetx, -offsety]; - dragData.dropAction = "move"; + dragData.dropAction = 'move'; dragData.removeDocument = (doc: Doc | Doc[]) => { - const docs = (doc instanceof Doc) ? [doc] : doc; - docs.forEach(d => Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, d)); + const docs = doc instanceof Doc ? [doc] : doc; + docs.forEach(d => Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, d)); return true; }; dragData.moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { @@ -189,31 +201,39 @@ export class OverlayView extends React.Component { offsetx = NumCast(d.x) - e.clientX; offsety = NumCast(d.y) - e.clientY; }; - return
    - -
    ; + return ( +
    + +
    + ); }); } @@ -221,13 +241,10 @@ export class OverlayView extends React.Component { return OverlayView.Instance.addElement(, { x: 300, y: 200 }); } - render() { return (
    -
    - {this._elements} -
    +
    {this._elements}
    {this.overlayDocs}
    @@ -237,4 +254,4 @@ export class OverlayView extends React.Component { // bcz: ugh ... want to be able to pass ScriptingRepl as tag argument, but that doesn't seem to work.. runtime error ScriptingGlobals.add(function addOverlayWindow(type: string, options: OverlayElementOptions) { OverlayView.Instance.addWindow(, options); -}); \ No newline at end of file +}); diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index ef1360ef1..68f5f072d 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -1,17 +1,16 @@ import { action, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import "normalize.css"; +import 'normalize.css'; import * as React from 'react'; import { Doc } from '../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { returnFalse } from '../../Utils'; import { DocServer } from '../DocServer'; import { Docs, DocUtils } from '../documents/Documents'; -import { CurrentUserUtils } from '../util/CurrentUserUtils'; -import { Transform } from "../util/Transform"; +import { Transform } from '../util/Transform'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; -import "./PreviewCursor.scss"; -import { returnFalse } from '../../Utils'; +import './PreviewCursor.scss'; @observer export class PreviewCursor extends React.Component<{}> { @@ -24,47 +23,66 @@ export class PreviewCursor extends React.Component<{}> { @observable public static Visible = false; constructor(props: any) { super(props); - document.addEventListener("keydown", this.onKeyPress); - document.addEventListener("paste", this.paste); + document.addEventListener('keydown', this.onKeyPress); + document.addEventListener('paste', this.paste); } paste = async (e: ClipboardEvent) => { if (PreviewCursor.Visible && e.clipboardData) { const newPoint = PreviewCursor._getTransform().transformPoint(PreviewCursor._clickPoint[0], PreviewCursor._clickPoint[1]); - runInAction(() => PreviewCursor.Visible = false); + runInAction(() => (PreviewCursor.Visible = false)); // tests for URL and makes web document const re: any = /^https?:\/\//g; - const plain = e.clipboardData.getData("text/plain"); + const plain = e.clipboardData.getData('text/plain'); if (plain) { // tests for youtube and makes video document - if (plain.indexOf("www.youtube.com/watch") !== -1) { - const url = plain.replace("youtube.com/watch?v=", "youtube.com/embed/"); - undoBatch(() => PreviewCursor._addDocument(Docs.Create.VideoDocument(url, { - title: url, _width: 400, _height: 315, _nativeWidth: 600, _nativeHeight: 472.5, - x: newPoint[0], y: newPoint[1] - })))(); - } - - else if (re.test(plain)) { + if (plain.indexOf('www.youtube.com/watch') !== -1) { + const url = plain.replace('youtube.com/watch?v=', 'youtube.com/embed/'); + undoBatch(() => + PreviewCursor._addDocument( + Docs.Create.VideoDocument(url, { + title: url, + _width: 400, + _height: 315, + _nativeWidth: 600, + _nativeHeight: 472.5, + x: newPoint[0], + y: newPoint[1], + }) + ) + )(); + } else if (re.test(plain)) { const url = plain; - undoBatch(() => PreviewCursor._addDocument(Docs.Create.WebDocument(url, { - title: url, _width: 500, _height: 300, useCors: true, x: newPoint[0], y: newPoint[1] - })))(); - } - else if (plain.startsWith("__DashDocId(") || plain.startsWith("__DashCloneId(")) { - const clone = plain.startsWith("__DashCloneId("); - const docids = plain.split(":"); - const strs = docids[0].split(","); - const ptx = Number(strs[0].substring((clone ? "__DashCloneId(" : "__DashDocId(").length)); + undoBatch(() => + PreviewCursor._addDocument( + Docs.Create.WebDocument(url, { + title: url, + _width: 500, + _height: 300, + useCors: true, + x: newPoint[0], + y: newPoint[1], + }) + ) + )(); + } else if (plain.startsWith('__DashDocId(') || plain.startsWith('__DashCloneId(')) { + const clone = plain.startsWith('__DashCloneId('); + const docids = plain.split(':'); + const strs = docids[0].split(','); + const ptx = Number(strs[0].substring((clone ? '__DashCloneId(' : '__DashDocId(').length)); const pty = Number(strs[1].substring(0, strs[1].length - 1)); - const batch = UndoManager.StartBatch("cloning"); + const batch = UndoManager.StartBatch('cloning'); { - const docs = await Promise.all(docids.filter((did, i) => i).map(async (did) => { - const doc = Cast(await DocServer.GetRefField(did), Doc, null); - return clone ? (await Doc.MakeClone(doc)).clone : doc; - })); + const docs = await Promise.all( + docids + .filter((did, i) => i) + .map(async did => { + const doc = Cast(await DocServer.GetRefField(did), Doc, null); + return clone ? (await Doc.MakeClone(doc)).clone : doc; + }) + ); const firstx = docs.length ? NumCast(docs[0].x) + ptx - newPoint[0] : 0; const firsty = docs.length ? NumCast(docs[0].y) + pty - newPoint[1] : 0; docs.map(doc => { @@ -75,79 +93,99 @@ export class PreviewCursor extends React.Component<{}> { } batch.end(); e.stopPropagation(); - } - else { + } else { // creates text document FormattedTextBox.PasteOnLoad = e; - UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(CurrentUserUtils.GetNewTextDoc("-pasted text-", newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), "paste"); + UndoManager.RunInBatch(() => PreviewCursor._addLiveTextDoc(DocUtils.GetNewTextDoc('-pasted text-', newPoint[0], newPoint[1], 500, undefined, undefined, undefined, 750)), 'paste'); } - } else - //pasting in images - if (e.clipboardData.getData("text/html") !== "" && e.clipboardData.getData("text/html").includes(" PreviewCursor._addDocument(Docs.Create.ImageDocument( - arr[1], { - _width: 300, title: arr[1], - x: newPoint[0], - y: newPoint[1], - })))(); - } else if (e.clipboardData.items.length) { - const batch = UndoManager.StartBatch("collection view drop"); - const files: File[] = []; - Array.from(e.clipboardData.items).forEach(item => { - const file = item.getAsFile(); - file && files.push(file); - }); - const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] }); - generatedDocuments.forEach(PreviewCursor._addDocument); - batch.end(); - } + undoBatch(() => + PreviewCursor._addDocument( + Docs.Create.ImageDocument(arr[1], { + _width: 300, + title: arr[1], + x: newPoint[0], + y: newPoint[1], + }) + ) + )(); + } else if (e.clipboardData.items.length) { + const batch = UndoManager.StartBatch('collection view drop'); + const files: File[] = []; + Array.from(e.clipboardData.items).forEach(item => { + const file = item.getAsFile(); + file && files.push(file); + }); + const generatedDocuments = await DocUtils.uploadFilesToDocs(files, { x: newPoint[0], y: newPoint[1] }); + generatedDocuments.forEach(PreviewCursor._addDocument); + batch.end(); + } } - } + }; @action onKeyPress = (e: KeyboardEvent) => { - // Mixing events between React and Native is finicky. + // Mixing events between React and Native is finicky. //if not these keys, make a textbox if preview cursor is active! - if (e.key !== "Escape" && e.key !== "Backspace" && e.key !== "Delete" && e.key !== "CapsLock" && - e.key !== "Alt" && e.key !== "Shift" && e.key !== "Meta" && e.key !== "Control" && - e.key !== "Insert" && e.key !== "Home" && e.key !== "End" && e.key !== "PageUp" && e.key !== "PageDown" && - e.key !== "NumLock" && e.key !== " " && + if ( + e.key !== 'Escape' && + e.key !== 'Backspace' && + e.key !== 'Delete' && + e.key !== 'CapsLock' && + e.key !== 'Alt' && + e.key !== 'Shift' && + e.key !== 'Meta' && + e.key !== 'Control' && + e.key !== 'Insert' && + e.key !== 'Home' && + e.key !== 'End' && + e.key !== 'PageUp' && + e.key !== 'PageDown' && + e.key !== 'NumLock' && + e.key !== ' ' && (e.keyCode < 112 || e.keyCode > 123) && // F1 thru F12 keys - (e.keyCode < 173 || e.keyCode > 183 || e.key === "-") && // mute, volume up/down etc, - is there specifically because its keycode is 173 in Firefox so shouldn't be avoided - !e.key.startsWith("Arrow") && - !e.defaultPrevented) { - if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) {// /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { + (e.keyCode < 173 || e.keyCode > 183 || e.key === '-') && // mute, volume up/down etc, - is there specifically because its keycode is 173 in Firefox so shouldn't be avoided + !e.key.startsWith('Arrow') && + !e.defaultPrevented + ) { + if ((!e.metaKey && !e.ctrlKey) || (e.keyCode >= 48 && e.keyCode <= 57) || (e.keyCode >= 65 && e.keyCode <= 90)) { + // /^[a-zA-Z0-9$*^%#@+-=_|}{[]"':;?/><.,}]$/.test(e.key)) { PreviewCursor.Visible && PreviewCursor._onKeyPress?.(e); - ((!e.ctrlKey && !e.metaKey) || e.key !== "v") && (PreviewCursor.Visible = false); + ((!e.ctrlKey && !e.metaKey) || e.key !== 'v') && (PreviewCursor.Visible = false); } } else if (PreviewCursor.Visible) { - if (e.key === "ArrowRight") { + if (e.key === 'ArrowRight') { PreviewCursor._nudge?.(1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation(); - } else if (e.key === "ArrowLeft") { + } else if (e.key === 'ArrowLeft') { PreviewCursor._nudge?.(-1 * (e.shiftKey ? 2 : 1), 0) && e.stopPropagation(); - } else if (e.key === "ArrowUp") { + } else if (e.key === 'ArrowUp') { PreviewCursor._nudge?.(0, 1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation(); - } else if (e.key === "ArrowDown") { + } else if (e.key === 'ArrowDown') { PreviewCursor._nudge?.(0, -1 * (e.shiftKey ? 2 : 1)) && e.stopPropagation(); } } - } + }; //when focus is lost, this will remove the preview cursor @action onBlur = (): void => { PreviewCursor.Visible = false; - } + }; @action - public static Show(x: number, y: number, + public static Show( + x: number, + y: number, onKeyPress: (e: KeyboardEvent) => void, addLiveText: (doc: Doc) => void, getTransform: () => Transform, addDocument: undefined | ((doc: Doc | Doc[]) => boolean), - nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean)) { + nudge: undefined | ((nudgeX: number, nudgeY: number) => boolean) + ) { this._clickPoint = [x, y]; this._onKeyPress = onKeyPress; this._addLiveTextDoc = addLiveText; @@ -157,10 +195,10 @@ export class PreviewCursor extends React.Component<{}> { this.Visible = true; } render() { - return (!PreviewCursor._clickPoint || !PreviewCursor.Visible) ? (null) : -
    e?.focus()} - style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}> + return !PreviewCursor._clickPoint || !PreviewCursor.Visible ? null : ( +
    e?.focus()} style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)` }}> I -
    ; +
    + ); } -} \ No newline at end of file +} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 9c6d9a108..8c4c1d00b 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -1,221 +1,345 @@ -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, observable } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, Opt } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, Opt } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; -import { BoolCast, StrCast } from "../../fields/Types"; -import { ImageField } from "../../fields/URLField"; +import { BoolCast, StrCast } from '../../fields/Types'; +import { ImageField } from '../../fields/URLField'; import { DocUtils } from '../documents/Documents'; -import { DocumentType } from '../documents/DocumentTypes'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { SelectionManager } from '../util/SelectionManager'; import { undoBatch } from '../util/UndoManager'; -import { CollectionViewType } from './collections/CollectionView'; -import { Colors } from "./global/globalEnums"; +import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { DocumentView } from './nodes/DocumentView'; -import { VideoBox } from "./nodes/VideoBox"; -import { pasteImageBitmap } from "./nodes/WebBoxRenderer"; +import { VideoBox } from './nodes/VideoBox'; +import { pasteImageBitmap } from './nodes/WebBoxRenderer'; import './PropertiesButtons.scss'; -import React = require("react"); -const higflyout = require("@hig/flyout"); +import React = require('react'); +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; enum UtilityButtonState { Default, OpenRight, - OpenExternally + OpenExternally, } @observer export class PropertiesButtons extends React.Component<{}, {}> { @observable public static Instance: PropertiesButtons; - @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc; } - @computed get selectedTabView() { return !SelectionManager.SelectedSchemaDoc() && SelectionManager.Views().lastElement()?.topMost; } + @computed get selectedDoc() { + return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc; + } + @computed get selectedTabView() { + return !SelectionManager.SelectedSchemaDoc() && SelectionManager.Views().lastElement()?.topMost; + } propertyToggleBtn = (label: string, property: string, tooltip: (on?: any) => string, icon: (on: boolean) => string, onClick?: (dv: Opt, doc: Doc, property: string) => void, useUserDoc?: boolean) => { const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedDoc; - const onPropToggle = (dv: Opt, doc: Doc, prop: string) => (dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true; - return !targetDoc ? (null) : + const onPropToggle = (dv: Opt, doc: Doc, prop: string) => ((dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true); + return !targetDoc ? null : ( {tooltip(targetDoc?.[property])}
    } placement="top">
    -
    e.stopPropagation()} onClick={undoBatch(() => { if (SelectionManager.Views().length > 1) { SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property)); } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property); - })} > + })}>
    {label}
    - ; - } + + ); + }; @computed get lockButton() { - return this.propertyToggleBtn("No\xA0Drag", "_lockedPosition", on => `${on ? "Unlock" : "Lock"} position to prevent dragging`, on => "thumbtack"); + return this.propertyToggleBtn( + 'No\xA0Drag', + '_lockedPosition', + on => `${on ? 'Unlock' : 'Lock'} position to prevent dragging`, + on => 'thumbtack' + ); } @computed get dictationButton() { - return this.propertyToggleBtn("Dictate", "_showAudio", on => `${on ? "Hide" : "Show"} dictation/recording controls`, on => "microphone"); + return this.propertyToggleBtn( + 'Dictate', + '_showAudio', + on => `${on ? 'Hide' : 'Show'} dictation/recording controls`, + on => 'microphone' + ); } @computed get maskButton() { - return this.propertyToggleBtn("Mask", "isInkMask", on => on ? "Make plain ink" : "Make highlight mask", on => "paint-brush", (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc)); + return this.propertyToggleBtn( + 'Mask', + 'isInkMask', + on => (on ? 'Make plain ink' : 'Make highlight mask'), + on => 'paint-brush', + (dv, doc) => InkingStroke.toggleMask(dv?.layoutDoc || doc) + ); } @computed get clustersButton() { - return this.propertyToggleBtn("Clusters", "_useClusters", on => `${on ? "Hide" : "Show"} clusters`, on => "braille"); + return this.propertyToggleBtn( + 'Clusters', + '_useClusters', + on => `${on ? 'Hide' : 'Show'} clusters`, + on => 'braille' + ); } @computed get panButton() { - return this.propertyToggleBtn("Lock\xA0View", "_lockedTransform", on => `${on ? "Unlock" : "Lock"} panning of view`, on => "lock"); + return this.propertyToggleBtn( + 'Lock\xA0View', + '_lockedTransform', + on => `${on ? 'Unlock' : 'Lock'} panning of view`, + on => 'lock' + ); } @computed get fitContentButton() { - return this.propertyToggleBtn("View All", "_fitContentsToBox", on => `${on ? "Don't" : "Do"} fit content to container visible area`, on => "eye"); + return this.propertyToggleBtn( + 'View All', + '_fitContentsToBox', + on => `${on ? "Don't" : 'Do'} fit content to container visible area`, + on => 'eye' + ); } @computed get fitWidthButton() { - return this.propertyToggleBtn("Fit\xA0Width", "_fitWidth", on => `${on ? "Don't" : "Do"} fit content to width of container`, on => "arrows-alt-h"); + return this.propertyToggleBtn( + 'Fit\xA0Width', + '_fitWidth', + on => `${on ? "Don't" : 'Do'} fit content to width of container`, + on => 'arrows-alt-h' + ); } @computed get captionButton() { - return this.propertyToggleBtn("Caption", "_showCaption", on => `${on ? "Hide" : "Show"} caption footer`, on => "closed-captioning", (dv, doc) => (dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? "caption" : undefined); + return this.propertyToggleBtn( + 'Caption', + '_showCaption', + on => `${on ? 'Hide' : 'Show'} caption footer`, + on => 'closed-captioning', + (dv, doc) => ((dv?.rootDoc || doc)._showCaption = (dv?.rootDoc || doc)._showCaption === undefined ? 'caption' : undefined) + ); } @computed get chromeButton() { - return this.propertyToggleBtn("Controls", "_chromeHidden", on => `${on ? "Show" : "Hide"} editing UI`, on => "edit", (dv, doc) => (dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden); + return this.propertyToggleBtn( + 'Controls', + '_chromeHidden', + on => `${on ? 'Show' : 'Hide'} editing UI`, + on => 'edit', + (dv, doc) => ((dv?.rootDoc || doc)._chromeHidden = !(dv?.rootDoc || doc)._chromeHidden) + ); } @computed get titleButton() { - return this.propertyToggleBtn("Title", "_showTitle", on => "Switch between title styles", on => "text-width", (dv, doc) => (dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? "title" : (dv?.rootDoc || doc)._showTitle === "title" ? "title:hover" : undefined); + return this.propertyToggleBtn( + 'Title', + '_showTitle', + on => 'Switch between title styles', + on => 'text-width', + (dv, doc) => ((dv?.rootDoc || doc)._showTitle = !(dv?.rootDoc || doc)._showTitle ? 'title' : (dv?.rootDoc || doc)._showTitle === 'title' ? 'title:hover' : undefined) + ); } @computed get autoHeightButton() { - return this.propertyToggleBtn("Auto\xA0Size", "_autoHeight", on => `Automatical vertical sizing to show all content`, on => "arrows-alt-v"); + return this.propertyToggleBtn( + 'Auto\xA0Size', + '_autoHeight', + on => `Automatical vertical sizing to show all content`, + on => 'arrows-alt-v' + ); } @computed get gridButton() { - return this.propertyToggleBtn("Grid", "_backgroundGridShow", on => `Display background grid in collection`, on => "border-all"); + return this.propertyToggleBtn( + 'Grid', + '_backgroundGridShow', + on => `Display background grid in collection`, + on => 'border-all' + ); } @computed get groupButton() { - return this.propertyToggleBtn("Group", "isGroup", on => `Display collection as a Group`, on => "object-group", (dv, doc) => { doc.isGroup = !doc.isGroup; doc.forceActive = doc.isGroup; }); + return this.propertyToggleBtn( + 'Group', + 'isGroup', + on => `Display collection as a Group`, + on => 'object-group', + (dv, doc) => { + doc.isGroup = !doc.isGroup; + doc.forceActive = doc.isGroup; + } + ); } @computed get freezeThumb() { - return this.propertyToggleBtn("Freeze\Thumb", "_thumb-frozen", on => `${on ? "Freeze" : "Unfreeze"} thumbnail`, on => "arrows-alt-h", (dv, doc) => { - if (doc["thumb-frozen"]) doc["thumb-frozen"] = undefined; - else { - document.body.focus(); // so that we can access the clipboard without an error - setTimeout(() => - pasteImageBitmap((data_url: any, error: any) => { - error && console.log(error); - data_url && VideoBox.convertDataUri(data_url, doc[Id] + "-thumb-frozen", true).then( - returnedfilename => doc["thumb-frozen"] = new ImageField(returnedfilename)); - })); + return this.propertyToggleBtn( + 'FreezeThumb', + '_thumb-frozen', + on => `${on ? 'Freeze' : 'Unfreeze'} thumbnail`, + on => 'arrows-alt-h', + (dv, doc) => { + if (doc['thumb-frozen']) doc['thumb-frozen'] = undefined; + else { + document.body.focus(); // so that we can access the clipboard without an error + setTimeout(() => + pasteImageBitmap((data_url: any, error: any) => { + error && console.log(error); + data_url && VideoBox.convertDataUri(data_url, doc[Id] + '-thumb-frozen', true).then(returnedfilename => (doc['thumb-frozen'] = new ImageField(returnedfilename))); + }) + ); + } } - }); + ); } @computed get snapButton() { - return this.propertyToggleBtn("Snap\xA0Lines", "showSnapLines", on => `Display snapping lines when objects are dragged`, on => "border-all", undefined, true); + return this.propertyToggleBtn( + 'Snap\xA0Lines', + 'showSnapLines', + on => `Display snapping lines when objects are dragged`, + on => 'border-all', + undefined, + true + ); } @computed get onClickButton() { - return !this.selectedDoc ? (null) : Choose onClick behavior
    } placement="top"> -
    -
    - -
    e.stopPropagation()} > - -
    -
    + return !this.selectedDoc ? null : ( + Choose onClick behavior
    } placement="top"> +
    +
    + +
    e.stopPropagation()}> + +
    +
    +
    +
    onclick
    -
    onclick
    -
    - ; + + ); } @computed get perspectiveButton() { - return !this.selectedDoc ? (null) : Choose view perspective
    } placement="top"> -
    -
    - -
    e.stopPropagation()} > - -
    -
    + return !this.selectedDoc ? null : ( + Choose view perspective
    } placement="top"> +
    +
    + +
    e.stopPropagation()}> + +
    +
    +
    +
    Perspective
    -
    Perspective
    -
    - ; + + ); } @undoBatch handlePerspectiveChange = (e: any) => { this.selectedDoc && (this.selectedDoc._viewType = e.target.value); - SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => docView.layoutDoc._viewType = e.target.value); - } + SelectionManager.Views() + .filter(dv => dv.docView) + .map(dv => dv.docView!) + .forEach(docView => (docView.layoutDoc._viewType = e.target.value)); + }; @undoBatch @action handleOptionChange = (onClick: string) => { this.selectedDoc && (this.selectedDoc.onClickBehavior = onClick); - SelectionManager.Views().filter(dv => dv.docView).map(dv => dv.docView!).forEach(docView => { - docView.noOnClick(); - switch (onClick) { - case "enterPortal": docView.makeIntoPortal(); break; - case "toggleDetail": docView.setToggleDetail(); break; - case "linkInPlace": docView.toggleFollowLink("inPlace", true, false); break; - case "linkOnRight": docView.toggleFollowLink("add:right", false, false); break; - } - }); - } + SelectionManager.Views() + .filter(dv => dv.docView) + .map(dv => dv.docView!) + .forEach(docView => { + docView.noOnClick(); + switch (onClick) { + case 'enterPortal': + docView.makeIntoPortal(); + break; + case 'toggleDetail': + docView.setToggleDetail(); + break; + case 'linkInPlace': + docView.toggleFollowLink('inPlace', true, false); + break; + case 'linkOnRight': + docView.toggleFollowLink('add:right', false, false); + break; + } + }); + }; @undoBatch editOnClickScript = () => { - if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, "onClick")); - else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick"); - } + if (SelectionManager.Views().length) SelectionManager.Views().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, 'onClick')); + else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick'); + }; @computed get onClickFlyout() { const buttonList = [ - ["nothing", "Select Document"], - ["enterPortal", "Enter Portal"], - ["toggleDetail", "Toggle Detail"], - ["linkInPlace", "Follow Link"], - ["linkOnRight", "Open Link on Right"] + ['nothing', 'Select Document'], + ['enterPortal', 'Enter Portal'], + ['toggleDetail', 'Toggle Detail'], + ['linkInPlace', 'Follow Link'], + ['linkOnRight', 'Open Link on Right'], ]; const currentSelection = this.selectedDoc.onClickBehavior; // Get items to place into the list - const list = buttonList.map((value) => { + const list = buttonList.map(value => { const click = () => { this.handleOptionChange(value[0]); }; - return
    - {value[1]} -
    ; + return ( +
    + {value[1]} +
    + ); }); - return
    + return (
    -
    - {list} +
    +
    {list}
    + {Doc.noviceMode ? null : ( +
    + {' '} + Edit onClick Script +
    + )}
    - {Doc.noviceMode ? (null) :
    Edit onClick Script
    } -
    ; + ); } @computed get onPerspectiveFlyout() { const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; - const makeLabel = (value: string, label: string) =>
    - -
    ; - return
    - {Object.values(CollectionViewType).filter(type => !excludedViewTypes.includes(type)).map(type => makeLabel(type, type))} -
    ; + const makeLabel = (value: string, label: string) => ( +
    + +
    + ); + return ( +
    + {Object.values(CollectionViewType) + .filter(type => !excludedViewTypes.includes(type)) + .map(type => makeLabel(type, type))} +
    + ); } render() { @@ -228,27 +352,33 @@ export class PropertiesButtons extends React.Component<{}, {}> { const isFreeForm = this.selectedDoc?._viewType === CollectionViewType.Freeform; const isTree = this.selectedDoc?._viewType === CollectionViewType.Tree; const isTabView = this.selectedTabView; - const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) =>
    {ele}
    ; + const toggle = (ele: JSX.Element | null, style?: React.CSSProperties) => ( +
    + {' '} + {ele}{' '} +
    + ); const isNovice = Doc.noviceMode; - return !this.selectedDoc ? (null) : + return !this.selectedDoc ? null : (
    {toggle(this.titleButton)} {toggle(this.captionButton)} {toggle(this.lockButton)} - {toggle(this.dictationButton, { display: isNovice ? "none" : "" })} + {toggle(this.dictationButton, { display: isNovice ? 'none' : '' })} {toggle(this.onClickButton)} {toggle(this.fitWidthButton)} {toggle(this.freezeThumb)} - {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? "none" : "" })} - {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? "none" : "" })} - {toggle(this.maskButton, { display: !isInk ? "none" : "" })} - {toggle(this.chromeButton, { display: !isCollection || isNovice ? "none" : "" })} - {toggle(this.gridButton, { display: !isCollection ? "none" : "" })} - {toggle(this.groupButton, { display: isTabView || !isCollection ? "none" : "" })} - {toggle(this.snapButton, { display: !isCollection ? "none" : "" })} - {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })} - {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })} - {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? "none" : "" })} -
    ; - } -} \ No newline at end of file + {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })} + {toggle(this.autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} + {toggle(this.maskButton, { display: !isInk ? 'none' : '' })} + {toggle(this.chromeButton, { display: !isCollection || isNovice ? 'none' : '' })} + {toggle(this.gridButton, { display: !isCollection ? 'none' : '' })} + {toggle(this.groupButton, { display: isTabView || !isCollection ? 'none' : '' })} + {toggle(this.snapButton, { display: !isCollection ? 'none' : '' })} + {toggle(this.clustersButton, { display: !isFreeForm ? 'none' : '' })} + {toggle(this.panButton, { display: !isFreeForm ? 'none' : '' })} + {toggle(this.perspectiveButton, { display: !isCollection || isNovice ? 'none' : '' })} +
    + ); + } +} diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 1af706bb5..0f63ebc1d 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -1,20 +1,20 @@ -import { computed } from "mobx"; -import { observer } from "mobx-react"; -import * as React from "react"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { DocFocusOrOpen } from "../util/DocumentManager"; -import { CollectionDockingView } from "./collections/CollectionDockingView"; -import { CollectionViewType } from "./collections/CollectionView"; -import { DocumentView } from "./nodes/DocumentView"; +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { Cast, NumCast, StrCast } from '../../fields/Types'; +import { CollectionViewType } from '../documents/DocumentTypes'; +import { DocFocusOrOpen } from '../util/DocumentManager'; +import { CollectionDockingView } from './collections/CollectionDockingView'; +import { DocumentView } from './nodes/DocumentView'; import './PropertiesDocContextSelector.scss'; type PropertiesDocContextSelectorProps = { - DocView?: DocumentView, - Stack?: any, - hideTitle?: boolean, - addDocTab(doc: Doc, location: string): void + DocView?: DocumentView; + Stack?: any; + hideTitle?: boolean; + addDocTab(doc: Doc, location: string): void; }; @observer @@ -26,10 +26,23 @@ export class PropertiesDocContextSelector extends React.Component alias.context && alias.context instanceof Doc && Cast(alias.context, Doc, null) !== targetContext).reduce((set, alias) => set.add(Cast(alias.context, Doc, null)), new Set()); const containerSets = Array.from(containerProtos.keys()).map(container => DocListCast(container.aliases)); - const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set()); + const containers = containerSets.reduce((p, set) => { + set.map(s => p.add(s)); + return p; + }, new Set()); const doclayoutSets = Array.from(containers.keys()).map(dp => DocListCast(dp.aliases)); - const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set()).keys()); - return doclayouts.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).filter(doc => !Doc.IsSystem(doc)).map(doc => ({ col: doc, target })); + const doclayouts = Array.from( + doclayoutSets + .reduce((p, set) => { + set.map(s => p.add(s)); + return p; + }, new Set()) + .keys() + ); + return doclayouts + .filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)) + .filter(doc => !Doc.IsSystem(doc)) + .map(doc => ({ col: doc, target })); } getOnClick = (col: Doc, target: Doc) => { @@ -40,14 +53,20 @@ export class PropertiesDocContextSelector extends React.Component DocFocusOrOpen(Doc.GetProto(this.props.DocView!.props.Document), col), 100); - } + }; render() { - return
    - {this.props.hideTitle ? (null) :

    Contexts:

    } - {this._docs.map(doc =>

    this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)}

    )} -
    ; + return ( +
    + {this.props.hideTitle ? null :

    Contexts:

    } + {this._docs.map(doc => ( +

    + this.getOnClick(doc.col, doc.target)}>{StrCast(doc.col.title)} +

    + ))} +
    + ); } -} \ No newline at end of file +} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index faab2ed26..aecbc4255 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1,44 +1,42 @@ -import React = require("react"); +import React = require('react'); +import { IconLookup } from '@fortawesome/fontawesome-svg-core'; import { faAnchor, faArrowRight } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Checkbox, Tooltip } from "@material-ui/core"; -import { intersection } from "lodash"; -import { action, autorun, computed, Lambda, observable } from "mobx"; -import { observer } from "mobx-react"; -import { ColorState, SketchPicker } from "react-color"; -import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from "../../fields/Doc"; -import { Id } from "../../fields/FieldSymbols"; -import { InkField } from "../../fields/InkField"; -import { List } from "../../fields/List"; -import { ComputedField } from "../../fields/ScriptField"; -import { Cast, NumCast, StrCast, DocCast } from "../../fields/Types"; -import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from "../../fields/util"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from "../../Utils"; -import { DocumentType } from "../documents/DocumentTypes"; -import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { DocumentManager } from "../util/DocumentManager"; -import { LinkManager } from "../util/LinkManager"; -import { SelectionManager } from "../util/SelectionManager"; -import { SharingManager } from "../util/SharingManager"; -import { Transform } from "../util/Transform"; -import { undoBatch, UndoManager } from "../util/UndoManager"; -import { CollectionDockingView } from "./collections/CollectionDockingView"; -import { CollectionViewType } from "./collections/CollectionView"; -import { EditableView } from "./EditableView"; -import { InkStrokeProperties } from "./InkStrokeProperties"; -import { DocumentView, StyleProviderFunc } from "./nodes/DocumentView"; -import { KeyValueBox } from "./nodes/KeyValueBox"; -import { PropertiesButtons } from "./PropertiesButtons"; -import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector"; -import "./PropertiesView.scss"; -import { DefaultStyleProvider } from "./StyleProvider"; -import { PresBox } from "./nodes/trails"; -import { IconLookup } from "@fortawesome/fontawesome-svg-core"; -import { PropertiesDocBacklinksSelector } from "./PropertiesDocBacklinksSelector"; -const higflyout = require("@hig/flyout"); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Checkbox, Tooltip } from '@material-ui/core'; +import { intersection } from 'lodash'; +import { action, autorun, computed, Lambda, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import { ColorState, SketchPicker } from 'react-color'; +import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, AclSelfEdit, AclSym, AclUnset, DataSym, Doc, Field, HeightSym, NumListCast, Opt, StrListCast, WidthSym } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { InkField } from '../../fields/InkField'; +import { List } from '../../fields/List'; +import { ComputedField } from '../../fields/ScriptField'; +import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; +import { denormalizeEmail, GetEffectiveAcl, SharingPermissions } from '../../fields/util'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../Utils'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; +import { DocumentManager } from '../util/DocumentManager'; +import { LinkManager } from '../util/LinkManager'; +import { SelectionManager } from '../util/SelectionManager'; +import { SharingManager } from '../util/SharingManager'; +import { Transform } from '../util/Transform'; +import { undoBatch, UndoManager } from '../util/UndoManager'; +import { EditableView } from './EditableView'; +import { InkStrokeProperties } from './InkStrokeProperties'; +import { DocumentView, StyleProviderFunc } from './nodes/DocumentView'; +import { FilterBox } from './nodes/FilterBox'; +import { KeyValueBox } from './nodes/KeyValueBox'; +import { PresBox } from './nodes/trails'; +import { PropertiesButtons } from './PropertiesButtons'; +import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector'; +import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; +import './PropertiesView.scss'; +import { DefaultStyleProvider } from './StyleProvider'; +const higflyout = require('@hig/flyout'); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -const _global = (window /* browser */ || global /* node */) as any; +const _global = (window /* browser */ || global) /* node */ as any; interface PropertiesViewProps { width: number; @@ -51,9 +49,13 @@ interface PropertiesViewProps { export class PropertiesView extends React.Component { private _widthUndo?: UndoManager.Batch; - @computed get MAX_EMBED_HEIGHT() { return 200; } + @computed get MAX_EMBED_HEIGHT() { + return 200; + } - @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || CurrentUserUtils.ActiveDashboard; } + @computed get selectedDoc() { + return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc || Doc.ActiveDashboard; + } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; if (PresBox.Instance?._selectedArray.size) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); @@ -65,7 +67,9 @@ export class PropertiesView extends React.Component { @computed get isLink(): boolean { return this.selectedDoc?.type === DocumentType.LINK; } - @computed get dataDoc() { return this.selectedDoc?.[DataSym]; } + @computed get dataDoc() { + return this.selectedDoc?.[DataSym]; + } @observable layoutFields: boolean = false; @@ -107,14 +111,16 @@ export class PropertiesView extends React.Component { this.selectedDocListenerDisposer?.(); } - @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; } + @computed get isInk() { + return this.selectedDoc?.type === DocumentType.INK; + } rtfWidth = () => { return !this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20); - } + }; rtfHeight = () => { return !this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - } + }; @action docWidth = () => { @@ -126,62 +132,74 @@ export class PropertiesView extends React.Component { } else { return 0; } - } + }; @action docHeight = () => { if (this.selectedDoc && this.dataDoc) { const layoutDoc = this.selectedDoc; - return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, - Doc.NativeAspect(layoutDoc, undefined, true) ? this.docWidth() / Doc.NativeAspect(layoutDoc, undefined, true) : - layoutDoc._fitWidth ? (!Doc.NativeHeight(this.dataDoc) ? NumCast(this.props.height) : - Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc)) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height))) : - NumCast(layoutDoc._height) || 50)); + return Math.max( + 70, + Math.min( + this.MAX_EMBED_HEIGHT, + Doc.NativeAspect(layoutDoc, undefined, true) + ? this.docWidth() / Doc.NativeAspect(layoutDoc, undefined, true) + : layoutDoc._fitWidth + ? !Doc.NativeHeight(this.dataDoc) + ? NumCast(this.props.height) + : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / Doc.NativeWidth(layoutDoc) || NumCast(this.props.height)) + : NumCast(layoutDoc._height) || 50 + ) + ); } return 0; - } + }; @computed get expandedField() { if (this.dataDoc && this.selectedDoc) { const ids: { [key: string]: string } = {}; - const docs = SelectionManager.Views().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : - SelectionManager.Views().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc); + const docs = SelectionManager.Views().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); const rows: JSX.Element[] = []; for (const key of Object.keys(ids).slice().sort()) { const docvals = new Set(); docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; - if (key[0] === "#") { - rows.push(
    - {key} -   -
    ); + const contents = Array.from(docvals.keys()).length > 1 ? '-multiple' : docs[0][key]; + if (key[0] === '#') { + rows.push( +
    + {key} +   +
    + ); } else { - const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} - SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} - />; - rows.push(
    - {key + ":"} -   - {contentElement} -
    ); + const contentElement = ( + (contents !== undefined ? Field.toString(contents as Field) : 'null')} + SetValue={(value: string) => { + docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); + return true; + }} + /> + ); + rows.push( +
    + {key + ':'} +   + {contentElement} +
    + ); } } - rows.push(
    - ""} - SetValue={this.setKeyValue} /> -
    ); + rows.push( +
    + ''} SetValue={this.setKeyValue} /> +
    + ); return rows; } } @@ -192,74 +210,80 @@ export class PropertiesView extends React.Component { const docs = SelectionManager.Views().length < 2 ? [this.dataDoc] : SelectionManager.Views().map(dv => dv.dataDoc); docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); const rows: JSX.Element[] = []; - const noviceReqFields = ["author", "creationDate", "tags"]; - const noviceLayoutFields = ["_curPage"]; - const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("acl"))), - ...noviceReqFields, ...noviceLayoutFields]; + const noviceReqFields = ['author', 'creationDate', 'tags']; + const noviceLayoutFields = ['_curPage']; + const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === '#' || key.indexOf('lastModified') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('acl'))), ...noviceReqFields, ...noviceLayoutFields]; for (const key of noviceKeys.sort()) { const docvals = new Set(); docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; - if (key[0] === "#") { - rows.push(
    - {key} -   -
    ); + const contents = Array.from(docvals.keys()).length > 1 ? '-multiple' : docs[0][key]; + if (key[0] === '#') { + rows.push( +
    + {key} +   +
    + ); } else if (contents !== undefined) { const value = Field.toString(contents as Field); - if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) { - rows.push(
    - {key + ": "} -
    {value}
    -
    ); + if (noviceReqFields.includes(key) || key.indexOf('lastModified') !== -1) { + rows.push( +
    + {key + ': '} +
    {value}
    +
    + ); } else { - const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} - SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} - />; + const contentElement = ( + (contents !== undefined ? Field.toString(contents as Field) : 'null')} + SetValue={(value: string) => { + docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); + return true; + }} + /> + ); - rows.push(
    - {key + ":"} -   - {contentElement} -
    ); + rows.push( +
    + {key + ':'} +   + {contentElement} +
    + ); } } } - rows.push(
    - ""} - SetValue={this.setKeyValue} /> -
    ); + rows.push( +
    + ''} SetValue={this.setKeyValue} /> +
    + ); return rows; } } @undoBatch setKeyValue = (value: string) => { - const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc); + const docs = SelectionManager.Views().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); docs.forEach(doc => { - if (value.indexOf(":") !== -1) { + if (value.indexOf(':') !== -1) { const newVal = value[0].toUpperCase() + value.substring(1, value.length); - const splits = newVal.split(":"); + const splits = newVal.split(':'); KeyValueBox.SetField(doc, splits[0], splits[1], true); - const tags = StrCast(doc.tags, ":"); - if (tags.includes(`${splits[0]}:`) && splits[1] === "undefined") { - KeyValueBox.SetField(doc, "tags", `"${tags.replace(splits[0] + ":", "")}"`, true); + const tags = StrCast(doc.tags, ':'); + if (tags.includes(`${splits[0]}:`) && splits[1] === 'undefined') { + KeyValueBox.SetField(doc, 'tags', `"${tags.replace(splits[0] + ':', '')}"`, true); } return true; - } else if (value[0] === "#") { + } else if (value[0] === '#') { const newVal = value + `:'${value}'`; doc[DataSym][value] = value; - const tags = StrCast(doc.tags, ":"); + const tags = StrCast(doc.tags, ':'); if (!tags.includes(`${value}:`)) { doc[DataSym].tags = `${tags + value + ':'}`; } @@ -267,68 +291,72 @@ export class PropertiesView extends React.Component { } }); return false; - } + }; @observable transform: Transform = Transform.Identity(); getTransform = () => this.transform; propertiesDocViewRef = (ref: HTMLDivElement) => { - const observer = new _global.ResizeObserver(action((entries: any) => { - const cliRect = ref.getBoundingClientRect(); - this.transform = new Transform(-cliRect.x, -cliRect.y, 1); - })); + const observer = new _global.ResizeObserver( + action((entries: any) => { + const cliRect = ref.getBoundingClientRect(); + this.transform = new Transform(-cliRect.x, -cliRect.y, 1); + }) + ); ref && observer.observe(ref); - } + }; @computed get contexts() { - return !this.selectedDoc ? (null) : ; + return !this.selectedDoc ? null : ; } @computed get links() { - return !this.selectedDoc ? (null) : ; + return !this.selectedDoc ? null : ; } @computed get layoutPreview() { if (SelectionManager.Views().length > 1) { - return "-- multiple selected --"; + return '-- multiple selected --'; } if (this.selectedDoc) { const layoutDoc = Doc.Layout(this.selectedDoc); - const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight; - const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth; - return
    - -
    ; + const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfHeight : this.docHeight; + const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes('FormattedTextBox') ? this.rtfWidth : this.docWidth; + return ( +
    + +
    + ); } else { return null; } @@ -339,67 +367,85 @@ export class PropertiesView extends React.Component { */ @undoBatch changePermissions = (e: any, user: string) => { - const docs = SelectionManager.Views().length < 2 ? (this.selectedDoc ? [this.selectedDoc]:[]) : SelectionManager.Views().map(docView => docView.props.Document); + const docs = SelectionManager.Views().length < 2 ? (this.selectedDoc ? [this.selectedDoc] : []) : SelectionManager.Views().map(docView => docView.props.Document); SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); - } + }; /** * @returns the options for the permissions dropdown. */ getPermissionsSelect(user: string, permission: string) { const dropdownValues: string[] = Object.values(SharingPermissions); - if (permission === "-multiple-") dropdownValues.unshift(permission); - if (user === "Override") dropdownValues.unshift("None"); - return ; + if (permission === '-multiple-') dropdownValues.unshift(permission); + if (user === 'Override') dropdownValues.unshift('None'); + return ( + + ); } /** * @returns the notification icon. On clicking, it should notify someone of a document been shared with them. */ @computed get notifyIcon() { - return Notify with message
    }> -
    - -
    - ; + return ( + Notify with message
    }> +
    + +
    + + ); } /** * ... next to the owner that opens the main SharingManager interface on click. */ @computed get expansionIcon() { - return {"Show more permissions"}
    }> -
    { - if (this.selectedDocumentView || this.selectedDoc) { - SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc); - } - }}> - -
    - ; + return ( + {'Show more permissions'}
    }> +
    { + if (this.selectedDocumentView || this.selectedDoc) { + SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined, this.selectedDoc); + } + }}> + +
    + + ); } /** * @returns a row of the permissions panel */ sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { - return
    this.selectedUser = this.selectedUser === name ? "" : name)} - > -
    {name}
    - {/* {name !== "Me" ? this.notifyIcon : null} */} -
    - {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission} - {permission === "Owner" || showExpansionIcon ? this.expansionIcon : null} + return ( +
    this.selectedUser = this.selectedUser === name ? "" : name)} + > +
    + {' '} + {name}{' '} +
    + {/* {name !== "Me" ? this.notifyIcon : null} */} +
    + {admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : permission} + {permission === 'Owner' || showExpansionIcon ? this.expansionIcon : null} +
    -
    ; + ); } /** @@ -407,19 +453,18 @@ export class PropertiesView extends React.Component { */ @computed get sharingTable() { const AclMap = new Map([ - [AclUnset, "None"], + [AclUnset, 'None'], [AclPrivate, SharingPermissions.None], [AclReadonly, SharingPermissions.View], [AclAugment, SharingPermissions.Augment], [AclSelfEdit, SharingPermissions.SelfEdit], [AclEdit, SharingPermissions.Edit], - [AclAdmin, SharingPermissions.Admin] + [AclAdmin, SharingPermissions.Admin], ]); // all selected docs - const docs = SelectionManager.Views().length < 2 ? - [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc?.[DataSym]] - : SelectionManager.Views().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]); + const docs = + SelectionManager.Views().length < 2 ? [this.layoutDocAcls ? this.selectedDoc : this.selectedDoc?.[DataSym]] : SelectionManager.Views().map(docView => (this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym])); const target = docs[0]; @@ -428,7 +473,7 @@ export class PropertiesView extends React.Component { const showAdmin = effectiveAcls.every(acl => acl === AclAdmin); // users in common between all docs - const commonKeys: string[] = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym]))); + const commonKeys: string[] = intersection(...docs.map(doc => (this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym])))); const tableEntries = []; @@ -436,9 +481,9 @@ export class PropertiesView extends React.Component { if (commonKeys.length) { for (const key of commonKeys) { const name = denormalizeEmail(key.substring(4)); - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]); - if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) { - tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-")); + const uniform = docs.every(doc => (this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key])); + if (name !== Doc.CurrentUserEmail && name !== target.author && name !== 'Public' && name !== 'Override' /* && sidebarUsersDisplayed![name] !== false*/) { + tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : '-multiple-')); } } } @@ -446,62 +491,53 @@ export class PropertiesView extends React.Component { const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); // shifts the current user, owner, public to the top of the doc. // tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-")); - tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Public"] === docs[0]["acl-Public"]) ? (AclMap.get(target[AclSym]?.["acl-Public"]) || SharingPermissions.None) : "-multiple-")); - tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame)); - if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); + tableEntries.unshift(this.sharingItem('Public', showAdmin, docs.filter(doc => doc).every(doc => doc['acl-Public'] === docs[0]['acl-Public']) ? AclMap.get(target[AclSym]?.['acl-Public']) || SharingPermissions.None : '-multiple-')); + tableEntries.unshift( + this.sharingItem('Me', showAdmin, docs.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? 'Owner' : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : '-multiple-', !ownerSame) + ); + if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, 'Owner')); - return
    - {tableEntries} -
    ; + return
    {tableEntries}
    ; } @computed get fieldsCheckbox() { - return ; + return ; } @action toggleCheckbox = () => { this.layoutFields = !this.layoutFields; - } + }; @computed get editableTitle() { const titles = new Set(); SelectionManager.Views().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); - const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title); - return
    - title} - SetValue={this.setTitle} /> -
    ; + const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title); + return ( +
    + title} SetValue={this.setTitle} /> +
    + ); } @undoBatch @action setTitle = (value: string) => { if (SelectionManager.Views().length > 1) { - SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true)); + SelectionManager.Views().map(dv => Doc.SetInPlace(dv.rootDoc, 'title', value, true)); return true; } else if (this.dataDoc) { - if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true); - else KeyValueBox.SetField(this.dataDoc, "title", value, true); + if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true); + else KeyValueBox.SetField(this.dataDoc, 'title', value, true); return true; } return false; - } - + }; @undoBatch @action rotate = (angle: number) => { - const _centerPoints: { X: number, Y: number }[] = []; + const _centerPoints: { X: number; Y: number }[] = []; if (this.selectedDoc) { const doc = this.selectedDoc; if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { @@ -522,7 +558,7 @@ export class PropertiesView extends React.Component { doc.rotation = NumCast(doc.rotation) + angle; const inks = Cast(doc.data, InkField)?.inkData; if (inks) { - const newPoints: { X: number, Y: number }[] = []; + const newPoints: { X: number; Y: number }[] = []; inks.forEach(ink => { const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; @@ -536,117 +572,134 @@ export class PropertiesView extends React.Component { const right = Math.max(...xs); const bottom = Math.max(...ys); - doc._height = (bottom - top); - doc._width = (right - left); + doc._height = bottom - top; + doc._width = right - left; } index++; } } - } + }; @computed get controlPointsButton() { - return
    - {"Edit points"}
    }> -
    InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton)} > - -
    - - {InkStrokeProperties.Instance._lock ? "Unlock ratio" : "Lock ratio"}
    }> -
    InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock)} > - -
    - - {"Rotate 90Ëš"}
    }> -
    this.rotate(Math.PI / 2))}> - -
    - -
    ; + return ( +
    + {'Edit points'}
    }> +
    (InkStrokeProperties.Instance._controlButton = !InkStrokeProperties.Instance._controlButton))}> + +
    + + {InkStrokeProperties.Instance._lock ? 'Unlock ratio' : 'Lock ratio'}
    }> +
    (InkStrokeProperties.Instance._lock = !InkStrokeProperties.Instance._lock))}> + +
    + + {'Rotate 90Ëš'}
    }> +
    this.rotate(Math.PI / 2))}> + +
    + +
    + ); } inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => { - return
    -
    {title}
    - { - setter(e.target.value); - }} - onKeyPress={e => { - e.stopPropagation(); - }} /> -
    -
    this.upDownButtons("up", key)))} > - -
    -
    this.upDownButtons("down", key)))} > - + return ( +
    +
    {title}
    + { + setter(e.target.value); + }} + onKeyPress={e => { + e.stopPropagation(); + }} + /> +
    +
    this.upDownButtons('up', key)))}> + +
    +
    this.upDownButtons('down', key)))}> + +
    -
    ; - } + ); + }; inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => { - return
    - {this.inputBox(key, value, setter, title1)} - {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)} -
    ; - } + return ( +
    + {this.inputBox(key, value, setter, title1)} + {title2 === '' ? null : this.inputBox(key2, value2, setter2, title2)} +
    + ); + }; @action upDownButtons = (dirs: string, field: string) => { switch (field) { - case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break; - case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break; - case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break; - case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break; - case "wid": + case 'rot': + this.rotate(dirs === 'up' ? 0.1 : -0.1); + break; + case 'Xps': + this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10)); + break; + case 'Yps': + this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10)); + break; + case 'stk': + this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === 'up' ? 0.1 : -0.1)); + break; + case 'wid': const oldWidth = NumCast(this.selectedDoc?._width); const oldHeight = NumCast(this.selectedDoc?._height); const oldX = NumCast(this.selectedDoc?.x); const oldY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10)); - InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height))); + this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === 'up' ? 10 : -10)); + InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth) * NumCast(this.selectedDoc?._height)); const doc = this.selectedDoc; if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { const ink = Cast(doc.data, InkField)?.inkData; if (ink) { - const newPoints: { X: number, Y: number }[] = []; + const newPoints: { X: number; Y: number }[] = []; for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth; - const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight; + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = NumCast(doc.x) - oldX + (ink[j].X * NumCast(doc._width)) / oldWidth; + const newY = NumCast(doc.y) - oldY + (ink[j].Y * NumCast(doc._height)) / oldHeight; newPoints.push({ X: newX, Y: newY }); } doc.data = new InkField(newPoints); } } break; - case "hgt": + case 'hgt': const oWidth = NumCast(this.selectedDoc?._width); const oHeight = NumCast(this.selectedDoc?._height); const oX = NumCast(this.selectedDoc?.x); const oY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10)); - InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width))); + this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === 'up' ? 10 : -10)); + InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight) * NumCast(this.selectedDoc?._width)); const docu = this.selectedDoc; if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) { const ink = Cast(docu.data, InkField)?.inkData; if (ink) { - const newPoints: { X: number, Y: number }[] = []; + const newPoints: { X: number; Y: number }[] = []; for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth; - const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight; + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = NumCast(docu.x) - oX + (ink[j].X * NumCast(docu._width)) / oWidth; + const newY = NumCast(docu.y) - oY + (ink[j].Y * NumCast(docu._height)) / oHeight; newPoints.push({ X: newX, Y: newY }); } docu.data = new InkField(newPoints); @@ -654,7 +707,7 @@ export class PropertiesView extends React.Component { } break; } - } + }; getField(key: string) { //if (this.selectedDoc) { @@ -664,14 +717,30 @@ export class PropertiesView extends React.Component { // } } - @computed get shapeXps() { return this.getField("x"); } - @computed get shapeYps() { return this.getField("y"); } - @computed get shapeRot() { return this.getField("rotation"); } - @computed get shapeHgt() { return this.getField("_height"); } - @computed get shapeWid() { return this.getField("_width"); } - set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); } - set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); } - set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); } + @computed get shapeXps() { + return this.getField('x'); + } + @computed get shapeYps() { + return this.getField('y'); + } + @computed get shapeRot() { + return this.getField('rotation'); + } + @computed get shapeHgt() { + return this.getField('_height'); + } + @computed get shapeWid() { + return this.getField('_width'); + } + set shapeXps(value) { + this.selectedDoc && (this.selectedDoc.x = Number(value)); + } + set shapeYps(value) { + this.selectedDoc && (this.selectedDoc.y = Number(value)); + } + set shapeRot(value) { + this.selectedDoc && (this.selectedDoc.rotation = Number(value)); + } set shapeWid(value) { const oldWidth = NumCast(this.selectedDoc?._width); this.selectedDoc && (this.selectedDoc._width = Number(value)); @@ -683,38 +752,122 @@ export class PropertiesView extends React.Component { InkStrokeProperties.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight); } - @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); } - @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); } - @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); } - + @computed get hgtInput() { + return this.inputBoxDuo( + 'hgt', + this.shapeHgt, + (val: string) => { + if (!isNaN(Number(val))) { + this.shapeHgt = val; + } + return true; + }, + 'H:', + 'wid', + this.shapeWid, + (val: string) => { + if (!isNaN(Number(val))) { + this.shapeWid = val; + } + return true; + }, + 'W:' + ); + } + @computed get XpsInput() { + return this.inputBoxDuo( + 'Xps', + this.shapeXps, + (val: string) => { + if (val !== '0' && !isNaN(Number(val))) { + this.shapeXps = val; + } + return true; + }, + 'X:', + 'Yps', + this.shapeYps, + (val: string) => { + if (val !== '0' && !isNaN(Number(val))) { + this.shapeYps = val; + } + return true; + }, + 'Y:' + ); + } + @computed get rotInput() { + return this.inputBoxDuo( + 'rot', + this.shapeRot, + (val: string) => { + if (!isNaN(Number(val))) { + this.rotate(Number(val) - Number(this.shapeRot)); + this.shapeRot = val; + } + return true; + }, + '∠:', + 'rot', + this.shapeRot, + (val: string) => { + if (!isNaN(Number(val))) { + this.rotate(Number(val) - Number(this.shapeRot)); + this.shapeRot = val; + } + return true; + }, + '' + ); + } @observable private _fillBtn = false; @observable private _lineBtn = false; - private _lastFill = "#D0021B"; - private _lastLine = "#D0021B"; - private _lastDash: any = "2"; + private _lastFill = '#D0021B'; + private _lastLine = '#D0021B'; + private _lastDash: any = '2'; - @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; } - @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } - set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } - set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } + @computed get colorFil() { + const ccol = this.getField('fillColor') || ''; + ccol && (this._lastFill = ccol); + return ccol; + } + @computed get colorStk() { + const ccol = this.getField('color') || ''; + ccol && (this._lastLine = ccol); + return ccol; + } + set colorFil(value) { + value && (this._lastFill = value); + this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); + } + set colorStk(value) { + value && (this._lastLine = value); + this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); + } colorButton(value: string, type: string, setter: () => {}) { // return
    this.changeScrolling(false)} // onPointerLeave={e => this.changeScrolling(true)}> // - return
    setter()))}> -
    - {value === "" || value === "transparent" ?

    ☒

    : ""} -
    ; + return ( +
    setter()))}> +
    + {value === '' || value === 'transparent' ?

    ☒

    : ''} +
    + ); // //
    ; - } @undoBatch @@ -723,206 +876,271 @@ export class PropertiesView extends React.Component { const val = String(color.hex); this.colorStk = val; return true; - } + }; @undoBatch @action switchFil = (color: ColorState) => { const val = String(color.hex); this.colorFil = val; return true; - } + }; colorPicker(setter: (color: string) => {}, type: string) { - return ; + return ( + + ); + } + + @computed get fillButton() { + return this.colorButton(this.colorFil, 'fill', () => { + this._fillBtn = !this._fillBtn; + this._lineBtn = false; + return true; + }); + } + @computed get lineButton() { + return this.colorButton(this.colorStk, 'line', () => { + this._lineBtn = !this._lineBtn; + this._fillBtn = false; + return true; + }); } - @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } - @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } - - @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } - @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } + @computed get fillPicker() { + return this.colorPicker((color: string) => (this.colorFil = color), 'fil'); + } + @computed get linePicker() { + return this.colorPicker((color: string) => (this.colorStk = color), 'stk'); + } @computed get strokeAndFill() { - return
    -
    -
    -
    Fill:
    -
    {this.fillButton}
    -
    -
    -
    Stroke:
    -
    {this.lineButton}
    + return ( +
    +
    +
    +
    Fill:
    +
    {this.fillButton}
    +
    +
    +
    Stroke:
    +
    {this.lineButton}
    +
    + {this._fillBtn ? this.fillPicker : ''} + {this._lineBtn ? this.linePicker : ''}
    - {this._fillBtn ? this.fillPicker : ""} - {this._lineBtn ? this.linePicker : ""} -
    ; - } - - @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; } - @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; } - @computed get unStrokd() { return this.selectedDoc?.color ? true : false; } - @computed get widthStk() { return this.getField("strokeWidth") || "1"; } - @computed get markScal() { return Number(this.getField("strokeMakerScale") || "1"); } - @computed get markHead() { return this.getField("strokeStartMarker") || ""; } - @computed get markTail() { return this.getField("strokeEndMarker") || ""; } - set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; } + ); + } + + @computed get solidStk() { + return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === '0') ? true : false; + } + @computed get dashdStk() { + return this.selectedDoc?.strokeDash || ''; + } + @computed get unStrokd() { + return this.selectedDoc?.color ? true : false; + } + @computed get widthStk() { + return this.getField('strokeWidth') || '1'; + } + @computed get markScal() { + return Number(this.getField('strokeMakerScale') || '1'); + } + @computed get markHead() { + return this.getField('strokeStartMarker') || ''; + } + @computed get markTail() { + return this.getField('strokeEndMarker') || ''; + } + set solidStk(value) { + this.dashdStk = ''; + this.unStrokd = !value; + } set dashdStk(value) { value && (this._lastDash = value) && (this.unStrokd = false); this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined); } - set markScal(value) { this.selectedDoc && (this.selectedDoc.strokeMarkerScale = Number(value)); } - set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); } - set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; } - set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); } - set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); } - - - @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); } - @computed get markScaleInput() { return this.regInput("scale", this.markScal.toString(), (val: string) => this.markScal = Number(val)); } + set markScal(value) { + this.selectedDoc && (this.selectedDoc.strokeMarkerScale = Number(value)); + } + set widthStk(value) { + this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); + } + set unStrokd(value) { + this.colorStk = value ? '' : this._lastLine; + } + set markHead(value) { + this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); + } + set markTail(value) { + this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); + } + @computed get stkInput() { + return this.regInput('stk', this.widthStk, (val: string) => (this.widthStk = val)); + } + @computed get markScaleInput() { + return this.regInput('scale', this.markScal.toString(), (val: string) => (this.markScal = Number(val))); + } regInput = (key: string, value: any, setter: (val: string) => {}) => { - return
    - setter(e.target.value)} /> -
    -
    this.upDownButtons("up", key)))} > - -
    -
    this.upDownButtons("down", key)))} > - + return ( +
    + setter(e.target.value)} /> +
    +
    this.upDownButtons('up', key)))}> + +
    +
    this.upDownButtons('down', key)))}> + +
    -
    ; - } + ); + }; @computed get widthAndDash() { - return
    -
    -
    -
    Width:
    -
    {this.stkInput}
    -
    - this.widthStk = e.target.value))} - onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }} - onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }} - /> -
    - -
    -
    + return ( +
    +
    -
    Arrow Scale:
    - {/*
    {this.markScalInput}
    */} +
    Width:
    +
    {this.stkInput}
    - this.markScal = +e.target.value))} - onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("scale undo"); }} - onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }} + (this.widthStk = e.target.value))} + onMouseDown={e => { + this._widthUndo = UndoManager.StartBatch('width undo'); + }} + onMouseUp={e => { + this._widthUndo?.end(); + this._widthUndo = undefined; + }} />
    -
    -
    Arrow Head:
    - this.markHead = this.markHead ? "" : "arrow"))} /> + +
    +
    +
    +
    Arrow Scale:
    + {/*
    {this.markScalInput}
    */} +
    + (this.markScal = +e.target.value))} + onMouseDown={e => { + this._widthUndo = UndoManager.StartBatch('scale undo'); + }} + onMouseUp={e => { + this._widthUndo?.end(); + this._widthUndo = undefined; + }} + /> +
    +
    +
    Arrow Head:
    + (this.markHead = this.markHead ? '' : 'arrow')))} /> +
    +
    +
    Arrow End:
    + (this.markTail = this.markTail ? '' : 'arrow')))} /> +
    -
    -
    Arrow End:
    - this.markTail = this.markTail ? "" : "arrow"))} /> +
    +
    Dashed Line:
    +
    -
    -
    Dashed Line:
    - -
    -
    ; + ); } - @undoBatch @action + @undoBatch + @action changeDash = () => { - this.dashdStk = this.dashdStk === "2" ? "0" : "2"; - } + this.dashdStk = this.dashdStk === '2' ? '0' : '2'; + }; @computed get appearanceEditor() { - return
    - {this.widthAndDash} - {this.strokeAndFill} -
    ; + return ( +
    + {this.widthAndDash} + {this.strokeAndFill} +
    + ); } @computed get transformEditor() { - return
    - {this.controlPointsButton} - {this.hgtInput} - {this.XpsInput} - {this.rotInput} -
    ; + return ( +
    + {this.controlPointsButton} + {this.hgtInput} + {this.XpsInput} + {this.rotInput} +
    + ); } @computed get optionsSubMenu() { - return
    this.inOptions = true)} - onPointerLeave={action(() => this.inOptions = false)}> -
    this.openOptions = !this.openOptions)} - style={{ backgroundColor: this.openOptions ? "black" : "" }}> - Options -
    - + return ( +
    (this.inOptions = true))} onPointerLeave={action(() => (this.inOptions = false))}> +
    (this.openOptions = !this.openOptions))} style={{ backgroundColor: this.openOptions ? 'black' : '' }}> + Options +
    + +
    + {!this.openOptions ? null : ( +
    + +
    + )}
    - {!this.openOptions ? (null) : -
    - -
    } -
    ; + ); } @computed get sharingSubMenu() { - return
    -
    this.openSharing = !this.openSharing)} - style={{ backgroundColor: this.openSharing ? "black" : "" }}> - Sharing {"&"} Permissions -
    - + return ( +
    +
    (this.openSharing = !this.openSharing))} style={{ backgroundColor: this.openSharing ? 'black' : '' }}> + Sharing {'&'} Permissions +
    + +
    -
    - {!this.openSharing ? (null) : -
    -
    - {!Doc.noviceMode ? (
    - this.layoutDocAcls = !this.layoutDocAcls)} - checked={this.layoutDocAcls} - /> -
    Layout
    -
    ) : (null)} - {/*
    {"Re-distribute sharing settings"}
    }> + {!this.openSharing ? null : ( +
    +
    + {!Doc.noviceMode ? ( +
    + (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> +
    Layout
    +
    + ) : null} + {/*
    {"Re-distribute sharing settings"}
    }>
    */} +
    + {this.sharingTable}
    - {this.sharingTable} -
    } -
    ; + )} +
    + ); } /** @@ -930,11 +1148,11 @@ export class PropertiesView extends React.Component { * If it doesn't exist, it creates it. */ checkFilterDoc() { - if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = CurrentUserUtils.createFilterDoc(); + if (!this.selectedDoc?.currentFilter) this.selectedDoc!.currentFilter = FilterBox.createFilterDoc(); } /** - * Creates a new currentFilter for this.filterDoc, + * Creates a new currentFilter for this.filterDoc, */ createNewFilterDoc = () => { if (this.selectedDoc) { @@ -944,9 +1162,9 @@ export class PropertiesView extends React.Component { this.selectedDoc._docRangeFilters = new List(); (this.selectedDoc.currentFilter as Doc)._docFiltersList = currentDocFilters; (this.selectedDoc.currentFilter as Doc)._docRangeFiltersList = currentDocRangeFilters; - this.selectedDoc.currentFilter = CurrentUserUtils.createFilterDoc(); + this.selectedDoc.currentFilter = FilterBox.createFilterDoc(); } - } + }; /** * Updates this.filterDoc's currentFilter and saves the docFilters on the currentFilter @@ -970,20 +1188,18 @@ export class PropertiesView extends React.Component { doc._docRangeFiltersList = new List(); this.selectedDoc._docRangeFilters = savedDocRangeFilters; } - } + }; @computed get filtersSubMenu() { - return !(this.selectedDoc?.currentFilter instanceof Doc) ? (null) :
    -
    this.openFilters = !this.openFilters)} - style={{ backgroundColor: this.openFilters ? "black" : "" }}> - Filters -
    - + return !(this.selectedDoc?.currentFilter instanceof Doc) ? null : ( +
    +
    (this.openFilters = !this.openFilters))} style={{ backgroundColor: this.openFilters ? 'black' : '' }}> + Filters +
    + +
    -
    - { - !this.openFilters ? (null) : + {!this.openFilters ? null : (
    { dontCenter="y" />
    - } -
    ; + )} +
    + ); } @computed get inkSubMenu() { - return <> - {!this.isInk ? (null) : -
    -
    this.openAppearance = !this.openAppearance)} - style={{ backgroundColor: this.openAppearance ? "black" : "" }}> - Appearance -
    - + return ( + <> + {!this.isInk ? null : ( +
    +
    (this.openAppearance = !this.openAppearance))} style={{ backgroundColor: this.openAppearance ? 'black' : '' }}> + Appearance +
    + +
    + {!this.openAppearance ? null :
    {this.appearanceEditor}
    }
    - {!this.openAppearance ? (null) : -
    - {this.appearanceEditor} -
    } -
    } - - {this.isInk ?
    -
    this.openTransform = !this.openTransform)} - style={{ backgroundColor: this.openTransform ? "black" : "" }}> - Transform -
    - + )} + + {this.isInk ? ( +
    +
    (this.openTransform = !this.openTransform))} style={{ backgroundColor: this.openTransform ? 'black' : '' }}> + Transform +
    + +
    +
    + {this.openTransform ?
    {this.transformEditor}
    : null}
    -
    - {this.openTransform ?
    - {this.transformEditor} -
    : null} -
    : null} - ; + ) : null} + + ); } @computed get fieldsSubMenu() { - return
    -
    this.openFields = !this.openFields)} - style={{ backgroundColor: this.openFields ? "black" : "" }}> - Fields {"&"} Tags -
    - + return ( +
    +
    (this.openFields = !this.openFields))} style={{ backgroundColor: this.openFields ? 'black' : '' }}> + Fields {'&'} Tags +
    + +
    + {!Doc.noviceMode && this.openFields ? ( +
    + {this.fieldsCheckbox} +
    Layout
    +
    + ) : null} + {!this.openFields ? null :
    {Doc.noviceMode ? this.noviceFields : this.expandedField}
    }
    - {!Doc.noviceMode && this.openFields ?
    - {this.fieldsCheckbox} -
    Layout
    -
    : null} - {!this.openFields ? (null) : -
    - {Doc.noviceMode ? this.noviceFields : this.expandedField} -
    } -
    ; + ); } @computed get contextsSubMenu() { - return
    -
    this.openContexts = !this.openContexts)} - style={{ backgroundColor: this.openContexts ? "black" : "" }}> - Other Contexts -
    - + return ( +
    +
    (this.openContexts = !this.openContexts))} style={{ backgroundColor: this.openContexts ? 'black' : '' }}> + Other Contexts +
    + +
    + {this.openContexts ?
    {this.contexts}
    : null}
    - {this.openContexts ?
    {this.contexts}
    : null} -
    ; + ); } @computed get linksSubMenu() { - return
    -
    this.openLinks = !this.openLinks)} - style={{ backgroundColor: this.openLinks ? "black" : "" }}> - Linked To -
    - + return ( +
    +
    (this.openLinks = !this.openLinks))} style={{ backgroundColor: this.openLinks ? 'black' : '' }}> + Linked To +
    + +
    + {this.openLinks ?
    {this.links}
    : null}
    - {this.openLinks ?
    {this.links}
    : null} -
    ; + ); } @computed get layoutSubMenu() { - return
    -
    this.openLayout = !this.openLayout)} - style={{ backgroundColor: this.openLayout ? "black" : "" }}> - Layout -
    - + return ( +
    +
    (this.openLayout = !this.openLayout))} style={{ backgroundColor: this.openLayout ? 'black' : '' }}> + Layout +
    + +
    + {this.openLayout ?
    {this.layoutPreview}
    : null}
    - {this.openLayout ?
    {this.layoutPreview}
    : null} -
    ; + ); } @observable description = Field.toString(LinkManager.currentLink?.description as any as Field); @observable relationship = StrCast(LinkManager.currentLink?.linkRelationship); - @observable private relationshipButtonColor: string = ""; + @observable private relationshipButtonColor: string = ''; // @action // handleDescriptionChange = (e: React.ChangeEvent) => { this.description = e.target.value; } @@ -1159,11 +1371,11 @@ export class PropertiesView extends React.Component { const linkRelationshipSizes = NumListCast(Doc.UserDoc().linkRelationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().linkColorList); - // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color + // if the relationship does not exist in the list, add it and a corresponding unique randomly generated color if (!linkRelationshipList?.includes(value)) { linkRelationshipList.push(value); linkRelationshipSizes.push(1); - const randColor = "rgb(" + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + "," + Math.floor(Math.random() * 255) + ")"; + const randColor = 'rgb(' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ',' + Math.floor(Math.random() * 255) + ')'; linkColorList.push(randColor); // if the relationship is already in the list AND the new rel is different from the prev rel, update the rel sizes } else if (linkRelationshipList && value !== prevRelationship) { @@ -1171,20 +1383,22 @@ export class PropertiesView extends React.Component { //increment size of new relationship size if (index !== -1 && index < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[index]; - linkRelationshipSizes[index] = (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1); + linkRelationshipSizes[index] = pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue + 1; } //decrement the size of the previous relationship if it already exists (i.e. not default 'link' relationship upon link creation) if (linkRelationshipList.includes(prevRelationship)) { const pindex = linkRelationshipList.indexOf(prevRelationship); if (pindex !== -1 && pindex < linkRelationshipSizes.length) { const pvalue = linkRelationshipSizes[pindex]; - linkRelationshipSizes[pindex] = Math.max(0, (pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1)); + linkRelationshipSizes[pindex] = Math.max(0, pvalue === undefined || !Number.isFinite(pvalue) ? 1 : pvalue - 1); } } - } - this.relationshipButtonColor = "rgb(62, 133, 55)"; - setTimeout(action(() => this.relationshipButtonColor = ""), 750); + this.relationshipButtonColor = 'rgb(62, 133, 55)'; + setTimeout( + action(() => (this.relationshipButtonColor = '')), + 750 + ); return true; } }); @@ -1200,68 +1414,72 @@ export class PropertiesView extends React.Component { onSelectOutDesc = () => { this.setDescripValue(this.description); document.getElementById('link_description_input')?.blur(); - } + }; onDescriptionKey = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setDescripValue(this.description); document.getElementById('link_description_input')?.blur(); } - } + }; onSelectOutRelationship = () => { this.setLinkRelationshipValue(this.relationship); document.getElementById('link_relationship_input')?.blur(); - } + }; onRelationshipKey = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { this.setLinkRelationshipValue(this.relationship); document.getElementById('link_relationship_input')?.blur(); } - } + }; toggleAnchor = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.linkAutoMove = !this.selectedDoc.linkAutoMove)))); - } + }; toggleArrow = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (this.selectedDoc.displayArrow = !this.selectedDoc.displayArrow)))); - } + }; toggleZoomToTarget1 = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor1).followLinkZoom = !DocCast(this.selectedDoc.anchor1).followLinkZoom)))); - } + }; toggleZoomToTarget2 = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedDoc && (DocCast(this.selectedDoc.anchor2).followLinkZoom = !DocCast(this.selectedDoc.anchor2).followLinkZoom)))); - } + }; @computed get editRelationship() { - return this.handleLinkRelationshipChange(e.currentTarget.value)} - className="text" - type="text" - />; + return ( + this.handleLinkRelationshipChange(e.currentTarget.value)} + className="text" + type="text" + /> + ); } @computed get editDescription() { - return this.handleDescriptionChange(e.currentTarget.value)} - className="text" - type="text" - />; + return ( + this.handleDescriptionChange(e.currentTarget.value)} + className="text" + type="text" + /> + ); } /** @@ -1278,123 +1496,120 @@ export class PropertiesView extends React.Component { render() { const isNovice = Doc.noviceMode; if (!this.selectedDoc && !this.isPres) { - return
    -
    - No Document Selected + return ( +
    +
    + No Document Selected +
    -
    ; - + ); } else { if (this.selectedDoc && this.isLink) { - return
    -
    - Linking -
    -
    -

    Information

    -
    -

    Link Relationship

    - {this.editRelationship} -
    -
    -

    Description

    - {this.editDescription} -
    -
    -
    -

    Behavior

    -
    -

    Follow

    - -
    -
    -

    Auto-move anchor

    - -
    -
    -

    Display arrow

    - -
    -
    -

    Zoom to target

    - + return ( +
    +
    Linking
    +
    +

    Information

    +
    +

    Link Relationship

    + {this.editRelationship} +
    +
    +

    Description

    + {this.editDescription} +
    -
    -

    Zoom to source

    - +
    +

    Behavior

    +
    +

    Follow

    + +
    +
    +

    Auto-move anchor

    + +
    +
    +

    Display arrow

    + +
    +
    +

    Zoom to target

    + +
    +
    +

    Zoom to source

    + +
    -
    ; + ); } if (this.selectedDoc && !this.isPres) { - return
    -
    - Properties -
    -
    - {this.editableTitle} -
    + return ( +
    +
    + Properties +
    +
    {this.editableTitle}
    - {this.contextsSubMenu} + {this.contextsSubMenu} - {this.linksSubMenu} + {this.linksSubMenu} - {this.inkSubMenu} + {this.inkSubMenu} - {this.optionsSubMenu} + {this.optionsSubMenu} - {this.fieldsSubMenu} + {this.fieldsSubMenu} - {isNovice ? null : this.sharingSubMenu} + {isNovice ? null : this.sharingSubMenu} - {isNovice ? null : this.filtersSubMenu} + {isNovice ? null : this.filtersSubMenu} - {isNovice ? null : this.layoutSubMenu} -
    ; + {isNovice ? null : this.layoutSubMenu} +
    + ); } if (this.isPres) { const selectedItem: boolean = PresBox.Instance?._selectedArray.size > 0; @@ -1402,35 +1617,35 @@ export class PropertiesView extends React.Component { const viewType = PresBox.Instance.activeItem?._viewType; const pannable: boolean = (type === DocumentType.COL && viewType === CollectionViewType.Freeform) || type === DocumentType.IMG; const scrollable: boolean = type === DocumentType.PDF || type === DocumentType.WEB || type === DocumentType.RTF || viewType === CollectionViewType.Stacking; - return
    -
    - Presentation -
    -
    - {this.editableTitle} -
    -
    - {PresBox.Instance?._selectedArray.size} selected -
    -
    - {PresBox.Instance?.listOfSelected} -
    + return ( +
    +
    + Presentation
    -
    - {!selectedItem ? (null) :
    -
    { this.openPresTransitions = !this.openPresTransitions; })} - style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> -     Transitions -
    - +
    + {this.editableTitle} +
    +
    {PresBox.Instance?._selectedArray.size} selected
    +
    {PresBox.Instance?.listOfSelected}
    - {this.openPresTransitions ?
    - {PresBox.Instance.transitionDropdown} -
    : null} -
    } - {/* {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) :
    + {!selectedItem ? null : ( +
    +
    { + this.openPresTransitions = !this.openPresTransitions; + })} + style={{ backgroundColor: this.openPresTransitions ? 'black' : '' }}> +     Transitions +
    + +
    +
    + {this.openPresTransitions ?
    {PresBox.Instance.transitionDropdown}
    : null} +
    + )} + {/* {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) :
    this.openPresProgressivize = !this.openPresProgressivize)} style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> @@ -1443,20 +1658,23 @@ export class PropertiesView extends React.Component { {PresBox.Instance.progressivizeDropdown}
    : null}
    } */} - {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) :
    -
    { this.openSlideOptions = !this.openSlideOptions; })} - style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> -     {type === DocumentType.AUDIO ? "Audio Options" : "Video Options"} -
    - + {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : ( +
    +
    { + this.openSlideOptions = !this.openSlideOptions; + })} + style={{ backgroundColor: this.openSlideOptions ? 'black' : '' }}> +     {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'} +
    + +
    +
    + {this.openSlideOptions ?
    {PresBox.Instance.mediaOptionsDropdown}
    : null}
    -
    - {this.openSlideOptions ?
    - {PresBox.Instance.mediaOptionsDropdown} -
    : null} -
    } - {/*
    + )} + {/*
    { this.openAddSlide = !this.openAddSlide; })} style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> @@ -1469,8 +1687,9 @@ export class PropertiesView extends React.Component { {PresBox.Instance.newDocumentDropdown}
    : null}
    */} -
    ; +
    + ); } } } -} \ No newline at end of file +} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 8f73ac2c3..e81a9c40f 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -1,22 +1,20 @@ import { computed } from 'mobx'; -import { observer } from "mobx-react"; -import { Doc, DocListCast, StrListCast, Opt } from "../../fields/Doc"; +import { observer } from 'mobx-react'; +import { Doc, DocListCast, StrListCast } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { NumCast, StrCast } from '../../fields/Types'; import { emptyFunction, OmitKeys, returnAll, returnOne, returnTrue, returnZero } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { Transform } from '../util/Transform'; import { CollectionStackingView } from './collections/CollectionStackingView'; -import { CollectionViewType } from './collections/CollectionView'; import { FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { SearchBox } from './search/SearchBox'; -import "./SidebarAnnos.scss"; +import './SidebarAnnos.scss'; import { StyleProp } from './StyleProvider'; -import React = require("react"); -import { DocumentViewProps } from './nodes/DocumentView'; -import { DocumentType } from '../documents/DocumentTypes'; +import React = require('react'); interface ExtraProps { fieldKey: string; @@ -28,8 +26,8 @@ interface ExtraProps { nativeWidth: number; whenChildContentsActiveChanged: (isActive: boolean) => void; ScreenToLocalTransform: () => Transform; - sidebarAddDocument: (doc: (Doc | Doc[]), suffix: string) => boolean; - removeDocument: (doc: (Doc | Doc[]), suffix: string) => boolean; + sidebarAddDocument: (doc: Doc | Doc[], suffix: string) => boolean; + removeDocument: (doc: Doc | Doc[], suffix: string) => boolean; moveDocument: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string) => boolean; } @observer @@ -42,29 +40,41 @@ export class SidebarAnnos extends React.Component { @computed get allMetadata() { const keys = new Set(); DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => SearchBox.documentKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys.keys()).filter(key => key[0]).filter(key => key[0] !== "_" && (key[0] === key[0].toUpperCase())).sort(); + return Array.from(keys.keys()) + .filter(key => key[0]) + .filter(key => key[0] !== '_' && key[0] === key[0].toUpperCase()) + .sort(); } @computed get allUsers() { const keys = new Set(); DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author))); return Array.from(keys.keys()).sort(); } - get filtersKey() { return "_" + this.sidebarKey + "-docFilters"; } + get filtersKey() { + return '_' + this.sidebarKey + '-docFilters'; + } anchorMenuClick = (anchor: Doc) => { - const startup = StrListCast(this.props.rootDoc.docFilters).map(filter => filter.split(":")[0]).join(" "); + const startup = StrListCast(this.props.rootDoc.docFilters) + .map(filter => filter.split(':')[0]) + .join(' '); const target = Docs.Create.TextDocument(startup, { - title: "-note-", - annotationOn: this.props.rootDoc, _width: 200, _height: 50, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), - _fontFamily: StrCast(Doc.UserDoc().fontFamily) + title: '-note-', + annotationOn: this.props.rootDoc, + _width: 200, + _height: 50, + _fitWidth: true, + _autoHeight: true, + _fontSize: StrCast(Doc.UserDoc().fontSize), + _fontFamily: StrCast(Doc.UserDoc().fontFamily), }); FormattedTextBox.SelectOnLoad = target[Id]; FormattedTextBox.DontSelectInitialText = true; - this.allMetadata.map(tag => target[tag] = tag); - DocUtils.MakeLink({ doc: anchor }, { doc: target }, "inline comment:comment on"); + this.allMetadata.map(tag => (target[tag] = tag)); + DocUtils.MakeLink({ doc: anchor }, { doc: target }, 'inline comment:comment on'); this.addDocument(target); this._stackRef.current?.focusDocument(target); - } + }; makeDocUnfiltered = (doc: Doc) => { if (DocListCast(this.props.rootDoc[this.sidebarKey]).includes(doc)) { if (this.props.layoutDoc[this.filtersKey]) { @@ -73,65 +83,84 @@ export class SidebarAnnos extends React.Component { return true; } return false; - } + }; - get sidebarKey() { return this.props.fieldKey + "-sidebar"; } + get sidebarKey() { + return this.props.fieldKey + '-sidebar'; + } filtersHeight = () => 38; - screenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(Doc.NativeWidth(this.props.dataDoc), 0).scale(this.props.scaling?.() || 1); + screenToLocalTransform = () => + this.props + .ScreenToLocalTransform() + .translate(Doc.NativeWidth(this.props.dataDoc), 0) + .scale(this.props.scaling?.() || 1); // panelWidth = () => !this.props.layoutDoc._showSidebar ? 0 : // this.props.usePanelWidth ? this.props.PanelWidth() : // (NumCast(this.props.layoutDoc.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.layoutDoc.nativeWidth); - panelWidth = () => !this.props.showSidebar ? 0 : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP ? this.props.PanelWidth() : (NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth() / NumCast(this.props.nativeWidth); + panelWidth = () => + !this.props.showSidebar + ? 0 + : this.props.layoutDoc.type === DocumentType.RTF || this.props.layoutDoc.type === DocumentType.MAP + ? this.props.PanelWidth() + : ((NumCast(this.props.nativeWidth) - Doc.NativeWidth(this.props.dataDoc)) * this.props.PanelWidth()) / NumCast(this.props.nativeWidth); panelHeight = () => this.props.PanelHeight() - this.filtersHeight(); addDocument = (doc: Doc | Doc[]) => this.props.sidebarAddDocument(doc, this.sidebarKey); moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument(doc, targetCollection, addDocument, this.sidebarKey); removeDocument = (doc: Doc | Doc[]) => this.props.removeDocument(doc, this.sidebarKey); docFilters = () => [...StrListCast(this.props.layoutDoc._docFilters), ...StrListCast(this.props.layoutDoc[this.filtersKey])]; - showTitle = () => "title"; + showTitle = () => 'title'; setHeightCallback = (height: number) => this.props.setHeight?.(height + this.filtersHeight()); render() { const renderTag = (tag: string) => { const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`); - return
    Doc.setDocFilter(this.props.rootDoc, tag, tag, "check", true, this.sidebarKey, e.shiftKey)}> - {tag} -
    ; + return ( +
    Doc.setDocFilter(this.props.rootDoc, tag, tag, 'check', true, this.sidebarKey, e.shiftKey)}> + {tag} +
    + ); }; const renderMeta = (tag: string) => { const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:exists`); - return
    Doc.setDocFilter(this.props.rootDoc, tag, tag, "exists", true, this.sidebarKey, e.shiftKey)}> - {tag} -
    ; + return ( +
    Doc.setDocFilter(this.props.rootDoc, tag, tag, 'exists', true, this.sidebarKey, e.shiftKey)}> + {tag} +
    + ); }; const renderUsers = (user: string) => { const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`author:${user}:check`); - return
    Doc.setDocFilter(this.props.rootDoc, "author", user, "check", true, this.sidebarKey, e.shiftKey)}> - {user} -
    ; + return ( +
    Doc.setDocFilter(this.props.rootDoc, 'author', user, 'check', true, this.sidebarKey, e.shiftKey)}> + {user} +
    + ); }; // TODO: Calculation of the topbar is hardcoded and different for text nodes - it should all be the same and all be part of SidebarAnnos - return !this.props.showSidebar ? (null) : -
    -
    e.stopPropagation()}> + return !this.props.showSidebar ? null : ( +
    +
    e.stopPropagation()}> {this.allUsers.map(renderUsers)} {this.allMetadata.map(renderMeta)}
    -
    - + { pointerEvents={returnAll} />
    -
    ; +
    + ); } -} \ No newline at end of file +} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index a9770d253..340a5df45 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -2,18 +2,13 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, runInAction } from 'mobx'; import { extname } from 'path'; -import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { List } from '../../fields/List'; -import { listSpec } from '../../fields/Schema'; +import { Doc, Opt } from '../../fields/Doc'; import { BoolCast, Cast, ImageCast, NumCast, StrCast } from '../../fields/Types'; import { DashColor, lightOrDark } from '../../Utils'; -import { DocumentType } from '../documents/DocumentTypes'; -import { CurrentUserUtils } from '../util/CurrentUserUtils'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocFocusOrOpen } from '../util/DocumentManager'; import { ColorScheme } from '../util/SettingsManager'; -import { SnappingManager } from '../util/SnappingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; -import { CollectionViewType } from './collections/CollectionView'; import { TreeSort } from './collections/TreeView'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; @@ -49,7 +44,7 @@ export enum StyleProp { } function darkScheme() { - return CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark; + return Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark; } function toggleLockedPosition(doc: Doc) { diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 689ee4fc1..156513f47 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,43 +1,42 @@ -import { action, computed, observable, ObservableSet, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, DocListCast } from "../../fields/Doc"; -import { List } from "../../fields/List"; -import { ScriptField } from "../../fields/ScriptField"; -import { Cast, StrCast } from "../../fields/Types"; -import { TraceMobx } from "../../fields/util"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../Utils"; -import { Docs, DocUtils } from "../documents/Documents"; -import { ScriptingGlobals } from "../util/ScriptingGlobals"; -import { Transform } from "../util/Transform"; -import { undoBatch } from "../util/UndoManager"; -import { CollectionTreeView } from "./collections/CollectionTreeView"; -import { DocumentView } from "./nodes/DocumentView"; -import { DefaultStyleProvider } from "./StyleProvider"; +import { action, computed, observable, ObservableSet, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, DocListCast } from '../../fields/Doc'; +import { List } from '../../fields/List'; +import { ScriptField } from '../../fields/ScriptField'; +import { Cast, StrCast } from '../../fields/Types'; +import { TraceMobx } from '../../fields/util'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../Utils'; +import { Docs, DocUtils } from '../documents/Documents'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { Transform } from '../util/Transform'; +import { undoBatch } from '../util/UndoManager'; +import { CollectionTreeView } from './collections/CollectionTreeView'; +import { DocumentView } from './nodes/DocumentView'; +import { DefaultStyleProvider } from './StyleProvider'; import './TemplateMenu.scss'; -import React = require("react"); -import { CurrentUserUtils } from "../util/CurrentUserUtils"; +import React = require('react'); @observer -class TemplateToggle extends React.Component<{ template: string, checked: boolean, toggle: (event: React.ChangeEvent, template: string) => void }> { +class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent, template: string) => void }> { render() { if (this.props.template) { return (
  • - this.props.toggle(event, this.props.template)} /> + this.props.toggle(event, this.props.template)} /> {this.props.template}
  • ); } else { - return (null); + return null; } } } @observer -class OtherToggle extends React.Component<{ checked: boolean, name: string, toggle: (event: React.ChangeEvent) => void }> { +class OtherToggle extends React.Component<{ checked: boolean; name: string; toggle: (event: React.ChangeEvent) => void }> { render() { return (
  • - this.props.toggle(event)} /> + this.props.toggle(event)} /> {this.props.name}
  • ); @@ -49,7 +48,6 @@ export interface TemplateMenuProps { templates?: Map; } - @observer export class TemplateMenu extends React.Component { _addedKeys = new ObservableSet(); @@ -58,109 +56,118 @@ export class TemplateMenu extends React.Component { toggleLayout = (e: React.ChangeEvent, layout: string): void => { this.props.docViews.map(dv => dv.switchViews(e.target.checked, layout)); - } + }; toggleDefault = (e: React.ChangeEvent): void => { - this.props.docViews.map(dv => dv.switchViews(false, "layout")); - } + this.props.docViews.map(dv => dv.switchViews(false, 'layout')); + }; toggleAudio = (e: React.ChangeEvent): void => { - this.props.docViews.map(dv => dv.props.Document._showAudio = e.target.checked); - } + this.props.docViews.map(dv => (dv.props.Document._showAudio = e.target.checked)); + }; @undoBatch @action toggleTemplate = (event: React.ChangeEvent, template: string): void => { - this.props.docViews.forEach(d => Doc.Layout(d.layoutDoc)["_show" + template] = event.target.checked ? template.toLowerCase() : ""); - } + this.props.docViews.forEach(d => (Doc.Layout(d.layoutDoc)['_show' + template] = event.target.checked ? template.toLowerCase() : '')); + }; @action toggleTemplateActivity = (): void => { this._hidden = !this._hidden; - } + }; @undoBatch @action toggleChrome = (): void => { - this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout => layout._chromeHidden = !layout._chromeHidden); - } + this.props.docViews.map(dv => Doc.Layout(dv.layoutDoc)).forEach(layout => (layout._chromeHidden = !layout._chromeHidden)); + }; // todo: add brushes to brushMap to save with a style name onCustomKeypress = (e: React.KeyboardEvent) => { - if (e.key === "Enter") { + if (e.key === 'Enter') { runInAction(() => this._addedKeys.add(this._customRef.current!.value)); } - } + }; componentDidMount() { !this._addedKeys && (this._addedKeys = new ObservableSet()); - Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))). - filter(key => key.startsWith("layout_")). - map(key => runInAction(() => this._addedKeys.add(key.replace("layout_", "")))); + Array.from(Object.keys(Doc.GetProto(this.props.docViews[0].props.Document))) + .filter(key => key.startsWith('layout_')) + .map(key => runInAction(() => this._addedKeys.add(key.replace('layout_', '')))); } return100 = () => 100; @computed get scriptField() { - const script = ScriptField.MakeScript("docs.map(d => switchView(d, this))", { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name, firstDoc: Doc.name }, - { docs: new List(this.props.docViews.map(dv => dv.props.Document)) }); + const script = ScriptField.MakeScript( + 'docs.map(d => switchView(d, this))', + { this: Doc.name, heading: 'string', checked: 'string', containingTreeView: Doc.name, firstDoc: Doc.name }, + { docs: new List(this.props.docViews.map(dv => dv.props.Document)) } + ); return script ? () => script : undefined; } templateIsUsed = (selDoc: Doc, templateDoc: Doc) => { const template = StrCast(templateDoc.dragFactory ? Cast(templateDoc.dragFactory, Doc, null)?.title : templateDoc.title); - return StrCast(selDoc.layoutKey) === "layout_" + template ? 'check' : 'unchecked'; - } + return StrCast(selDoc.layoutKey) === 'layout_' + template ? 'check' : 'unchecked'; + }; render() { TraceMobx(); const firstDoc = this.props.docViews[0].props.Document; - const templateName = StrCast(firstDoc.layoutKey, "layout").replace("layout_", ""); - const noteTypes = DocListCast(Cast(Doc.UserDoc()["template-notes"], Doc, null)?.data); - const addedTypes = Doc.noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); + const templateName = StrCast(firstDoc.layoutKey, 'layout').replace('layout_', ''); + const noteTypes = DocListCast(Cast(Doc.UserDoc()['template-notes'], Doc, null)?.data); + const addedTypes = Doc.noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()['template-buttons'], Doc, null)?.data); const layout = Doc.Layout(firstDoc); const templateMenu: Array = []; - this.props.templates?.forEach((checked, template) => - templateMenu.push()); - templateMenu.push(); - templateMenu.push(); - !Doc.noviceMode && templateMenu.push(); - addedTypes.concat(noteTypes).map(template => template.treeViewChecked = this.templateIsUsed(firstDoc, template)); - this._addedKeys && Array.from(this._addedKeys).filter(key => !noteTypes.some(nt => nt.title === key)).forEach(template => templateMenu.push( - this.toggleLayout(e, template)} />)); - return
      - {Doc.noviceMode ? (null) : } - {templateMenu} - {Doc.noviceMode ? (null) : } -
    ; + this.props.templates?.forEach((checked, template) => templateMenu.push()); + templateMenu.push(); + templateMenu.push(); + !Doc.noviceMode && templateMenu.push(); + addedTypes.concat(noteTypes).map(template => (template.treeViewChecked = this.templateIsUsed(firstDoc, template))); + this._addedKeys && + Array.from(this._addedKeys) + .filter(key => !noteTypes.some(nt => nt.title === key)) + .forEach(template => templateMenu.push( this.toggleLayout(e, template)} />)); + return ( +
      + {Doc.noviceMode ? null : } + {templateMenu} + {Doc.noviceMode ? null : ( + + )} +
    + ); } } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 27478e59b..d47dfbea0 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -12,18 +12,17 @@ import { inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { DashboardView } from '../DashboardView'; import { LightboxView } from '../LightboxView'; import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; -import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require('react'); const _global = (window /* browser */ || global) /* node */ as any; @@ -121,7 +120,7 @@ export class CollectionDockingView extends CollectionSubView() { SelectionManager.DeselectAll(); const instance = CollectionDockingView.Instance; if (doc._viewType === CollectionViewType.Docking && doc.layoutKey === 'layout') { - return CurrentUserUtils.openDashboard(doc); + return DashboardView.openDashboard(doc); } const newItemStackConfig = { type: 'stack', @@ -170,7 +169,7 @@ export class CollectionDockingView extends CollectionSubView() { @undoBatch @action public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { - if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(document); + if (document._viewType === CollectionViewType.Docking) return DashboardView.openDashboard(document); const tab = Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === document); if (tab) { @@ -378,7 +377,7 @@ export class CollectionDockingView extends CollectionSubView() { } } } - if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(CurrentUserUtils.ActiveTool)) { + if (!e.nativeEvent.cancelBubble && !InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { e.stopPropagation(); } }; @@ -404,7 +403,7 @@ export class CollectionDockingView extends CollectionSubView() { const cloned = await Doc.MakeClone(doc); Array.from(cloned.map.entries()).map(entry => (json = json.replace(entry[0], entry[1][Id]))); Doc.GetProto(cloned.clone).dockingConfig = json; - return CurrentUserUtils.openDashboard(cloned.clone); + return DashboardView.openDashboard(cloned.clone); } const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); const origtabids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')) || []; @@ -424,7 +423,7 @@ export class CollectionDockingView extends CollectionSubView() { return newtab; }); const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); - return CurrentUserUtils.openDashboard(await copy); + return DashboardView.openDashboard(await copy); } @action @@ -451,8 +450,8 @@ export class CollectionDockingView extends CollectionSubView() { tabDestroyed = (tab: any) => { if (tab.DashDoc?.type !== DocumentType.KVP) { - Doc.AddDocToList(CurrentUserUtils.MyHeaderBar, 'data', tab.DashDoc); - Doc.AddDocToList(CurrentUserUtils.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); + Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); + Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); } const dview = CollectionDockingView.Instance.props.Document; const fieldKey = CollectionDockingView.Instance.props.fieldKey; @@ -469,7 +468,7 @@ export class CollectionDockingView extends CollectionSubView() { stackCreated = (stack: any) => { stack.header?.element.on('mousedown', (e: any) => { - const dashboard = CurrentUserUtils.ActiveDashboard; + const dashboard = Doc.ActiveDashboard; if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; const docToAdd = Docs.Create.FreeformDocument([], { @@ -507,7 +506,7 @@ export class CollectionDockingView extends CollectionSubView() { .click( action(() => { // stack.config.fixed = !stack.config.fixed; // force the stack to have a fixed size - const dashboard = CurrentUserUtils.ActiveDashboard; + const dashboard = Doc.ActiveDashboard; if (dashboard) { dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; const docToAdd = Docs.Create.FreeformDocument([], { @@ -545,7 +544,7 @@ ScriptingGlobals.add( ); ScriptingGlobals.add( function openInOverlay(doc: any) { - return Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc); + return Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); }, 'opens up document in screen overlay layer', '(doc: any)' diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 668d82387..2c0e44bc3 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,46 +1,46 @@ -import React = require("react"); +import React = require('react'); import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome"; -import { Tooltip } from "@material-ui/core"; -import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx"; -import { observer } from "mobx-react"; -import { ColorState } from "react-color"; -import { Doc, DocListCast, Opt } from "../../../fields/Doc"; -import { Document } from "../../../fields/documentSchemas"; -import { Id } from "../../../fields/FieldSymbols"; -import { InkTool } from "../../../fields/InkField"; -import { List } from "../../../fields/List"; -import { ObjectField } from "../../../fields/ObjectField"; -import { RichTextField } from "../../../fields/RichTextField"; -import { listSpec } from "../../../fields/Schema"; -import { ScriptField } from "../../../fields/ScriptField"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils"; -import { Docs } from "../../documents/Documents"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { DragManager } from "../../util/DragManager"; -import { ScriptingGlobals } from "../../util/ScriptingGlobals"; -import { SelectionManager } from "../../util/SelectionManager"; -import { Transform } from "../../util/Transform"; -import { undoBatch } from "../../util/UndoManager"; -import { AntimodeMenu, AntimodeMenuProps } from "../AntimodeMenu"; -import { EditableView } from "../EditableView"; -import { GestureOverlay } from "../GestureOverlay"; -import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; -import { LightboxView } from "../LightboxView"; -import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { DocumentView } from "../nodes/DocumentView"; -import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox"; -import { RichTextMenu } from "../nodes/formattedText/RichTextMenu"; -import { PresBox } from "../nodes/trails/PresBox"; -import { DefaultStyleProvider } from "../StyleProvider"; -import { CollectionDockingView } from "./CollectionDockingView"; -import { CollectionLinearView } from "./collectionLinear"; -import "./CollectionMenu.scss"; -import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView"; -import { TabDocView } from "./TabDocView"; -import { Colors } from "../global/globalEnums"; +import { FontAwesomeIcon, FontAwesomeIconProps } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@material-ui/core'; +import { action, computed, Lambda, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { ColorState } from 'react-color'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { Document } from '../../../fields/documentSchemas'; +import { Id } from '../../../fields/FieldSymbols'; +import { InkTool } from '../../../fields/InkField'; +import { List } from '../../../fields/List'; +import { ObjectField } from '../../../fields/ObjectField'; +import { RichTextField } from '../../../fields/RichTextField'; +import { listSpec } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; +import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils'; +import { Docs } from '../../documents/Documents'; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DragManager } from '../../util/DragManager'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; +import { SelectionManager } from '../../util/SelectionManager'; +import { SettingsManager } from '../../util/SettingsManager'; +import { Transform } from '../../util/Transform'; +import { undoBatch } from '../../util/UndoManager'; +import { AntimodeMenu } from '../AntimodeMenu'; +import { EditableView } from '../EditableView'; +import { GestureOverlay } from '../GestureOverlay'; +import { Colors } from '../global/globalEnums'; +import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from '../InkingStroke'; +import { LightboxView } from '../LightboxView'; +import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; +import { DocumentView } from '../nodes/DocumentView'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; +import { PresBox } from '../nodes/trails/PresBox'; +import { DefaultStyleProvider } from '../StyleProvider'; +import { CollectionDockingView } from './CollectionDockingView'; +import { CollectionLinearView } from './collectionLinear'; +import './CollectionMenu.scss'; +import { COLLECTION_BORDER_WIDTH } from './CollectionView'; +import { TabDocView } from './TabDocView'; interface CollectionMenuProps { panelHeight: () => number; @@ -48,7 +48,7 @@ interface CollectionMenuProps { } @observer -export class CollectionMenu extends AntimodeMenu{ +export class CollectionMenu extends AntimodeMenu { @observable static Instance: CollectionMenu; @observable SelectedCollection: DocumentView | undefined; @@ -58,16 +58,18 @@ export class CollectionMenu extends AntimodeMenu{ constructor(props: any) { super(props); - this.FieldKey = ""; - runInAction(() => CollectionMenu.Instance = this); + this.FieldKey = ''; + runInAction(() => (CollectionMenu.Instance = this)); this._canFade = false; // don't let the inking menu fade away - runInAction(() => this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true)); + runInAction(() => (this.Pinned = Cast(Doc.UserDoc()['menuCollections-pinned'], 'boolean', true))); this.jumpTo(300, 300); } componentDidMount() { - reaction(() => SelectionManager.Views().length && SelectionManager.Views()[0], - view => view && this.SetSelection(view)); + reaction( + () => SelectionManager.Views().length && SelectionManager.Views()[0], + view => view && this.SetSelection(view) + ); } @action @@ -77,84 +79,87 @@ export class CollectionMenu extends AntimodeMenu{ @action toggleMenuPin = (e: React.MouseEvent) => { - Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned; + Doc.UserDoc()['menuCollections-pinned'] = this.Pinned = !this.Pinned; if (!this.Pinned && this._left < 0) { this.jumpTo(300, 300); } - } + }; @action toggleTopBar = () => { - if (CurrentUserUtils.headerBarHeight > 0) { - CurrentUserUtils.headerBarHeight = 0; + if (SettingsManager.headerBarHeight > 0) { + SettingsManager.headerBarHeight = 0; } else { - CurrentUserUtils.headerBarHeight = 60; + SettingsManager.headerBarHeight = 60; } - } + }; buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(this._docBtnRef.current); return new Transform(-translateX, -translateY, 1 / scale); - } + }; @computed get contMenuButtons() { - const selDoc = CurrentUserUtils.MyContextMenuBtns; - return !(selDoc instanceof Doc) ? (null) :
    - -
    ; + const selDoc = Doc.MyContextMenuBtns; + return !(selDoc instanceof Doc) ? null : ( +
    + +
    + ); } render() { + const propIcon = SettingsManager.headerBarHeight > 0 ? 'angle-double-up' : 'angle-double-down'; + const propTitle = SettingsManager.headerBarHeight > 0 ? 'Close Header Bar' : 'Open Header Bar'; - const propIcon = CurrentUserUtils.headerBarHeight > 0 ? "angle-double-up" : "angle-double-down"; - const propTitle = CurrentUserUtils.headerBarHeight > 0 ? "Close Header Bar" : "Open Header Bar"; - - const prop = {propTitle}
    } key="topar" placement="bottom"> -
    0 ? Colors.MEDIUM_BLUE : undefined }} - onPointerDown={this.toggleTopBar}> - -
    - ; + const prop = ( + {propTitle}
    } key="topar" placement="bottom"> +
    0 ? Colors.MEDIUM_BLUE : undefined }} onPointerDown={this.toggleTopBar}> + +
    + + ); // NEW BUTTONS //dash col linear view buttons - const contMenuButtons = + const contMenuButtons = (
    {this.contMenuButtons} {prop} -
    ; +
    + ); return contMenuButtons; @@ -187,49 +192,62 @@ const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation(); export class CollectionViewBaseChrome extends React.Component { //(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\) - get document() { return this.props.docView?.props.Document; } - get target() { return this.document; } + get document() { + return this.props.docView?.props.Document; + } + get target() { + return this.document; + } _templateCommand = { - params: ["target", "source"], title: "item view", - script: "self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])", + params: ['target', 'source'], + title: 'item view', + script: 'self.target.childLayoutTemplate = getDocTemplate(self.source?.[0])', immediate: undoBatch((source: Doc[]) => { let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text; - try { formatStr && JSON.parse(formatStr); } catch (e) { formatStr = ""; } + try { + formatStr && JSON.parse(formatStr); + } catch (e) { + formatStr = ''; + } if (source.length === 1 && formatStr) { - Doc.SetInPlace(this.target, "childLayoutString", formatStr, false); + Doc.SetInPlace(this.target, 'childLayoutString', formatStr, false); } else if (source.length) { this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]); } else { - Doc.SetInPlace(this.target, "childLayoutString", undefined, true); - Doc.SetInPlace(this.target, "childLayoutTemplate", undefined, true); + Doc.SetInPlace(this.target, 'childLayoutString', undefined, true); + Doc.SetInPlace(this.target, 'childLayoutTemplate', undefined, true); } }), initialize: emptyFunction, }; _narrativeCommand = { - params: ["target", "source"], title: "child click view", - script: "self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])", + params: ['target', 'source'], + title: 'child click view', + script: 'self.target.childClickedOpenTemplateView = getDocTemplate(self.source?.[0])', immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))), initialize: emptyFunction, }; _contentCommand = { - params: ["target", "source"], title: "set content", - script: "getProto(self.target).data = copyField(self.source);", - immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List(source)), + params: ['target', 'source'], + title: 'set content', + script: 'getProto(self.target).data = copyField(self.source);', + immediate: undoBatch((source: Doc[]) => (Doc.GetProto(this.target).data = new List(source))), initialize: emptyFunction, }; _onClickCommand = { - params: ["target", "proxy"], title: "copy onClick", + params: ['target', 'proxy'], + title: 'copy onClick', script: `{ if (self.proxy?.[0]) { getProto(self.proxy[0]).onClick = copyField(self.target.onClick); getProto(self.proxy[0]).target = self.target.target; getProto(self.proxy[0]).source = copyField(self.target.source); }}`, - immediate: undoBatch((source: Doc[]) => { }), + immediate: undoBatch((source: Doc[]) => {}), initialize: emptyFunction, }; _openLinkInCommand = { - params: ["target", "container"], title: "link follow target", + params: ['target', 'container'], + title: 'link follow target', script: `{ if (self.container?.length) { getProto(self.target).linkContainer = self.container[0]; getProto(self.target).isLinkButton = true; @@ -239,126 +257,180 @@ export class CollectionViewBaseChrome extends React.Component { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; this.target._currentFrame = (this.target._currentFrame === undefined ? undefined : 0); }), - initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; button['target-currentFrame'] = this.target._currentFrame; }, + immediate: undoBatch((source: Doc[]) => { + this.target._panX = 0; + this.target._panY = 0; + this.target._viewScale = 1; + this.target._currentFrame = this.target._currentFrame === undefined ? undefined : 0; + }), + initialize: (button: Doc) => { + button['target-panX'] = this.target._panX; + button['target-panY'] = this.target._panY; + button['target-viewScale'] = this.target._viewScale; + button['target-currentFrame'] = this.target._currentFrame; + }, }; _clusterCommand = { - params: ["target"], title: "fit content", - script: "self.target._fitContentsToBox = !self.target._fitContentsToBox;", - immediate: undoBatch((source: Doc[]) => this.target._fitContentsToBox = !this.target._fitContentsToBox), - initialize: emptyFunction + params: ['target'], + title: 'fit content', + script: 'self.target._fitContentsToBox = !self.target._fitContentsToBox;', + immediate: undoBatch((source: Doc[]) => (this.target._fitContentsToBox = !this.target._fitContentsToBox)), + initialize: emptyFunction, }; _fitContentCommand = { - params: ["target"], title: "toggle clusters", - script: "self.target._useClusters = !self.target._useClusters;", - immediate: undoBatch((source: Doc[]) => this.target._useClusters = !this.target._useClusters), - initialize: emptyFunction + params: ['target'], + title: 'toggle clusters', + script: 'self.target._useClusters = !self.target._useClusters;', + immediate: undoBatch((source: Doc[]) => (this.target._useClusters = !this.target._useClusters)), + initialize: emptyFunction, }; _saveFilterCommand = { - params: ["target"], title: "save filter", + params: ['target'], + title: 'save filter', script: `self.target._docFilters = compareLists(self['target-docFilters'],self.target._docFilters) ? undefined : copyField(self['target-docFilters']); self.target._searchFilterDocs = compareLists(self['target-searchFilterDocs'],self.target._searchFilterDocs) ? undefined: copyField(self['target-searchFilterDocs']);`, - immediate: undoBatch((source: Doc[]) => { this.target._docFilters = undefined; this.target._searchFilterDocs = undefined; }), + immediate: undoBatch((source: Doc[]) => { + this.target._docFilters = undefined; + this.target._searchFilterDocs = undefined; + }), initialize: (button: Doc) => { - const activeDash = CurrentUserUtils.ActiveDashboard; + const activeDash = Doc.ActiveDashboard; if (activeDash) { - button['target-docFilters'] = (CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) instanceof ObjectField ? - ObjectField.MakeCopy((CurrentUserUtils.MySearcher._docFilters || activeDash._docFilters) as any as ObjectField) : undefined; + button['target-docFilters'] = (Doc.MySearcher._docFilters || activeDash._docFilters) instanceof ObjectField ? ObjectField.MakeCopy((Doc.MySearcher._docFilters || activeDash._docFilters) as any as ObjectField) : undefined; button['target-searchFilterDocs'] = activeDash._searchFilterDocs instanceof ObjectField ? ObjectField.MakeCopy(activeDash._searchFilterDocs as any as ObjectField) : undefined; } }, }; - @computed get _freeform_commands() { return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } - @computed get _stacking_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } - @computed get _masonry_commands() { return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } - @computed get _schema_commands() { return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } - @computed get _doc_commands() { return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } - @computed get _tree_commands() { return undefined; } + @computed get _freeform_commands() { + return Doc.noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; + } + @computed get _stacking_commands() { + return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; + } + @computed get _masonry_commands() { + return Doc.noviceMode ? undefined : [this._contentCommand, this._templateCommand]; + } + @computed get _schema_commands() { + return Doc.noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; + } + @computed get _doc_commands() { + return Doc.noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; + } + @computed get _tree_commands() { + return undefined; + } private get _buttonizableCommands() { switch (this.props.type) { - default: return this._doc_commands; - case CollectionViewType.Freeform: return this._freeform_commands; - case CollectionViewType.Tree: return this._tree_commands; - case CollectionViewType.Schema: return this._schema_commands; - case CollectionViewType.Stacking: return this._stacking_commands; - case CollectionViewType.Masonry: return this._stacking_commands; - case CollectionViewType.Time: return this._freeform_commands; - case CollectionViewType.Carousel: return this._freeform_commands; - case CollectionViewType.Carousel3D: return this._freeform_commands; + default: + return this._doc_commands; + case CollectionViewType.Freeform: + return this._freeform_commands; + case CollectionViewType.Tree: + return this._tree_commands; + case CollectionViewType.Schema: + return this._schema_commands; + case CollectionViewType.Stacking: + return this._stacking_commands; + case CollectionViewType.Masonry: + return this._stacking_commands; + case CollectionViewType.Time: + return this._freeform_commands; + case CollectionViewType.Carousel: + return this._freeform_commands; + case CollectionViewType.Carousel3D: + return this._freeform_commands; } } private _commandRef = React.createRef(); private _viewRef = React.createRef(); - @observable private _currentKey: string = ""; + @observable private _currentKey: string = ''; componentDidMount = action(() => { - this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : ""); + this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : ''); }); @undoBatch viewChanged = (e: React.ChangeEvent) => { - const target = this.document !== CurrentUserUtils.MyLeftSidebarPanel ? this.document : this.document.proto as Doc; + const target = this.document !== Doc.MyLeftSidebarPanel ? this.document : (this.document.proto as Doc); //@ts-ignore target._viewType = e.target.selectedOptions[0].value; - } + }; commandChanged = (e: React.ChangeEvent) => { //@ts-ignore - runInAction(() => this._currentKey = e.target.selectedOptions[0].value); - } - + runInAction(() => (this._currentKey = e.target.selectedOptions[0].value)); + }; @action closeViewSpecs = () => { this.document._facetWidth = 0; - } + }; @computed get subChrome() { - switch (this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type) { // bcz: ARgh! hack to get menu for tree view outline items - default: return this.otherSubChrome; + switch ( + this.props.docView.props.LayoutTemplateString ? CollectionViewType.Freeform : this.props.type // bcz: ARgh! hack to get menu for tree view outline items + ) { + default: + return this.otherSubChrome; case CollectionViewType.Invalid: - case CollectionViewType.Freeform: return (); - case CollectionViewType.Stacking: return (); - case CollectionViewType.Schema: return (); - case CollectionViewType.Tree: return (); - case CollectionViewType.Masonry: return (); + case CollectionViewType.Freeform: + return ; + case CollectionViewType.Stacking: + return ; + case CollectionViewType.Schema: + return ; + case CollectionViewType.Tree: + return ; + case CollectionViewType.Masonry: + return ; case CollectionViewType.Carousel: - case CollectionViewType.Carousel3D: return (); - case CollectionViewType.Grid: return (); - case CollectionViewType.Docking: return (); + case CollectionViewType.Carousel3D: + return ; + case CollectionViewType.Grid: + return ; + case CollectionViewType.Docking: + return ; } } @computed get otherSubChrome() { const docType = this.props.docView.Document.type; switch (docType) { - default: return (null); - case DocumentType.IMG: return (); - case DocumentType.PDF: return (); - case DocumentType.INK: return (); - case DocumentType.WEB: return (); - case DocumentType.VID: return (); - case DocumentType.RTF: return (); - case DocumentType.MAP: return (); + default: + return null; + case DocumentType.IMG: + return ; + case DocumentType.PDF: + return ; + case DocumentType.INK: + return ; + case DocumentType.WEB: + return ; + case DocumentType.VID: + return ; + case DocumentType.RTF: + return ; + case DocumentType.MAP: + return ; } } - private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { this.dropDisposer?.(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document); } - } + }; @undoBatch @action @@ -372,120 +444,139 @@ export class CollectionViewBaseChrome extends React.Component { - setupMoveUpEvents(this, e, (e, down, delta) => { - const vtype = this.props.type; - const c = { - params: ["target"], title: vtype, - script: `this.target._viewType = '${StrCast(this.props.type)}'`, - immediate: (source: Doc[]) => this.document._viewType = Doc.getDocTemplate(source?.[0]), - initialize: emptyFunction, - }; - DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), - { target: this.document }, c.params, c.initialize, e.clientX, e.clientY); - return true; - }, emptyFunction, emptyFunction); - } + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + const vtype = this.props.type; + const c = { + params: ['target'], + title: vtype, + script: `this.target._viewType = '${StrCast(this.props.type)}'`, + immediate: (source: Doc[]) => (this.document._viewType = Doc.getDocTemplate(source?.[0])), + initialize: emptyFunction, + }; + DragManager.StartButtonDrag([this._viewRef.current!], c.script, StrCast(c.title), { target: this.document }, c.params, c.initialize, e.clientX, e.clientY); + return true; + }, + emptyFunction, + emptyFunction + ); + }; dragCommandDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, (e, down, delta) => { - this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => - DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, - { target: this.document }, c.params, c.initialize, e.clientX, e.clientY)); - return true; - }, emptyFunction, () => { - this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate([])); - }); - } + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY)); + return true; + }, + emptyFunction, + () => { + this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate([])); + } + ); + }; @computed get templateChrome() { - return
    - drop document to apply or drag to create button
    } placement="bottom"> -
    - - -
    - -
    ; + return ( +
    + drop document to apply or drag to create button
    } placement="bottom"> +
    + + +
    + +
    + ); } @computed get viewModes() { const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; - const isPres: boolean = (this.document && this.document.type === DocumentType.PRES); - return isPres ? (null) : (
    - drop document to apply or drag to create button
    } placement="bottom"> -
    - - -
    - -
    ); + const isPres: boolean = this.document && this.document.type === DocumentType.PRES; + return isPres ? null : ( +
    + drop document to apply or drag to create button
    } placement="bottom"> +
    + + +
    + +
    + ); } - @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); } - @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); } + @computed get selectedDocumentView() { + return SelectionManager.Views().lastElement(); + } + @computed get selectedDoc() { + return SelectionManager.Docs().lastElement(); + } @computed get notACollection() { if (this.selectedDoc) { const layoutField = Doc.LayoutField(this.selectedDoc); - return this.props.type === CollectionViewType.Docking || - typeof (layoutField) === "string" && !layoutField?.includes("CollectionView"); - } - else return false; + return this.props.type === CollectionViewType.Docking || (typeof layoutField === 'string' && !layoutField?.includes('CollectionView')); + } else return false; } @computed get pinButton() { const targetDoc = this.selectedDoc; const isPinned = targetDoc && Doc.isDocPinned(targetDoc); - return !targetDoc ? (null) : {Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}
    } placement="top"> - - ; + return !targetDoc ? null : ( + {Doc.isDocPinned(targetDoc) ? 'Unpin from presentation' : 'Pin to presentation'}
    } placement="top"> + + + ); } @undoBatch @action startRecording = () => { - const doc = Docs.Create.ScreenshotDocument({ title: "screen recording", _fitWidth: true, _width: 400, _height: 200, mediaState: "pendingRecording" }); - //Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, doc); - CollectionDockingView.AddSplit(doc, "right"); - } + const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' }); + //Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + CollectionDockingView.AddSplit(doc, 'right'); + }; @computed get recordButton() { const targetDoc = this.selectedDoc; - return {"Capture screen"}
    } placement="top"> -
    - - ; + + + ); } @undoBatch @@ -517,21 +608,20 @@ export class CollectionViewBaseChrome extends React.Component; - return !this.selectedDoc ? (null) : - {"Pin with current view"}
    } placement="top"> -
    } placement="top"> + - ; + + ); } - @undoBatch onAlias = () => { if (this.selectedDoc && this.selectedDocumentView) { @@ -544,10 +634,10 @@ export class CollectionViewBaseChrome extends React.Component { setupMoveUpEvents(this, e, this.onAliasButtonMoved, emptyFunction, emptyFunction); - } + }; @undoBatch onAliasButtonMoved = (e: PointerEvent) => { @@ -555,62 +645,72 @@ export class CollectionViewBaseChrome extends React.Component{"Tap or Drag to create an alias"}
    } placement="top"> - - ; + return !targetDoc || targetDoc.type === DocumentType.PRES ? null : ( + {'Tap or Drag to create an alias'}
    } placement="top"> + + + ); } @computed get lightboxButton() { const targetDoc = this.selectedDoc; - return !targetDoc ? (null) : {"View in Lightbox"}
    } placement="top"> - - ; + return !targetDoc ? null : ( + {'View in Lightbox'}
    } placement="top"> + + + ); } @computed get toggleOverlayButton() { - return <> - Toggle Overlay Layer
    } placement="bottom"> - - - ; + return ( + <> + Toggle Overlay Layer
    } placement="bottom"> + + + + ); } render() { return ( -
    +
    - {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} + {this.notACollection || this.props.type === CollectionViewType.Invalid ? null : this.viewModes}
    {this.aliasButton} {/* {this.pinButton} */} @@ -621,7 +721,7 @@ export class CollectionViewBaseChrome extends React.Component
    {this.lightboxButton} {this.recordButton} - {!this._buttonizableCommands ? (null) : this.templateChrome} + {!this._buttonizableCommands ? null : this.templateChrome}
    @@ -632,30 +732,40 @@ export class CollectionViewBaseChrome extends React.Component { render() { - return (null); + return null; } } @observer -export class CollectionFreeFormViewChrome extends React.Component { +export class CollectionFreeFormViewChrome extends React.Component { public static Instance: CollectionFreeFormViewChrome; constructor(props: any) { super(props); CollectionFreeFormViewChrome.Instance = this; } - get document() { return this.props.docView.props.Document; } + get document() { + return this.props.docView.props.Document; + } @computed get dataField() { - return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? "-annotations" : "")]; + return this.document[this.props.docView.LayoutFieldKey + (this.props.isOverlay ? '-annotations' : '')]; + } + @computed get childDocs() { + return DocListCast(this.dataField); + } + @computed get selectedDocumentView() { + return SelectionManager.Views().lastElement(); + } + @computed get selectedDoc() { + return SelectionManager.Docs().lastElement(); + } + @computed get isText() { + return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; } - @computed get childDocs() { return DocListCast(this.dataField); } - @computed get selectedDocumentView() { return SelectionManager.Views().lastElement(); } - @computed get selectedDoc() { return SelectionManager.Docs().lastElement(); } - @computed get isText() { return this.selectedDoc?.type === DocumentType.RTF || (RichTextMenu.Instance?.view as any) ? true : false; } @undoBatch @action nextKeyframe = (): void => { - const currentFrame = Cast(this.document._currentFrame, "number", null); + const currentFrame = Cast(this.document._currentFrame, 'number', null); if (currentFrame === undefined) { this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); @@ -663,72 +773,88 @@ export class CollectionFreeFormViewChrome extends React.Component { - const currentFrame = Cast(this.document._currentFrame, "number", null); + const currentFrame = Cast(this.document._currentFrame, 'number', null); if (currentFrame === undefined) { this.document._currentFrame = 0; CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); } CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); this.document._currentFrame = Math.max(0, (currentFrame || 0) - 1); - } + }; - private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""]; - private _width = ["1", "5", "10", "100"]; + private _palette = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '']; + private _width = ['1', '5', '10', '100']; private _dotsize = [10, 20, 30, 40]; - private _draw = ["∿", "=", "⎯", "→", "↔︎", "ロ", "O"]; - private _head = ["", "", "", "", "arrow", "", ""]; - private _end = ["", "", "", "arrow", "arrow", "", ""]; - private _shapePrims = ["", "", "line", "line", "line", "rectangle", "circle"]; - private _title = ["pen", "highlighter", "line", "line with arrow", "line with double arrows", "square", "circle"]; - private _faName = ["pen-fancy", "highlighter", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"]; + private _draw = ['∿', '=', '⎯', '→', '↔︎', 'ロ', 'O']; + private _head = ['', '', '', '', 'arrow', '', '']; + private _end = ['', '', '', 'arrow', 'arrow', '', '']; + private _shapePrims = ['', '', 'line', 'line', 'line', 'rectangle', 'circle']; + private _title = ['pen', 'highlighter', 'line', 'line with arrow', 'line with double arrows', 'square', 'circle']; + private _faName = ['pen-fancy', 'highlighter', 'minus', 'long-arrow-alt-right', 'arrows-alt-h', 'square', 'circle']; @observable _selectedPrimitive = this._shapePrims.length; @observable _keepPrimitiveMode = false; // for whether primitive selection enters a one-shot or persistent mode @observable _colorBtn = false; @observable _widthBtn = false; @observable _fillBtn = false; - @action clearKeepPrimitiveMode() { this._selectedPrimitive = this._shapePrims.length; } + @action clearKeepPrimitiveMode() { + this._selectedPrimitive = this._shapePrims.length; + } @action primCreated() { - if (!this._keepPrimitiveMode) { //get out of ink mode after each stroke= - if (CurrentUserUtils.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor); - CurrentUserUtils.ActiveTool = InkTool.None; + if (!this._keepPrimitiveMode) { + //get out of ink mode after each stroke= + if (Doc.ActiveTool === InkTool.Highlighter && GestureOverlay.Instance.SavedColor) SetActiveInkColor(GestureOverlay.Instance.SavedColor); + Doc.ActiveTool = InkTool.None; this._selectedPrimitive = this._shapePrims.length; - SetActiveArrowStart("none"); - SetActiveArrowEnd("none"); + SetActiveArrowStart('none'); + SetActiveArrowEnd('none'); } } @action changeColor = (color: string, type: string) => { const col: ColorState = { - hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" }, - rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "", + hex: color, + hsl: { a: 0, h: 0, s: 0, l: 0, source: '' }, + hsv: { a: 0, h: 0, s: 0, v: 0, source: '' }, + rgb: { a: 0, r: 0, b: 0, g: 0, source: '' }, + oldHue: 0, + source: '', }; - if (type === "color") { + if (type === 'color') { SetActiveInkColor(Utils.colorString(col)); - } else if (type === "fill") { + } else if (type === 'fill') { SetActiveFillColor(Utils.colorString(col)); } - } + }; @action editProperties = (value: any, field: string) => { - SelectionManager.Views().forEach(action((element: DocumentView) => { - const doc = Document(element.rootDoc); - if (doc.type === DocumentType.INK) { - switch (field) { - case "width": doc.strokeWidth = Number(value); break; - case "color": doc.color = String(value); break; - case "fill": doc.fillColor = String(value); break; - case "dash": doc.strokeDash = value; + SelectionManager.Views().forEach( + action((element: DocumentView) => { + const doc = Document(element.rootDoc); + if (doc.type === DocumentType.INK) { + switch (field) { + case 'width': + doc.strokeWidth = Number(value); + break; + case 'color': + doc.color = String(value); + break; + case 'fill': + doc.fillColor = String(value); + break; + case 'dash': + doc.strokeDash = value; + } } - } - })); - } + }) + ); + }; @computed get drawButtons() { const func = action((e: React.MouseEvent | React.PointerEvent, i: number, keep: boolean) => { @@ -736,147 +862,184 @@ export class CollectionFreeFormViewChrome extends React.Component - {this._draw.map((icon, i) => - {this._title[i]}
    } placement="bottom"> - - )} -
    ; + return ( +
    + {this._draw.map((icon, i) => ( + {this._title[i]}
    } placement="bottom"> + + + ))} +
    + ); } - toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => { - return {key}
    } placement="bottom"> - - ; - } + toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps['icon'], ele: JSX.Element | null) => { + return ( + {key}
    } placement="bottom"> + + + ); + }; @computed get widthPicker() { - const widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null); - return !this._widthBtn ? widthPicker : + const widthPicker = this.toggleButton('stroke width', this._widthBtn, () => (this._widthBtn = !this._widthBtn), 'bars', null); + return !this._widthBtn ? ( + widthPicker + ) : (
    {widthPicker} - {this._width.map((wid, i) => + {this._width.map((wid, i) => ( change width
    } placement="bottom"> - - )} -
    ; + + ))} +
    + ); } @computed get colorPicker() { - const colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib", -
    ); - return !this._colorBtn ? colorPicker : + const colorPicker = this.toggleButton('stroke color', this._colorBtn, () => (this._colorBtn = !this._colorBtn), 'pen-nib',
    ); + return !this._colorBtn ? ( + colorPicker + ) : (
    {colorPicker} - {this._palette.map(color => - )} -
    ; + + ))} +
    + ); } @computed get fillPicker() { - const fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip", -
    ); - return !this._fillBtn ? fillPicker : -
    + const fillPicker = this.toggleButton('shape fill color', this._fillBtn, () => (this._fillBtn = !this._fillBtn), 'fill-drip',
    ); + return !this._fillBtn ? ( + fillPicker + ) : ( +
    {fillPicker} - {this._palette.map(color => - )} - -
    ; + + ))} +
    + ); } render() { - return !this.props.docView.layoutDoc ? (null) : + return !this.props.docView.layoutDoc ? null : (
    - {!this.isText ? + {!this.isText ? ( <> {this.drawButtons} {this.widthPicker} {this.colorPicker} {this.fillPicker} - {Doc.noviceMode || this.props.isDoc ? (null) : + {Doc.noviceMode || this.props.isDoc ? null : ( <> Back Frame
    } placement="bottom">
    - +
    Toggle View All
    } placement="bottom"> -
    this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))} > +
    this.props.docView.ComponentView?.setKeyFrameEditing?.(!this.props.docView.ComponentView?.getKeyFrameEditing?.()))}> {NumCast(this.document._currentFrame)}
    Forward Frame
    } placement="bottom">
    - +
    - } - : (null) - } - {!this.selectedDocumentView?.ComponentView?.menuControls ? (null) : this.selectedDocumentView?.ComponentView?.menuControls?.()} -
    ; + + )} + + ) : null} + {!this.selectedDocumentView?.ComponentView?.menuControls ? null : this.selectedDocumentView?.ComponentView?.menuControls?.()} +
    + ); } } @observer export class CollectionStackingViewChrome extends React.Component { - @observable private _currentKey: string = ""; + @observable private _currentKey: string = ''; @observable private suggestions: string[] = []; - get document() { return this.props.docView.props.Document; } + get document() { + return this.props.docView.props.Document; + } - @computed private get descending() { return StrCast(this.document._columnsSort) === "descending"; } - @computed get pivotField() { return StrCast(this.document._pivotField); } + @computed private get descending() { + return StrCast(this.document._columnsSort) === 'descending'; + } + @computed get pivotField() { + return StrCast(this.document._pivotField); + } getKeySuggestions = async (value: string): Promise => { const val = value.toLowerCase(); @@ -884,16 +1047,14 @@ export class CollectionStackingViewChrome extends React.Component key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || - key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || - (key[0].toUpperCase() === key[0] && key[0] !== "_")); + const keys = Object.keys(docs).filter(key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0].toUpperCase() === key[0] && key[0] !== '_')); return keys.filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); - const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || - key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || - (key[0]?.toUpperCase() === key[0] && key[0] !== "_")); + const noviceKeys = Array.from(keys).filter( + key => key.indexOf('title') >= 0 || key.indexOf('author') >= 0 || key.indexOf('creationDate') >= 0 || key.indexOf('lastModified') >= 0 || (key[0]?.toUpperCase() === key[0] && key[0] !== '_') + ); return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } } @@ -905,81 +1066,77 @@ export class CollectionStackingViewChrome extends React.Component Doc.allKeys(doc).forEach(key => keys.add(key))); return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); } - } + }; @action onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => { this._currentKey = newValue; - } + }; getSuggestionValue = (suggestion: string) => suggestion; renderSuggestion = (suggestion: string) => { return

    {suggestion}

    ; - } + }; onSuggestionFetch = async ({ value }: { value: string }) => { const sugg = await this.getKeySuggestions(value); runInAction(() => { this.suggestions = sugg; }); - } + }; @action onSuggestionClear = () => { this.suggestions = []; - } + }; @action setValue = (value: string) => { this.document._pivotField = value; return true; - } + }; @action toggleSort = () => { - this.document._columnsSort = - this.document._columnsSort === "descending" ? "ascending" : - this.document._columnsSort === "ascending" ? undefined : "descending"; - } - @action resetValue = () => { this._currentKey = this.pivotField; }; + this.document._columnsSort = this.document._columnsSort === 'descending' ? 'ascending' : this.document._columnsSort === 'ascending' ? undefined : 'descending'; + }; + @action resetValue = () => { + this._currentKey = this.pivotField; + }; render() { const doctype = this.props.docView.Document.type; - const isPres: boolean = (doctype === DocumentType.PRES); - return ( - isPres ? (null) :
    + const isPres: boolean = doctype === DocumentType.PRES; + return isPres ? null : ( +
    -
    - GROUP BY: -
    -
    +
    GROUP BY:
    +
    this.pivotField} - autosuggestProps={ - { - resetValue: this.resetValue, - value: this._currentKey, - onChange: this.onKeyChange, - autosuggestProps: { - inputProps: - { - value: this._currentKey, - onChange: this.onKeyChange - }, - getSuggestionValue: this.getSuggestionValue, - suggestions: this.suggestions, - alwaysRenderSuggestions: true, - renderSuggestion: this.renderSuggestion, - onSuggestionsFetchRequested: this.onSuggestionFetch, - onSuggestionsClearRequested: this.onSuggestionClear - } - }} + autosuggestProps={{ + resetValue: this.resetValue, + value: this._currentKey, + onChange: this.onKeyChange, + autosuggestProps: { + inputProps: { + value: this._currentKey, + onChange: this.onKeyChange, + }, + getSuggestionValue: this.getSuggestionValue, + suggestions: this.suggestions, + alwaysRenderSuggestions: true, + renderSuggestion: this.renderSuggestion, + onSuggestionsFetchRequested: this.onSuggestionFetch, + onSuggestionsClearRequested: this.onSuggestionClear, + }, + }} oneLine SetValue={this.setValue} - contents={this.pivotField ? this.pivotField : "N/A"} + contents={this.pivotField ? this.pivotField : 'N/A'} />
    @@ -988,11 +1145,12 @@ export class CollectionStackingViewChrome extends React.Component { // private _textwrapAllRows: boolean = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0; - get document() { return this.props.docView.props.Document; } + get document() { + return this.props.docView.props.Document; + } @undoBatch togglePreview = () => { @@ -1002,12 +1160,12 @@ export class CollectionSchemaViewChrome extends React.Component { - const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []); + const textwrappedRows = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []); if (textwrappedRows.length) { this.document.textwrappedSchemaRows = new List([]); } else { @@ -1015,56 +1173,52 @@ export class CollectionSchemaViewChrome extends React.Component doc[Id]); this.document.textwrappedSchemaRows = new List(allRows); } - } - + }; render() { const previewWidth = NumCast(this.document.schemaPreviewWidth); - const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec("string"), []).length > 0; + const textWrapped = Cast(this.document.textwrappedSchemaRows, listSpec('string'), []).length > 0; return (
    Show Preview:
    -
    - {previewWidth !== 0 ? "on" : "off"} -
    +
    {previewWidth !== 0 ? 'on' : 'off'}
    -
    +
    ); } } @observer export class CollectionTreeViewChrome extends React.Component { - - get document() { return this.props.docView.props.Document; } + get document() { + return this.props.docView.props.Document; + } get sortAscending() { - return this.document[this.props.fieldKey + "-sortAscending"]; + return this.document[this.props.fieldKey + '-sortAscending']; } set sortAscending(value) { - this.document[this.props.fieldKey + "-sortAscending"] = value; + this.document[this.props.fieldKey + '-sortAscending'] = value; } @computed private get ascending() { - return Cast(this.sortAscending, "boolean", null); + return Cast(this.sortAscending, 'boolean', null); } @action toggleSort = () => { if (this.sortAscending) this.sortAscending = undefined; else if (this.sortAscending === undefined) this.sortAscending = false; else this.sortAscending = true; - } + }; render() { return (
    @@ -1073,10 +1227,12 @@ export class CollectionTreeViewChrome extends React.Component { - get document() { return this.props.docView.props.Document; } + get document() { + return this.props.docView.props.Document; + } @computed get scrollSpeed() { return this.document._autoScrollSpeed; } @@ -1089,22 +1245,16 @@ export class Collection3DCarouselViewChrome extends React.Component
    - {FormattedTextBox.Focused ? : (null)} -
    - AUTOSCROLL SPEED: -
    + {FormattedTextBox.Focused ? : null} +
    AUTOSCROLL SPEED:
    - StrCast(this.scrollSpeed)} - oneLine - SetValue={this.setValue} - contents={this.scrollSpeed ? this.scrollSpeed : 1000} /> + StrCast(this.scrollSpeed)} oneLine SetValue={this.setValue} contents={this.scrollSpeed ? this.scrollSpeed : 1000} />
    @@ -1117,21 +1267,21 @@ export class Collection3DCarouselViewChrome extends React.Component { - private clicked: boolean = false; private entered: boolean = false; private decrementLimitReached: boolean = false; @observable private resize = false; private resizeListenerDisposer: Opt; - get document() { return this.props.docView.props.Document; } + get document() { + return this.props.docView.props.Document; + } componentDidMount() { - - runInAction(() => this.resize = this.props.docView.props.PanelWidth() < 700); + runInAction(() => (this.resize = this.props.docView.props.PanelWidth() < 700)); // listener to reduce text on chrome resize (panel resize) this.resizeListenerDisposer = computed(() => this.props.docView.props.PanelWidth()).observe(({ newValue }) => { - runInAction(() => this.resize = newValue < 700); + runInAction(() => (this.resize = newValue < 700)); }); } @@ -1139,14 +1289,16 @@ export class CollectionGridViewChrome extends React.Component) => { - if (e.currentTarget.valueAsNumber > 0) undoBatch(() => this.document.gridNumCols = e.currentTarget.valueAsNumber)(); - } + if (e.currentTarget.valueAsNumber > 0) undoBatch(() => (this.document.gridNumCols = e.currentTarget.valueAsNumber))(); + }; /** * Sets the value of `rowHeight` on the grid's Document to the value entered. @@ -1166,7 +1318,7 @@ export class CollectionGridViewChrome extends React.Component { this.document.gridFlex = !BoolCast(this.document.gridFlex, true); - } + }; /** * Increments the value of numCols on button click @@ -1174,9 +1326,9 @@ export class CollectionGridViewChrome extends React.Component { this.clicked = true; this.entered && (this.document.gridNumCols as number)--; - undoBatch(() => this.document.gridNumCols = this.numCols + 1)(); + undoBatch(() => (this.document.gridNumCols = this.numCols + 1))(); this.entered = false; - } + }; /** * Decrements the value of numCols on button click @@ -1185,11 +1337,11 @@ export class CollectionGridViewChrome extends React.Component 1 && !this.decrementLimitReached) { this.entered && (this.document.gridNumCols as number)++; - undoBatch(() => this.document.gridNumCols = this.numCols - 1)(); + undoBatch(() => (this.document.gridNumCols = this.numCols - 1))(); if (this.numCols === 1) this.decrementLimitReached = true; } this.entered = false; - } + }; /** * Increments the value of numCols on button hover @@ -1201,7 +1353,7 @@ export class CollectionGridViewChrome extends React.Component 1) { this.document.gridNumCols = this.numCols - 1; - } - else { + } else { this.decrementLimitReached = true; } } this.clicked = false; - } + }; /** * Toggles the value of preventCollision */ toggleCollisions = () => { this.document.gridPreventCollision = !this.document.gridPreventCollision; - } + }; /** * Changes the value of the compactType @@ -1233,16 +1384,26 @@ export class CollectionGridViewChrome extends React.Component) => { // need to change startCompaction so that this operation will be undoable. this.document.gridStartCompaction = e.target.selectedOptions[0].value; - } + }; render() { return ( -
    - +
    + - ) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} /> + ) => { + e.stopPropagation(); + e.preventDefault(); + e.currentTarget.focus(); + }} + /> @@ -1252,36 +1413,30 @@ export class CollectionGridViewChrome extends React.Component ) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} /> */} - + - + - - - - + + + - - +
    ); } @@ -1289,7 +1444,7 @@ export class CollectionGridViewChrome extends React.Component void; @@ -68,7 +51,6 @@ export enum TrimScope { None = 0, } - @observer export class CollectionStackedTimeline extends CollectionSubView() { @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; @@ -94,21 +76,38 @@ export class CollectionStackedTimeline extends CollectionSubView NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val) ?? null); - // converts screen pixel offset to time toTimeline = (screen_delta: number, width: number) => { - return Math.max( - this.clipStart, - Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart)); - } - + return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart)); + }; rangeClickScript = () => CollectionStackedTimeline.RangeScript; rangePlayScript = () => CollectionStackedTimeline.RangePlayScript; - // handles key events for for creating key anchors, scrubbing, exiting trim @action keyEvents = (e: KeyboardEvent) => { if ( // need to include range inputs because after dragging video time slider it becomes target element - !(e.target instanceof HTMLInputElement && !(e.target.type === "range")) && + !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) && this.props.isSelected(true) ) { // if shift pressed scrub 1 second otherwise 1/10th const jump = e.shiftKey ? 1 : 0.1; switch (e.key) { - case " ": + case ' ': if (!CollectionStackedTimeline.SelectingRegion) { this._markerStart = this._markerEnd = this.currentTime; CollectionStackedTimeline.SelectingRegion = this; } else { this._markerEnd = this.currentTime; - CollectionStackedTimeline.createAnchor( - this.rootDoc, - this.dataDoc, - this.props.fieldKey, - this.props.startTag, - this.props.endTag, - this._markerStart, - this._markerEnd - ); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd); this._markerEnd = undefined; CollectionStackedTimeline.SelectingRegion = undefined; } e.stopPropagation(); break; - case "Escape": + case 'Escape': // abandons current trim this._trimStart = this.clipStart; this._trimStart = this.clipEnd; this._trimming = TrimScope.None; e.stopPropagation(); break; - case "ArrowLeft": + case 'ArrowLeft': this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd)); e.stopPropagation(); break; - case "ArrowRight": + case 'ArrowRight': this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd)); e.stopPropagation(); break; } } - } - + }; getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; let la2 = l.anchor2 as Doc; - const linkTime = NumCast( - la2[this.props.startTag], - NumCast(la1[this.props.startTag]) - ); + const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag])); if (Doc.AreProtosEqual(la1, this.dataDoc)) { la1 = l.anchor2 as Doc; la2 = l.anchor1 as Doc; @@ -244,7 +226,6 @@ export class CollectionStackedTimeline extends CollectionSubView { @@ -259,7 +240,7 @@ export class CollectionStackedTimeline extends CollectionSubView { + action(e => { if (!wasSelecting) { this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); wasSelecting = true; @@ -274,24 +255,11 @@ export class CollectionStackedTimeline extends CollectionSubView 15 && - !this.IsTrimming - ) { - const anchor = CollectionStackedTimeline.createAnchor( - this.rootDoc, - this.dataDoc, - this.props.fieldKey, - this.props.startTag, - this.props.endTag, - this._markerStart, - this._markerEnd - ); + if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) { + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd); setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } - (!isClick || !wasSelecting) && - (this._markerEnd = undefined); + (!isClick || !wasSelecting) && (this._markerEnd = undefined); }), (e, doubleTap) => { if (e.button !== 2) { @@ -303,23 +271,14 @@ export class CollectionStackedTimeline extends CollectionSubView { if (shiftKey) { - CollectionStackedTimeline.createAnchor( - this.rootDoc, - this.dataDoc, - this.props.fieldKey, - this.props.startTag, - this.props.endTag, - this.currentTime - ); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime); } else { !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } } ); } - - } - + }; @action onHover = (e: React.MouseEvent): void => { @@ -329,15 +288,14 @@ export class CollectionStackedTimeline extends CollectionSubView 0 ? new ImageField(thumbnails[nearest]) : new ImageField(""); - const src = imgField && imgField.url.href ? imgField.url.href.replace(".png", "_s.png") : ""; + const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails); + const thumbnails = Cast(this.dataDoc.thumbnails, listSpec('string'), []); + const imgField = thumbnails && thumbnails.length > 0 ? new ImageField(thumbnails[nearest]) : new ImageField(''); + const src = imgField && imgField.url.href ? imgField.url.href.replace('.png', '_s.png') : ''; this._thumbnail = src ? src : undefined; } } - } - + }; // for dragging trim start handle @action @@ -348,13 +306,7 @@ export class CollectionStackedTimeline extends CollectionSubView { if (rect && this.props.isContentActive()) { - this._trimStart = Math.min( - Math.max( - this.trimStart + (e.movementX / rect.width) * this.clipDuration, - this.clipStart - ), - this.trimEnd - this.minTrimLength - ); + this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength); } return false; }), @@ -363,7 +315,7 @@ export class CollectionStackedTimeline extends CollectionSubView { if (rect && this.props.isContentActive()) { - this._trimEnd = Math.max( - Math.min( - this.trimEnd + (e.movementX / rect.width) * this.clipDuration, - this.clipEnd - ), - this.trimStart + this.minTrimLength - ); + this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength); } return false; }), @@ -389,15 +335,14 @@ export class CollectionStackedTimeline extends CollectionSubView { e.stopPropagation(); this._scroll = this._timelineWrapper!.scrollLeft; - } + }; // smooth scrolls to time like when following links overflowed due to zoom @action @@ -406,14 +351,12 @@ export class CollectionStackedTimeline extends CollectionSubView this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) { this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth()); smoothScrollHorizontal(200, this._timelineWrapper, this._scroll); - } - else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) { - this._scroll = time / this.timelineContentWidth * this.clipDuration; + } else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) { + this._scroll = (time / this.timelineContentWidth) * this.clipDuration; smoothScrollHorizontal(200, this._timelineWrapper, this._scroll); } } - } - + }; // handles dragging and dropping markers in timeline @action @@ -428,9 +371,9 @@ export class CollectionStackedTimeline extends CollectionSubView { const anchorEnd = this.anchorEnd(drop); if (anchorEnd !== undefined) { - Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : "timecodeToHide", timelinePt + anchorEnd - this.anchorStart(drop), false); + Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false); } - Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : "timecodeToShow", timelinePt, false); + Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false); }); return true; @@ -439,38 +382,28 @@ export class CollectionStackedTimeline extends CollectionSubView { if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0); return false; - } - + }; // creates marker on timeline @undoBatch @action - static createAnchor( - rootDoc: Doc, - dataDoc: Doc, - fieldKey: string, - startTag: string, - endTag: string, - anchorStartTime?: number, - anchorEndTime?: number, - docAnchor?: Doc - ) { + static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number, docAnchor?: Doc) { if (anchorStartTime === undefined) return rootDoc; - const anchor = docAnchor ?? Docs.Create.LabelDocument({ - title: ComputedField.MakeFunction( - `self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])` - ) as any, - _minFontSize: 12, - _maxFontSize: 24, - _singleLine: false, - _stayInCollection: true, - useLinkSmallAnchor: true, - hideLinkButton: true, - _isLinkButton: true, - annotationOn: rootDoc, - _timelineLabel: true, - borderRounding: anchorEndTime === undefined ? "100%" : undefined - }); + const anchor = + docAnchor ?? + Docs.Create.LabelDocument({ + title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any, + _minFontSize: 12, + _maxFontSize: 24, + _singleLine: false, + _stayInCollection: true, + useLinkSmallAnchor: true, + hideLinkButton: true, + _isLinkButton: true, + annotationOn: rootDoc, + _timelineLabel: true, + borderRounding: anchorEndTime === undefined ? '100%' : undefined, + }); Doc.GetProto(anchor)[startTag] = anchorStartTime; Doc.GetProto(anchor)[endTag] = anchorEndTime; if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) { @@ -481,7 +414,6 @@ export class CollectionStackedTimeline extends CollectionSubView { const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; @@ -493,10 +425,7 @@ export class CollectionStackedTimeline extends CollectionSubView NumCast(this.layoutDoc._currentTimecode) - ) { + if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) { if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) { this.props.Pause(); } else { @@ -508,59 +437,43 @@ export class CollectionStackedTimeline extends CollectionSubView { if (anchorDoc.isLinkButton) { - LinkManager.FollowLink(undefined, anchorDoc, this.props, false); + LinkFollower.FollowLink(undefined, anchorDoc, this.props, false); } const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; const endTime = this.anchorEnd(anchorDoc); - if ( - seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && - endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4 - ) { + if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) { if (this.props.playing()) this.props.Pause(); else if (this.layoutDoc.autoPlayAnchors) this.props.Play(); else if (!this.layoutDoc.autoPlayAnchors) { const rect = this._timeline?.getBoundingClientRect(); - rect && - this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); + rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } } else { if (this.layoutDoc.autoPlayAnchors) { this.props.playFrom(seekTimeInSeconds, endTime); - } - else { + } else { this.props.setTime(seekTimeInSeconds); } } return { select: true }; - } + }; // makes sure no anchors overlaps each other by setting the correct position and width - getLevel = ( - m: Doc, - placed: { anchorStartTime: number; anchorEndTime: number; level: number }[] - ) => { + getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => { const timelineContentWidth = this.timelineContentWidth; const x1 = this.anchorStart(m); - const x2 = this.anchorEnd( - m, - x1 + (10 / timelineContentWidth) * this.clipDuration - ); + const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration); let max = 0; const overlappedLevels = new Set( - placed.map((p) => { + placed.map(p => { const y1 = p.anchorStartTime; const y2 = p.anchorEndTime; - if ( - (x1 >= y1 && x1 <= y2) || - (x2 >= y1 && x2 <= y2) || - (y1 >= x1 && y1 <= x2) || - (y2 >= x1 && y2 <= x2) - ) { + if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) || (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) { max = Math.max(max, p.level); return p.level; } @@ -571,14 +484,17 @@ export class CollectionStackedTimeline extends CollectionSubView (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; - @computed get timelineContentHeight() { return this.props.PanelHeight() * this.dictationHeightPercent / 100; } - @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor; } // subtract size of container border + @computed get timelineContentHeight() { + return (this.props.PanelHeight() * this.dictationHeightPercent) / 100; + } + @computed get timelineContentWidth() { + return this.props.PanelWidth() * this.zoomFactor; + } // subtract size of container border dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight); @@ -586,24 +502,18 @@ export class CollectionStackedTimeline extends CollectionSubView this.currentTime; - @computed get renderDictation() { const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); return !dictation ? null : (
    + }}> + renderDepth={this.props.renderDepth + 1}>
    ); } @@ -644,145 +553,131 @@ export class CollectionStackedTimeline extends CollectionSubView ({ + const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor, })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; - return (
    -
    e.stopPropagation()} - onScroll={this.setScroll} - onMouseMove={(e) => this.isContentActive() && this.onHover(e)} - ref={wrapper => this._timelineWrapper = wrapper}> + return ( +
    (this._timeline = timeline)} - onClick={(e) => this.isContentActive() && StopEvent(e)} - onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)} - style={{ width: this.timelineContentWidth }}> - - {drawAnchors.map((d) => { - const start = this.anchorStart(d.anchor); - const end = this.anchorEnd( - d.anchor, - start + (10 / this.timelineContentWidth) * this.clipDuration - ); - if (end < this.clipStart || start > this.clipEnd) return (null); - const left = Math.max((start - this.clipStart) / this.clipDuration * this.timelineContentWidth, 0); - const top = (d.level / maxLevel) * this.props.PanelHeight(); - const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart); - const width = (timespan / this.clipDuration) * this.timelineContentWidth; - const height = this.props.PanelHeight() / maxLevel; - return this.props.Document.hideAnchors ? null : ( -
    { - this.props.playFrom(start, this.anchorEnd(d.anchor)); - e.stopPropagation(); - }} - > - -
    - ); - })} - {!this.IsTrimming && this.selectionContainer} - - {/* {this.renderDictation} */} - + className="timeline-container" + style={{ width: this.props.PanelWidth() }} + onWheel={e => e.stopPropagation()} + onScroll={this.setScroll} + onMouseMove={e => this.isContentActive() && this.onHover(e)} + ref={wrapper => (this._timelineWrapper = wrapper)}>
    + className="collectionStackedTimeline" + ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)} + onClick={e => this.isContentActive() && StopEvent(e)} + onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)} + style={{ width: this.timelineContentWidth }}> + {drawAnchors.map(d => { + const start = this.anchorStart(d.anchor); + const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration); + if (end < this.clipStart || start > this.clipEnd) return null; + const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0); + const top = (d.level / maxLevel) * this.props.PanelHeight(); + const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart); + const width = (timespan / this.clipDuration) * this.timelineContentWidth; + const height = this.props.PanelHeight() / maxLevel; + return this.props.Document.hideAnchors ? null : ( +
    { + this.props.playFrom(start, this.anchorEnd(d.anchor)); + e.stopPropagation(); + }}> + +
    + ); + })} + {!this.IsTrimming && this.selectionContainer} + + {/* {this.renderDictation} */} + +
    + +
    + + {this.IsTrimming !== TrimScope.None && ( + <> +
    -
    - - {this.IsTrimming !== TrimScope.None && ( - <> -
    - -
    + className="collectionStackedTimeline-trim-controls" + style={{ + left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`, + width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`, + }}> +
    +
    +
    +
    -
    - -
    - - )} + className="collectionStackedTimeline-trim-shade" + style={{ + left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`, + width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`, + }}>
    + + )} +
    +
    +
    +
    {formatTime(this._hoverTime - this.clipStart)}
    + {this._thumbnail && }
    -
    -
    {formatTime(this._hoverTime - this.clipStart)}
    - {this._thumbnail && } -
    -
    ); + ); } } - /** * StackedTimelineAnchor * creates the anchors to display markers, links, and embedded documents on timeline @@ -814,7 +709,6 @@ interface StackedTimelineAnchorProps { trimEnd: number; } - @observer class StackedTimelineAnchor extends React.Component { _lastTimecode: number; @@ -831,23 +725,14 @@ class StackedTimelineAnchor extends React.Component const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart; const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart; return `#${formatTime(start)}-${formatTime(end)}`; - } + }; componentDidMount() { this._disposer = reaction( () => this.props.currentTimecode(), - (time) => { - const dictationDoc = Cast( - this.props.layoutDoc["data-dictation"], - Doc, - null - ); - const isDictation = - dictationDoc && - DocListCast(this.props.mark.links).some( - (link) => - Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc - ); + time => { + const dictationDoc = Cast(this.props.layoutDoc['data-dictation'], Doc, null); + const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc); if ( !LightboxView.LightboxDoc && // bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront. @@ -859,13 +744,7 @@ class StackedTimelineAnchor extends React.Component time < NumCast(this.props.mark[this.props.endTag]) && this._lastTimecode < NumCast(this.props.mark[this.props.startTag]) - 1e-5 ) { - LinkManager.FollowLink( - undefined, - this.props.mark, - this.props as any as DocumentViewProps, - false, - true - ); + LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true); } this._lastTimecode = time; } @@ -876,7 +755,6 @@ class StackedTimelineAnchor extends React.Component this._disposer?.(); } - // starting the drag event for anchor resizing onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { this.props._timeline?.setPointerCapture(e.pointerId); @@ -885,19 +763,13 @@ class StackedTimelineAnchor extends React.Component return this.props.toTimeline(e.clientX - rect.x, rect.width); }; const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => { - const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; + const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== undefined; if (timelineOnly) { if (!left && time !== undefined && time <= NumCast(anchor[this.props.startTag])) time = undefined; - Doc.SetInPlace( - anchor, - left ? this.props.startTag : this.props.endTag, - time, - true - ); - if (!left) Doc.SetInPlace(anchor, "borderRounding", time !== undefined ? undefined : "100%", true); - } - else { - anchor[left ? "_timecodeToShow" : "_timecodeToHide"] = time; + Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true); + if (!left) Doc.SetInPlace(anchor, 'borderRounding', time !== undefined ? undefined : '100%', true); + } else { + anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time; } return false; }; @@ -906,46 +778,34 @@ class StackedTimelineAnchor extends React.Component setupMoveUpEvents( this, e, - (e) => { - if (!undo) undo = UndoManager.StartBatch("drag anchor"); + e => { + if (!undo) undo = UndoManager.StartBatch('drag anchor'); this.props.setTime(newTime(e)); return changeAnchor(anchor, left, newTime(e)); }, - (e) => { + e => { this.props.setTime(newTime(e)); this.props._timeline?.releasePointerCapture(e.pointerId); undo?.end(); }, emptyFunction ); - } - + }; // context menu contextMenuItems = () => { - const resetTitle = { script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${this.props.startTag}"])`)!, icon: "folder-plus", label: "Reset Title" }; + const resetTitle = { + script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${this.props.startTag}"])`)!, + icon: 'folder-plus', + label: 'Reset Title', + }; return [resetTitle]; - } - + }; // renders anchor LabelBox - renderInner = computedFn(function ( - this: StackedTimelineAnchor, - mark: Doc, - script: undefined | (() => ScriptField), - doublescript: undefined | (() => ScriptField), - screenXf: () => Transform, - width: () => number, - height: () => number - ) { + renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as any }); - const focusFunc = ( - doc: Doc, - willZoom?: boolean, - scale?: number, - afterFocus?: DocAfterFocusFunc, - docTransform?: Transform - ) => { + const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => { this.props.playLink(mark); this.props.focus(doc, { willZoom, scale, afterFocus, docTransform }); }; @@ -954,13 +814,13 @@ class StackedTimelineAnchor extends React.Component view: ( anchor.view = r)} + {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit} + ref={action((r: DocumentView | null) => (anchor.view = r))} Document={mark} DataDoc={undefined} renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} - LayoutTemplateString={LabelBox.LayoutStringWithTitle("data", this.computeTitle())} + LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())} isDocumentActive={this.props.isDocumentActive} PanelWidth={width} PanelHeight={height} @@ -985,32 +845,14 @@ class StackedTimelineAnchor extends React.Component height = () => this.props.height; render() { - const inner = this.renderInner( - this.props.mark, - this.props.rangeClickScript, - this.props.rangePlayScript, - this.anchorScreenToLocalXf, - this.width, - this.height - ); + const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height); return ( <> {inner.view} - {!inner.anchor.view || - !SelectionManager.IsSelected(inner.anchor.view) ? null : ( + {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : ( <> -
    this.onAnchorDown(e, this.props.mark, true)} - /> -
    - this.onAnchorDown(e, this.props.mark, false) - } - /> +
    this.onAnchorDown(e, this.props.mark, true)} /> +
    this.onAnchorDown(e, this.props.mark, false)} /> )} @@ -1025,4 +867,4 @@ ScriptingGlobals.add(function min(num1: number, num2: number): number { }); ScriptingGlobals.add(function max(num1: number, num2: number): number { return Math.max(num1, num2); -}); \ No newline at end of file +}); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 7d40cab8c..6850fb23a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -12,6 +12,7 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; +import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -22,14 +23,13 @@ import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment } from '../nodes/DocumentView'; +import { FieldViewProps } from '../nodes/FieldView'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; import { CollectionSubView } from './CollectionSubView'; -import { CollectionViewType } from './CollectionView'; -import { FieldViewProps } from '../nodes/FieldView'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionStackingViewProps = { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 03450b798..5479929bd 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,24 +1,23 @@ -import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; -import CursorField from "../../../fields/CursorField"; -import { Doc, Opt, Field, DocListCast, AclPrivate, StrListCast } from "../../../fields/Doc"; -import { Id } from "../../../fields/FieldSymbols"; -import { List } from "../../../fields/List"; -import { listSpec } from "../../../fields/Schema"; -import { ScriptField } from "../../../fields/ScriptField"; -import { WebField } from "../../../fields/URLField"; -import { Cast, ScriptCast, NumCast, StrCast } from "../../../fields/Types"; -import { GestureUtils } from "../../../pen-gestures/GestureUtils"; -import { Utils, returnFalse, returnEmptyFilter } from "../../../Utils"; -import { DocServer } from "../../DocServer"; -import { ImageUtils } from "../../util/Import & Export/ImageUtils"; -import { InteractionUtils } from "../../util/InteractionUtils"; -import { undoBatch, UndoManager } from "../../util/UndoManager"; -import { DocComponent } from "../DocComponent"; -import React = require("react"); +import { action, computed, observable } from 'mobx'; import ReactLoading from 'react-loading'; import * as rp from 'request-promise'; -import { Networking } from "../../Network"; - +import CursorField from '../../../fields/CursorField'; +import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { listSpec } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; +import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; +import { WebField } from '../../../fields/URLField'; +import { GestureUtils } from '../../../pen-gestures/GestureUtils'; +import { returnFalse, Utils } from '../../../Utils'; +import { DocServer } from '../../DocServer'; +import { Networking } from '../../Network'; +import { ImageUtils } from '../../util/Import & Export/ImageUtils'; +import { InteractionUtils } from '../../util/InteractionUtils'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { DocComponent } from '../DocComponent'; +import React = require('react'); export interface SubCollectionViewProps extends CollectionViewProps { CollectionView: Opt; @@ -33,7 +32,8 @@ export function CollectionSubView(moreProps?: X) { protected _mainCont?: HTMLDivElement; @observable _focusFilters: Opt; // docFilters that are overridden when previewing a link to an anchor which has docFilters set on it @observable _focusRangeFilters: Opt; // docRangeFilters that are overridden when previewing a link to an anchor which has docRangeFilters set on it - protected createDashEventsTarget = (ele: HTMLDivElement | null) => { //used for stacking and masonry view + protected createDashEventsTarget = (ele: HTMLDivElement | null) => { + //used for stacking and masonry view this.dropDisposer?.(); this.gestureDisposer?.(); this._multiTouchDisposer?.(); @@ -43,8 +43,9 @@ export function CollectionSubView(moreProps?: X) { this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this)); } - } - protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view + }; + protected CreateDropTarget(ele: HTMLDivElement) { + //used in schema view this.createDashEventsTarget(ele); } @@ -54,13 +55,12 @@ export function CollectionSubView(moreProps?: X) { } @computed get dataDoc() { - return (this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : - this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document)); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template + return this.props.DataDoc instanceof Doc && this.props.Document.isTemplateForField ? Doc.GetProto(this.props.DataDoc) : this.props.Document.resolvedDataDoc ? this.props.Document : Doc.GetProto(this.props.Document); // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template } rootSelected = (outsideReaction?: boolean) => { return this.props.isSelected(outsideReaction) || (this.rootDoc && this.props.rootSelected(outsideReaction)); - } + }; // The data field for rendering this collection will be on the this.props.Document unless we're rendering a template in which case we try to use props.DataDoc. // When a document has a DataDoc but it's not a template, then it contains its own rendering data, but needs to pass the DataDoc through @@ -73,10 +73,12 @@ export function CollectionSubView(moreProps?: X) { return this.dataDoc[this.props.fieldKey]; } - get childLayoutPairs(): { layout: Doc; data: Doc; }[] { + get childLayoutPairs(): { layout: Doc; data: Doc }[] { const { Document, DataDoc } = this.props; - const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc)). - filter(pair => { // filter out any documents that have a proto that we don't have permissions to + const validPairs = this.childDocs + .map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.isAnnotationOverlay ? DataDoc : undefined, doc)) + .filter(pair => { + // filter out any documents that have a proto that we don't have permissions to return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)); }); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types @@ -85,21 +87,24 @@ export function CollectionSubView(moreProps?: X) { return Cast(this.dataField, listSpec(Doc)); } collectionFilters = () => this._focusFilters ?? StrListCast(this.props.Document._docFilters); - collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec("string"), []); + collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.props.Document._docRangeFilters, listSpec('string'), []); childDocFilters = () => [...(this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; unrecursiveDocFilters = () => [...(this.props.docFilters?.().filter(f => !Utils.IsRecursiveFilter(f)) || [])]; childDocRangeFilters = () => [...(this.props.docRangeFilters?.() || []), ...this.collectionRangeDocFilters()]; - IsFiltered = () => this.collectionFilters().length || this.collectionRangeDocFilters().length ? "hasFilter" : - this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? "inheritsFilter" : undefined + IsFiltered = () => + this.collectionFilters().length || this.collectionRangeDocFilters().length ? 'hasFilter' : this.props.docFilters?.().filter(f => Utils.IsRecursiveFilter(f)).length || this.props.docRangeFilters().length ? 'inheritsFilter' : undefined; searchFilterDocs = () => this.props.searchFilterDocs?.() ?? DocListCast(this.props.Document._searchFilterDocs); @computed.struct get childDocs() { TraceMobx(); let rawdocs: (Doc | Promise)[] = []; - if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; + if (this.dataField instanceof Doc) { + // if collection data is just a document, then promote it to a singleton list; rawdocs = [this.dataField]; - } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. + } else if (Cast(this.dataField, listSpec(Doc), null)) { + // otherwise, if the collection data is a list, then use it. rawdocs = Cast(this.dataField, listSpec(Doc), null); - } else { // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc. + } else { + // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc. // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection. // Since the data field is actually an image, we set the list of documents to the singleton of root document's proto which will be an image. const rootDoc = Cast(this.props.Document.rootDocument, Doc, null); @@ -117,19 +122,19 @@ export function CollectionSubView(moreProps?: X) { return childDocs.filter(cd => !cd.cookies); // remove any documents that require a cookie if there are no filters to provide one } - // console.log(CurrentUserUtils.ActiveDashboard._docFilters); + // console.log(Doc.ActiveDashboard._docFilters); // if (!this.props.Document._docFilters && this.props.Document.currentFilter) { // (this.props.Document.currentFilter as Doc).filterBoolean = (this.props.ContainingCollectionDoc?.currentFilter as Doc)?.filterBoolean; // } const docsforFilter: Doc[] = []; - childDocs.forEach((d) => { + childDocs.forEach(d => { // if (DocUtils.Excluded(d, docFilters)) return; - let notFiltered = d.z || Doc.IsSystem(d) || (DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0); + let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), docRangeFilters, viewSpecScript, this.props.Document).length > 0; if (notFiltered) { - notFiltered = ((!searchDocs.length || searchDocs.includes(d)) && (DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0)); + notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, docRangeFilters, viewSpecScript, this.props.Document).length > 0; const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); + const data = d[annos ? fieldKey + '-annotations' : fieldKey]; if (data !== undefined) { let subDocs = DocListCast(data); if (subDocs.length > 0) { @@ -137,11 +142,12 @@ export function CollectionSubView(moreProps?: X) { notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, viewSpecScript, d).length); while (subDocs.length > 0 && !notFiltered) { newarray = []; - subDocs.forEach((t) => { + subDocs.forEach(t => { const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView"); - notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length)); - DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc)); + const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView'); + notFiltered = + notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, viewSpecScript, d).length)); + DocListCast(t[annos ? fieldKey + '-annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); }); subDocs = newarray; } @@ -157,7 +163,7 @@ export function CollectionSubView(moreProps?: X) { protected async setCursorPosition(position: [number, number]) { let ind; const doc = this.props.Document; - const id = CurrentUserUtils.id; + const id = Doc.UserDoc()[Id]; const email = Doc.CurrentUserEmail; const pos = { x: position[0], y: position[1] }; if (id && email) { @@ -167,7 +173,7 @@ export function CollectionSubView(moreProps?: X) { } // The following conditional detects a recurring bug we've seen on the server if (proto[Id] === Docs.Prototypes.get(DocumentType.COL)[Id]) { - alert("COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info..."); + alert('COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...'); console.log(doc); console.log(proto); throw new Error(`AHA! You were trying to set a cursor on a collection's proto, which is the original collection proto! Look at the two previously printed lines for document values!`); @@ -186,8 +192,7 @@ export function CollectionSubView(moreProps?: X) { } @undoBatch - protected onGesture(e: Event, ge: GestureUtils.GestureEvent) { - } + protected onGesture(e: Event, ge: GestureUtils.GestureEvent) {} protected onInternalPreDrop(e: Event, de: DragManager.DropEvent, targetAction: dropActionType) { if (de.complete.docDragData) { @@ -210,12 +215,16 @@ export function CollectionSubView(moreProps?: X) { const dropAction = docDragData.dropAction || docDragData.userDropAction; const targetDocments = DocListCast(this.dataDoc[this.props.fieldKey]); const someMoved = !docDragData.userDropAction && docDragData.draggedDocuments.some(drag => targetDocments.includes(drag)); - if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop); - if ((!dropAction || dropAction === "same" || dropAction === "move" || someMoved) && docDragData.moveDocument) { + if (someMoved) docDragData.droppedDocuments = docDragData.droppedDocuments.map((drop, i) => (targetDocments.includes(docDragData.draggedDocuments[i]) ? docDragData.draggedDocuments[i] : drop)); + if ((!dropAction || dropAction === 'same' || dropAction === 'move' || someMoved) && docDragData.moveDocument) { const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); if (movedDocs.length) { - const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || (!this.props.isAnnotationOverlay || this.props.Document.allowOverlayDrop) || + const canAdd = + this.props.Document._viewType === CollectionViewType.Pile || + de.embedKey || + !this.props.isAnnotationOverlay || + this.props.Document.allowOverlayDrop || Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); } else { @@ -228,11 +237,10 @@ export function CollectionSubView(moreProps?: X) { ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); added = this.addDocument(docDragData.droppedDocuments); } - !added && alert("You cannot perform this move"); + !added && alert('You cannot perform this move'); e.stopPropagation(); return added; - } - else if (de.complete.annoDragData) { + } else if (de.complete.annoDragData) { const dropCreator = de.complete.annoDragData.dropDocCreator; de.complete.annoDragData.dropDocCreator = () => { const dropped = dropCreator(this.props.isAnnotationOverlay ? this.rootDoc : undefined); @@ -253,11 +261,11 @@ export function CollectionSubView(moreProps?: X) { } const { dataTransfer } = e; - const html = dataTransfer.getData("text/html"); - const text = dataTransfer.getData("text/plain"); - const uriList = dataTransfer.getData("text/uri-list"); + const html = dataTransfer.getData('text/html'); + const text = dataTransfer.getData('text/plain'); + const uriList = dataTransfer.getData('text/uri-list'); - if (text && text.startsWith("(moreProps?: X) { const href = FormattedTextBox.GetHref(html); if (href) { const docid = FormattedTextBox.GetDocFromUrl(href); - if (docid) { // prosemirror text containing link to dash document + if (docid) { + // prosemirror text containing link to dash document DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x as number; f.y = options.y as number; } // should be in CollectionFreeFormView - (f instanceof Doc) && addDocument(f); + if (options.x || options.y) { + f.x = options.x as number; + f.y = options.y as number; + } // should be in CollectionFreeFormView + f instanceof Doc && addDocument(f); } }); } else { @@ -286,47 +298,50 @@ export function CollectionSubView(moreProps?: X) { } return; } - if (!html.startsWith(" 1 && tags[1].startsWith("img") ? tags[1] : ""; - const cors = img.includes("corsProxy") ? img.match(/http.*corsProxy\//)![0] : ""; - img = cors ? img.replace(cors, "") : img; + if (!html.startsWith(' 1 && tags[1].startsWith('img') ? tags[1] : ''; + const cors = img.includes('corsProxy') ? img.match(/http.*corsProxy\//)![0] : ''; + img = cors ? img.replace(cors, '') : img; if (img) { - const split = img.split("src=\"")[1].split("\"")[0]; + const split = img.split('src="')[1].split('"')[0]; let source = split; - if (split.startsWith("data:image") && split.includes("base64")) { - const [{ accessPaths }] = await Networking.PostToServer("/uploadRemoteImage", { sources: [split] }); + if (split.startsWith('data:image') && split.includes('base64')) { + const [{ accessPaths }] = await Networking.PostToServer('/uploadRemoteImage', { sources: [split] }); source = Utils.prepend(accessPaths.agnostic.client); } - if (source.startsWith("http")) { + if (source.startsWith('http')) { const doc = Docs.Create.ImageDocument(source, { ...options, _width: 300 }); ImageUtils.ExtractExif(doc); addDocument(doc); } return; } else { - const path = window.location.origin + "/doc/"; + const path = window.location.origin + '/doc/'; if (text.startsWith(path)) { - const docid = text.replace(Doc.globalServerPath(), "").split("?")[0]; + const docid = text.replace(Doc.globalServerPath(), '').split('?')[0]; DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x as number; f.y = options.y as number; } // should be in CollectionFreeFormView - (f instanceof Doc) && addDocument(f); + if (options.x || options.y) { + f.x = options.x as number; + f.y = options.y as number; + } // should be in CollectionFreeFormView + f instanceof Doc && addDocument(f); } }); } else { const srcWeb = SelectionManager.Views().lastElement(); const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0]; - const reg = new RegExp(Utils.prepend(""), "g"); + const reg = new RegExp(Utils.prepend(''), 'g'); const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; - const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, "$1")).filter(t => t)?.[0]; - const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? "from:" + srcUrl : "-web clip-", _width: 300, _height: 300, backgroundColor }); - Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text; + const backgroundColor = tags.map(tag => tag.match(/.*(background-color: ?[^;]*)/)?.[1]?.replace(/background-color: ?(.*)/, '$1')).filter(t => t)?.[0]; + const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: srcUrl ? 'from:' + srcUrl : '-web clip-', _width: 300, _height: 300, backgroundColor }); + Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text; addDocument(htmlDoc); if (srcWeb) { - const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName("iframe")?.[0]; - const focusNode = (iframe?.contentDocument?.getSelection()?.focusNode as any); + const iframe = SelectionManager.Views()[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; + const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; if (focusNode) { const anchor = srcWeb?.ComponentView?.getAnchor?.(); anchor && DocUtils.MakeLink({ doc: htmlDoc }, { doc: anchor }); @@ -339,11 +354,10 @@ export function CollectionSubView(moreProps?: X) { } if (uriList || text) { - if ((uriList || text).includes("www.youtube.com/watch") || text.includes("www.youtube.com/embed")) { - - const batch = UndoManager.StartBatch("youtube upload"); + if ((uriList || text).includes('www.youtube.com/watch') || text.includes('www.youtube.com/embed')) { + const batch = UndoManager.StartBatch('youtube upload'); const generatedDocuments: Doc[] = []; - this.slowLoadDocuments((uriList || text).split("v=")[1].split("&")[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); + this.slowLoadDocuments((uriList || text).split('v=')[1].split('&')[0], options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); return; } @@ -374,15 +388,16 @@ export function CollectionSubView(moreProps?: X) { // alias._height = 512; // alias._width = 400; // addDocument(alias); - // } else + // } else { - const newDoc = Docs.Create.WebDocument(uriList.split("#annotations:")[0], {// clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig) + const newDoc = Docs.Create.WebDocument(uriList.split('#annotations:')[0], { + // clean hypothes.is URLs that reference a specific annotation (eg. https://en.wikipedia.org/wiki/Cartoon#annotations:t7qAeNbCEeqfG5972KR2Ig) ...options, - title: uriList.split("#annotations:")[0], + title: uriList.split('#annotations:')[0], _width: 400, _height: 512, _nativeWidth: 850, - useCors: true + useCors: true, }); addDocument(newDoc); } @@ -394,82 +409,99 @@ export function CollectionSubView(moreProps?: X) { const files: File[] = []; const generatedDocuments: Doc[] = []; if (!length) { - alert("No uploadable content found."); + alert('No uploadable content found.'); return; } - const batch = UndoManager.StartBatch("collection view drop"); + const batch = UndoManager.StartBatch('collection view drop'); for (let i = 0; i < length; i++) { const item = e.dataTransfer.items[i]; - if (item.kind === "string" && item.type.includes("uri")) { + if (item.kind === 'string' && item.type.includes('uri')) { const stringContents = await new Promise(resolve => item.getAsString(resolve)); - const type = (await rp.head(Utils.CorsProxy(stringContents)))["content-type"]; + const type = (await rp.head(Utils.CorsProxy(stringContents)))['content-type']; if (type) { const doc = await DocUtils.DocumentFromType(type, Utils.CorsProxy(stringContents), options); doc && generatedDocuments.push(doc); } } - if (item.kind === "file") { + if (item.kind === 'file') { const file = item.getAsFile(); file?.type && files.push(file); - file?.type === "application/json" && Utils.readUploadedFileAsText(file).then(result => { - const json = JSON.parse(result as string); - addDocument(Docs.Create.TreeDocument( - json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) => { - const label = Docs.Create.LabelDocument({ title: c["#text"], _width: 120, _height: 20 }); - const proto = Doc.GetProto(label); - proto._width = 120; - proto._height = 20; - return proto; - } - ), { _width: 150, _height: 600, title: "across", backgroundColor: "white", _singleLine: true })); - }); + file?.type === 'application/json' && + Utils.readUploadedFileAsText(file).then(result => { + const json = JSON.parse(result as string); + addDocument( + Docs.Create.TreeDocument( + json['rectangular-puzzle'].crossword.clues[0].clue.map((c: any) => { + const label = Docs.Create.LabelDocument({ title: c['#text'], _width: 120, _height: 20 }); + const proto = Doc.GetProto(label); + proto._width = 120; + proto._height = 20; + return proto; + }), + { _width: 150, _height: 600, title: 'across', backgroundColor: 'white', _singleLine: true } + ) + ); + }); } } this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); } - slowLoadDocuments = async (files: (File[] | string), options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: ((doc: Doc[]) => void) | undefined, clientX: number, clientY: number, addDocument: (doc: Doc | Doc[]) => boolean) => { - const disposer = OverlayView.Instance.addElement( - , { x: clientX - 125, y: clientY - 125 }); - if (typeof files === "string") { - generatedDocuments.push(...await DocUtils.uploadYoutubeVideo(files, options)); + slowLoadDocuments = async ( + files: File[] | string, + options: DocumentOptions, + generatedDocuments: Doc[], + text: string, + completed: ((doc: Doc[]) => void) | undefined, + clientX: number, + clientY: number, + addDocument: (doc: Doc | Doc[]) => boolean + ) => { + const disposer = OverlayView.Instance.addElement(, { x: clientX - 125, y: clientY - 125 }); + if (typeof files === 'string') { + generatedDocuments.push(...(await DocUtils.uploadYoutubeVideo(files, options))); } else { - generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options)); + generatedDocuments.push(...(await DocUtils.uploadFilesToDocs(files, options))); } if (generatedDocuments.length) { // Creating a dash document const isFreeformView = this.props.Document._viewType === CollectionViewType.Freeform; - const set = !isFreeformView ? generatedDocuments : - generatedDocuments.length > 1 ? generatedDocuments.map(d => { DocUtils.iconify(d); return d; }) : []; + const set = !isFreeformView + ? generatedDocuments + : generatedDocuments.length > 1 + ? generatedDocuments.map(d => { + DocUtils.iconify(d); + return d; + }) + : []; if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!,); + addDocument(DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!); } else { generatedDocuments.forEach(addDocument); } } } else { - if (text && !text.includes("https://")) { + if (text && !text.includes('https://')) { addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })); } else { - alert("Document upload failed - possibly an unsupported file type."); + alert('Document upload failed - possibly an unsupported file type.'); } } disposer(); - } + }; } return CollectionSubView; } -import { DragManager, dropActionType } from "../../util/DragManager"; -import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { DocumentType } from "../../documents/DocumentTypes"; -import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox"; -import { CollectionView, CollectionViewType, CollectionViewProps } from "./CollectionView"; -import { SelectionManager } from "../../util/SelectionManager"; -import { OverlayView } from "../OverlayView"; -import { GetEffectiveAcl, TraceMobx } from "../../../fields/util"; \ No newline at end of file +import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; +import { Docs, DocumentOptions, DocUtils } from '../../documents/Documents'; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DragManager, dropActionType } from '../../util/DragManager'; +import { SelectionManager } from '../../util/SelectionManager'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { OverlayView } from '../OverlayView'; +import { CollectionView, CollectionViewProps } from './CollectionView'; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index f5b9162d3..809a73a77 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -9,7 +9,6 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, OmitKeys, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; @@ -20,6 +19,7 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; +import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import { CollectionFreeFormView } from './collectionFreeForm'; @@ -27,7 +27,6 @@ import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; import React = require('react'); -import { FieldViewProps } from '../nodes/FieldView'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { @@ -81,7 +80,7 @@ export class CollectionTreeView extends CollectionSubView this.props.whenChildContentsActiveChanged((this._isAnyChildContentActive = isActive))); isContentActive = (outsideReaction?: boolean) => - CurrentUserUtils.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) - ? true - : false; + Doc.ActiveTool !== InkTool.None || this.props.isContentActive?.() || this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || this.props.rootSelected(outsideReaction) ? true : false; componentWillUnmount() { this._isDisposing = true; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 1576ec40f..f38efe578 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -9,12 +9,13 @@ import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { returnEmptyString } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; +import { CollectionViewType } from '../../documents/DocumentTypes'; import { BranchCreate, BranchTask } from '../../documents/Gitlike'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; +import { DashboardView } from '../DashboardView'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; @@ -35,26 +36,6 @@ import './CollectionView.scss'; export const COLLECTION_BORDER_WIDTH = 2; const path = require('path'); -export enum CollectionViewType { - Invalid = 'invalid', - Freeform = 'freeform', - Schema = 'schema', - Docking = 'docking', - Tree = 'tree', - Stacking = 'stacking', - Masonry = 'masonry', - Multicolumn = 'multicolumn', - Multirow = 'multirow', - Time = 'time', - Carousel = 'carousel', - Carousel3D = '3D Carousel', - Linear = 'linear', - //Staff = "staff", - Map = 'map', - Grid = 'grid', - Pile = 'pileup', - StackedTimeline = 'stacked timeline', -} interface CollectionViewProps_ extends FieldViewProps { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) isAnnotationOverlayScrollable?: boolean; // whether the annotation overlay can be vertically scrolled (just for tree views, currently) @@ -127,39 +108,26 @@ export class CollectionView extends ViewBoxAnnotatableComponent (this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth())); + // prettier-ignore private renderSubView = (type: CollectionViewType | undefined, props: SubCollectionViewProps) => { TraceMobx(); if (type === undefined) return null; switch (type) { default: - case CollectionViewType.Freeform: - return ; - case CollectionViewType.Schema: - return ; - case CollectionViewType.Docking: - return ; - case CollectionViewType.Tree: - return ; - case CollectionViewType.Multicolumn: - return ; - case CollectionViewType.Multirow: - return ; - case CollectionViewType.Linear: - return ; - case CollectionViewType.Pile: - return ; - case CollectionViewType.Carousel: - return ; - case CollectionViewType.Carousel3D: - return ; - case CollectionViewType.Stacking: - return ; - case CollectionViewType.Masonry: - return ; - case CollectionViewType.Time: - return ; - case CollectionViewType.Grid: - return ; + case CollectionViewType.Freeform: return ; + case CollectionViewType.Schema: return ; + case CollectionViewType.Docking: return ; + case CollectionViewType.Tree: return ; + case CollectionViewType.Multicolumn: return ; + case CollectionViewType.Multirow: return ; + case CollectionViewType.Linear: return ; + case CollectionViewType.Pile: return ; + case CollectionViewType.Carousel: return ; + case CollectionViewType.Carousel3D: return ; + case CollectionViewType.Stacking: return ; + case CollectionViewType.Masonry: return ; + case CollectionViewType.Time: return ; + case CollectionViewType.Grid: return ; //case CollectionViewType.Staff: return ; } }; @@ -193,7 +161,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent { const cm = ContextMenu.Instance; if (e.nativeEvent.cancelBubble) return; // nested calls to React to render can cause the same event to trigger in the outer view even if the inner view has handled it. This avoid CollectionDockingView menu options from being added when the event has been handled by a sub-document. - if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { + if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== Doc.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 this.setupViewTypes( 'UI Controls...', @@ -235,7 +203,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent CurrentUserUtils.createNewDashboard(), icon: 'project-diagram' }); + optionItems.push({ description: 'Create Dashboard', event: () => DashboardView.createNewDashboard(), icon: 'project-diagram' }); } !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); @@ -286,9 +254,8 @@ export class CollectionView extends ViewBoxAnnotatableComponent { - return this.props.isContentActive(); - }; + isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(); + render() { TraceMobx(); const props: SubCollectionViewProps = { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index c0a61c90f..b8aaea622 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -9,18 +9,20 @@ import { DataSym, Doc, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; +import { listSpec } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { DocUtils } from '../../documents/Documents'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { DashboardView } from '../DashboardView'; import { Colors, Shadows } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { MainView } from '../MainView'; @@ -30,11 +32,9 @@ import { PinProps, PresBox, PresMovement } from '../nodes/trails'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; -import { CollectionView, CollectionViewType } from './CollectionView'; +import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import React = require('react'); -import { listSpec } from '../../../fields/Schema'; -import { ScriptField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; interface TabDocViewProps { @@ -215,7 +215,7 @@ export class TabDocView extends React.Component { const batch = UndoManager.StartBatch('pinning doc'); // all docs will be added to the ActivePresentation as stored on CurrentUserUtils - const curPres = CurrentUserUtils.ActivePresentation; + const curPres = Doc.ActivePresentation; curPres && docList.forEach(doc => { // Edge Case 1: Cannot pin document to itself @@ -306,7 +306,7 @@ export class TabDocView extends React.Component { .map(d => d.DashDoc) .includes(curPres) ) { - const docs = Cast(CurrentUserUtils.MyOverlayDocs.data, listSpec(Doc), []); + const docs = Cast(Doc.MyOverlayDocs.data, listSpec(Doc), []); if (docs.includes(curPres)) docs.splice(docs.indexOf(curPres), 1); CollectionDockingView.AddSplit(curPres, 'right'); setTimeout(() => DocumentManager.Instance.jumpToDocument(docList.lastElement(), false, undefined, []), 100); // keeps the pinned doc in view since the sidebar shifts things @@ -361,7 +361,7 @@ export class TabDocView extends React.Component { const locationParams = locationFields.length > 1 ? locationFields[1] : ''; switch (locationFields[0]) { case 'dashboard': - return CurrentUserUtils.openDashboard(doc); + return DashboardView.openDashboard(doc); case 'close': return CollectionDockingView.CloseSplit(doc, locationParams); case 'fullScreen': diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 704b8989a..eb5faf4e1 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,7 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; -import { observer } from "mobx-react"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx'; +import { observer } from 'mobx-react'; import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -12,26 +12,25 @@ import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Ty import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, returnTrue, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { DocumentManager, DocFocusOrOpen } from '../../util/DocumentManager'; -import { DragManager, dropActionType } from "../../util/DragManager"; +import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { EditableView } from "../EditableView"; +import { EditableView } from '../EditableView'; import { TREE_BULLET_WIDTH } from '../global/globalCssVariables.scss'; import { DocumentView, DocumentViewInternal, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView'; +import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; +import { KeyValueBox } from '../nodes/KeyValueBox'; import { StyleProp } from '../StyleProvider'; import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; -import { CollectionView, CollectionViewType } from './CollectionView'; -import "./TreeView.scss"; -import React = require("react"); -import { KeyValueBox } from '../nodes/KeyValueBox'; -import { FieldViewProps } from '../nodes/FieldView'; +import { CollectionView } from './CollectionView'; +import './TreeView.scss'; +import React = require('react'); export interface TreeViewProps { treeView: CollectionTreeView; @@ -55,7 +54,7 @@ export interface TreeViewProps { indentDocument?: (editTitle: boolean) => void; outdentDocument?: (editTitle: boolean) => void; ScreenToLocalTransform: () => Transform; - contextMenuItems: { script: ScriptField, filter: ScriptField, icon: string, label: string }[]; + contextMenuItems: { script: ScriptField; filter: ScriptField; icon: string; label: string }[]; dontRegisterView?: boolean; styleProvider?: StyleProviderFunc | undefined; treeViewHideHeaderFields: () => boolean; @@ -70,24 +69,26 @@ export interface TreeViewProps { hierarchyIndex?: number[]; } -const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("px", "")); }; +const treeBulletWidth = function () { + return Number(TREE_BULLET_WIDTH.replace('px', '')); +}; export enum TreeSort { - Up = "up", - Down = "down", - Zindex = "z", - None = "none" + Up = 'up', + Down = 'down', + Zindex = 'z', + None = 'none', } /** * Renders a treeView of a collection of documents - * + * * special fields: * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ @observer export class TreeView extends React.Component { - static _editTitleOnLoad: Opt<{ id: string, parent: TreeView | CollectionTreeView | undefined }>; + static _editTitleOnLoad: Opt<{ id: string; parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt; static _openLevelScript: Opt; private _header: React.RefObject = React.createRef(); @@ -98,7 +99,9 @@ export class TreeView extends React.Component { private _openScript: (() => ScriptField) | undefined; private _treedropDisposer?: DragManager.DragDropDisposer; - get treeViewOpenIsTransient() { return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc); } + get treeViewOpenIsTransient() { + return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc); + } set treeViewOpen(c: boolean) { if (this.treeViewOpenIsTransient) this._transientOpenState = c; else { @@ -109,26 +112,61 @@ export class TreeView extends React.Component { @observable _transientOpenState = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state @observable _editTitle: boolean = false; @observable _dref: DocumentView | undefined | null; - get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive + get displayName() { + return 'TreeView(' + this.props.document.title + ')'; + } // this makes mobx trace() statements more descriptive get defaultExpandedView() { - return this.doc.viewType === CollectionViewType.Docking ? this.fieldKey : - this.props.treeView.dashboardMode ? this.fieldKey : - this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : // for displaying - this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); - } - - @computed get doc() { return this.props.document; } - @computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._transientOpenState; } - @computed get treeViewExpandedView() { return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; } - @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); } - @computed get dataDoc() { return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; } - @computed get layoutDoc() { return Doc.Layout(this.doc); } - @computed get fieldKey() { return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); } - @computed get childDocs() { return this.childDocList(this.fieldKey); } - @computed get childLinks() { return this.childDocList("links"); } - @computed get childAliases() { return this.childDocList("aliases"); } - @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } - @computed get selected() { return SelectionManager.IsSelected(this._docRef); } + return this.doc.viewType === CollectionViewType.Docking + ? this.fieldKey + : this.props.treeView.dashboardMode + ? this.fieldKey + : this.props.treeView.fileSysMode + ? this.doc.isFolder + ? this.fieldKey + : 'aliases' // for displaying + : this.props.treeView.outlineMode || this.childDocs + ? this.fieldKey + : Doc.noviceMode + ? 'layout' + : StrCast(this.props.treeView.doc.treeViewExpandedView, 'fields'); + } + + @computed get doc() { + return this.props.document; + } + @computed get treeViewOpen() { + return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, 'treeViewOpen', 'boolean', true)) || this._transientOpenState; + } + @computed get treeViewExpandedView() { + return this.validExpandViewTypes.includes(StrCast(this.doc.treeViewExpandedView)) ? StrCast(this.doc.treeViewExpandedView) : this.defaultExpandedView; + } + @computed get MAX_EMBED_HEIGHT() { + return NumCast(this.props.containerCollection.maxEmbedHeight, 200); + } + @computed get dataDoc() { + return this.props.document.treeViewChildrenOnRoot ? this.doc : this.doc[DataSym]; + } + @computed get layoutDoc() { + return Doc.Layout(this.doc); + } + @computed get fieldKey() { + return StrCast(this.doc._treeViewFieldKey, Doc.LayoutFieldKey(this.doc)); + } + @computed get childDocs() { + return this.childDocList(this.fieldKey); + } + @computed get childLinks() { + return this.childDocList('links'); + } + @computed get childAliases() { + return this.childDocList('aliases'); + } + @computed get childAnnos() { + return this.childDocList(this.fieldKey + '-annotations'); + } + @computed get selected() { + return SelectionManager.IsSelected(this._docRef); + } // SelectionManager.Views().lastElement()?.props.Document === this.props.document; } @observable _presTimer!: NodeJS.Timeout; @@ -136,64 +174,76 @@ export class TreeView extends React.Component { @observable _selectedArray: ObservableMap = new ObservableMap(); // the selected item's index - @computed get itemIndex() { return NumCast(this.doc._itemIndex); } - // the item that's active - @computed get activeItem() { return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined; } - @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } + @computed get itemIndex() { + return NumCast(this.doc._itemIndex); + } + // the item that's active + @computed get activeItem() { + return this.childDocs ? Cast(this.childDocs[NumCast(this.doc._itemIndex)], Doc, null) : undefined; + } + @computed get targetDoc() { + return Cast(this.activeItem?.presentationTargetDoc, Doc, null); + } childDocList(field: string) { const layout = Cast(Doc.LayoutField(this.doc), Doc, null); - return (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field + return ( + (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields - DocListCastOrNull(this.doc[field]); // otherwise use the document's data field + DocListCastOrNull(this.doc[field]) + ); // otherwise use the document's data field } @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - if (this.doc !== target && addDoc !== returnFalse) { // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse + if (this.doc !== target && addDoc !== returnFalse) { + // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse if (this.props.removeDoc?.(doc) === true) { return addDoc(doc); } } return false; - } + }; @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { this.props.treeView.props.select(false); const ind = this.dataDoc[key].indexOf(doc); const res = (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); res && ind > 0 && DocumentManager.Instance.getDocumentView(this.dataDoc[key][ind - 1], this.props.treeView.props.CollectionView)?.select(false); return res; - } + }; @action setEditTitle = (docView?: DocumentView) => { this._selDisposer?.(); if (!docView) { this._editTitle = false; - } - else if (docView.isSelected()) { + } else if (docView.isSelected()) { const doc = docView.Document; SelectionManager.SelectSchemaViewDoc(doc); this._editTitle = true; - this._selDisposer = reaction(() => SelectionManager.SelectedSchemaDoc(), seldoc => seldoc !== doc && this.setEditTitle(undefined)); + this._selDisposer = reaction( + () => SelectionManager.SelectedSchemaDoc(), + seldoc => seldoc !== doc && this.setEditTitle(undefined) + ); } else { docView.select(false); } - } + }; @action openLevel = (docView: DocumentView) => { if (this.props.document.isFolder || Doc.IsSystem(this.props.document)) { this.treeViewOpen = !this.treeViewOpen; } else { // choose an appropriate alias or make one. --- choose the first alias that (1) user owns, (2) has no context field ... otherwise make a new alias - const bestAlias = docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); + const bestAlias = + docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsPrototype(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.aliases).find(doc => !doc.context && doc.author === Doc.CurrentUserEmail); const nextBestAlias = DocListCast(this.props.document.aliases).find(doc => doc.author === Doc.CurrentUserEmail); - this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), "lightbox"); + this.props.addDocTab(bestAlias ?? nextBestAlias ?? Doc.MakeAlias(this.props.document), 'lightbox'); } - } + }; constructor(props: any) { super(props); if (!TreeView._openLevelScript) { - TreeView._openTitleScript = ScriptField.MakeScript("scriptContext.setEditTitle(documentView)", { scriptContext: "any", documentView: "any" }); - TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: "any", documentView: "any" }); + TreeView._openTitleScript = ScriptField.MakeScript('scriptContext.setEditTitle(documentView)', { scriptContext: 'any', documentView: 'any' }); + TreeView._openLevelScript = ScriptField.MakeScript(`scriptContext.openLevel(documentView)`, { scriptContext: 'any', documentView: 'any' }); } this._openScript = Doc.IsSystem(this.props.document) ? undefined : () => TreeView._openLevelScript!; this._editTitleScript = Doc.IsSystem(this.props.document) ? () => TreeView._openLevelScript! : () => TreeView._openTitleScript!; @@ -202,16 +252,16 @@ export class TreeView extends React.Component { _treeEle: any; protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer?.(); - ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.doc); + ele && ((this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this))), this.doc); if (this._treeEle) this.props.unobserveHeight(this._treeEle); - this.props.observeHeight(this._treeEle = ele); - } + this.props.observeHeight((this._treeEle = ele)); + }; componentWillUnmount() { this._selDisposer?.(); this._treeEle && this.props.unobserveHeight(this._treeEle); - document.removeEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointermove", this.onDragUp, true); + document.removeEventListener('pointermove', this.onDragMove, true); + document.removeEventListener('pointermove', this.onDragUp, true); // TODO: [AL] add these this.props.hierarchyIndex !== undefined && this.props.RemFromMap?.(this.doc, this.props.hierarchyIndex); } @@ -225,49 +275,60 @@ export class TreeView extends React.Component { } onDragUp = (e: PointerEvent) => { - document.removeEventListener("pointerup", this.onDragUp, true); - document.removeEventListener("pointermove", this.onDragMove, true); - } + document.removeEventListener('pointerup', this.onDragUp, true); + document.removeEventListener('pointermove', this.onDragMove, true); + }; onPointerEnter = (e: React.PointerEvent): void => { this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc); if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header.current!.className = "treeView-header"; - document.removeEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointerup", this.onDragUp, true); - document.addEventListener("pointermove", this.onDragMove, true); - document.addEventListener("pointerup", this.onDragUp, true); + this._header.current!.className = 'treeView-header'; + document.removeEventListener('pointermove', this.onDragMove, true); + document.removeEventListener('pointerup', this.onDragUp, true); + document.addEventListener('pointermove', this.onDragMove, true); + document.addEventListener('pointerup', this.onDragUp, true); } - } + }; onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); - if (this._header.current?.className !== "treeView-header-editing") { - this._header.current!.className = "treeView-header"; + if (this._header.current?.className !== 'treeView-header-editing') { + this._header.current!.className = 'treeView-header'; } - document.removeEventListener("pointerup", this.onDragUp, true); - document.removeEventListener("pointermove", this.onDragMove, true); - } + document.removeEventListener('pointerup', this.onDragUp, true); + document.removeEventListener('pointermove', this.onDragMove, true); + }; onDragMove = (e: PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); const pt = [e.clientX, e.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); - this._header.current!.className = "treeView-header"; + const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + this._header.current!.className = 'treeView-header'; if (!this.props.treeView.outlineMode || DragManager.DocDragData?.treeViewDoc === this.props.treeView.rootDoc) { - if (inside) this._header.current!.className += " treeView-header-inside"; - else if (before) this._header.current!.className += " treeView-header-above"; - else if (!before) this._header.current!.className += " treeView-header-below"; + if (inside) this._header.current!.className += ' treeView-header-inside'; + else if (before) this._header.current!.className += ' treeView-header-above'; + else if (!before) this._header.current!.className += ' treeView-header-below'; } e.stopPropagation(); - } + }; public static makeTextBullet() { - const bullet = Docs.Create.TextDocument("-text-", { - layout: CollectionView.LayoutString("data"), - title: "-title-", - treeViewExpandedViewLock: true, treeViewExpandedView: "data", - _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: TreeViewType.outline, - x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _width: 1000, _height: 10 + const bullet = Docs.Create.TextDocument('-text-', { + layout: CollectionView.LayoutString('data'), + title: '-title-', + treeViewExpandedViewLock: true, + treeViewExpandedView: 'data', + _viewType: CollectionViewType.Tree, + hideLinkButton: true, + _showSidebar: true, + treeViewType: TreeViewType.outline, + x: 0, + y: 0, + _xMargin: 0, + _yMargin: 0, + _autoHeight: true, + _singleLine: true, + _width: 1000, + _height: 10, }); Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); Doc.GetProto(bullet).data = new List([]); @@ -279,19 +340,19 @@ export class TreeView extends React.Component { const bullet = TreeView.makeTextBullet(); TreeView._editTitleOnLoad = { id: bullet[Id], parent: this }; return this.props.addDocument(bullet); - } + }; makeFolder = () => { - const folder = Docs.Create.TreeDocument([], { title: "Untitled folder", _stayInCollection: true, isFolder: true }); + const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _stayInCollection: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView }; return this.props.addDocument(folder); - } + }; deleteItem = () => this.props.removeDoc?.(this.doc); preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { const dragData = de.complete.docDragData; - dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction); - } + dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? 'same' : dragData.dropAction); + }; @undoBatch treeDrop = (e: Event, de: DragManager.DropEvent) => { @@ -299,11 +360,11 @@ export class TreeView extends React.Component { if (!this._header.current) return; const rect = this._header.current.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceGetAnchor(); const destDoc = this.doc; - DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", ""); + DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, 'tree link', ''); e.stopPropagation(); } const docDragData = de.complete.docDragData; @@ -313,22 +374,16 @@ export class TreeView extends React.Component { e.stopPropagation(); } } - } + }; dropDocuments(droppedDocuments: Doc[], before: boolean, inside: number | boolean, dropAction: dropActionType, moveDocument: DragManager.MoveFunction | undefined, forceAdd: boolean) { const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); - const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add")) || forceAdd; - const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false; - const addDoc = !inside ? parentAddDoc : - (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); - const move = (!dropAction || dropAction === "proto" || dropAction === "move" || dropAction === "same") && moveDocument; + const canAdd = (!this.props.treeView.outlineMode && !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes('add')) || forceAdd; + const localAdd = (doc: Doc) => (Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false); + const addDoc = !inside ? parentAddDoc : (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); + const move = (!dropAction || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same') && moveDocument; if (canAdd) { - return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => - (move ? - move(d, undefined, addDoc) || (dropAction === "proto" ? addDoc(d) : false) - : - addDoc(d)) || added, - false)); + return UndoManager.RunInTempBatch(() => droppedDocuments.reduce((added, d) => (move ? move(d, undefined, addDoc) || (dropAction === 'proto' ? addDoc(d) : false) : addDoc(d)) || added, false)); } return false; } @@ -339,7 +394,7 @@ export class TreeView extends React.Component { const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle()); const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); - } + }; docTransform = () => this.refTransform(this._dref?.ContentRef?.current); getTransform = () => this.refTransform(this._tref.current); docWidth = () => { @@ -348,22 +403,25 @@ export class TreeView extends React.Component { if (layoutDoc._fitWidth) return Math.min(this.props.panelWidth() - treeBulletWidth(), layoutDoc[WidthSym]()); if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.panelWidth() - treeBulletWidth())); return Math.min((this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1), Doc.NativeWidth(layoutDoc) ? layoutDoc[WidthSym]() : this.layoutDoc[WidthSym]()); - } + }; docHeight = () => { const layoutDoc = this.layoutDoc; - return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => { - const aspect = Doc.NativeAspect(layoutDoc); - if (aspect) return this.docWidth() / (aspect || 1); - return layoutDoc._fitWidth ? - (!Doc.NativeHeight(this.doc) ? - NumCast(this.props.containerCollection._height) - : - Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc)) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)) - )) - : - (layoutDoc[HeightSym]() || 50); - })())); - } + return Math.max( + 70, + Math.min( + this.MAX_EMBED_HEIGHT, + (() => { + const aspect = Doc.NativeAspect(layoutDoc); + if (aspect) return this.docWidth() / (aspect || 1); + return layoutDoc._fitWidth + ? !Doc.NativeHeight(this.doc) + ? NumCast(this.props.containerCollection._height) + : Math.min((this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc))) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height))) + : layoutDoc[HeightSym]() || 50; + })() + ) + ); + }; @computed get expandedField() { const ids: { [key: string]: string } = {}; @@ -372,11 +430,11 @@ export class TreeView extends React.Component { doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); for (const key of Object.keys(ids).slice().sort()) { - if (this.props.skipFields?.includes(key) || key === "title" || key === "treeViewOpen") continue; + if (this.props.skipFields?.includes(key) || key === 'title' || key === 'treeViewOpen') continue; const contents = doc[key]; let contentElement: (JSX.Element | null)[] | JSX.Element = []; - if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) { + if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc)) { const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); @@ -384,74 +442,109 @@ export class TreeView extends React.Component { return added; }; const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); - contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), - this.props.treeView, this, doc, undefined, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, - this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, - [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, - this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), + contentElement = TreeView.GetChildElements( + contents instanceof Doc ? [contents] : DocListCast(contents), + this.props.treeView, + this, + doc, + undefined, + this.props.containerCollection, + this.props.prevSibling, + addDoc, + remDoc, + this.move, + this.props.dropAction, + this.props.addDocTab, + this.titleStyleProvider, + this.props.ScreenToLocalTransform, + this.props.isContentActive, + this.props.panelWidth, + this.props.renderDepth, + this.props.treeViewHideHeaderFields, + [...this.props.renderedIds, doc[Id]], + this.props.onCheckedClick, + this.props.onChildClick, + this.props.skipFields, + false, + this.props.whenChildContentsActiveChanged, + this.props.dontRegisterView, + emptyFunction, + emptyFunction, + this.childContextMenuItems(), // TODO: [AL] Add these this.props.AddToMap, this.props.RemFromMap, this.props.hierarchyIndex ); } else { - contentElement = Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; + contentElement = ( + Field.toKeyValueString(doc, key)} + SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} + /> + ); } - rows.push(
    - {key + ":"} -   - {contentElement} -
    ); + rows.push( +
    + {key + ':'} +   + {contentElement} +
    + ); } - rows.push(
    - value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true)} /> -
    ); + rows.push( +
    + value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)} + /> +
    + ); return rows; } rtfWidth = () => { - const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; - return Math.min(layout[WidthSym](), (this.props.panelWidth() - treeBulletWidth())) / (this.props.treeView.props.scaling?.() || 1); - } + const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + return Math.min(layout[WidthSym](), this.props.panelWidth() - treeBulletWidth()) / (this.props.treeView.props.scaling?.() || 1); + }; rtfHeight = () => { - const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ""))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; + const layout = (temp => temp && Doc.expandTemplateLayout(temp, this.props.document, ''))(this.props.treeView.props.childLayoutTemplate?.()) || this.layoutDoc; return this.rtfWidth() <= layout[WidthSym]() ? Math.min(layout[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - } + }; rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), treeBulletWidth()); expandPanelHeight = () => { if (this.layoutDoc._fitWidth) return this.docHeight(); const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); const docAspect = this.docWidth() / this.docHeight(); - return (docAspect < aspect) ? this.docWidth() / aspect : this.docHeight(); - } + return docAspect < aspect ? this.docWidth() / aspect : this.docHeight(); + }; expandPanelWidth = () => { if (this.layoutDoc._fitWidth) return this.docWidth(); const aspect = this.layoutDoc[WidthSym]() / this.layoutDoc[HeightSym](); const docAspect = this.docWidth() / this.docHeight(); - return (docAspect > aspect) ? this.docHeight() * aspect : this.docWidth(); - } + return docAspect > aspect ? this.docHeight() * aspect : this.docWidth(); + }; @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; - const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string, label: string } }; - if (["links", "annotations", "aliases", this.fieldKey].includes(expandKey)) { + const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }; + if (['links', 'annotations', 'aliases', this.fieldKey].includes(expandKey)) { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); const sortKeys = Object.keys(sortings); - const curSortIndex = Math.max(0, sortKeys.findIndex(val => val === sorting)); - const key = (expandKey === "annotations" ? `${this.fieldKey}-` : "") + expandKey; + const curSortIndex = Math.max( + 0, + sortKeys.findIndex(val => val === sorting) + ); + const key = (expandKey === 'annotations' ? `${this.fieldKey}-` : '') + expandKey; const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { // if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't), @@ -461,107 +554,161 @@ export class TreeView extends React.Component { const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering); doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000; docs.push(doc); - docs.sort((a, b) => NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1).forEach((d, i) => d.zIndex = i); + docs.sort((a, b) => (NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1)).forEach((d, i) => (d.zIndex = i)); } const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); added && (doc.context = this.doc.context); return added; }; const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); - const docs = expandKey === "aliases" ? this.childAliases : expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; - let downX = 0, downY = 0; - return <> - {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? (null) :
    - {sortings[sorting]?.label} -
    } -
      { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }} - onClick={(e) => { - if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { - !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); + const docs = expandKey === 'aliases' ? this.childAliases : expandKey === 'links' ? this.childLinks : expandKey === 'annotations' ? this.childAnnos : this.childDocs; + let downX = 0, + downY = 0; + return ( + <> + {!docs?.length || this.props.AddToMap /* hack to identify pres box trees */ ? null : ( +
      + {sortings[sorting]?.label} +
      + )} +
        { + downX = e.clientX; + downY = e.clientY; e.stopPropagation(); - } - }}> - {!docs ? (null) : - TreeView.GetChildElements(docs, this.props.treeView, this, this.layoutDoc, - this.dataDoc, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, - StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, - this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, - [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, - this.props.dontRegisterView, emptyFunction, emptyFunction, this.childContextMenuItems(), - // TODO: [AL] add these - this.props.AddToMap, - this.props.RemFromMap, - this.props.hierarchyIndex)} -
      - ; - } else if (this.treeViewExpandedView === "fields") { - return
        -
        - {this.expandedField} -
        -
      ; + }} + onClick={e => { + if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { + !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortKeys[(curSortIndex + 1) % sortKeys.length]); + e.stopPropagation(); + } + }}> + {!docs + ? null + : TreeView.GetChildElements( + docs, + this.props.treeView, + this, + this.layoutDoc, + this.dataDoc, + this.props.containerCollection, + this.props.prevSibling, + addDoc, + remDoc, + this.move, + StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, + this.props.addDocTab, + this.titleStyleProvider, + this.props.ScreenToLocalTransform, + this.props.isContentActive, + this.props.panelWidth, + this.props.renderDepth, + this.props.treeViewHideHeaderFields, + [...this.props.renderedIds, this.doc[Id]], + this.props.onCheckedClick, + this.props.onChildClick, + this.props.skipFields, + false, + this.props.whenChildContentsActiveChanged, + this.props.dontRegisterView, + emptyFunction, + emptyFunction, + this.childContextMenuItems(), + // TODO: [AL] add these + this.props.AddToMap, + this.props.RemFromMap, + this.props.hierarchyIndex + )} +
    + + ); + } else if (this.treeViewExpandedView === 'fields') { + return ( +
      +
      {this.expandedField}
      +
    + ); } - return
      { e.preventDefault(); e.stopPropagation(); }}>{this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)}
    ; // "layout" + return ( +
      { + e.preventDefault(); + e.stopPropagation(); + }}> + {this.renderEmbeddedDocument(false, this.props.treeView.props.childDocumentsActive ?? returnFalse)} +
    + ); // "layout" } - get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } + get onCheckedClick() { + return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); + } @action bulletClick = (e: React.MouseEvent) => { if (this.onCheckedClick) { - this.onCheckedClick?.script.run({ - this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, - heading: this.props.containerCollection.title, - checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? "remove" : "check", - containingTreeView: this.props.treeView.props.Document, - }, console.log); + this.onCheckedClick?.script.run( + { + this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, + heading: this.props.containerCollection.title, + checked: this.doc.treeViewChecked === 'check' ? 'x' : this.doc.treeViewChecked === 'x' ? 'remove' : 'check', + containingTreeView: this.props.treeView.props.Document, + }, + console.log + ); } else { this.treeViewOpen = !this.treeViewOpen; } e.stopPropagation(); - } + }; @computed get renderBullet() { TraceMobx(); - const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ":open" : "")) || "question"; - const checked = this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined; - return
    - {this.props.treeView.outlineMode ? - !(this.doc.text as RichTextField)?.Text ? (null) : - : -
    -
    - + const iconType = this.props.treeView.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewIcon + (this.treeViewOpen ? ':open' : '')) || 'question'; + const checked = this.onCheckedClick ? this.doc.treeViewChecked ?? 'unchecked' : undefined; + return ( +
    + {this.props.treeView.outlineMode ? ( + !(this.doc.text as RichTextField)?.Text ? null : ( + + ) + ) : ( +
    +
    + +
    + {this.onCheckedClick ? null : typeof iconType === 'string' ? : iconType}
    - {this.onCheckedClick ? (null) : typeof iconType === "string" ? : iconType} -
    - } -
    ; + )} +
    + ); } @computed get validExpandViewTypes() { - const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length && !this.props.treeView.dashboardMode ? "annotations" : ""; - const links = () => DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? "links" : ""; - const data = () => this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ""; - const aliases = () => this.props.treeView.dashboardMode ? "" : "aliases"; - const fields = () => Doc.noviceMode ? "" : "fields"; - const layout = (Doc.noviceMode) || this.doc.viewType === CollectionViewType.Docking ? [] : ["layout"]; + const annos = () => (DocListCast(this.doc[this.fieldKey + '-annotations']).length && !this.props.treeView.dashboardMode ? 'annotations' : ''); + const links = () => (DocListCast(this.doc.links).length && !this.props.treeView.dashboardMode ? 'links' : ''); + const data = () => (this.childDocs || this.props.treeView.dashboardMode ? this.fieldKey : ''); + const aliases = () => (this.props.treeView.dashboardMode ? '' : 'aliases'); + const fields = () => (Doc.noviceMode ? '' : 'fields'); + const layout = Doc.noviceMode || this.doc.viewType === CollectionViewType.Docking ? [] : ['layout']; return [data(), ...layout, ...(this.props.treeView.fileSysMode ? [aliases(), links(), annos()] : []), fields()].filter(m => m); } @action @@ -571,49 +718,68 @@ export class TreeView extends React.Component { this.doc.treeViewExpandedView = next(this.validExpandViewTypes); } this.treeViewOpen = true; - } + }; @computed get headerElements() { - return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? (null) - : <> - {this.doc.hideContextMenu ? (null) : { this.showContextMenu(e); e.stopPropagation(); }} />} - {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? (null) : + return this.props.treeViewHideHeaderFields() || this.doc.treeViewHideHeaderFields ? null : ( + <> + {this.doc.hideContextMenu ? null : ( + { + this.showContextMenu(e); + e.stopPropagation(); + }} + /> + )} + {this.doc.treeViewExpandedViewLock || Doc.IsSystem(this.doc) ? null : ( {this.treeViewExpandedView} - } - ; + + )} + + ); } showContextMenu = (e: React.MouseEvent) => { DocumentViewInternal.SelectAfterContextMenu = false; simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); DocumentViewInternal.SelectAfterContextMenu = true; - } + }; contextMenuItems = () => { - const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, icon: "folder-plus", label: "New Folder" }; - const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: "any" })!, icon: "folder-plus", label: "Delete" }; + const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; + const deleteItem = { script: ScriptField.MakeFunction(`scriptContext.deleteItem()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'Delete' }; const folderOp = this.childDocs?.length ? [makeFolder] : []; - const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: "copy", label: "Open Alias" }; - const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: "eye", label: "Focus or Open" }; - return [...this.props.contextMenuItems.filter(mi => !mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result), ... (this.doc.isFolder ? folderOp : - Doc.IsSystem(this.doc) ? [] : - this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ? - [openAlias, makeFolder] : - this.doc.viewType === CollectionViewType.Docking ? [] : - [deleteItem, openAlias, focusDoc])]; - } + const openAlias = { script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, icon: 'copy', label: 'Open Alias' }; + const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, icon: 'eye', label: 'Focus or Open' }; + return [ + ...this.props.contextMenuItems.filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.doc })?.result)), + ...(this.doc.isFolder + ? folderOp + : Doc.IsSystem(this.doc) + ? [] + : this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) + ? [openAlias, makeFolder] + : this.doc.viewType === CollectionViewType.Docking + ? [] + : [deleteItem, openAlias, focusDoc]), + ]; + }; childContextMenuItems = () => { const customScripts = Cast(this.doc.childContextMenuScripts, listSpec(ScriptField), []); const customFilters = Cast(this.doc.childContextMenuFilters, listSpec(ScriptField), []); const icons = StrListCast(this.doc.childContextMenuIcons); return StrListCast(this.doc.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); - } + }; onChildClick = () => { return this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!); - } + }; - onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick,(!this.props.treeView.outlineMode ? this._openScript?.():null)); + onChildDoubleClick = () => ScriptCast(this.props.treeView.Document.treeViewChildDoubleClick, !this.props.treeView.outlineMode ? this._openScript?.() : null); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { @@ -621,51 +787,59 @@ export class TreeView extends React.Component { e.stopPropagation(); e.preventDefault(); } - } - titleStyleProvider = (doc: (Doc | undefined), props: Opt, property: string): any => { + }; + titleStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView - switch (property.split(":")[0]) { - case StyleProp.Opacity: return this.props.treeView.outlineMode ? undefined : 1; - case StyleProp.BackgroundColor: return this.selected ? "#7089bb" : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); - case StyleProp.DocContents: return this.props.treeView.outlineMode ? (null) : -
    - {StrCast(doc?.title)} -
    ; - default: return this.props?.treeView?.props.styleProvider?.(doc, props, property); + switch (property.split(':')[0]) { + case StyleProp.Opacity: + return this.props.treeView.outlineMode ? undefined : 1; + case StyleProp.BackgroundColor: + return this.selected ? '#7089bb' : StrCast(doc._backgroundColor, StrCast(doc.backgroundColor)); + case StyleProp.DocContents: + return this.props.treeView.outlineMode ? null : ( +
    + {StrCast(doc?.title)} +
    + ); + default: + return this.props?.treeView?.props.styleProvider?.(doc, props, property); } - } - embeddedStyleProvider = (doc: (Doc | undefined), props: Opt, property: string): any => { - if (property.startsWith(StyleProp.Decorations)) return (null); + }; + embeddedStyleProvider = (doc: Doc | undefined, props: Opt, property: string): any => { + if (property.startsWith(StyleProp.Decorations)) return null; return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView - } + }; onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { if (this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode) { switch (e.key) { - case "Tab": + case 'Tab': e.stopPropagation?.(); e.preventDefault?.(); setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); - UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true), "tab"); + UndoManager.RunInBatch(() => (e.shiftKey ? this.props.outdentDocument?.(true) : this.props.indentDocument?.(true)), 'tab'); return true; - case "Backspace": + case 'Backspace': if (!(this.doc.text as RichTextField)?.Text && this.props.removeDoc?.(this.doc)) { e.stopPropagation?.(); e.preventDefault?.(); return true; } break; - case "Enter": + case 'Enter': e.stopPropagation?.(); e.preventDefault?.(); - return UndoManager.RunInBatch(this.makeTextCollection, "bullet"); + return UndoManager.RunInBatch(this.makeTextCollection, 'bullet'); } } return false; - } + }; titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - 2 * treeBulletWidth())); return18 = () => 18; @@ -675,28 +849,32 @@ export class TreeView extends React.Component { @computed get renderTitle() { TraceMobx(); - const view = this._editTitle ? StrCast(this.doc.title)} - OnTab={undoBatch((shift?: boolean) => { - if (!shift) this.props.indentDocument?.(true); - else this.props.outdentDocument?.(true); - })} - OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))} - OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()} - SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { - Doc.SetInPlace(this.doc, "title", value, false); - this.props.treeView.outlineMode && enterKey && this.makeTextCollection(); - })} - /> - : StrCast(this.doc.title)} + OnTab={undoBatch((shift?: boolean) => { + if (!shift) this.props.indentDocument?.(true); + else this.props.outdentDocument?.(true); + })} + OnEmpty={undoBatch(() => this.props.treeView.outlineMode && this.props.removeDoc?.(this.doc))} + OnFillDown={val => this.props.treeView.fileSysMode && this.makeFolder()} + SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { + Doc.SetInPlace(this.doc, 'title', value, false); + this.props.treeView.outlineMode && enterKey && this.makeTextCollection(); + })} + /> + ) : ( + { this._docRef = r ? r : undefined; if (this._docRef && TreeView._editTitleOnLoad?.id === this.props.document[Id] && TreeView._editTitleOnLoad.parent === this.props.parentTreeView) { @@ -743,139 +921,161 @@ export class TreeView extends React.Component { ContainingCollectionView={undefined} ContainingCollectionDoc={this.props.treeView.props.Document} ContentScaling={returnOne} - />; - - const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ":afterHeader" : "")); - return <> -
    - {view} -
    -
    - {buttons} {/* hide and lock buttons */} - {this.headerElements} -
    - ; - } + /> + ); - renderBulletHeader = (contents: JSX.Element, editing: boolean) => { - return <> -
    - {contents} -
    - {this.renderBorder} - ; + const buttons = this.props.styleProvider?.(this.doc, { ...this.props.treeView.props, ContainingCollectionDoc: this.props.parentTreeView?.doc }, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ':afterHeader' : '')); + return ( + <> +
    + {view} +
    +
    + {buttons} {/* hide and lock buttons */} + {this.headerElements} +
    + + ); } + renderBulletHeader = (contents: JSX.Element, editing: boolean) => { + return ( + <> +
    + {contents} +
    + {this.renderBorder} + + ); + }; renderEmbeddedDocument = (asText: boolean, isActive: () => boolean | undefined) => { const isExpandable = this.doc._treeViewGrowsHorizontally; const panelWidth = asText || isExpandable ? this.rtfWidth : this.expandPanelWidth; const panelHeight = asText ? this.rtfOutlineHeight : isExpandable ? this.rtfHeight : this.expandPanelHeight; - return this._dref = r)} - Document={this.doc} - DataDoc={undefined} - PanelWidth={panelWidth} - PanelHeight={panelHeight} - NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} - NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} - LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined} - LayoutTemplate={this.props.treeView.props.childLayoutTemplate} - isContentActive={isActive} - isDocumentActive={isActive} - styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} - hideTitle={asText} - fitContentsToBox={returnTrue} - hideDecorationTitle={this.props.treeView.outlineMode} - hideResizeHandles={this.props.treeView.outlineMode} - onClick={this.onChildClick} - focus={this.refocus} - ContentScaling={returnOne} - onKey={this.onKeyDown} - hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} - dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} - ScreenToLocalTransform={this.docTransform} - renderDepth={this.props.renderDepth + 1} - treeViewDoc={this.props.treeView?.props.Document} - rootSelected={returnTrue} - docViewPath={this.props.treeView.props.docViewPath} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containerCollection} - ContainingCollectionView={undefined} - addDocument={this.props.addDocument} - moveDocument={this.move} - removeDocument={this.props.removeDoc} - whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.treeView.props.pinToPres} - disableDocBrushing={this.props.treeView.props.disableDocBrushing} - bringToFront={returnFalse} - />; - } + return ( + (this._dref = r))} + Document={this.doc} + DataDoc={undefined} + PanelWidth={panelWidth} + PanelHeight={panelHeight} + NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} + NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} + LayoutTemplateString={asText ? FormattedTextBox.LayoutString('text') : undefined} + LayoutTemplate={this.props.treeView.props.childLayoutTemplate} + isContentActive={isActive} + isDocumentActive={isActive} + styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} + hideTitle={asText} + fitContentsToBox={returnTrue} + hideDecorationTitle={this.props.treeView.outlineMode} + hideResizeHandles={this.props.treeView.outlineMode} + onClick={this.onChildClick} + focus={this.refocus} + ContentScaling={returnOne} + onKey={this.onKeyDown} + hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} + ScreenToLocalTransform={this.docTransform} + renderDepth={this.props.renderDepth + 1} + treeViewDoc={this.props.treeView?.props.Document} + rootSelected={returnTrue} + docViewPath={this.props.treeView.props.docViewPath} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.containerCollection} + ContainingCollectionView={undefined} + addDocument={this.props.addDocument} + moveDocument={this.move} + removeDocument={this.props.removeDoc} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.treeView.props.pinToPres} + disableDocBrushing={this.props.treeView.props.disableDocBrushing} + bringToFront={returnFalse} + /> + ); + }; // renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views. @computed get renderTitleAsHeader() { - return <> - {this.renderBullet} - {this.renderTitle} - ; + return ( + <> + {this.renderBullet} + {this.renderTitle} + + ); } // renders the document in the header field instead of a text proxy. renderDocumentAsHeader = (asText: boolean) => { - return <> - {this.renderBullet} - {this.renderEmbeddedDocument(asText, this.props.isContentActive)} - ; - } + return ( + <> + {this.renderBullet} + {this.renderEmbeddedDocument(asText, this.props.isContentActive)} + + ); + }; @computed get renderBorder() { const sorting = StrCast(this.doc.treeViewSortCriterion, TreeSort.None); - const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string, label: string } }; - return
    - {!this.treeViewOpen ? (null) : this.renderContent} -
    ; + const sortings = this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.TreeViewSortings) as { [key: string]: { color: string; label: string } }; + return ( +
    + {!this.treeViewOpen ? null : this.renderContent} +
    + ); } onTreeDrop = (de: React.DragEvent) => { const pt = [de.clientX, de.clientY]; const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); - - const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, "copy", undefined, false)); - } + const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * 0.75) || (!before && this.treeViewOpen && this.childDocList.length); + const docs = this.props.treeView.onTreeDrop(de, (docs: Doc[]) => this.dropDocuments(docs, before, inside, 'copy', undefined, false)); + }; render() { TraceMobx(); const hideTitle = this.doc.treeViewHideHeader || (this.doc.treeViewHideHeaderIfTemplate && this.props.treeView.props.childLayoutTemplate?.()) || this.props.treeView.outlineMode; - return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles -
    ' // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles + ) : ( +
    this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document - // onKeyDown={this.onKeyDown} + //onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document + // onKeyDown={this.onKeyDown} >
  • - {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader ? // should test for prop 'treeViewRenderDocWithBulletAsHeader" - this.renderEmbeddedDocument(false, returnFalse) : - this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)} + {hideTitle && this.doc.type !== DocumentType.RTF && !this.doc.treeViewRenderAsBulletHeader // should test for prop 'treeViewRenderDocWithBulletAsHeader" + ? this.renderEmbeddedDocument(false, returnFalse) + : this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader(!this.doc.treeViewRenderAsBulletHeader) : this.renderTitleAsHeader, this._editTitle)}
  • -
    ; +
    + ); } public static sortDocs(childDocs: Doc[], criterion: string | undefined) { @@ -883,9 +1083,10 @@ export class TreeView extends React.Component { if (criterion !== TreeSort.None) { const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => { const reN = /[0-9]*$/; - const aA = a.replace(reN, "") ? a.replace(reN, "") : +a; // get rid of trailing numbers - const bA = b.replace(reN, "") ? b.replace(reN, "") : +b; - if (aA === bA) { // if header string matches, then compare numbers numerically + const aA = a.replace(reN, '') ? a.replace(reN, '') : +a; // get rid of trailing numbers + const bA = b.replace(reN, '') ? b.replace(reN, '') : +b; + if (aA === bA) { + // if header string matches, then compare numbers numerically const aN = parseInt(a.match(reN)![0], 10); const bN = parseInt(b.match(reN)![0], 10); return aN === bN ? 0 : aN > bN ? 1 : -1; @@ -894,11 +1095,11 @@ export class TreeView extends React.Component { } }; docs.sort(function (d1, d2): 0 | 1 | -1 { - const a = (criterion === TreeSort.Up ? d2 : d1); - const b = (criterion === TreeSort.Up ? d1 : d2); - const first = a[criterion === TreeSort.Zindex ? "zIndex" : "title"]; - const second = b[criterion === TreeSort.Zindex ? "zIndex" : "title"]; - if (typeof first === 'number' && typeof second === 'number') return (first - second) > 0 ? 1 : -1; + const a = criterion === TreeSort.Up ? d2 : d1; + const b = criterion === TreeSort.Up ? d1 : d2; + const first = a[criterion === TreeSort.Zindex ? 'zIndex' : 'title']; + const second = b[criterion === TreeSort.Zindex ? 'zIndex' : 'title']; + if (typeof first === 'number' && typeof second === 'number') return first - second > 0 ? 1 : -1; if (typeof first === 'string' && typeof second === 'string') return sortAlphaNum(first, second); return criterion ? 1 : -1; }); @@ -934,11 +1135,11 @@ export class TreeView extends React.Component { dontRegisterView: boolean | undefined, observerHeight: (ref: any) => void, unobserveHeight: (ref: any) => void, - contextMenuItems: ({ script: ScriptField, filter: ScriptField, label: string, icon: string }[]), + contextMenuItems: { script: ScriptField; filter: ScriptField; label: string; icon: string }[], // TODO: [AL] add these AddToMap?: (treeViewDoc: Doc, index: number[]) => Doc[], RemFromMap?: (treeViewDoc: Doc, index: number[]) => Doc[], - hierarchyIndex?: number[], + hierarchyIndex?: number[] ) { const viewSpecScript = Cast(containerCollection.viewSpecScript, ScriptField); if (viewSpecScript) { @@ -948,68 +1149,77 @@ export class TreeView extends React.Component { const docs = TreeView.sortDocs(childDocs, StrCast(containerCollection.treeViewSortCriterion, TreeSort.None)); const rowWidth = () => panelWidth() - treeBulletWidth(); const treeViewRefs = new Map(); - return docs.filter(child => child instanceof Doc).map((child, i) => { - const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); - if (!pair.layout || pair.data instanceof Promise) { - return (null); - } - - const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => { - if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return; - const fieldKey = Doc.LayoutFieldKey(newParent); - if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { - remove(child); - FormattedTextBox.SelectOnLoad = child[Id]; - TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; - Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); - newParent.treeViewOpen = true; - child.context = treeView.Document; + return docs + .filter(child => child instanceof Doc) + .map((child, i) => { + const pair = Doc.GetLayoutDataDocPair(containerCollection, dataDoc, child); + if (!pair.layout || pair.data instanceof Promise) { + return null; } - }; - const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); - const outdent = parentCollectionDoc?._viewType !== CollectionViewType.Tree ? undefined : ((editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined)); - const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); - const childLayout = Doc.Layout(pair.layout); - const rowHeight = () => { - const aspect = Doc.NativeAspect(childLayout); - return (aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym]()); - }; - return treeViewRefs.set(child, r ? r : undefined)} - document={pair.layout} - dataDoc={pair.data} - containerCollection={containerCollection} - prevSibling={docs[i]} - // TODO: [AL] add these - hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined} - AddToMap={AddToMap} - RemFromMap={RemFromMap} - treeView={treeView} - indentDocument={indent} - outdentDocument={outdent} - onCheckedClick={onCheckedClick} - onChildClick={onChildClick} - renderDepth={renderDepth} - removeDoc={StrCast(containerCollection.freezeChildren).includes("remove") ? undefined : remove} - addDocument={addDocument} - styleProvider={styleProvider} - panelWidth={rowWidth} - panelHeight={rowHeight} - dontRegisterView={dontRegisterView} - moveDocument={move} - dropAction={dropAction} - addDocTab={addDocTab} - ScreenToLocalTransform={screenToLocalXf} - isContentActive={isContentActive} - treeViewHideHeaderFields={treeViewHideHeaderFields} - renderedIds={renderedIds} - skipFields={skipFields} - firstLevel={firstLevel} - whenChildContentsActiveChanged={whenChildContentsActiveChanged} - parentTreeView={parentTreeView} - observeHeight={observerHeight} - unobserveHeight={unobserveHeight} - contextMenuItems={contextMenuItems} - />; - }); + + const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => { + if (parent instanceof TreeView && parent.props.treeView.fileSysMode && !newParent.isFolder) return; + const fieldKey = Doc.LayoutFieldKey(newParent); + if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { + remove(child); + FormattedTextBox.SelectOnLoad = child[Id]; + TreeView._editTitleOnLoad = editTitle ? { id: child[Id], parent } : undefined; + Doc.AddDocToList(newParent, fieldKey, child, addAfter, false); + newParent.treeViewOpen = true; + child.context = treeView.Document; + } + }; + const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); + const outdent = + parentCollectionDoc?._viewType !== CollectionViewType.Tree + ? undefined + : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined); + const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); + const childLayout = Doc.Layout(pair.layout); + const rowHeight = () => { + const aspect = Doc.NativeAspect(childLayout); + return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); + }; + return ( + treeViewRefs.set(child, r ? r : undefined)} + document={pair.layout} + dataDoc={pair.data} + containerCollection={containerCollection} + prevSibling={docs[i]} + // TODO: [AL] add these + hierarchyIndex={hierarchyIndex ? [...hierarchyIndex, i + 1] : undefined} + AddToMap={AddToMap} + RemFromMap={RemFromMap} + treeView={treeView} + indentDocument={indent} + outdentDocument={outdent} + onCheckedClick={onCheckedClick} + onChildClick={onChildClick} + renderDepth={renderDepth} + removeDoc={StrCast(containerCollection.freezeChildren).includes('remove') ? undefined : remove} + addDocument={addDocument} + styleProvider={styleProvider} + panelWidth={rowWidth} + panelHeight={rowHeight} + dontRegisterView={dontRegisterView} + moveDocument={move} + dropAction={dropAction} + addDocTab={addDocTab} + ScreenToLocalTransform={screenToLocalXf} + isContentActive={isContentActive} + treeViewHideHeaderFields={treeViewHideHeaderFields} + renderedIds={renderedIds} + skipFields={skipFields} + firstLevel={firstLevel} + whenChildContentsActiveChanged={whenChildContentsActiveChanged} + parentTreeView={parentTreeView} + observeHeight={observerHeight} + unobserveHeight={unobserveHeight} + contextMenuItems={contextMenuItems} + /> + ); + }); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 9de2cfcf9..a0ebe4cdc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,13 +1,12 @@ -import { Doc, Field, FieldResult, HeightSym, WidthSym } from "../../../../fields/Doc"; -import { Id, ToString } from "../../../../fields/FieldSymbols"; -import { ObjectField } from "../../../../fields/ObjectField"; -import { RefField } from "../../../../fields/RefField"; -import { listSpec } from "../../../../fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../../fields/Types"; -import { aggregateBounds } from "../../../../Utils"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import React = require("react"); -import { ColorScheme } from "../../../util/SettingsManager"; +import { Doc, Field, FieldResult, HeightSym, WidthSym } from '../../../../fields/Doc'; +import { Id, ToString } from '../../../../fields/FieldSymbols'; +import { ObjectField } from '../../../../fields/ObjectField'; +import { RefField } from '../../../../fields/RefField'; +import { listSpec } from '../../../../fields/Schema'; +import { Cast, NumCast, StrCast } from '../../../../fields/Types'; +import { aggregateBounds } from '../../../../Utils'; +import { ColorScheme } from '../../../util/SettingsManager'; +import React = require('react'); export interface ViewDefBounds { type: string; @@ -25,7 +24,7 @@ export interface ViewDefBounds { color?: string; opacity?: number; replica?: string; - pair?: { layout: Doc, data?: Doc }; + pair?: { layout: Doc; data?: Doc }; } export interface PoolData { @@ -40,7 +39,7 @@ export interface PoolData { transition?: string; highlight?: boolean; replica: string; - pair: { layout: Doc, data?: Doc }; + pair: { layout: Doc; data?: Doc }; } export interface ViewDefResult { @@ -48,7 +47,7 @@ export interface ViewDefResult { bounds?: ViewDefBounds; } function toLabel(target: FieldResult) { - if (typeof target === "number" || Number(target)) { + if (typeof target === 'number' || Number(target)) { const truncated = Number(Number(target).toFixed(0)); const precise = Number(Number(target).toFixed(2)); return truncated === precise ? Number(target).toFixed(0) : Number(target).toFixed(2); @@ -60,16 +59,16 @@ function toLabel(target: FieldResult) { } /** * Uses canvas.measureText to compute and return the width of the given text of given font in pixels. - * + * * @param {String} text The text to be rendered. * @param {String} font The css font descriptor that text is to be rendered with (e.g. "bold 14px verdana"). - * + * * @see https://stackoverflow.com/questions/118241/calculate-text-width-with-javascript/21015393#21015393 */ function getTextWidth(text: string, font: string): number { // re-use canvas object for better performance - const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement("canvas")); - const context = canvas.getContext("2d"); + const canvas = (getTextWidth as any).canvas || ((getTextWidth as any).canvas = document.createElement('canvas')); + const context = canvas.getContext('2d'); context.font = font; const metrics = context.measureText(text); return metrics.width; @@ -81,14 +80,7 @@ interface PivotColumn { filters: string[]; } -export function computerPassLayout( - poolData: Map, - pivotDoc: Doc, - childPairs: { layout: Doc, data?: Doc }[], - panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], - engineProps: any -) { +export function computerPassLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map(); childPairs.forEach(({ layout, data }, i) => { docMap.set(layout[Id], { @@ -97,60 +89,49 @@ export function computerPassLayout( width: layout[WidthSym](), height: layout[HeightSym](), pair: { layout, data }, - replica: "" + replica: '', }); }); return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } -export function computerStarburstLayout( - poolData: Map, - pivotDoc: Doc, - childPairs: { layout: Doc, data?: Doc }[], - panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], - engineProps: any -) { +export function computerStarburstLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const mustFit = pivotDoc[WidthSym]() !== panelDim[0]; // if a panel size is set that's not the same as the pivot doc's size, then assume this is in a panel for a content fitting view (like a grid) in which case everything must be scaled to stay within the panel const docMap = new Map(); - const docSize = mustFit ? panelDim[0] * .33 : 75; // assume an icon sized at 75 + const docSize = mustFit ? panelDim[0] * 0.33 : 75; // assume an icon sized at 75 const burstRadius = mustFit ? panelDim : [NumCast(pivotDoc._starburstRadius, panelDim[0]) - docSize, NumCast(pivotDoc._starburstRadius, panelDim[1]) - docSize]; const scaleDim = [burstRadius[0] * 2 + docSize, burstRadius[1] * 2 + docSize]; childPairs.forEach(({ layout, data }, i) => { - const docSize = layout.layoutKey === "layout_icon" ? (mustFit ? panelDim[0] * .33 : 75) : 400; // assume a icon sized at 75 - const deg = i / childPairs.length * Math.PI * 2; + const docSize = layout.layoutKey === 'layout_icon' ? (mustFit ? panelDim[0] * 0.33 : 75) : 400; // assume a icon sized at 75 + const deg = (i / childPairs.length) * Math.PI * 2; docMap.set(layout[Id], { x: Math.cos(deg) * burstRadius[0] - docSize / 2, - y: Math.sin(deg) * burstRadius[1] - docSize * layout[HeightSym]() / layout[WidthSym]() / 2, - width: docSize,//layout[WidthSym](), - height: docSize * layout[HeightSym]() / layout[WidthSym](), + y: Math.sin(deg) * burstRadius[1] - (docSize * layout[HeightSym]()) / layout[WidthSym]() / 2, + width: docSize, //layout[WidthSym](), + height: (docSize * layout[HeightSym]()) / layout[WidthSym](), zIndex: NumCast(layout.zIndex), pair: { layout, data }, - replica: "" + replica: '', }); }); - const divider = { type: "div", color: "transparent", x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined }; + const divider = { type: 'div', color: 'transparent', x: -burstRadius[0], y: 0, width: 15, height: 15, payload: undefined }; return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, [divider]); } - -export function computePivotLayout( - poolData: Map, - pivotDoc: Doc, - childPairs: { layout: Doc, data?: Doc }[], - panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], - engineProps: any -) { +export function computePivotLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps: any) { const docMap = new Map(); - const fieldKey = "data"; + const fieldKey = 'data'; const pivotColumnGroups = new Map, PivotColumn>(); let nonNumbers = 0; - const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || "author"; + const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField) || 'author'; childPairs.map(pair => { - const lval = pivotFieldKey === "#" || pivotFieldKey === "tags" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) : - Cast(pair.layout[pivotFieldKey], listSpec("string"), null); + const lval = + pivotFieldKey === '#' || pivotFieldKey === 'tags' + ? Array.from(Object.keys(Doc.GetProto(pair.layout))) + .filter(k => k.startsWith('#')) + .map(k => k.substring(1)) + : Cast(pair.layout[pivotFieldKey], listSpec('string'), null); const num = toNumber(pair.layout[pivotFieldKey]); if (num === undefined || Number.isNaN(num)) { @@ -166,7 +147,7 @@ export function computePivotLayout( } else if (val) { !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); pivotColumnGroups.get(val)!.docs.push(pair.layout); - pivotColumnGroups.get(val)!.replicas.push(""); + pivotColumnGroups.get(val)!.replicas.push(''); } else { docMap.set(pair.layout[Id], { x: 0, @@ -175,11 +156,11 @@ export function computePivotLayout( width: 0, height: 0, pair, - replica: "" + replica: '', }); } }); - const pivotNumbers = nonNumbers / childPairs.length < .1; + const pivotNumbers = nonNumbers / childPairs.length < 0.1; if (pivotColumnGroups.size > 10) { const arrayofKeys = Array.from(pivotColumnGroups.keys()); const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort(); @@ -196,9 +177,11 @@ export function computePivotLayout( } } } - const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3)); + const fontSize = NumCast(pivotDoc[fieldKey + '-timelineFontSize'], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3)); const desc = `${fontSize}px ${getComputedStyle(document.body).fontFamily}`; - const textlen = Array.from(pivotColumnGroups.keys()).map(c => getTextWidth(toLabel(c), desc)).reduce((p, c) => Math.max(p, c), 0 as number); + const textlen = Array.from(pivotColumnGroups.keys()) + .map(c => getTextWidth(toLabel(c), desc)) + .reduce((p, c) => Math.max(p, c), 0 as number); const max_text = Math.min(Math.ceil(textlen / 120) * 28, panelDim[1] / 2); const maxInColumn = Array.from(pivotColumnGroups.values()).reduce((p, s) => Math.max(p, s.docs.length), 1); @@ -222,7 +205,7 @@ export function computePivotLayout( const groupNames: ViewDefBounds[] = []; const expander = 1.05; - const gap = .15; + const gap = 0.15; const maxColHeight = pivotAxisWidth * expander * Math.ceil(maxInColumn / numCols); let x = 0; const sortedPivotKeys = pivotNumbers ? Array.from(pivotColumnGroups.keys()).sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : Array.from(pivotColumnGroups.keys()).sort(); @@ -232,14 +215,14 @@ export function computePivotLayout( let xCount = 0; const text = toLabel(key); groupNames.push({ - type: "text", + type: 'text', text, x, y: pivotAxisWidth, width: pivotAxisWidth * expander * numCols, height: max_text, fontSize, - payload: val + payload: val, }); val.docs.forEach((doc, i) => { const layoutDoc = Doc.Layout(doc); @@ -249,13 +232,13 @@ export function computePivotLayout( hgt = pivotAxisWidth; wid = (Doc.NativeAspect(layoutDoc) || 1) * pivotAxisWidth; } - docMap.set(doc[Id] + (val.replicas || ""), { - x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0), + docMap.set(doc[Id] + (val.replicas || ''), { + x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? ((numCols - val.docs.length) * pivotAxisWidth) / 2 : 0), y: -y + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt, pair: { layout: doc }, - replica: val.replicas[i] + replica: val.replicas[i], }); xCount++; if (xCount >= numCols) { @@ -266,13 +249,14 @@ export function computePivotLayout( x += pivotAxisWidth * (numCols * expander + gap); }); - const dividers = sortedPivotKeys.map((key, i) => - ({ - type: "div", color: "lightGray", - x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, - y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, + const dividers = sortedPivotKeys.map((key, i) => ({ + type: 'div', + color: 'lightGray', + x: i * pivotAxisWidth * (numCols * expander + gap) - (pivotAxisWidth * (expander - 1)) / 2, + y: -maxColHeight + pivotAxisWidth, + width: pivotAxisWidth * numCols * expander, height: maxColHeight, - payload: pivotColumnGroups.get(key)!.filters + payload: pivotColumnGroups.get(key)!.filters, })); groupNames.push(...dividers); return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []); @@ -282,24 +266,17 @@ function toNumber(val: FieldResult) { return val === undefined ? undefined : NumCast(val, Number(StrCast(val))); } -export function computeTimelineLayout( - poolData: Map, - pivotDoc: Doc, - childPairs: { layout: Doc, data?: Doc }[], - panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], - engineProps?: any -) { - const fieldKey = "data"; +export function computeTimelineLayout(poolData: Map, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], engineProps?: any) { + const fieldKey = 'data'; const pivotDateGroups = new Map(); const docMap = new Map(); const groupNames: ViewDefBounds[] = []; const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field); - const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]); - const curTimeSpan = Cast(pivotDoc[fieldKey + "-timelineSpan"], "number", null); - const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMinReq"], "number", null) : curTime && (curTime - curTimeSpan); - const maxTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + "-timelineMaxReq"], "number", null) : curTime && (curTime + curTimeSpan); - const fontSize = NumCast(pivotDoc[fieldKey + "-timelineFontSize"], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3)); + const curTime = toNumber(pivotDoc[fieldKey + '-timelineCur']); + const curTimeSpan = Cast(pivotDoc[fieldKey + '-timelineSpan'], 'number', null); + const minTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMinReq'], 'number', null) : curTime && curTime - curTimeSpan; + const maxTimeReq = curTimeSpan === undefined ? Cast(pivotDoc[fieldKey + '-timelineMaxReq'], 'number', null) : curTime && curTime + curTimeSpan; + const fontSize = NumCast(pivotDoc[fieldKey + '-timelineFontSize'], panelDim[1] > 58 ? 20 : Math.max(7, panelDim[1] / 3)); const fontHeight = panelDim[1] > 58 ? 30 : panelDim[1] / 2; const findStack = (time: number, stack: number[]) => { const index = stack.findIndex(val => val === undefined || val < x); @@ -325,8 +302,8 @@ export function computeTimelineLayout( } } setTimeout(() => { - pivotDoc[fieldKey + "-timelineMin"] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime; - pivotDoc[fieldKey + "-timelineMax"] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime; + pivotDoc[fieldKey + '-timelineMin'] = minTime = minTimeReq ? Math.min(minTimeReq, minTime) : minTime; + pivotDoc[fieldKey + '-timelineMax'] = maxTime = maxTimeReq ? Math.max(maxTimeReq, maxTime) : maxTime; }, 0); if (maxTime === minTime) { @@ -340,10 +317,10 @@ export function computeTimelineLayout( let prevKey = Math.floor(minTime); if (sortedKeys.length && scaling * (sortedKeys[0] - prevKey) > 25) { - groupNames.push({ type: "text", text: toLabel(prevKey), x: x, y: 0, height: fontHeight, fontSize, payload: undefined }); + groupNames.push({ type: 'text', text: toLabel(prevKey), x: x, y: 0, height: fontHeight, fontSize, payload: undefined }); } if (!sortedKeys.length && curTime !== undefined) { - groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, zIndex: 1000, color: "orange", y: 0, height: fontHeight, fontSize, payload: undefined }); + groupNames.push({ type: 'text', text: toLabel(curTime), x: (curTime - minTime) * scaling, zIndex: 1000, color: 'orange', y: 0, height: fontHeight, fontSize, payload: undefined }); } const pivotAxisWidth = NumCast(pivotDoc.pivotTimeWidth, panelDim[1] / 2.5); @@ -351,26 +328,26 @@ export function computeTimelineLayout( let zind = 0; sortedKeys.forEach(key => { if (curTime !== undefined && curTime > prevKey && curTime <= key) { - groupNames.push({ type: "text", text: toLabel(curTime), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: key }); + groupNames.push({ type: 'text', text: toLabel(curTime), x: (curTime - minTime) * scaling, y: 0, zIndex: 1000, color: 'orange', height: fontHeight, fontSize, payload: key }); } const keyDocs = pivotDateGroups.get(key)!; x += scaling * (key - prevKey); const stack = findStack(x, stacking); prevKey = key; if (!stack && (curTime === undefined || Math.abs(x - (curTime - minTime) * scaling) > pivotAxisWidth)) { - groupNames.push({ type: "text", text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined }); + groupNames.push({ type: 'text', text: toLabel(key), x: x, y: stack * 25, height: fontHeight, fontSize, payload: undefined }); } layoutDocsAtTime(keyDocs, key); }); if (sortedKeys.length && curTime !== undefined && curTime > sortedKeys[sortedKeys.length - 1]) { x = (curTime - minTime) * scaling; - groupNames.push({ type: "text", text: toLabel(curTime), x: x, y: 0, zIndex: 1000, color: "orange", height: fontHeight, fontSize, payload: undefined }); + groupNames.push({ type: 'text', text: toLabel(curTime), x: x, y: 0, zIndex: 1000, color: 'orange', height: fontHeight, fontSize, payload: undefined }); } if (Math.ceil(maxTime - minTime) * scaling > x + 25) { - groupNames.push({ type: "text", text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined }); + groupNames.push({ type: 'text', text: toLabel(Math.ceil(maxTime)), x: Math.ceil(maxTime - minTime) * scaling, y: 0, height: fontHeight, fontSize, payload: undefined }); } - const divider = { type: "div", color: CurrentUserUtils.ActiveDashboard?.colorScheme === ColorScheme.Dark ? "dimgray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; + const divider = { type: 'div', color: Doc.ActiveDashboard?.colorScheme === ColorScheme.Dark ? 'dimgray' : 'black', x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); function layoutDocsAtTime(keyDocs: Doc[], key: number) { @@ -384,13 +361,14 @@ export function computeTimelineLayout( wid = (Doc.NativeAspect(layoutDoc) || 1) * pivotAxisWidth; } docMap.set(doc[Id], { - x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2, - zIndex: (curTime === key ? 1000 : zind++), + 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 / (Math.max(stack, 1)), + width: wid / Math.max(stack, 1), + height: hgt / Math.max(stack, 1), pair: { layout: doc }, - replica: "" + replica: '', }); stacking[stack] = x + pivotAxisWidth; }); @@ -407,41 +385,49 @@ function normalizeResults( minWidth: number, extras: ViewDefBounds[] ): ViewDefResult[] { - const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); + const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds)); const docEles = Array.from(docMap.entries()).map(ele => ele[1]); - const aggBounds = aggregateBounds(extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" })))).filter(e => e.zIndex !== -99), 0, 0); + const aggBounds = aggregateBounds( + extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99), + 0, + 0 + ); aggBounds.r = aggBounds.x + 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; + let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? panelDim[1] / (aggBounds.b - aggBounds.y) : wscale; if (Number.isNaN(scale)) scale = 1; - Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => { - const newPosRaw = ele[1]; - if (newPosRaw) { - const newPos = { - x: newPosRaw.x * scale, - y: newPosRaw.y * scale, - z: newPosRaw.z, - replica: newPosRaw.replica, - highlight: newPosRaw.highlight, - zIndex: newPosRaw.zIndex, - width: (newPosRaw.width || 0) * scale, - height: newPosRaw.height! * scale, - pair: ele[1].pair - }; - poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "all 1s", ...newPos }); - } - }); + Array.from(docMap.entries()) + .filter(ele => ele[1].pair) + .map(ele => { + const newPosRaw = ele[1]; + if (newPosRaw) { + const newPos = { + x: newPosRaw.x * scale, + y: newPosRaw.y * scale, + z: newPosRaw.z, + replica: newPosRaw.replica, + highlight: newPosRaw.highlight, + zIndex: newPosRaw.zIndex, + width: (newPosRaw.width || 0) * scale, + height: newPosRaw.height! * scale, + pair: ele[1].pair, + }; + poolData.set(newPos.pair.layout[Id] + (newPos.replica || ''), { transition: 'all 1s', ...newPos }); + } + }); - return viewDefsToJSX(extras.concat(groupNames).map(gname => ({ - type: gname.type, - text: gname.text, - x: gname.x * scale, - y: gname.y * scale, - color: gname.color, - width: gname.width === undefined ? undefined : gname.width * scale, - height: gname.height === -1 ? 1 : gname.type === "text" ? Math.max(fontHeight * scale, (gname.height || 0) * scale) : (gname.height || 0) * scale, - fontSize: gname.fontSize, - payload: gname.payload - }))); + return viewDefsToJSX( + extras.concat(groupNames).map(gname => ({ + type: gname.type, + text: gname.text, + x: gname.x * scale, + y: gname.y * scale, + color: gname.color, + width: gname.width === undefined ? undefined : gname.width * scale, + height: gname.height === -1 ? 1 : gname.type === 'text' ? Math.max(fontHeight * scale, (gname.height || 0) * scale) : (gname.height || 0) * scale, + fontSize: gname.fontSize, + payload: gname.payload, + })) + ); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index 5f890c810..d979ef961 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -1,19 +1,18 @@ -import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Doc, Field } from "../../../../fields/Doc"; -import { Id } from "../../../../fields/FieldSymbols"; -import { List } from "../../../../fields/List"; -import { Cast, NumCast } from "../../../../fields/Types"; +import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc, Field } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; +import { Cast, NumCast } from '../../../../fields/Types'; import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; -import { LinkManager } from "../../../util/LinkManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { SnappingManager } from "../../../util/SnappingManager"; -import { DocumentView } from "../../nodes/DocumentView"; -import "./CollectionFreeFormLinkView.scss"; -import React = require("react"); -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { Colors } from "../../global/globalEnums"; - +import { LinkManager } from '../../../util/LinkManager'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { SettingsManager } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { Colors } from '../../global/globalEnums'; +import { DocumentView } from '../../nodes/DocumentView'; +import './CollectionFreeFormLinkView.scss'; +import React = require('react'); export interface CollectionFreeFormLinkViewProps { A: DocumentView; @@ -27,31 +26,41 @@ export class CollectionFreeFormLinkView extends React.Component (Date.now() < this._start++ + 1000) && (this._timeout = setTimeout(this.timeout, 25))); + componentWillUnmount() { + this._anchorDisposer?.(); + } + @action timeout = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25))); componentDidMount() { - this._anchorDisposer = reaction(() => [ - this.props.A.props.ScreenToLocalTransform(), - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?.scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?._highlights, - this.props.B.props.ScreenToLocalTransform(), - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?.scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?._highlights, - ], + this._anchorDisposer = reaction( + () => [ + this.props.A.props.ScreenToLocalTransform(), + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?.scrollTop, + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor1, Doc, null)?.annotationOn, Doc, null)?._highlights, + this.props.B.props.ScreenToLocalTransform(), + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?.scrollTop, + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.anchor2, Doc, null)?.annotationOn, Doc, null)?._highlights, + ], action(() => { this._start = Date.now(); this._timeout && clearTimeout(this._timeout); this._timeout = setTimeout(this.timeout, 25); setTimeout(this.placeAnchors); - }) - , { fireImmediately: true }); + }), + { fireImmediately: true } + ); } placeAnchors = () => { const { A, B, LinkDocs } = this.props; const linkDoc = LinkDocs[0]; if (SnappingManager.GetIsDragging() || !A.ContentDiv || !B.ContentDiv) return; - setTimeout(action(() => this._opacity = 0.75), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() - setTimeout(action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line. + setTimeout( + action(() => (this._opacity = 0.75)), + 0 + ); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() + setTimeout( + action(() => (!LinkDocs.length || !linkDoc.linkDisplay) && (this._opacity = 0.05)), + 750 + ); // this will unhighlight the link line. const a = A.ContentDiv.getBoundingClientRect(); const b = B.ContentDiv.getBoundingClientRect(); const { left: aleft, top: atop, width: awidth, height: aheight } = A.ContentDiv.parentElement!.getBoundingClientRect(); @@ -60,7 +69,7 @@ export class CollectionFreeFormLinkView extends React.Component= 0 && mpx <= 1) linkDoc.anchor1_x = mpx * 100; if (mpy >= 0 && mpy <= 1) linkDoc.anchor1_y = mpy * 100; - if (getComputedStyle(targetAhyperlink).fontSize === "0px") linkDoc.opacity = 0; + if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0; else linkDoc.opacity = 1; } if (!targetBhyperlink) { if (linkDoc.linkAutoMove) { - linkDoc.anchor2_x = (bpt.point.x - bleft) / bwidth * 100; - linkDoc.anchor2_y = (bpt.point.y - btop) / bheight * 100; + linkDoc.anchor2_x = ((bpt.point.x - bleft) / bwidth) * 100; + linkDoc.anchor2_y = ((bpt.point.y - btop) / bheight) * 100; } } else { const m = targetBhyperlink.getBoundingClientRect(); @@ -93,80 +102,86 @@ export class CollectionFreeFormLinkView extends React.Component= 0 && mpx <= 1) linkDoc.anchor2_x = mpx * 100; if (mpy >= 0 && mpy <= 1) linkDoc.anchor2_y = mpy * 100; - if (getComputedStyle(targetBhyperlink).fontSize === "0px") linkDoc.opacity = 0; + if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0; else linkDoc.opacity = 1; } - } - + }; pointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, (e, down, delta) => { - this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0]; - this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1]; - return false; - }, emptyFunction, () => { - // OverlayView.Instance.addElement( - // { })} - // />, { x: 300, y: 300 }); - }); - - - } + setupMoveUpEvents( + this, + e, + (e, down, delta) => { + this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0]; + this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1]; + return false; + }, + emptyFunction, + () => { + // OverlayView.Instance.addElement( + // { })} + // />, { x: 300, y: 300 }); + } + ); + }; visibleY = (el: any) => { let rect = el.getBoundingClientRect(); - const top = rect.top, height = rect.height; + const top = rect.top, + height = rect.height; var el = el.parentNode; while (el && el !== document.body) { rect = el.getBoundingClientRect?.(); if (rect?.width) { - if (top <= rect.bottom === false && getComputedStyle(el).overflow === "hidden") return rect.bottom; + if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom; // Check if the element is out of view due to a container scrolling - if ((top + height) <= rect.top && getComputedStyle(el).overflow === "hidden") return rect.top; + if (top + height <= rect.top && getComputedStyle(el).overflow === 'hidden') return rect.top; } el = el.parentNode; } // Check its within the document viewport return top; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden"; - } + }; visibleX = (el: any) => { let rect = el.getBoundingClientRect(); - const left = rect.left, width = rect.width; + const left = rect.left, + width = rect.width; var el = el.parentNode; while (el && el !== document.body) { rect = el?.getBoundingClientRect(); if (rect?.width) { - if (left <= rect.right === false && getComputedStyle(el).overflow === "hidden") return rect.right; + if (left <= rect.right === false && getComputedStyle(el).overflow === 'hidden') return rect.right; // Check if the element is out of view due to a container scrolling - if ((left + width) <= rect.left && getComputedStyle(el).overflow === "hidden") return rect.left; + if (left + width <= rect.left && getComputedStyle(el).overflow === 'hidden') return rect.left; } el = el.parentNode; } // Check its within the document viewport return left; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden"; - } + }; @action toggleProperties = () => { - if (CurrentUserUtils.propertiesWidth > 0) { - CurrentUserUtils.propertiesWidth = 0; + if (SettingsManager.propertiesWidth > 0) { + SettingsManager.propertiesWidth = 0; } else { - CurrentUserUtils.propertiesWidth = 250; + SettingsManager.propertiesWidth = 250; } - } + }; onClickLine = () => { SelectionManager.SelectSchemaViewDoc(this.props.LinkDocs[0], true); this.toggleProperties(); - } + }; @computed.struct get renderData() { - this._start; SnappingManager.GetIsDragging(); + this._start; + SnappingManager.GetIsDragging(); const { A, B, LinkDocs } = this.props; if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined; - const acont = A.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); - const bcont = B.ContentDiv.getElementsByClassName("linkAnchorBox-cont"); + const acont = A.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); + const bcont = B.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); const adiv = acont.length ? acont[0] : A.ContentDiv; const bdiv = bcont.length ? bcont[0] : B.ContentDiv; for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return; @@ -185,11 +200,11 @@ export class CollectionFreeFormLinkView extends React.Component= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex]; //access stroke color using index of the relationship in the color list (default black) - const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? "black" : linkColorList[currRelationshipIndex]; + const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? 'black' : linkColorList[currRelationshipIndex]; // const hexStroke = this.rgbToHex(stroke) //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) //thickness varies linearly from 3px to 12px for increasing link count - const strokeWidth = linkSize === -1 ? "3px" : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + "px"; + const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px'; if (this.props.LinkDocs[0].displayArrow === undefined) { this.props.LinkDocs[0].displayArrow = false; } - return this.props.LinkDocs[0].opacity === 0 || !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<> - - - - - - - {textX === undefined ? (null) : - {Field.toString(this.props.LinkDocs[0].description as any as Field)} - } - ); + return this.props.LinkDocs[0].opacity === 0 || !a.width || !b.width || (!this.props.LinkDocs[0].linkDisplay && !aActive && !bActive) ? null : ( + <> + + + + + + + {textX === undefined ? null : ( + + {Field.toString(this.props.LinkDocs[0].description as any as Field)} + + )} + + ); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index 9f6938e67..9e8d92d7d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -1,79 +1,82 @@ -import { computed } from "mobx"; -import { observer } from "mobx-react"; +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; import * as mobxUtils from 'mobx-utils'; -import CursorField from "../../../../fields/CursorField"; -import { FieldResult } from "../../../../fields/Doc"; -import { List } from "../../../../fields/List"; -import { listSpec } from "../../../../fields/Schema"; -import { Cast } from "../../../../fields/Types"; -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { CollectionViewProps } from "../CollectionView"; -import "./CollectionFreeFormView.scss"; -import React = require("react"); -import v5 = require("uuid/v5"); +import CursorField from '../../../../fields/CursorField'; +import { Doc, FieldResult } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; +import { listSpec } from '../../../../fields/Schema'; +import { Cast } from '../../../../fields/Types'; +import { CollectionViewProps } from '../CollectionView'; +import './CollectionFreeFormView.scss'; +import React = require('react'); +import v5 = require('uuid/v5'); @observer export class CollectionFreeFormRemoteCursors extends React.Component { - @computed protected get cursors(): CursorField[] { const doc = this.props.Document; let cursors: FieldResult>; - const { id } = CurrentUserUtils; + const id = Doc.UserDoc()[Id]; if (!id || !(cursors = Cast(doc.cursors, listSpec(CursorField)))) { return []; } const now = mobxUtils.now(); - return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && (now - metadata.timestamp) < 1000); + return (cursors || []).filter(({ data: { metadata } }) => metadata.id !== id && now - metadata.timestamp < 1000); } @computed get renderedCursors() { - return this.cursors.map(({ data: { metadata, position: { x, y } } }) => { - return ( -
    - { - if (el) { - const ctx = el.getContext('2d'); - if (ctx) { - ctx.fillStyle = "#" + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + "22"; - ctx.fillRect(0, 0, 20, 20); + return this.cursors.map( + ({ + data: { + metadata, + position: { x, y }, + }, + }) => { + return ( +
    + { + if (el) { + const ctx = el.getContext('2d'); + if (ctx) { + ctx.fillStyle = '#' + v5(metadata.id, v5.URL).substring(0, 6).toUpperCase() + '22'; + ctx.fillRect(0, 0, 20, 20); - ctx.fillStyle = "black"; - ctx.lineWidth = 0.5; + ctx.fillStyle = 'black'; + ctx.lineWidth = 0.5; - ctx.beginPath(); + ctx.beginPath(); - ctx.moveTo(10, 0); - ctx.lineTo(10, 8); + ctx.moveTo(10, 0); + ctx.lineTo(10, 8); - ctx.moveTo(10, 20); - ctx.lineTo(10, 12); + ctx.moveTo(10, 20); + ctx.lineTo(10, 12); - ctx.moveTo(0, 10); - ctx.lineTo(8, 10); + ctx.moveTo(0, 10); + ctx.lineTo(8, 10); - ctx.moveTo(20, 10); - ctx.lineTo(12, 10); + ctx.moveTo(20, 10); + ctx.lineTo(12, 10); - ctx.stroke(); + ctx.stroke(); + } } - } - }} - width={20} - height={20} - /> -

    - {metadata.identifier[0].toUpperCase()} -

    -
    - ); - }); + }} + width={20} + height={20} + /> +

    {metadata.identifier[0].toUpperCase()}

    +
    + ); + } + ); } render() { return this.renderedCursors; } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8444c9119..07ea26346 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -18,13 +18,13 @@ import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; import { aggregateBounds, emptyFunction, intersectRect, returnFalse, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; -import { DocumentType } from '../../../documents/DocumentTypes'; -import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { HistoryUtil } from '../../../util/History'; import { InteractionUtils } from '../../../util/InteractionUtils'; import { RecordingApi } from '../../../util/RecordingApi'; +import { ReplayMovements } from '../../../util/ReplayMovements'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; import { ColorScheme } from '../../../util/SettingsManager'; @@ -48,7 +48,6 @@ import { StyleProp } from '../../StyleProvider'; import { CollectionDockingView } from '../CollectionDockingView'; import { CollectionSubView } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeView'; -import { CollectionViewType } from '../CollectionView'; import { TabDocView } from '../TabDocView'; import { computePivotLayout, computerPassLayout, computerStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; @@ -56,8 +55,6 @@ import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; import React = require('react'); import e = require('connect-flash'); -import { ReplayMovements } from '../../../util/ReplayMovements'; - export type collectionFreeformViewProps = { annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) @@ -517,7 +514,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - CurrentUserUtils.ActiveTool = InkTool.None; + Doc.ActiveTool = InkTool.None; if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { if (this.tryDragCluster(e, this._hitCluster)) { @@ -907,7 +904,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) - return; + if (this.layoutDoc._Transform || (this.layoutDoc._fitWidth && this.layoutDoc.nativeHeight) || DocListCast(Doc.MyOverlayDocs?.data).includes(this.props.Document) || this.props.Document.treeViewOutlineMode === TreeViewType.outline) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); @@ -1079,7 +1075,7 @@ export class CollectionFreeFormView extends CollectionSubView Transform; @@ -46,15 +43,14 @@ interface MarqueeViewProps { } export interface MarqueeViewBounds { - left: number; - top: number; - width: number; - height: number; + left: number; + top: number; + width: number; + height: number; } @observer -export class MarqueeView extends React.Component -{ +export class MarqueeView extends React.Component { private _commandExecuted = false; @observable _lastX: number = 0; @observable _lastY: number = 0; @@ -64,18 +60,26 @@ export class MarqueeView extends React.Component) { this.props.Document.ink = value; } + get inkDoc() { + return this.props.Document; + } + get ink() { + return Cast(this.props.Document.ink, InkField); + } + set ink(value: Opt) { + this.props.Document.ink = value; + } componentDidMount() { this.props.setPreviewCursor?.(this.setPreviewCursor); @@ -84,14 +88,14 @@ export class MarqueeView extends React.Component { if (all) { - document.removeEventListener("pointerup", this.onPointerUp, true); - document.removeEventListener("pointermove", this.onPointerMove, true); + document.removeEventListener('pointerup', this.onPointerUp, true); + document.removeEventListener('pointermove', this.onPointerMove, true); } - document.removeEventListener("keydown", this.marqueeCommand, true); + document.removeEventListener('keydown', this.marqueeCommand, true); hideMarquee && this.hideMarquee(); this._lassoPts = []; - } + }; @undoBatch @action @@ -100,76 +104,75 @@ export class MarqueeView extends React.Component this.props.addDocTab( - Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: "bing", useCors: true }), "add:right")); + if (e.key === '?') { + cm.setDefaultItem('?', (str: string) => this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', useCors: true }), 'add:right')); cm.displayMenu(this._downX, this._downY, undefined, true); e.stopPropagation(); - } else - if (e.key === "u" && this.props.ungroup) { - e.stopPropagation(); - this.props.ungroup(); - } - else if (e.key === ":") { - DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y); + } else if (e.key === 'u' && this.props.ungroup) { + e.stopPropagation(); + this.props.ungroup(); + } else if (e.key === ':') { + DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument || returnFalse, x, y); - cm.displayMenu(this._downX, this._downY, undefined, true); - e.stopPropagation(); - } else if (e.key === "a" && (e.ctrlKey || e.metaKey)) { - e.preventDefault(); - this.props.selectDocuments(this.props.activeDocuments()); - e.stopPropagation(); - } else if (e.key === "q" && e.ctrlKey) { - e.preventDefault(); - (async () => { - const text: string = await navigator.clipboard.readText(); - const ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== ""); - for (let i = 0; i < ns.length - 1; i++) { - while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") || - ns[i].endsWith(";\r") || ns[i].endsWith(";") || - ns[i].endsWith(".\r") || ns[i].endsWith(".") || - ns[i].endsWith(":\r") || ns[i].endsWith(":")) && i < ns.length - 1) { - const sub = ns[i].endsWith("\r") ? 1 : 0; - const br = ns[i + 1].trim() === ""; - ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft()); - if (br) break; - } + cm.displayMenu(this._downX, this._downY, undefined, true); + e.stopPropagation(); + } else if (e.key === 'a' && (e.ctrlKey || e.metaKey)) { + e.preventDefault(); + this.props.selectDocuments(this.props.activeDocuments()); + e.stopPropagation(); + } else if (e.key === 'q' && e.ctrlKey) { + e.preventDefault(); + (async () => { + const text: string = await navigator.clipboard.readText(); + const ns = text.split('\n').filter(t => t.trim() !== '\r' && t.trim() !== ''); + for (let i = 0; i < ns.length - 1; i++) { + while ( + !(ns[i].trim() === '' || ns[i].endsWith('-\r') || ns[i].endsWith('-') || ns[i].endsWith(';\r') || ns[i].endsWith(';') || ns[i].endsWith('.\r') || ns[i].endsWith('.') || ns[i].endsWith(':\r') || ns[i].endsWith(':')) && + i < ns.length - 1 + ) { + const sub = ns[i].endsWith('\r') ? 1 : 0; + const br = ns[i + 1].trim() === ''; + ns.splice(i, 2, ns[i].substr(0, ns[i].length - sub) + ns[i + 1].trimLeft()); + if (br) break; } - let ypos = y; - ns.map(line => { - const indent = line.search(/\S|$/); - const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: ypos, title: line }); - this.props.addDocument?.(newBox); - ypos += 40 * this.Transform.Scale; - }); - })(); - e.stopPropagation(); - } else if (e.key === "b" && e.ctrlKey) { - document.body.focus(); // so that we can access the clipboard without an error - setTimeout(() => - pasteImageBitmap((data: any, error: any) => { - error && console.log(error); - data && VideoBox.convertDataUri(data, this.props.Document[Id] + "-thumb-frozen").then(returnedfilename => { - this.props.Document["thumb-frozen"] = new ImageField(returnedfilename); + } + let ypos = y; + ns.map(line => { + const indent = line.search(/\S|$/); + const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + (indent / 3) * 10, y: ypos, title: line }); + this.props.addDocument?.(newBox); + ypos += 40 * this.Transform.Scale; + }); + })(); + e.stopPropagation(); + } else if (e.key === 'b' && e.ctrlKey) { + document.body.focus(); // so that we can access the clipboard without an error + setTimeout(() => + pasteImageBitmap((data: any, error: any) => { + error && console.log(error); + data && + VideoBox.convertDataUri(data, this.props.Document[Id] + '-thumb-frozen').then(returnedfilename => { + this.props.Document['thumb-frozen'] = new ImageField(returnedfilename); }); - })); - } else if (e.key === "s" && e.ctrlKey) { - e.preventDefault(); - const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!; - slide.x = x; - slide.y = y; - FormattedTextBox.SelectOnLoad = slide[Id]; - TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined }; - this.props.addDocument?.(slide); - e.stopPropagation(); - } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { - FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : ""; - FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch"); - this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xPadding === 0)); - e.stopPropagation(); - } - } + }) + ); + } else if (e.key === 's' && e.ctrlKey) { + e.preventDefault(); + const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!; + slide.x = x; + slide.y = y; + FormattedTextBox.SelectOnLoad = slide[Id]; + TreeView._editTitleOnLoad = { id: slide[Id], parent: undefined }; + this.props.addDocument?.(slide); + e.stopPropagation(); + } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { + FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : ''; + FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('live text batch'); + this.props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100, this.props.xPadding === 0)); + e.stopPropagation(); + } + }; //heuristically converts pasted text into a table. // assumes each entry is separated by a tab // skips all rows until it gets to a row with more than one entry @@ -178,26 +181,26 @@ export class MarqueeView extends React.Component 0 && ns[0].split("\t").length < 2) { + while (ns.length > 0 && ns[0].split('\t').length < 2) { ns.splice(0, 1); } if (ns.length > 0) { - const columns = ns[0].split("\t"); + const columns = ns[0].split('\t'); const docList: Doc[] = []; - let groupAttr: string | number = ""; + let groupAttr: string | number = ''; const rowProto = new Doc(); rowProto.title = rowProto.Id; rowProto._width = 200; rowProto.isPrototype = true; for (let i = 1; i < ns.length - 1; i++) { - const values = ns[i].split("\t"); + const values = ns[i].split('\t'); if (values.length === 1 && columns.length > 1) { groupAttr = values[0]; continue; } const docDataProto = Doc.MakeDelegate(rowProto); docDataProto.isPrototype = true; - columns.forEach((col, i) => docDataProto[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined)); + columns.forEach((col, i) => (docDataProto[columns[i]] = values.length > i ? (values[i].indexOf(Number(values[i]).toString()) !== -1 ? Number(values[i]) : values[i]) : undefined)); if (groupAttr) { docDataProto._group = groupAttr; } @@ -206,7 +209,13 @@ export class MarqueeView extends React.Component c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", _width: 300, _height: 100 }); + const newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField('_group', '#f1efeb')] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, '#f1efeb'))], docList, { + x: x, + y: y, + title: 'droppedTable', + _width: 300, + _height: 100, + }); this.props.addDocument?.(newCol); } @@ -227,10 +236,9 @@ export class MarqueeView extends React.Component { @@ -238,8 +246,7 @@ export class MarqueeView extends React.Component Utils.DRAG_THRESHOLD || - Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) { + if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) { if (!this._commandExecuted) { this.showMarquee(); } @@ -253,7 +260,7 @@ export class MarqueeView extends React.Component { @@ -270,14 +277,14 @@ export class MarqueeView extends React.Component { this.hideMarquee(); MarqueeOptionsMenu.Instance.fadeOut(true); - document.removeEventListener("pointerdown", hideMarquee); - document.removeEventListener("wheel", hideMarquee); + document.removeEventListener('pointerdown', hideMarquee); + document.removeEventListener('wheel', hideMarquee); }; if (PresBox.startMarquee) { this.pinWithView(); PresBox.startMarquee = false; } - if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100) && !PresBox.startMarquee) { + if (!this._commandExecuted && Math.abs(this.Bounds.height * this.Bounds.width) > 100 && !PresBox.startMarquee) { MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; @@ -285,19 +292,22 @@ export class MarqueeView extends React.Component { @@ -312,9 +322,9 @@ export class MarqueeView extends React.Component { - if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && - Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { - if (CurrentUserUtils.ActiveTool === InkTool.None) { + if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD && Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) { + if (Doc.ActiveTool === InkTool.None) { if (!(e.nativeEvent as any).marqueeHit) { (e.nativeEvent as any).marqueeHit = true; if (!this.props.trySelectCluster(e.shiftKey)) { @@ -339,17 +348,22 @@ export class MarqueeView extends React.Component { this._visible = true; } + showMarquee = () => { + this._visible = true; + }; @action - hideMarquee = () => { this._visible = false; } + hideMarquee = () => { + this._visible = false; + }; @undoBatch @action @@ -361,16 +375,18 @@ export class MarqueeView extends React.Component, options: DocumentOptions, id?: string) => Doc>, makeGroup: Opt) => { - const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { - Doc.GetProto(doc).data = new List(selected); - Doc.GetProto(doc).title = makeGroup ? "grouping" : "nested freeform"; - !this.props.isAnnotationOverlay && Doc.AddDocToList(CurrentUserUtils.MyFileOrphans, undefined, Doc.GetProto(doc)); - doc._panX = doc._panY = 0; - return doc; - })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); + const newCollection = creator + ? creator(selected, { title: 'nested stack' }) + : ((doc: Doc) => { + Doc.GetProto(doc).data = new List(selected); + Doc.GetProto(doc).title = makeGroup ? 'grouping' : 'nested freeform'; + !this.props.isAnnotationOverlay && Doc.AddDocToList(Doc.MyFileOrphans, undefined, Doc.GetProto(doc)); + doc._panX = doc._panY = 0; + return doc; + })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); newCollection.system = undefined; newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; @@ -378,7 +394,7 @@ export class MarqueeView extends React.Component d.context = newCollection); + selected.forEach(d => (d.context = newCollection)); this.hideMarquee(); return newCollection; }); @@ -393,74 +409,76 @@ export class MarqueeView extends React.Component { const scale = Math.min(this.props.PanelWidth() / this.Bounds.width, this.props.PanelHeight() / this.Bounds.height); - const doc = this.props.Document; - const viewOptions:PinViewProps = { - bounds: this.Bounds, - scale: scale - }; - TabDocView.PinDoc(doc, {pinWithView: viewOptions}); - MarqueeOptionsMenu.Instance.fadeOut(true); + const doc = this.props.Document; + const viewOptions: PinViewProps = { + bounds: this.Bounds, + scale: scale, + }; + TabDocView.PinDoc(doc, { pinWithView: viewOptions }); + MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); - } + }; @undoBatch @action collection = (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean) => { const selected = this.marqueeSelect(false); - if (e instanceof KeyboardEvent ? "cg".includes(e.key) : true) { - selected.map(action(d => { - const dx = NumCast(d.x); - const dy = NumCast(d.y); - delete d.x; - delete d.y; - delete d.activeFrame; - delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - d.x = dx - this.Bounds.left - this.Bounds.width / 2; - d.y = dy - this.Bounds.top - this.Bounds.height / 2; - return d; - })); + if (e instanceof KeyboardEvent ? 'cg'.includes(e.key) : true) { + selected.map( + action(d => { + const dx = NumCast(d.x); + const dy = NumCast(d.y); + delete d.x; + delete d.y; + delete d.activeFrame; + delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection + d.x = dx - this.Bounds.left - this.Bounds.width / 2; + d.y = dy - this.Bounds.top - this.Bounds.height / 2; + return d; + }) + ); this.props.removeDocument?.(selected); } // TODO: nda - this is the code to actually get a new grouped collection - const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined, group); + const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === 't' ? Docs.Create.StackingDocument : undefined, group); this.props.addDocument?.(newCollection); this.props.selectDocuments([newCollection]); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); - } + }; @undoBatch @action syntaxHighlight = (e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false); - if (e instanceof KeyboardEvent ? e.key === "i" : true) { + if (e instanceof KeyboardEvent ? e.key === 'i' : true) { const inks = selected.filter(s => s.type === DocumentType.INK); const setDocs = selected.filter(s => s.type === DocumentType.RTF && s.color); - const sets = setDocs.map((sd) => Cast(sd.data, RichTextField)?.Text as string); + const sets = setDocs.map(sd => Cast(sd.data, RichTextField)?.Text as string); const colors = setDocs.map(sd => FieldValue(sd.color) as string); const wordToColor = new Map(); - sets.forEach((st: string, i: number) => st.split(",").forEach(word => wordToColor.set(word, colors[i]))); + sets.forEach((st: string, i: number) => st.split(',').forEach(word => wordToColor.set(word, colors[i]))); const strokes: InkData[] = []; inks.filter(i => Cast(i.data, InkField)).forEach(i => { const d = Cast(i.data, InkField, null); - const left = Math.min(...d?.inkData.map(pd => pd.X) ?? [0]); - const top = Math.min(...d?.inkData.map(pd => pd.Y) ?? [0]); + const left = Math.min(...(d?.inkData.map(pd => pd.X) ?? [0])); + const top = Math.min(...(d?.inkData.map(pd => pd.Y) ?? [0])); strokes.push(d.inkData.map(pd => ({ X: pd.X + NumCast(i.x) - left, Y: pd.Y + NumCast(i.y) - top }))); }); - CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => { + CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then(results => { // const wordResults = results.filter((r: any) => r.category === "inkWord"); // for (const word of wordResults) { // const indices: number[] = word.strokeIds; @@ -501,12 +519,12 @@ export class MarqueeView extends React.Component r.category === "line"); - const text = lines.map((l: any) => l.recognizedText).join("\r\n"); + const lines = results.filter((r: any) => r.category === 'line'); + const text = lines.map((l: any) => l.recognizedText).join('\r\n'); this.props.addDocument?.(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text })); }); } - } + }; @undoBatch @action @@ -517,15 +535,15 @@ export class MarqueeView extends React.Component { @@ -534,33 +552,33 @@ export class MarqueeView extends React.Component this.props.selectDocuments([newCollection])); - } + }; @undoBatch marqueeCommand = action((e: KeyboardEvent) => { if (this._commandExecuted || (e as any).propagationIsStopped) { return; } - if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") { + if (e.key === 'Backspace' || e.key === 'Delete' || e.key === 'd') { this._commandExecuted = true; e.stopPropagation(); (e as any).propagationIsStopped = true; this.delete(); e.stopPropagation(); } - if ("cbtsSpg".indexOf(e.key) !== -1) { + if ('cbtsSpg'.indexOf(e.key) !== -1) { this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); (e as any).propagationIsStopped = true; - if (e.key === "g") this.collection(e, true); - if (e.key === "c" || e.key === "t") this.collection(e); - if (e.key === "s" || e.key === "S") this.summary(e); - if (e.key === "b") this.background(e); - if (e.key === "p") this.pileup(e); + if (e.key === 'g') this.collection(e, true); + if (e.key === 'c' || e.key === 't') this.collection(e); + if (e.key === 's' || e.key === 'S') this.summary(e); + if (e.key === 'b') this.background(e); + if (e.key === 'p') this.pileup(e); this.cleanupInteractions(false); } - if (e.key === "r" || e.key === " ") { + if (e.key === 'r' || e.key === ' ') { this._commandExecuted = true; e.stopPropagation(); e.preventDefault(); @@ -568,18 +586,17 @@ export class MarqueeView extends React.Component pair[0]); const ys = this._lassoPts.map(pair => pair[1]); const tl = this.Transform.transformPoint(Math.min(...xs), Math.min(...ys)); @@ -592,10 +609,10 @@ export class MarqueeView extends React.Component tl[0] && truePoint[0] < r1.left) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); - hasTop = hasTop || (truePoint[1] > tl[1] && truePoint[1] < r1.top) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width); - hasRight = hasRight || (truePoint[0] < br[0] && truePoint[0] > r1.left + r1.width) && (truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); - hasBottom = hasBottom || (truePoint[1] < br[1] && truePoint[1] > r1.top + r1.height) && (truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width); + hasLeft = hasLeft || (truePoint[0] > tl[0] && truePoint[0] < r1.left && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); + hasTop = hasTop || (truePoint[1] > tl[1] && truePoint[1] < r1.top && truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width); + hasRight = hasRight || (truePoint[0] < br[0] && truePoint[0] > r1.left + r1.width && truePoint[1] > r1.top && truePoint[1] < r1.top + r1.height); + hasBottom = hasBottom || (truePoint[1] < br[1] && truePoint[1] > r1.top + r1.height && truePoint[0] > r1.left && truePoint[0] < r1.left + r1.width); if (hasTop && hasLeft && hasBottom && hasRight) { return true; } @@ -615,9 +632,20 @@ export class MarqueeView extends React.Component !doc.z && !doc._lockedPosition).map(selectFunc); - if (!selection.length && selectBackgrounds) this.props.activeDocuments().filter(doc => doc.z === undefined).map(selectFunc); - if (!selection.length) this.props.activeDocuments().filter(doc => doc.z !== undefined).map(selectFunc); + this.props + .activeDocuments() + .filter(doc => !doc.z && !doc._lockedPosition) + .map(selectFunc); + if (!selection.length && selectBackgrounds) + this.props + .activeDocuments() + .filter(doc => doc.z === undefined) + .map(selectFunc); + if (!selection.length) + this.props + .activeDocuments() + .filter(doc => doc.z !== undefined) + .map(selectFunc); return selection; } @@ -625,31 +653,42 @@ export class MarqueeView extends React.Component {this._lassoFreehand ? - - s + pt[0] + "," + pt[1] + " ", "")} fill="none" stroke="black" strokeWidth="1" strokeDasharray="3" /> - - : - } -
    ; + return ( +
    + {' '} + {this._lassoFreehand ? ( + + s + pt[0] + ',' + pt[1] + ' ', '')} fill="none" stroke="black" strokeWidth="1" strokeDasharray="3" /> + + ) : ( + + )} +
    + ); } render() { - return
    e.preventDefault()} - onScroll={(e) => e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0} onClick={this.onClick} onPointerDown={this.onPointerDown}> - {this._visible ? this.marqueeDiv : null} - {this.props.children} -
    ; + return ( +
    e.preventDefault()} + onScroll={e => (e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0)} + onClick={this.onClick} + onPointerDown={this.onPointerDown}> + {this._visible ? this.marqueeDiv : null} + {this.props.children} +
    + ); } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 8adfdc70b..0d7d67dd8 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -7,20 +7,17 @@ import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; -import { DocUtils } from '../../../documents/Documents'; -import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { CollectionViewType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { Colors, Shadows } from '../../global/globalEnums'; -import { AudioBox } from '../../nodes/AudioBox'; import { DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { DocumentView } from '../../nodes/DocumentView'; import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; import { StyleProp } from '../../StyleProvider'; import { CollectionStackedTimeline } from '../CollectionStackedTimeline'; import { CollectionSubView } from '../CollectionSubView'; -import { CollectionViewType } from '../CollectionView'; import './CollectionLinearView.scss'; /** @@ -228,7 +225,7 @@ export class CollectionLinearView extends CollectionSubView() { }}> {this.childLayoutPairs.map(pair => this.getDisplayDoc(pair.layout))}
    - {!DocumentLinksButton.StartLink || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : ( + {!DocumentLinksButton.StartLink || this.layoutDoc !== Doc.MyDockedBtns ? null : ( e.stopPropagation()}> Creating link from:{' '} @@ -263,7 +260,7 @@ export class CollectionLinearView extends CollectionSubView() { )} - {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== CurrentUserUtils.MyDockedBtns ? null : ( + {!CollectionStackedTimeline.CurrentlyPlaying || !CollectionStackedTimeline.CurrentlyPlaying.length || this.layoutDoc !== Doc.MyDockedBtns ? null : ( Currently playing: diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 1e7f4f10b..ed856a4ab 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -11,6 +11,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { Hypothesis } from '../../util/HypothesisUtils'; +import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { undoBatch } from '../../util/UndoManager'; import { DocumentView } from '../nodes/DocumentView'; @@ -118,7 +119,7 @@ export class LinkMenuItem extends React.Component { : undefined; if (focusDoc) this.props.docView.ComponentView?.scrollFocus?.(focusDoc, true); - LinkManager.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false); + LinkFollower.FollowLink(this.props.linkDoc, this.props.sourceDoc, this.props.docView.props, false); } } ); diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index a6f6bd35f..0bcb68f82 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -1,17 +1,16 @@ import { action, observable } from 'mobx'; -import { observer } from "mobx-react"; +import { observer } from 'mobx-react'; import { EditorView } from 'prosemirror-view'; +import { Doc } from '../../../fields/Doc'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { SearchBox } from '../search/SearchBox'; import { DefaultStyleProvider } from '../StyleProvider'; import './LinkPopup.scss'; -import React = require("react"); -import { Doc, Opt } from '../../../fields/Doc'; +import React = require('react'); interface LinkPopupProps { showPopup: boolean; @@ -23,33 +22,30 @@ interface LinkPopupProps { } /** - * Popup component for creating links from text to Dash documents + * Popup component for creating links from text to Dash documents */ @observer export class LinkPopup extends React.Component { - @observable private linkURL: string = ""; + @observable private linkURL: string = ''; @observable public view?: EditorView; - - // TODO: should check for valid URL @undoBatch makeLinkToURL = (target: string, lcoation: string) => { - ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, "onRadd:rightight", target, target); - } + ((this.view as any)?.TextView as FormattedTextBox).makeLinkAnchor(undefined, 'onRadd:rightight', target, target); + }; @action onLinkChange = (e: React.ChangeEvent) => { this.linkURL = e.target.value; - } - + }; getPWidth = () => 500; getPHeight = () => 500; render() { - const popupVisibility = this.props.showPopup ? "block" : "none"; + const popupVisibility = this.props.showPopup ? 'block' : 'none'; const linkDoc = this.props.linkFrom ? this.props.linkFrom : undefined; return (
    @@ -68,8 +64,8 @@ export class LinkPopup extends React.Component { className="linkPopup-searchBox searchBox-input" /> */} { docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} ContainingCollectionView={undefined} - ContainingCollectionDoc={undefined} /> + ContainingCollectionDoc={undefined} + />
    ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index c42c2306a..8437736ae 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,31 +1,29 @@ -import React = require("react"); -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; -import { observer } from "mobx-react"; -import { DateField } from "../../../fields/DateField"; -import { Doc, DocListCast } from "../../../fields/Doc"; -import { ComputedField } from "../../../fields/ScriptField"; -import { Cast, DateCast, NumCast } from "../../../fields/Types"; -import { AudioField, nullAudio } from "../../../fields/URLField"; -import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from "../../../Utils"; -import { DocUtils } from "../../documents/Documents"; -import { Networking } from "../../Network"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; -import { DragManager } from "../../util/DragManager"; -import { undoBatch } from "../../util/UndoManager"; -import { CollectionStackedTimeline, TrimScope } from "../collections/CollectionStackedTimeline"; -import { ContextMenu } from "../ContextMenu"; -import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; -import "./AudioBox.scss"; -import { FieldView, FieldViewProps } from "./FieldView"; - +import React = require('react'); +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import { DateField } from '../../../fields/DateField'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { ComputedField } from '../../../fields/ScriptField'; +import { Cast, DateCast, NumCast } from '../../../fields/Types'; +import { AudioField, nullAudio } from '../../../fields/URLField'; +import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; +import { Networking } from '../../Network'; +import { DragManager } from '../../util/DragManager'; +import { undoBatch } from '../../util/UndoManager'; +import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline'; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import './AudioBox.scss'; +import { FieldView, FieldViewProps } from './FieldView'; /** * AudioBox * Main component: AudioBox.tsx * Supporting Components: CollectionStackedTimeline, AudioWaveform - * + * * AudioBox is a node that supports the recording and playback of audio files in Dash. * When an audio file is importeed into Dash, it is immediately rendered as an AudioBox document. * When a blank AudioBox node is created in Dash, audio recording controls are displayed and the user can start a recording which can be paused or stopped, and can use dictation to create a text transcript. @@ -34,24 +32,23 @@ import { FieldView, FieldViewProps } from "./FieldView"; * User can trim audio: nondestructive, just sets new bounds for playback and rendering timelin */ - // used as a wrapper class for MediaStream from MediaDevices API declare class MediaRecorder { constructor(e: any); // whatever MediaRecorder has } enum media_state { - PendingRecording = "pendingRecording", - Recording = "recording", - Paused = "paused", - Playing = "playing" + PendingRecording = 'pendingRecording', + Recording = 'recording', + Paused = 'paused', + Playing = 'playing', } - @observer export class AudioBox extends ViewBoxAnnotatableComponent() { - - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(AudioBox, fieldKey); + } public static Enabled = false; static topControlsHeight = 30; // height of upper controls above timeline @@ -73,27 +70,41 @@ export class AudioBox extends ViewBoxAnnotatableComponent disposer?.()); + Object.values(this._disposers).forEach(disposer => disposer?.()); this.mediaState === media_state.Recording && this.stopRecording(); } @@ -110,14 +121,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - return CollectionStackedTimeline.createAnchor( - this.rootDoc, - this.dataDoc, - this.annotationKey, - "_timecodeToShow" /* audioStart */, - "_timecodeToHide" /* audioEnd */, - this._ele?.currentTime || - Cast(this.props.Document._currentTimecode, "number", null) || - (this.mediaState === media_state.Recording - ? (Date.now() - (this.recordingStart || 0)) / 1000 - : undefined) - ) || this.rootDoc; - } - + return ( + CollectionStackedTimeline.createAnchor( + this.rootDoc, + this.dataDoc, + this.annotationKey, + '_timecodeToShow' /* audioStart */, + '_timecodeToHide' /* audioEnd */, + this._ele?.currentTime || Cast(this.props.Document._currentTimecode, 'number', null) || (this.mediaState === media_state.Recording ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined) + ) || this.rootDoc + ); + }; // updates timecode and shows it in timeline, follows links at time @action @@ -148,24 +152,23 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.getLinkData(l)) .forEach(({ la1, la2, linkTime }) => { - if (linkTime > NumCast(this.layoutDoc._currentTimecode) && - linkTime < this._ele!.currentTime) { + if (linkTime > NumCast(this.layoutDoc._currentTimecode) && linkTime < this._ele!.currentTime) { Doc.linkFollowHighlight(la1); } }); this.layoutDoc._currentTimecode = this._ele.currentTime; this.timeline?.scrollToTime(NumCast(this.layoutDoc._currentTimecode)); } - } + }; // play back the audio from seekTimeInSeconds, fullPlay tells whether clip is being played to end vs link range @action playFrom = (seekTimeInSeconds: number, endTime?: number, fullPlay: boolean = false) => { clearTimeout(this._play); // abort any previous clip ending - if (Number.isNaN(this._ele?.duration)) { // audio element isn't loaded yet... wait 1/2 second and try again + if (Number.isNaN(this._ele?.duration)) { + // audio element isn't loaded yet... wait 1/2 second and try again setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); - } - else if (this.timeline && this._ele && AudioBox.Enabled) { + } else if (this.timeline && this._ele && AudioBox.Enabled) { // trimBounds override requested playback bounds const end = Math.min(this.timeline.trimEnd, endTime ?? this.timeline.trimEnd); const start = Math.max(this.timeline.trimStart, seekTimeInSeconds); @@ -175,21 +178,18 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - // need to keep track of if end of clip is reached so on next play, clip restarts - if (fullPlay) this._finished = true; - // removes from currently playing if playback has reached end of range marker - else this.removeCurrentlyPlaying(); - this.Pause(); - }, - (end - start) * 1000); + this._play = setTimeout(() => { + // need to keep track of if end of clip is reached so on next play, clip restarts + if (fullPlay) this._finished = true; + // removes from currently playing if playback has reached end of range marker + else this.removeCurrentlyPlaying(); + this.Pause(); + }, (end - start) * 1000); } else { this.Pause(); } } - } - + }; // removes from currently playing display @action @@ -198,7 +198,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { @@ -220,13 +219,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent { this._stream = await navigator.mediaDevices.getUserMedia({ audio: true }); this._recorder = new MediaRecorder(this._stream); - this.dataDoc[this.fieldKey + "-recordingStart"] = new DateField(); + this.dataDoc[this.fieldKey + '-recordingStart'] = new DateField(); DocUtils.ActiveRecordings.push(this); this._recorder.ondataavailable = async (e: any) => { const [{ result }] = await Networking.UploadFilesToServer(e.data); @@ -235,11 +234,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.mediaState = media_state.Recording); + runInAction(() => (this.mediaState = media_state.Recording)); setTimeout(this.updateRecordTime); this._recorder.start(); setTimeout(this.stopRecording, 60 * 60 * 1000); // stop after an hour - } + }; // stops recording @action @@ -249,52 +248,59 @@ export class AudioBox extends ViewBoxAnnotatableComponent { const funcs: ContextMenuProps[] = []; funcs.push({ - description: (this.layoutDoc.hideAnchors ? "Don't hide" : "Hide") + " anchors", - event: e => this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors, - icon: "expand-arrows-alt", + description: (this.layoutDoc.hideAnchors ? "Don't hide" : 'Hide') + ' anchors', + event: e => (this.layoutDoc.hideAnchors = !this.layoutDoc.hideAnchors), + icon: 'expand-arrows-alt', }); funcs.push({ - description: (this.layoutDoc.dontAutoFollowLinks ? "" : "Don't") + " follow links when encountered", - event: e => this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks, - icon: "expand-arrows-alt", + description: (this.layoutDoc.dontAutoFollowLinks ? '' : "Don't") + ' follow links when encountered', + event: e => (this.layoutDoc.dontAutoFollowLinks = !this.layoutDoc.dontAutoFollowLinks), + icon: 'expand-arrows-alt', }); funcs.push({ - description: (this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") + " play when link is selected", - event: e => this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks, - icon: "expand-arrows-alt", + description: (this.layoutDoc.dontAutoPlayFollowedLinks ? '' : "Don't") + ' play when link is selected', + event: e => (this.layoutDoc.dontAutoPlayFollowedLinks = !this.layoutDoc.dontAutoPlayFollowedLinks), + icon: 'expand-arrows-alt', }); funcs.push({ - description: (this.layoutDoc.autoPlayAnchors ? "Don't auto" : "Auto") + " play anchors onClick", - event: e => this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors, - icon: "expand-arrows-alt", + description: (this.layoutDoc.autoPlayAnchors ? "Don't auto" : 'Auto') + ' play anchors onClick', + event: e => (this.layoutDoc.autoPlayAnchors = !this.layoutDoc.autoPlayAnchors), + icon: 'expand-arrows-alt', }); ContextMenu.Instance?.addItem({ - description: "Options...", + description: 'Options...', subitems: funcs, - icon: "asterisk", + icon: 'asterisk', }); - } - + }; // button for starting and stopping the recording Record = (e: React.PointerEvent) => { - e.button === 0 && !e.ctrlKey && setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => { - this._recorder ? this.stopRecording() : this.recordAudioAnnotation(); - }), false); - } + e.button === 0 && + !e.ctrlKey && + setupMoveUpEvents( + this, + e, + returnFalse, + returnFalse, + action(() => { + this._recorder ? this.stopRecording() : this.recordAudioAnnotation(); + }), + false + ); + }; // for play button Play = (e?: any) => { @@ -314,7 +320,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => { - const newDoc = CurrentUserUtils.GetNewTextDoc( - "", - NumCast(this.rootDoc.x), - NumCast(this.rootDoc.y) + - NumCast(this.layoutDoc._height) + - 10, - NumCast(this.layoutDoc._width), - 2 * NumCast(this.layoutDoc._height) - ); - Doc.GetProto(newDoc).recordingSource = this.dataDoc; - Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`); - Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction("self.recordingSource.mediaState"); - if (DocListCast(CurrentUserUtils.MyOverlayDocs?.data).includes(this.rootDoc)) { - newDoc.x = this.rootDoc.x; - newDoc.y = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); - Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, newDoc); - } else { - this.props.addDocument?.(newDoc); - } - }), false); - } - + setupMoveUpEvents( + this, + e, + returnFalse, + returnFalse, + action(() => { + const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.rootDoc.x), NumCast(this.rootDoc.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); + Doc.GetProto(newDoc).recordingSource = this.dataDoc; + Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`); + Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); + if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) { + newDoc.x = this.rootDoc.x; + newDoc.y = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); + Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc); + } else { + this.props.addDocument?.(newDoc); + } + }), + false + ); + }; // sets