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. --- 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 +- 59 files changed, 9719 insertions(+), 7728 deletions(-) (limited to 'src/client/views') 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