From 9d69ab27de83ead3e499edc9028ba85749407a1e Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 23 Apr 2024 18:35:59 -0400 Subject: more lint cleanup --- src/client/views/Main.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) (limited to 'src/client/views/Main.tsx') diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 17c21326d..01f3b032e 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-new */ // if ((module as any).hot) { // (module as any).hot.accept(); // } @@ -5,7 +6,7 @@ import * as dotenv from 'dotenv'; // see https://github.com/motdotla/dotenv#how-do-i-use-dotenv-with-import import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { AssignAllExtensions } from '../../extensions/General/Extensions'; +import { AssignAllExtensions } from '../../extensions/Extensions'; import { FieldLoader } from '../../fields/FieldLoader'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { PingManager } from '../util/PingManager'; @@ -15,6 +16,7 @@ import { CollectionView } from './collections/CollectionView'; import './global/globalScripts'; import { MainView } from './MainView'; import { BranchingTrailManager } from '../util/BranchingTrailManager'; + dotenv.config(); AssignAllExtensions(); -- cgit v1.2.3-70-g09d2 From dd08c20ec6df3fad6ecd6b16c787f10b0c23feb4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 2 May 2024 00:39:31 -0400 Subject: lots more dependency cycle unwinding. --- src/client/documents/DocUtils.ts | 36 ++-- src/client/documents/Documents.ts | 4 +- src/client/util/BranchingTrailManager.tsx | 5 +- src/client/util/CalendarManager.tsx | 20 +- src/client/util/CaptureManager.tsx | 9 +- src/client/util/CurrentUserUtils.ts | 24 +-- src/client/util/DictationManager.ts | 44 ++++- src/client/util/DocumentManager.ts | 85 +++----- src/client/util/DragManager.ts | 13 -- src/client/util/GroupManager.tsx | 2 +- src/client/util/HypothesisUtils.ts | 10 +- .../util/Import & Export/DirectoryImportBox.tsx | 1 - src/client/util/LinkFollower.ts | 31 ++- src/client/util/LinkManager.ts | 9 +- src/client/util/PingManager.ts | 4 +- src/client/util/ReplayMovements.ts | 6 +- src/client/util/Scripting.ts | 9 +- src/client/util/SelectionManager.ts | 29 +-- src/client/util/SettingsManager.tsx | 20 +- src/client/util/SharingManager.tsx | 12 +- src/client/util/SnappingManager.ts | 16 +- src/client/views/AntimodeMenu.tsx | 8 +- src/client/views/ComponentDecorations.tsx | 4 +- src/client/views/DocComponent.tsx | 2 + src/client/views/DocViewUtils.ts | 21 ++ src/client/views/DocumentButtonBar.tsx | 29 +-- src/client/views/DocumentDecorations.tsx | 117 ++++++----- src/client/views/FieldsDropdown.tsx | 22 +-- src/client/views/FilterPanel.tsx | 8 +- src/client/views/GestureOverlay.tsx | 6 +- src/client/views/GlobalKeyHandler.ts | 78 +++++--- src/client/views/InkControlPtHandles.tsx | 6 +- src/client/views/InkStrokeProperties.ts | 3 +- src/client/views/InkTranscription.tsx | 1 - src/client/views/InkingStroke.tsx | 2 +- src/client/views/LightboxView.tsx | 36 ++-- src/client/views/Main.tsx | 12 +- src/client/views/MainView.tsx | 85 ++++---- src/client/views/MarqueeAnnotator.tsx | 1 - src/client/views/OverlayView.tsx | 5 +- src/client/views/PropertiesButtons.tsx | 29 ++- .../views/PropertiesDocBacklinksSelector.tsx | 8 +- src/client/views/PropertiesDocContextSelector.tsx | 3 +- src/client/views/PropertiesView.tsx | 50 +++-- src/client/views/ScriptBox.tsx | 8 +- src/client/views/ScriptingRepl.tsx | 16 +- src/client/views/SidebarAnnos.tsx | 8 +- src/client/views/StyleProp.ts | 24 +++ src/client/views/StyleProvider.tsx | 105 ++++------ .../views/collections/CollectionCarousel3DView.tsx | 5 +- .../views/collections/CollectionCarouselView.tsx | 2 +- .../views/collections/CollectionDockingView.tsx | 18 +- src/client/views/collections/CollectionMenu.tsx | 32 ++- .../views/collections/CollectionNoteTakingView.tsx | 2 +- .../views/collections/CollectionPileView.tsx | 4 +- .../collections/CollectionStackedTimeline.tsx | 16 +- .../views/collections/CollectionStackingView.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 11 +- .../views/collections/CollectionTimeView.tsx | 3 +- .../views/collections/CollectionTreeView.tsx | 17 +- .../views/collections/CollectionTreeViewType.ts | 5 + src/client/views/collections/TabDocView.tsx | 216 +++++++++++---------- src/client/views/collections/TreeView.tsx | 15 +- .../CollectionFreeFormClusters.ts | 31 ++- .../CollectionFreeFormInfoUI.tsx | 10 +- .../CollectionFreeFormPannableContents.tsx | 9 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 88 ++++----- .../collections/collectionFreeForm/MarqueeView.tsx | 15 +- .../collectionLinear/CollectionLinearView.tsx | 10 +- .../collectionMulticolumn/MulticolumnResizer.tsx | 2 +- .../collectionMulticolumn/MultirowResizer.tsx | 2 +- .../collectionSchema/CollectionSchemaView.tsx | 28 +-- .../collectionSchema/SchemaTableCell.tsx | 8 +- src/client/views/global/globalScripts.ts | 58 +++--- src/client/views/linking/LinkMenuGroup.tsx | 4 +- src/client/views/linking/LinkMenuItem.tsx | 31 ++- .../views/newlightbox/ButtonMenu/ButtonMenu.tsx | 3 +- src/client/views/newlightbox/NewLightboxView.tsx | 27 ++- .../components/Recommendation/Recommendation.tsx | 12 +- src/client/views/nodes/AudioBox.tsx | 25 +-- .../views/nodes/CollectionFreeFormDocumentView.tsx | 41 ++-- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 3 +- .../nodes/DataVizBox/components/LineChart.tsx | 6 +- .../views/nodes/DataVizBox/components/PieChart.tsx | 2 +- .../views/nodes/DataVizBox/components/TableBox.tsx | 9 +- src/client/views/nodes/DocumentContentsView.tsx | 13 +- src/client/views/nodes/DocumentIcon.tsx | 11 +- src/client/views/nodes/DocumentLinksButton.tsx | 13 -- src/client/views/nodes/DocumentView.tsx | 203 +++++++++---------- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 38 ++-- src/client/views/nodes/FunctionPlotBox.tsx | 7 +- src/client/views/nodes/ImageBox.tsx | 6 +- src/client/views/nodes/KeyValueBox.tsx | 4 +- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/LinkAnchorBox.scss | 34 ---- src/client/views/nodes/LinkAnchorBox.tsx | 118 ----------- src/client/views/nodes/LinkBox.tsx | 5 +- src/client/views/nodes/LinkDescriptionPopup.tsx | 3 +- src/client/views/nodes/LinkDocPreview.tsx | 26 ++- src/client/views/nodes/LoadingBox.tsx | 21 +- .../views/nodes/MapBox/DirectionsAnchorMenu.tsx | 4 +- src/client/views/nodes/MapBox/MapAnchorMenu.tsx | 4 +- src/client/views/nodes/MapBox/MapBox.tsx | 6 +- src/client/views/nodes/MapBox/MapBox2.tsx | 6 +- .../views/nodes/MapboxMapBox/MapboxContainer.tsx | 6 +- src/client/views/nodes/PDFBox.tsx | 6 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 16 +- src/client/views/nodes/ScreenshotBox.tsx | 11 +- src/client/views/nodes/VideoBox.tsx | 28 ++- src/client/views/nodes/WebBox.tsx | 5 +- .../views/nodes/formattedText/DashFieldView.tsx | 5 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 45 ++--- .../formattedText/ProsemirrorExampleTransfer.ts | 4 +- .../views/nodes/formattedText/RichTextMenu.tsx | 17 +- .../views/nodes/formattedText/SummaryView.tsx | 5 +- .../views/nodes/generativeFill/GenerativeFill.tsx | 8 +- src/client/views/nodes/trails/PresBox.tsx | 58 +++--- src/client/views/nodes/trails/PresElementBox.tsx | 11 +- src/client/views/pdf/AnchorMenu.tsx | 6 +- src/client/views/pdf/Annotation.tsx | 6 +- src/client/views/pdf/PDFViewer.tsx | 6 +- src/client/views/search/SearchBox.tsx | 31 ++- src/client/views/selectedDoc/SelectedDocView.tsx | 14 +- src/client/views/topbar/TopBar.tsx | 8 +- src/fields/Doc.ts | 19 +- src/fields/ScriptField.ts | 1 - src/fields/documentSchemas.ts | 1 - src/mobile/MobileInterface.tsx | 9 +- src/server/SharedMediaTypes.ts | 5 + 130 files changed, 1254 insertions(+), 1461 deletions(-) create mode 100644 src/client/views/DocViewUtils.ts create mode 100644 src/client/views/StyleProp.ts create mode 100644 src/client/views/collections/CollectionTreeViewType.ts delete mode 100644 src/client/views/nodes/LinkAnchorBox.scss delete mode 100644 src/client/views/nodes/LinkAnchorBox.tsx (limited to 'src/client/views/Main.tsx') diff --git a/src/client/documents/DocUtils.ts b/src/client/documents/DocUtils.ts index 0a47e0359..0c9fe0315 100644 --- a/src/client/documents/DocUtils.ts +++ b/src/client/documents/DocUtils.ts @@ -9,7 +9,7 @@ import { ClientUtils } from '../../ClientUtils'; import * as JSZipUtils from '../../JSZipUtils'; import { decycle } from '../../decycler/decycler'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, Field, FieldType, LinkedTo, Opt, SetActiveAudioLinker, StrListCast } from '../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, FieldType, LinkedTo, Opt, StrListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { InkDataFieldName, InkField } from '../../fields/InkField'; @@ -29,9 +29,7 @@ import { SerializationHelper } from '../util/SerializationHelper'; import { UndoManager, undoable } from '../util/UndoManager'; import { ContextMenu } from '../views/ContextMenu'; import { ContextMenuProps } from '../views/ContextMenuItem'; -import { FieldViewProps } from '../views/nodes/FieldView'; import { LinkDescriptionPopup } from '../views/nodes/LinkDescriptionPopup'; -import { LoadingBox } from '../views/nodes/LoadingBox'; import { OpenWhere } from '../views/nodes/OpenWhere'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import { DocumentType } from './DocumentTypes'; @@ -50,7 +48,7 @@ export namespace DocUtils { } if (key === LinkedTo) { // links are not a field value, so handled here. value is an expression of form ([field=]idToDoc("...")) - const allLinks = LinkManager.Instance.getAllRelatedLinks(doc); + const allLinks = Doc.Links(doc); const matchLink = (val: string, anchor: Doc) => { const linkedToExp = (val ?? '').split('='); if (linkedToExp.length === 1) return Field.toScriptString(anchor) === val; @@ -131,7 +129,7 @@ export namespace DocUtils { if (!unsets.length && !exists.length && !xs.length && !checks.length && !matches.length) return true; const failsNotEqualFacets = !xs.length ? false : xs.some(value => matchFieldValue(d, facetKey, value)); const satisfiesCheckFacets = !checks.length ? true : checks.some(value => matchFieldValue(d, facetKey, value)); - const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : LinkManager.Instance.getAllRelatedLinks(d).length; + const satisfiesExistsFacets = !exists.length ? true : facetKey !== LinkedTo ? d[facetKey] !== undefined : Doc.Links(d).length; const satisfiesUnsetsFacets = !unsets.length ? true : d[facetKey] === undefined; const satisfiesMatchFacets = !matches.length ? true @@ -170,19 +168,7 @@ export namespace DocUtils { return rangeFilteredDocs; } - export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; - - export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { - broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore - return DocUtils.ActiveRecordings.map(audio => { - const sourceDoc = getSourceDoc(); - return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_displayLine: false, link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' }); - }); - } - - SetActiveAudioLinker(MakeLinkToActiveAudio); - - export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string; link_displayLine?: boolean }, id?: string, showPopup?: number[]) { + export function MakeLink(source: Doc, target: Doc, linkSettings: { link_relationship?: string; link_description?: string }, id?: string, showPopup?: number[]) { if (!linkSettings.link_relationship) linkSettings.link_relationship = target.type === DocumentType.RTF ? 'Commentary:Comments On' : 'link'; if (target.doc === Doc.UserDoc()) return undefined; @@ -232,7 +218,6 @@ export namespace DocUtils { title: ComputedField.MakeFunction('generateLinkTitle(this)') as any, link_anchor_1_useSmallAnchor: source.useSmallAnchor ? true : undefined, link_anchor_2_useSmallAnchor: target.useSmallAnchor ? true : undefined, - link_displayLine: linkSettings.link_displayLine, link_relationship: linkSettings.link_relationship, link_description: linkSettings.link_description, x: ComputedField.MakeFunction(`((this.${a}?.x||0)+(this.${b}?.x||0))/2`) as any, @@ -256,7 +241,6 @@ export namespace DocUtils { const script = scripts[key]; if (ScriptCast(doc[key])?.script.originalScript !== scripts[key] && script) { (key.startsWith('_') ? doc : Doc.GetProto(doc))[key] = ScriptField.MakeScript(script, { - self: Doc.name, this: Doc.name, dragData: Doc.DocDragDataName, value: 'any', @@ -579,7 +563,7 @@ export namespace DocUtils { export function LeavePushpin(doc: Doc, annotationField: string) { if (doc.followLinkToggle) return undefined; const context = Cast(doc.embedContainer, Doc, null) ?? Cast(doc.annotationOn, Doc, null); - const hasContextAnchor = LinkManager.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context)); + const hasContextAnchor = Doc.Links(doc).some(l => (l.link_anchor_2 === doc && Cast(l.link_anchor_1, Doc, null)?.annotationOn === context) || (l.link_anchor_1 === doc && Cast(l.link_anchor_2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { const pushpin = Docs.Create.FontIconDocument({ title: '', @@ -673,7 +657,7 @@ export namespace DocUtils { proto.data_duration = result.duration; } if (overwriteDoc) { - LoadingBox.removeCurrentlyLoading(overwriteDoc); + Doc.removeCurrentlyLoading(overwriteDoc); } generatedDocuments.push(doc); } @@ -717,7 +701,7 @@ export namespace DocUtils { if (overwriteDoc) { overwriteDoc.isLoading = false; overwriteDoc.loadingError = (result as any).message; - LoadingBox.removeCurrentlyLoading(overwriteDoc); + Doc.removeCurrentlyLoading(overwriteDoc); } } else newFilename && processFileupload(generatedDocuments, newFilename, mimetype ?? '', result, options, overwriteDoc); }); @@ -755,7 +739,7 @@ export namespace DocUtils { if ((result as any).message) { if (overwriteDoc) { overwriteDoc.loadingError = (result as any).message; - LoadingBox.removeCurrentlyLoading(overwriteDoc); + Doc.removeCurrentlyLoading(overwriteDoc); } } else newFilename && mimetype && processFileupload(generatedDocuments, newFilename, mimetype, result, options, overwriteDoc); }); @@ -868,6 +852,10 @@ export function FollowLinkScript() { return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' }); } +export function IsFollowLinkScript(field: FieldResult) { + return ScriptCast(field)?.script.originalScript.includes('return followLink('); +} + ScriptingGlobals.add('Docs', Docs); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function copyDragFactory(dragFactory: Doc, asDelegate?: boolean) { diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a3fea1ce4..1c18c0d84 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -19,7 +19,6 @@ import { SharingPermissions } from '../../fields/util'; import { PointData } from '../../pen-gestures/GestureTypes'; import { DocServer } from '../DocServer'; import { dropActionType } from '../util/DropActionTypes'; -import { LinkManager } from '../util/LinkManager'; import { CollectionViewType, DocumentType } from './DocumentTypes'; class EmptyBox { @@ -394,7 +393,6 @@ export class DocumentOptions { link?: string; link_description?: string; // added for links link_relationship?: string; // type of relatinoship a link represents - link_displayLine?: BOOLt = new BoolInfo('whether a link line should be dipslayed between the two link anchors'); link_displayArrow?: BOOLt = new BoolInfo("whether to display link's directional arrowhead"); link_anchor_1?: DOCt = new DocInfo('start anchor of a link'); link_anchor_2?: DOCt = new DocInfo('end anchor of a link'); @@ -810,7 +808,7 @@ export namespace Docs { 'link' ); - LinkManager.Instance.addLink(linkDoc); + Doc.AddLink(linkDoc); return linkDoc; } diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx index 28c00644f..119d103c5 100644 --- a/src/client/util/BranchingTrailManager.tsx +++ b/src/client/util/BranchingTrailManager.tsx @@ -6,8 +6,8 @@ import * as React from 'react'; import { Doc } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { OverlayView } from '../views/OverlayView'; +import { DocumentView } from '../views/nodes/DocumentView'; import { PresBox } from '../views/nodes/trails'; -import { DocumentManager } from './DocumentManager'; @observer export class BranchingTrailManager extends React.Component { @@ -46,7 +46,6 @@ export class BranchingTrailManager extends React.Component { // hi.overlayY = 100; // Doc.AddToMyOverlay(hi); - console.log(DocumentManager._overlayViews); }; @action setSlideHistoryStack = action((newArr: String[]) => { @@ -103,7 +102,7 @@ export class BranchingTrailManager extends React.Component { this.setSlideHistoryStack(newStack); removed.forEach(info => this.containsSet.delete(info.toString())); - DocumentManager.Instance.showDocument(targetDoc, { willZoomCentered: true }); + DocumentView.showDocument(targetDoc, { willZoomCentered: true }); if (this.slideHistoryStack.length === 0) { Doc.UserDoc().isBranchingMode = false; } diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx index 8e3936d34..77cf80151 100644 --- a/src/client/util/CalendarManager.tsx +++ b/src/client/util/CalendarManager.tsx @@ -13,15 +13,12 @@ import { Doc, DocListCast } from '../../fields/Doc'; import { DocData } from '../../fields/DocSymbols'; import { StrCast } from '../../fields/Types'; import { Docs } from '../documents/Documents'; -import { DictationOverlay } from '../views/DictationOverlay'; import { MainViewModal } from '../views/MainViewModal'; import { ObservableReactComponent } from '../views/ObservableReactComponent'; import { DocumentView } from '../views/nodes/DocumentView'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import './CalendarManager.scss'; -import { DocumentManager } from './DocumentManager'; -import { SelectionManager } from './SelectionManager'; -import { SettingsManager } from './SettingsManager'; +import { SnappingManager } from './SnappingManager'; // import 'react-date-range/dist/styles.css'; // import 'react-date-range/dist/theme/default.css'; @@ -85,11 +82,9 @@ export class CalendarManager extends ObservableReactComponent<{}> { }; public open = (target?: DocumentView, targetDoc?: Doc) => { - console.log('hi'); runInAction(() => { this.targetDoc = targetDoc || target?.Document; this.targetDocView = target; - DictationOverlay.Instance.hasActiveModal = true; this.isOpen = this.targetDoc !== undefined; }); }; @@ -99,7 +94,6 @@ export class CalendarManager extends ObservableReactComponent<{}> { TaskCompletionBox.taskCompleted = false; setTimeout( action(() => { - DictationOverlay.Instance.hasActiveModal = false; this.targetDoc = undefined; }), 500 @@ -137,7 +131,7 @@ export class CalendarManager extends ObservableReactComponent<{}> { // TODO: Make undoable private addToCalendar = () => { - const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); + const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document); const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar console.log(targetDoc); @@ -190,14 +184,14 @@ export class CalendarManager extends ObservableReactComponent<{}> { private focusOn = (contents: string) => { const title = this.targetDoc ? StrCast(this.targetDoc.title) : ''; - const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.Document) : [this.targetDoc]; + const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.Document) : [this.targetDoc]; return ( { if (this.targetDoc && this.targetDocView && docs.length === 1) { - DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true }); + DocumentView.showDocument(this.targetDoc, { willZoomCentered: true }); } }} onPointerEnter={action(() => { @@ -251,17 +245,17 @@ export class CalendarManager extends ObservableReactComponent<{}> { @computed get calendarInterface() { - const docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); + const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document); const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; return (
-

+

{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}

diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index a03c80f2a..4fd934774 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -10,8 +10,7 @@ import { DocCast, StrCast } from '../../fields/Types'; import { LightboxView } from '../views/LightboxView'; import { MainViewModal } from '../views/MainViewModal'; import './CaptureManager.scss'; -import { LinkManager } from './LinkManager'; -import { SelectionManager } from './SelectionManager'; +import { DocumentView } from '../views/nodes/DocumentView'; @observer export class CaptureManager extends React.Component<{}> { @@ -56,7 +55,7 @@ export class CaptureManager extends React.Component<{}> { const doc = this._document; const order: JSX.Element[] = []; if (doc) { - LinkManager.Links(doc).forEach((l, i) => + Doc.Links(doc).forEach((l, i) => order.push(
{i}
@@ -88,8 +87,8 @@ export class CaptureManager extends React.Component<{}> {
{ - const selected = SelectionManager.Views.slice(); - SelectionManager.DeselectAll(); + const selected = DocumentView.Selected(); + DocumentView.DeselectAll(); selected.map(dv => dv.props.removeDocument?.(dv.Document)); this.close(); }}> diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 15c8fec5a..ffc1304bf 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,4 +1,4 @@ -import { observable, reaction, runInAction } from "mobx"; +import { reaction, runInAction } from "mobx"; import * as rp from 'request-promise'; import { ClientUtils, OmitKeys } from "../../ClientUtils"; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; @@ -19,7 +19,8 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents"; import { DashboardView } from "../views/DashboardView"; import { OverlayView } from "../views/OverlayView"; -import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView"; +import { CollectionTreeView } from "../views/collections/CollectionTreeView"; +import { TreeViewType } from "../views/collections/CollectionTreeViewType"; import { Colors } from "../views/global/globalEnums"; import { mediaState } from "../views/nodes/AudioBox"; import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox"; @@ -30,8 +31,9 @@ import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox"; import { DragManager } from "./DragManager"; import { dropActionType } from "./DropActionTypes"; import { MakeTemplate } from "./DropConverter"; -import { LinkManager, UPDATE_SERVER_CACHE } from "./LinkManager"; +import { UPDATE_SERVER_CACHE } from "./LinkManager"; import { ScriptingGlobals } from "./ScriptingGlobals"; +import { SelectionManager } from "./SelectionManager"; import { ColorScheme } from "./SettingsManager"; import { SnappingManager } from "./SnappingManager"; import { UndoManager } from "./UndoManager"; @@ -937,14 +939,13 @@ pie title Minerals in my tap water DocUtils.AssignDocField(doc, "globalScriptDatabase", () => Docs.Prototypes.MainScriptDocument(), {}); DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, layout_maxShown: 10, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: dropActionType.move}); // drop down panel at top of dashboard for stashing documents - Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards) - Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs) - Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed) - - Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org") + SelectionManager.DeselectAll(); // this forces SelectionManager implementation to copy over to DocumentView's API. This also triggers the LinkManager to be created + + Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards); + Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs); + Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed); - // eslint-disable-next-line no-new - new LinkManager(); + Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org"); DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500); setInterval(UPDATE_SERVER_CACHE, 120000); @@ -969,12 +970,11 @@ pie title Minerals in my tap water }); } - @observable public static ServerVersion: string = ';' public static async loadCurrentUser() { return rp.get(ClientUtils.prepend("/getCurrentUser")).then(async response => { if (response) { const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response); - runInAction(() => { CurrentUserUtils.ServerVersion = result.version; }); + runInAction(() => { SnappingManager.SetServerVersion(result.version); }); ClientUtils.SetCurrentUserEmail(result.email); resolvedPorts = result.resolvedPorts as any; DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email); diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 97ee628e2..bc9fe813f 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -10,12 +10,13 @@ import { RichTextField } from '../../fields/RichTextField'; import { listSpec } from '../../fields/Schema'; import { Cast, CastCtor } from '../../fields/Types'; import { AudioField, ImageField } from '../../fields/URLField'; +import { AudioAnnoState } from '../../server/SharedMediaTypes'; +import { Networking } from '../Network'; import { DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; import { DictationOverlay } from '../views/DictationOverlay'; import { DocumentView } from '../views/nodes/DocumentView'; import { OpenWhere } from '../views/nodes/OpenWhere'; -import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; /** @@ -244,7 +245,7 @@ export namespace DictationManager { export const execute = async (phrase: string) => UndoManager.RunInBatch(async () => { console.log('PHRASE: ' + phrase); - const targets = SelectionManager.Views; + const targets = DocumentView.Selected(); if (!targets || !targets.length) { return undefined; } @@ -395,4 +396,43 @@ export namespace DictationManager { }, ]; } + export function recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) { + let gumStream: any; + let recorder: any; + navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { + let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); + if (audioTextAnnos) audioTextAnnos.push(''); + else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List(['']); + DictationManager.Controls.listen({ + interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + onEnd?.(); + }); + + gumStream = stream; + recorder = new MediaRecorder(stream); + recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); + if (!(result instanceof Error)) { + const audioField = new AudioField(result.accessPaths.agnostic.client); + const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); + if (audioAnnos) audioAnnos.push(audioField); + else dataDoc[field + '_audioAnnotations'] = new List([audioField]); + } + }; + recorder.start(); + const stopFunc = () => { + recorder.stop(); + DictationManager.Controls.stop(/* false */); + dataDoc.audioAnnoState = AudioAnnoState.stopped; + gumStream.getAudioTracks()[0].stop(); + }; + if (onRecording) onRecording(stopFunc); + else setTimeout(stopFunc, 5000); + }); + } } diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 8f0c2b0bf..5bcac7330 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,23 +1,17 @@ import { Howl } from 'howler'; import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx'; import { Doc, Opt } from '../../fields/Doc'; -import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols'; +import { Animation, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { AudioField } from '../../fields/URLField'; -import { GetEffectiveAcl } from '../../fields/util'; import { CollectionViewType } from '../documents/DocumentTypes'; -import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView'; import { FocusViewOptions } from '../views/nodes/FocusViewOptions'; -import { KeyValueBox } from '../views/nodes/KeyValueBox'; -import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { OpenWhere } from '../views/nodes/OpenWhere'; import { PresBox } from '../views/nodes/trails'; -import { ScriptingGlobals } from './ScriptingGlobals'; -import { SelectionManager } from './SelectionManager'; type childIterator = { viewSpec: Opt; childDocView: Opt; focused: boolean; contextPath: Doc[] }; export class DocumentManager { @@ -29,10 +23,9 @@ export class DocumentManager { } // global holds all of the nodes (regardless of which collection they're in) - @observable _documentViews = new Set(); - @observable.shallow public CurrentlyLoading: Doc[] = []; + @observable private _documentViews = new Set(); @computed public get DocumentViews() { - return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view))); + return Array.from(this._documentViews).filter(view => (!view.ComponentView?.dontRegisterView?.() && !LightboxView.LightboxDoc) || LightboxView.Contains(view)); } public AddDocumentView(dv: DocumentView) { this._documentViews.add(dv); @@ -44,7 +37,19 @@ export class DocumentManager { // private constructor so no other class can create a nodemanager private constructor() { makeObservable(this); - observe(this.CurrentlyLoading, change => { + + DocumentView.allViews = () => this.DocumentViews; + DocumentView.addView = this.AddView; + DocumentView.removeView = this.RemoveView; + DocumentView.showDocument = this.showDocument; + DocumentView.showDocumentView = this.showDocumentView; + DocumentView.linkCommonAncestor = DocumentManager.LinkCommonAncestor; + DocumentView.addViewRenderedCb = this.AddViewRenderedCb; + DocumentView.getFirstDocumentView = this.getFirstDocumentView; + DocumentView.getDocumentView = this.getDocumentView; + DocumentView.getContextPath = DocumentManager.GetContextPath; + DocumentView.getLightboxDocumentView = this.getLightboxDocumentView; + observe(Doc.CurrentlyLoading, change => { // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become switch (change.type as any) { case 'update': @@ -92,17 +97,12 @@ export class DocumentManager { @action public AddView = (view: DocumentView) => { - if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && - !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { - this.AddDocumentView(view); - this.callAddViewFuncs(view); - } // prettier-ignore + this.AddDocumentView(view); + this.callAddViewFuncs(view); }; public RemoveView = action((view: DocumentView) => { - if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { - this.DeleteDocumentView(view); - } - SelectionManager.DeselectView(view); + this.DeleteDocumentView(view); + DocumentView.DeselectView(view); }); // gets all views @@ -203,6 +203,11 @@ export class DocumentManager { } static _overlayViews = new ObservableSet(); + /** + * Find the nearest common ancestor collection that contains a link's source and target + * @param linkDoc + * @returns common ancestor DocumentView + */ public static LinkCommonAncestor(linkDoc: Doc) { const getAnchor = (which: number) => { const anch = DocCast(linkDoc['link_anchor_' + which]); @@ -245,11 +250,8 @@ export class DocumentManager { Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc); const docContextPath = DocumentManager.GetContextPath(targetDoc, true); if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false; - const tabView = Array.from(TabDocView._allTabs).find(view => view._document === docContextPath[0]); - if (!tabView?._activated && tabView?._document) { - options.toggleTarget = false; - TabDocView.Activate(tabView?._document); - } + if (DocumentView.activateTabView(docContextPath[0])) options.toggleTarget = false; + const rootContextView = docContextPath.length && (await new Promise(res => { @@ -260,7 +262,7 @@ export class DocumentManager { return; } options.didMove = true; - (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); + (!LightboxView.LightboxDoc && docContextPath.some(doc => DocumentView.activateTabView(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); })); if (options.openLocation === OpenWhere.lightbox) { @@ -355,33 +357,4 @@ export class DocumentManager { } } } -// eslint-disable-next-line default-param-last -export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { - let doc = docIn; - const options = optionsIn; - const func = () => { - const cv = DocumentManager.Instance.getDocumentView(containingDoc); - const dv = DocumentManager.Instance.getDocumentView(doc, cv); - if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) { - DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document)); - } else { - const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc)); - const showDoc = !Doc.IsSystem(container) && !cv ? container : doc; - options.toggleTarget = undefined; - DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => { - const cvFound = DocumentManager.Instance.getDocumentView(containingDoc); - const dvFound = DocumentManager.Instance.getDocumentView(doc, cvFound); - dvFound && Doc.linkFollowHighlight(dvFound.Document); - }); - } - }; - if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) { - doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!; - } - if (doc.hidden) { - doc.hidden = false; - options.toggleTarget = false; - setTimeout(func); - } else func(); -} -ScriptingGlobals.add(DocFocusOrOpen); +setTimeout(() => DocumentManager.Instance); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 34f54be2e..fda505420 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -27,8 +27,6 @@ import { ScriptCast } from '../../fields/Types'; import { Docs } from '../documents/Documents'; import { DocumentView } from '../views/nodes/DocumentView'; import { dropActionType } from './DropActionTypes'; -import { ScriptingGlobals } from './ScriptingGlobals'; -import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; @@ -648,14 +646,3 @@ export namespace DragManager { document.addEventListener('pointerup', upHandler, true); } } - -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { - if (readOnly) { - return SelectionManager.Views.some(dv => dv.Document.keepZWhenDragged); - } - SelectionManager.Views.forEach(dv => { - dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged; - }); - return undefined; -}); diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index e1a503ac9..5701a22c0 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -77,7 +77,7 @@ export class GroupManager extends ObservableReactComponent<{}> { */ @action open = () => { - // SelectionManager.DeselectAll(); + // DocumentView.DeselectAll(); this.isOpen = true; this.populateUsers(); }; diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts index dd18b2533..5a6522dc8 100644 --- a/src/client/util/HypothesisUtils.ts +++ b/src/client/util/HypothesisUtils.ts @@ -6,8 +6,6 @@ import { WebField } from '../../fields/URLField'; import { Docs } from '../documents/Documents'; import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton'; import { DocumentView } from '../views/nodes/DocumentView'; -import { DocumentManager } from './DocumentManager'; -import { SelectionManager } from './SelectionManager'; export namespace Hypothesis { /** @@ -25,7 +23,7 @@ export namespace Hypothesis { * Search for a WebDocument whose url field matches the given uri, return undefined if not found */ export const findWebDoc = async (uri: string) => { - const currentDoc = SelectionManager.Docs.lastElement(); + const currentDoc = DocumentView.Selected().lastElement()?.Document; if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise const results: Doc[] = []; @@ -39,7 +37,7 @@ export namespace Hypothesis { // }) // ); - const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc)); + const onScreenResults = results.filter(doc => DocumentView.getFirstDocumentView(doc)); return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen }; @@ -65,7 +63,7 @@ export namespace Hypothesis { DocumentLinksButton.AnnotationId = annotationId; DocumentLinksButton.AnnotationUri = annotationUri; }); - const endLinkView = DocumentManager.Instance.getFirstDocumentView(sourceDoc); + const endLinkView = DocumentView.getFirstDocumentView(sourceDoc); const rect = document.body.getBoundingClientRect(); const x = rect.x + rect.width / 2; const y = 250; @@ -182,7 +180,7 @@ export namespace Hypothesis { bubbles: true, }) ); - const targetView: Opt = DocumentManager.Instance.getFirstDocumentView(target); + const targetView: Opt = DocumentView.getFirstDocumentView(target); const position = targetView?.screenToViewTransform().inverse().transformPoint(0, 0); targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false); }, 300); diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 398ba3c04..baa0786b7 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -17,7 +17,6 @@ // import { DocumentType } from '../../documents/DocumentTypes'; // import { Networking } from '../../Network'; // import { FieldView, FieldViewProps } from '../../views/nodes/FieldView'; -// import { DocumentManager } from '../DocumentManager'; // import './DirectoryImportBox.scss'; // import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry'; // import * as React from 'react'; diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index a6f6122ab..9a0edcfec 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -1,14 +1,12 @@ import { action, runInAction } from 'mobx'; -import { Doc, DocListCast, FieldResult, FieldType, Opt } from '../../fields/Doc'; -import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; +import { DocumentView } from '../views/nodes/DocumentView'; import { FocusViewOptions } from '../views/nodes/FocusViewOptions'; import { OpenWhere } from '../views/nodes/OpenWhere'; import { PresBox } from '../views/nodes/trails'; -import { DocumentManager } from './DocumentManager'; -import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; -import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; /* @@ -24,6 +22,9 @@ import { UndoManager } from './UndoManager'; * - user defined kvps */ export class LinkFollower { + public static Init() { + DocumentView.FollowLink = LinkFollower.FollowLink; + } // follows a link - if the target is on screen, it highlights/pans to it. // if the target isn't onscreen, then it will open up the target in the lightbox, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); @@ -42,9 +43,9 @@ export class LinkFollower { }; public static traverseLink(link: Opt, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) { - const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc)); + const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc)); const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source); - const linkDocs = link ? [link] : LinkManager.Links(sourceDoc); + const linkDocs = link ? [link] : Doc.Links(sourceDoc); const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1 const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2 const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2))); @@ -68,7 +69,7 @@ export class LinkFollower { ? linkDoc.link_anchor_2 : linkDoc.link_anchor_1 ) as Doc; - const srcAnchor: Doc = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc; + const srcAnchor: Doc = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc; if (target) { const doFollow = (canToggle?: boolean) => { const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle); @@ -87,14 +88,14 @@ export class LinkFollower { zoomTextSelections: BoolCast(srcAnchor.followLinkZoomText), }; if (target.type === DocumentType.PRES) { - const containerDocContext = DocumentManager.GetContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail - SelectionManager.DeselectAll(); - if (!DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) { + const containerDocContext = DocumentView.getContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail + DocumentView.DeselectAll(); + if (!DocumentView.addViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) { PresBox.OpenPresMinimized(target, [0, 0]); } finished?.(); } else { - DocumentManager.Instance.showDocument(target, options, allFinished); + DocumentView.showDocument(target, options, allFinished); } }; let movedTarget = false; @@ -133,10 +134,6 @@ export class LinkFollower { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true }; }); - -export function IsFollowLinkScript(field: FieldResult) { - return ScriptCast(field)?.script.originalScript.includes('return followLink('); -} diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index dbd13f978..56d5dce4e 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -30,8 +30,8 @@ export class LinkManager { @observable.shallow userLinkDBs: Doc[] = []; @observable public currentLink: Opt = undefined; @observable public currentLinkAnchor: Opt = undefined; - public static get Instance() { - return LinkManager._instance; + public static get Instance(): LinkManager { + return Doc.UserDoc() ? LinkManager._instance ?? new LinkManager() : (undefined as any as LinkManager); } public static Links(doc: Doc | undefined) { @@ -47,10 +47,13 @@ export class LinkManager { this.userLinkDBs.push(linkDb); }; public static AutoKeywords = 'keywords:Usages'; - constructor() { + private constructor() { makeObservable(this); LinkManager._instance = this; Doc.AddLink = this.addLink; + Doc.DeleteLink = this.deleteLink; + Doc.Links = LinkManager.Links; + Doc.getOppositeAnchor = LinkManager.getOppositeAnchor; this.createlink_relationshipLists(); // since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs // Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts index e5e69c5ac..255e9cee0 100644 --- a/src/client/util/PingManager.ts +++ b/src/client/util/PingManager.ts @@ -1,6 +1,6 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; import { Networking } from '../Network'; -import { CurrentUserUtils } from './CurrentUserUtils'; +import { SnappingManager } from './SnappingManager'; export class PingManager { // create static instance and getter for global use @@ -32,7 +32,7 @@ export class PingManager { try { const res = await Networking.PostToServer('/ping', { date: new Date() }); runInAction(() => { - CurrentUserUtils.ServerVersion = res.message; + SnappingManager.SetServerVersion(res.message); }); !this.IsBeating && this.setIsBeating(true); } catch { diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index dcc6aabab..530fcf211 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -4,8 +4,8 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; import { OpenWhereMod } from '../views/nodes/OpenWhere'; import { VideoBox } from '../views/nodes/VideoBox'; -import { DocumentManager } from './DocumentManager'; import { Movement, Presentation } from './TrackMovements'; +import { DocumentView } from '../views/nodes/DocumentView'; export class ReplayMovements { private timers: NodeJS.Timeout[] | null; @@ -106,7 +106,7 @@ export class ReplayMovements { // returns undefined if the docView isn't open on the screen getCollectionFFView = (doc: Doc) => { - const isInView = DocumentManager.Instance.getDocumentView(doc); + const isInView = DocumentView.getDocumentView(doc); return isInView?.ComponentView as CollectionFreeFormView; }; @@ -118,7 +118,7 @@ export class ReplayMovements { } // console.log('openTab', docId, doc); CollectionDockingView.AddSplit(doc, OpenWhereMod.right); - const docView = DocumentManager.Instance.getDocumentView(doc); + const docView = DocumentView.getDocumentView(doc); // BUG - this returns undefined if the doc is already open return docView?.ComponentView as CollectionFreeFormView; }; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 8df579e75..6948469cc 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -5,8 +5,8 @@ // import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts' // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' // eslint-disable-next-line node/no-unpublished-import -import * as ts from 'typescript'; import * as typescriptlib from '!!raw-loader!./type_decls.d'; +import * as ts from 'typescript'; import { Doc, FieldType } from '../../fields/Doc'; import { RefField } from '../../fields/RefField'; import { ScriptField } from '../../fields/ScriptField'; @@ -195,7 +195,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp if (found) return found as CompiledScript; const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options; if (options.params && !options.params.this) options.params.this = Doc.name; - if (options.params && !options.params.self) options.params.self = Doc.name; if (options.globals) { ScriptingGlobals.setScriptingGlobals(options.globals); } @@ -266,9 +265,3 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp !signature.includes('XXX') && ScriptField._scriptFieldCache.set(script + ':' + signature, result as CompiledScript); return result; } - -ScriptingGlobals.add(CompileScript); -// eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function runScript(doc: Doc, script: ScriptField) { - return script?.script.run({ this: doc }).result; -}); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 7e8f42de9..0b942116c 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,6 +1,5 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; import { Doc, Opt } from '../../fields/Doc'; -import { DocViews } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast } from '../../fields/Types'; @@ -24,6 +23,13 @@ export class SelectionManager { private constructor() { SelectionManager._manager = this; makeObservable(this); + DocumentView.DeselectAll = SelectionManager.DeselectAll; + DocumentView.DeselectView = SelectionManager.DeselectView; + DocumentView.SelectView = SelectionManager.SelectView; + DocumentView.SelectedDocs = SelectionManager.Docs; + DocumentView.Selected = SelectionManager.Views; + DocumentView.SelectSchemaDoc = SelectionManager.SelectSchemaViewDoc; + DocumentView.SelectedSchemaDoc = () => this.SelectedSchemaDocument; } @action @@ -54,8 +60,10 @@ export class SelectionManager { public static DeselectAll = (except?: Doc): void => { const found = this.Instance.SelectedViews.find(dv => dv.Document === except); runInAction(() => { - LinkManager.Instance.currentLink = undefined; - LinkManager.Instance.currentLinkAnchor = undefined; + if (LinkManager.Instance) { + LinkManager.Instance.currentLink = undefined; + LinkManager.Instance.currentLinkAnchor = undefined; + } this.Instance.SelectedSchemaDocument = undefined; }); this.Instance.SelectedViews.forEach(dv => { @@ -68,19 +76,18 @@ export class SelectionManager { if (found) this.SelectView(found, false); }; - public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected); - public static get Views() { return this.Instance.SelectedViews; } // prettier-ignore - public static get SelectedSchemaDoc() { return this.Instance.SelectedSchemaDocument; } // prettier-ignore - public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore + public static Views() { return SelectionManager.Instance.SelectedViews; } // prettier-ignore + public static get SelectedSchemaDoc() { return SelectionManager.Instance.SelectedSchemaDocument; } // prettier-ignore + public static Docs() { return SelectionManager.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore } // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function SelectedDocType(type: string, expertMode: boolean, checkContext?: boolean) { if (Doc.noviceMode && expertMode) return false; if (type === 'tab') { - return SelectionManager.Views.lastElement()?._props.renderDepth === 0; + return DocumentView.Selected().lastElement()?._props.renderDepth === 0; } - const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement()); + const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(DocumentView.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); return selected?.type === type || selected?.type_collection === type || !type; }); // eslint-disable-next-line prefer-arrow-callback @@ -109,8 +116,6 @@ ScriptingGlobals.add(function redo() { }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { - const docs = SelectionManager.Views.map(dv => dv.Document).filter( - d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null)) - ); + const docs = SelectionManager.Docs().filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); return docs.length ? new List(docs) : prevValue; }); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 8cb97fe50..d3c10f9f4 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -8,7 +8,7 @@ import * as React from 'react'; import { BsGoogle } from 'react-icons/bs'; import { FaFillDrip, FaPalette } from 'react-icons/fa'; import { ClientUtils, addStyleSheet, addStyleSheetRule } from '../../ClientUtils'; -import { Doc, Opt } from '../../fields/Doc'; +import { Doc } from '../../fields/Doc'; import { DashVersion } from '../../fields/DocSymbols'; import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types'; import { DocServer } from '../DocServer'; @@ -17,7 +17,7 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager import { MainViewModal } from '../views/MainViewModal'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; -import { SnappingManager } from './SnappingManager'; +import { SnappingManager, freeformScrollMode } from './SnappingManager'; import { undoable } from './UndoManager'; export enum ColorScheme { @@ -28,11 +28,6 @@ export enum ColorScheme { Cupcake = 'Cupcake', } -export enum freeformScrollMode { - Pan = 'pan', - Zoom = 'zoom', -} - @observer export class SettingsManager extends React.Component<{}> { // eslint-disable-next-line no-use-before-define @@ -44,17 +39,15 @@ export class SettingsManager extends React.Component<{}> { @observable private _curr_password = ''; @observable private _new_password = ''; @observable private _new_confirm = ''; - @observable private _lastPressedSidebarBtn: Opt = undefined; // bcz: this is a hack to handle highlighting buttons in the leftpanel menu .. need to find a cleaner approach @observable private _activeTab = 'Accounts'; @observable private _isOpen = false; - @observable public propertiesWidth: number = 0; - private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)); public closeMgr = action(() => { this._isOpen = false; }); + // eslint-disable-next-line react/no-unused-class-component-methods public openMgr = action(() => { this._isOpen = true; }); @@ -154,9 +147,6 @@ export class SettingsManager extends React.Component<{}> { SnappingManager.SettingsStyle = SettingsManager._settingsStyle; } - public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore - public set LastPressedBtn(state:Doc|undefined) { this._lastPressedSidebarBtn = state; } // prettier-ignore - @computed public static get userColor() { return StrCast(Doc.UserDoc().userColor); } @@ -287,7 +277,7 @@ export class SettingsManager extends React.Component<{}> { size={Size.XSMALL} color={SettingsManager.userColor} /> - { toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)} size={Size.XSMALL} color={SettingsManager.userColor} - /> + /> */} { const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users; const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList; - let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document); + let docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document); if (this.myDocAcls) { const newDocs: Doc[] = []; @@ -442,7 +440,7 @@ export class SharingManager extends React.Component<{}> { const { user, sharingDoc } = recipient; const target = targetDoc || this.targetDoc!; const acl = `acl_${normalizeEmail(user.email)}`; - const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document); + const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document); docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined); if (permission !== SharingPermissions.None) { @@ -460,7 +458,7 @@ export class SharingManager extends React.Component<{}> { const target = targetDoc || this.targetDoc!; const acl = `acl_${normalizeEmail(StrCast(group.title))}`; - const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document); + const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document); docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => { distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined); @@ -643,14 +641,14 @@ export class SharingManager extends React.Component<{}> { private focusOn = (contents: string) => { const title = this.targetDoc ? StrCast(this.targetDoc.title) : ''; - const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.props.Document) : [this.targetDoc]; + const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.props.Document) : [this.targetDoc]; return ( { if (this.targetDoc && this.targetDocView && docs.length === 1) { - DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true }); + DocumentView.showDocument(this.targetDoc, { willZoomCentered: true }); } }} onPointerEnter={action(() => { diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 5cd6ecfe1..6789c2ab8 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -1,5 +1,9 @@ import { observable, action, runInAction, makeObservable } from 'mobx'; +export enum freeformScrollMode { + Pan = 'pan', + Zoom = 'zoom', +} export class SnappingManager { // eslint-disable-next-line no-use-before-define private static _manager: SnappingManager; @@ -19,6 +23,9 @@ export class SnappingManager { @observable _vertSnapLines: number[] = []; @observable _exploreMode = false; @observable _userPanned = false; + @observable _serverVersion: string = ''; + @observable _lastBtnId: string = ''; + @observable _propertyWid: number = 0; private constructor() { SnappingManager._manager = this; @@ -45,6 +52,10 @@ export class SnappingManager { public static get CanEmbed() { return this.Instance._canEmbed; } // prettier-ignore public static get ExploreMode() { return this.Instance._exploreMode; } // prettier-ignore public static get UserPanned() { return this.Instance._userPanned; } // prettier-ignore + public static get ServerVersion() { return this.Instance._serverVersion; } // prettier-ignore + public static get LastPressedBtn() { return this.Instance._lastBtnId; } // prettier-ignore + public static get PropertiesWidth(){ return this.Instance._propertyWid; } // prettier-ignore + public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore @@ -54,7 +65,10 @@ export class SnappingManager { public static SetIsResizing = (docid?:string) => runInAction(() => {this.Instance._isResizing = docid}); // prettier-ignore public static SetCanEmbed = (embed:boolean) => runInAction(() => {this.Instance._canEmbed = embed}); // prettier-ignore public static SetExploreMode = (state:boolean) => runInAction(() => {this.Instance._exploreMode = state}); // prettier-ignore - public static TriggerUserPanned = () => runInAction(() => {this.Instance._userPanned = !this.Instance._userPanned}); // prettier-ignore + public static TriggerUserPanned = () => runInAction(() => {this.Instance._userPanned = !this.Instance._userPanned}); // prettier-ignore + public static SetServerVersion = (version:string) =>runInAction(() => {this.Instance._serverVersion = version}); // prettier-ignore + public static SetLastPressedBtn = (id:string) =>runInAction(() => {this.Instance._lastBtnId = id}); // prettier-ignore + public static SetPropertiesWidth= (wid:number) =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore public static userColor: string | undefined; public static userVariantColor: string | undefined; diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index b1eb730fa..303672d90 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -1,6 +1,6 @@ import { action, makeObservable, observable, runInAction } from 'mobx'; import * as React from 'react'; -import { SettingsManager } from '../util/SettingsManager'; +import { SnappingManager } from '../util/SnappingManager'; import './AntimodeMenu.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -157,7 +157,7 @@ export abstract class AntimodeMenu extends Observab left: this._left, top: this._top, opacity: this._opacity, - background: SettingsManager.userBackgroundColor, + background: SnappingManager.userBackgroundColor, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, @@ -183,7 +183,7 @@ export abstract class AntimodeMenu extends Observab height: 'inherit', width: 200, opacity: this._opacity, - background: SettingsManager.userBackgroundColor, + background: SnappingManager.userBackgroundColor, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, @@ -206,7 +206,7 @@ export abstract class AntimodeMenu extends Observab left: this._left, top: this._top, opacity: this._opacity, - background: SettingsManager.userBackgroundColor, + background: SnappingManager.userBackgroundColor, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, diff --git a/src/client/views/ComponentDecorations.tsx b/src/client/views/ComponentDecorations.tsx index 64b8a8446..929b549e0 100644 --- a/src/client/views/ComponentDecorations.tsx +++ b/src/client/views/ComponentDecorations.tsx @@ -1,7 +1,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; -import { SelectionManager } from '../util/SelectionManager'; import './ComponentDecorations.scss'; +import { DocumentView } from './nodes/DocumentView'; @observer export class ComponentDecorations extends React.Component<{ boundsTop: number; boundsLeft: number }, { value: string }> { @@ -9,7 +9,7 @@ export class ComponentDecorations extends React.Component<{ boundsTop: number; b static Instance: ComponentDecorations; render() { - const seldoc = SelectionManager.Views.lastElement(); + const seldoc = DocumentView.Selected().lastElement(); return seldoc?.ComponentView?.componentUI?.(this.props.boundsLeft, this.props.boundsTop) ?? null; } } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index a0e3d2ddd..97ff346e4 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -60,6 +60,8 @@ export interface ViewBoxInterface { ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; + dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views + isUnstyledView?: () => boolean; // SchemaView and KeyValue are untyled -- not tiles, no opacity } /** * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's diff --git a/src/client/views/DocViewUtils.ts b/src/client/views/DocViewUtils.ts new file mode 100644 index 000000000..1f5f29c7e --- /dev/null +++ b/src/client/views/DocViewUtils.ts @@ -0,0 +1,21 @@ +/* eslint-disable prefer-destructuring */ +/* eslint-disable default-param-last */ +/* eslint-disable no-use-before-define */ +import { runInAction } from 'mobx'; +import { Doc, SetActiveAudioLinker } from '../../fields/Doc'; +import { DocUtils } from '../documents/DocUtils'; +import { FieldViewProps } from './nodes/FieldView'; + +export namespace DocViewUtils { + export const ActiveRecordings: { props: FieldViewProps; getAnchor: (addAsAnnotation: boolean) => Doc }[] = []; + + export function MakeLinkToActiveAudio(getSourceDoc: () => Doc | undefined, broadcastEvent = true) { + broadcastEvent && runInAction(() => { Doc.RecordingEvent += 1; }); // prettier-ignore + return ActiveRecordings.map(audio => { + const sourceDoc = getSourceDoc(); + return sourceDoc && DocUtils.MakeLink(sourceDoc, audio.getAnchor(true) || audio.props.Document, { link_relationship: 'recording annotation:linked recording', link_description: 'recording timeline' }); + }); + } + + SetActiveAudioLinker(MakeLinkToActiveAudio); +} diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index dd744f272..487868169 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -14,23 +14,21 @@ import { returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from ' import { emptyFunction } from '../../Utils'; import { Doc } from '../../fields/Doc'; import { Cast, DocCast } from '../../fields/Types'; -import { DocUtils } from '../documents/DocUtils'; +import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CalendarManager } from '../util/CalendarManager'; +import { DictationManager } from '../util/DictationManager'; import { DragManager } from '../util/DragManager'; import { dropActionType } from '../util/DropActionTypes'; -import { IsFollowLinkScript } from '../util/LinkFollower'; -import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; import { UndoManager, undoBatch } from '../util/UndoManager'; import './DocumentButtonBar.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; import { PinProps } from './PinFuncs'; import { TemplateMenu } from './TemplateMenu'; -import { TabDocView } from './collections/TabDocView'; import { Colors } from './global/globalEnums'; import { LinkPopup } from './linking/LinkPopup'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; import { DashFieldView } from './nodes/formattedText/DashFieldView'; @@ -206,7 +204,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( .views() .filter(v => v) .map(dv => dv!.Document); - TabDocView.PinDoc(docs, { + DocumentView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: pinLayoutView, pinData: { dataview: pinContentView }, @@ -221,7 +219,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( ); }; return !targetDoc ? null : ( - {`Pin Document ${SelectionManager.Views.length > 1 ? 'multiple documents' : ''} to Trail`}
}> + {`Pin Document ${DocumentView.Selected().length > 1 ? 'multiple documents' : ''} to Trail`}
}>
{ @@ -229,7 +227,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( .views() .filter(v => v) .map(dv => dv!.Document); - TabDocView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); + DocumentView.PinDoc(docs, { pinAudioPlay: true, pinDocLayout: e.shiftKey, pinData: { dataview: e.altKey }, activeFrame: Cast(docs.lastElement()?.activeFrame, 'number', null) }); e.stopPropagation(); }}>
@@ -299,7 +297,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( this._props.views().map( view => view && - DocumentViewInternal.recordAudioAnnotation( + DictationManager.recordAudioAnnotation( view.dataDoc, view.LayoutFieldKey, stopFunc => { @@ -381,7 +379,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( } openContextMenu = (e: PointerEvent) => { - let child = SelectionManager.Views[0].ContentDiv!.children[0]; + let child = DocumentView.Selected()[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'); if (next?.className?.toString().includes(DocumentView.ROOT_DIV)) break; @@ -442,14 +440,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => (
{this._showLinkPopup ? (
- { - link.link_displayLine = !IsFollowLinkScript(this._props.views().lastElement()?.Document.onClick); - }} - linkCreateAnchor={() => this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)} - linkFrom={() => this._props.views().lastElement()?.Document} - /> + this._props.views().lastElement()?.ComponentView?.getAnchor?.(true)} linkFrom={() => this._props.views().lastElement()?.Document} />
) : (
{this.linkButton}
@@ -457,7 +448,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ?
{this.endLinkButton}
: null}
{this.templateButton}
- {!SelectionManager.Views?.some(v => v.allLinks.length) ? null :
{this.followLinkButton}
} + {!DocumentView.Selected().some(v => v.allLinks.length) ? null :
{this.followLinkButton}
}
{this.pinButton}
{this.recordButton}
{this.calendarButton}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index ef493fb69..432b02782 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -17,9 +17,7 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; import { DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; -import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; -import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; @@ -81,14 +79,14 @@ export class DocumentDecorations extends ObservableReactComponent center.x+x || this.Bounds.r < center.x+x || this.Bounds.y > center.y+y || this.Bounds.b < center.y+y ))); @@ -110,7 +108,7 @@ export class DocumentDecorations extends ObservableReactComponent dv._props.renderDepth > 0) .map(dv => dv.getBounds) .reduce((bounds, rect) => !rect ? bounds @@ -127,7 +125,7 @@ export class DocumentDecorations extends ObservableReactComponent#')) { - SelectionManager.Docs.forEach(doc => { + DocumentView.SelectedDocs().forEach(doc => { doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`); }); } @@ -135,11 +133,11 @@ export class DocumentDecorations extends ObservableReactComponent titleFieldKey && - SelectionManager.Views.forEach(d => { + DocumentView.Selected().forEach(dv => { if (titleFieldKey === 'title') { - d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-'); + dv.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-'); } - KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle); + KeyValueBox.SetField(dv.Document, titleFieldKey, this._accumulatedTitle); }), 'edit title' ); @@ -154,7 +152,7 @@ export class DocumentDecorations extends ObservableReactComponent { - const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document); + const effectiveLayoutAcl = GetEffectiveAcl(DocumentView.Selected()[0].Document); if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) { setupMoveUpEvents(this, e, moveEv => this.onBackgroundMove(true, moveEv), emptyFunction, emptyFunction); e.stopPropagation(); @@ -162,7 +160,7 @@ export class DocumentDecorations extends ObservableReactComponent { - const effectiveLayoutAcl = GetEffectiveAcl(SelectionManager.Views[0].Document); + const effectiveLayoutAcl = GetEffectiveAcl(DocumentView.SelectedDocs()[0]); if (effectiveLayoutAcl === AclAdmin || effectiveLayoutAcl === AclEdit || effectiveLayoutAcl === AclAugment) { setupMoveUpEvents( this, @@ -170,7 +168,7 @@ export class DocumentDecorations extends ObservableReactComponent this.onBackgroundMove(true, moveEv), emptyFunction, action(() => { - const selected = SelectionManager.Views.length === 1 ? SelectionManager.Docs[0] : undefined; + const selected = DocumentView.SelectedDocs().length === 1 ? DocumentView.SelectedDocs()[0] : undefined; !this._editingTitle && (this._accumulatedTitle = this._titleControlString.startsWith('$') ? (selected && Field.toKeyValueString(selected, this._titleControlString.substring(1))) || '-unset-' : this._titleControlString); this._editingTitle = true; this._keyinput.current && setTimeout(this._keyinput.current.focus); @@ -186,19 +184,16 @@ export class DocumentDecorations extends ObservableReactComponent { - const dragDocView = SelectionManager.Views[0]; + const dragDocView = DocumentView.Selected()[0]; const effectiveLayoutAcl = GetEffectiveAcl(dragDocView.Document); if (effectiveLayoutAcl !== AclAdmin && effectiveLayoutAcl !== AclEdit && effectiveLayoutAcl !== AclAugment) { return false; } const containers = new Set(); - SelectionManager.Views.forEach(v => containers.add(DocCast(v.Document.embedContainer))); + DocumentView.Selected().forEach(v => containers.add(DocCast(v.Document.embedContainer))); if (containers.size > 1) return false; const { left, top } = dragDocView.getBounds || { left: 0, top: 0 }; - const dragData = new DragManager.DocumentDragData( - SelectionManager.Views.map(dv => dv.Document), - dragDocView._props.dropAction - ); + const dragData = new DragManager.DocumentDragData(DocumentView.SelectedDocs(), dragDocView._props.dropAction); dragData.offset = dragDocView.screenToContentsTransform().transformDirection(e.x - left, e.y - top); dragData.moveDocument = dragDocView._props.moveDocument; dragData.removeDocument = dragDocView._props.removeDocument; @@ -206,7 +201,7 @@ export class DocumentDecorations extends ObservableReactComponent dv.ContentDiv!), + DocumentView.Selected().map(dv => dv.ContentDiv!), dragData, e.x, e.y, @@ -223,7 +218,7 @@ export class DocumentDecorations extends ObservableReactComponent { - const views = SelectionManager.Views.filter(v => v && v._props.renderDepth > 0); + const views = DocumentView.Selected().filter(v => v && v._props.renderDepth > 0); if (forceDeleteOrIconify === false && this._iconifyBatch) return; this._deleteAfterIconify = !!(forceDeleteOrIconify || this._iconifyBatch); let iconifyingCount = views.length; @@ -239,7 +234,7 @@ export class DocumentDecorations extends ObservableReactComponent { - setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [SelectionManager.Views.lastElement().Document]) ?? false, emptyFunction, this.onMaximizeClick, false, false); + setupMoveUpEvents(this, e, () => DragManager.StartWindowDrag?.(e, [DocumentView.SelectedDocs().lastElement()]) ?? false, emptyFunction, this.onMaximizeClick, false, false); e.stopPropagation(); }; onMaximizeClick = (e: any): void => { - const selectedDocs = SelectionManager.Views; + const selectedDocs = DocumentView.SelectedDocs(); if (selectedDocs.length) { if (e.ctrlKey) { // open an embedding in a new tab with Ctrl Key - CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0].Document), OpenWhereMod.right); + CollectionDockingView.AddSplit(Doc.BestEmbedding(selectedDocs[0]), OpenWhereMod.right); } else if (e.shiftKey) { // open centered in a new workspace with Shift Key - const embedding = Doc.MakeEmbedding(selectedDocs[0].Document); + const embedding = Doc.MakeEmbedding(selectedDocs[0]); embedding.embedContainer = undefined; embedding.x = -NumCast(embedding._width) / 2; embedding.y = -NumCast(embedding._height) / 2; CollectionDockingView.AddSplit(Docs.Create.FreeformDocument([embedding], { title: 'Tab for ' + embedding.title }), OpenWhereMod.right); } else if (e.altKey) { // open same document in new tab - CollectionDockingView.ToggleSplit(selectedDocs[0].Document, OpenWhereMod.right); + CollectionDockingView.ToggleSplit(selectedDocs[0], OpenWhereMod.right); } else { - let openDoc = selectedDocs[0].Document; + let openDoc = selectedDocs[0]; if (openDoc.layout_fieldKey === 'layout_icon') { openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc); Doc.deiconifyView(openDoc); } - LightboxView.Instance.SetLightboxDoc( - openDoc, - undefined, - selectedDocs.slice(1).map(view => view.Document) - ); + LightboxView.Instance.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1)); } } - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); }; onIconifyClick = (): void => { - SelectionManager.Views.forEach(dv => dv?.iconify()); - SelectionManager.DeselectAll(); + DocumentView.Selected().forEach(dv => dv?.iconify()); + DocumentView.DeselectAll(); }; - onSelectContainerDocClick = () => SelectionManager.Views?.[0]?.containerViewPath?.().lastElement()?.select(false); + onSelectContainerDocClick = () => DocumentView.Selected()?.[0]?.containerViewPath?.().lastElement()?.select(false); /** * sets up events when user clicks on the border radius editor */ @action onRadiusDown = (e: React.PointerEvent): void => { - SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]); + SnappingManager.SetIsResizing(DocumentView.SelectedDocs().lastElement()?.[Id]); this._isRounding = true; this._resizeUndo = UndoManager.StartBatch('DocDecs set radius'); setupMoveUpEvents( @@ -314,7 +305,7 @@ export class DocumentDecorations extends ObservableReactComponent { + DocumentView.SelectedDocs().forEach(doc => { const docMax = Math.min(NumCast(doc.width) / 2, NumCast(doc.height) / 2); const radius = Math.min(1, dist / maxDist) * docMax; // set radius based on ratio of drag distance to half diagonal distance of bounding box doc._layout_borderRounding = `${radius}px`; @@ -339,7 +330,7 @@ export class DocumentDecorations extends ObservableReactComponent UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => Doc.toggleLockedPosition(doc)), 'toggleBackground') + () => UndoManager.RunInBatch(() => DocumentView.Selected().forEach(dv => Doc.toggleLockedPosition(dv.Document)), 'toggleBackground') ); e.stopPropagation(); }; @@ -356,7 +347,7 @@ export class DocumentDecorations extends ObservableReactComponent { this._isRotating = true; - const seldocview = SelectionManager.Views[0]; + const seldocview = DocumentView.Selected()[0]; setupMoveUpEvents( this, e, @@ -374,11 +365,11 @@ export class DocumentDecorations extends ObservableReactComponent i.ComponentView instanceof InkingStroke); + const selectedInk = DocumentView.Selected().filter(i => i.ComponentView instanceof InkingStroke); const centerPoint = this.rotCenter.slice(); const infos = new Map(); - const seldocview = SelectionManager.Views[0]; - SelectionManager.Views.forEach(dv => { + const seldocview = DocumentView.Selected()[0]; + DocumentView.Selected().forEach(dv => { const accumRot = (NumCast(dv.Document._rotation) / 180) * Math.PI; const localRotCtr = dv.screenToViewTransform().transformPoint(rcScreen.X, rcScreen.Y); const localRotCtrOffset = [localRotCtr[0] - NumCast(dv.Document.width) / 2, localRotCtr[1] - NumCast(dv.Document.height) / 2]; @@ -387,7 +378,7 @@ export class DocumentDecorations extends ObservableReactComponent { - SelectionManager.Views.forEach( + DocumentView.Selected().forEach( action(dv => { const { unrotatedDocPos, startRotCtr, accumRot } = infos.get(dv.Document)!; const endRotCtr = Utils.rotPt(startRotCtr.x, startRotCtr.y, isAbs ? angle : accumRot + angle); @@ -437,7 +428,7 @@ export class DocumentDecorations extends ObservableReactComponent { - SnappingManager.SetIsResizing(SelectionManager.Docs.lastElement()?.[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them + SnappingManager.SetIsResizing(DocumentView.Selected().lastElement()?.Document[Id]); // turns off pointer events on things like youtube videos and web pages so that dragging doesn't get "stuck" when cursor moves over them setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, emptyFunction); e.stopPropagation(); const id = (this._resizeHdlId = e.currentTarget.className); @@ -449,7 +440,7 @@ export class DocumentDecorations extends ObservableReactComponent CollectionFreeFormView.from(docView)?.dragStarting(false, false)); + DocumentView.Selected().forEach(docView => CollectionFreeFormView.from(docView)?.dragStarting(false, false)); }; projectDragToAspect = (e: PointerEvent, docView: DocumentView, fixedAspect: number) => { @@ -467,7 +458,7 @@ export class DocumentDecorations extends ObservableReactComponent { - const first = SelectionManager.Views[0]; + const first = DocumentView.Selected()[0]; const effectiveAcl = GetEffectiveAcl(first.Document); if (!(effectiveAcl === AclAdmin || effectiveAcl === AclEdit || effectiveAcl === AclAugment)) return false; if (!first) return false; @@ -484,10 +475,10 @@ export class DocumentDecorations extends ObservableReactComponent { // resize selected docs if we're not in the middle of a resize (ie, throttle input events to frame rate) this._interactionLock = true; this._snapPt = thisPt; - e.ctrlKey && (SelectionManager.Views.forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions())); - const hasFixedAspect = SelectionManager.Docs.some(this.hasFixedAspect); + e.ctrlKey && (DocumentView.Selected().forEach(docView => !Doc.NativeHeight(docView.Document) && docView.toggleNativeDimensions())); + const hasFixedAspect = DocumentView.Selected().map(dv => dv.Document).some(this.hasFixedAspect); const scaleAspect = {x:scale.x === 1 && hasFixedAspect ? scale.y : scale.x, y: scale.x !== 1 && hasFixedAspect ? scale.x : scale.y}; - SelectionManager.Views.forEach(docView => + DocumentView.Selected().forEach(docView => this.resizeView(docView, refPt, scaleAspect, { dragHdl, ctrlKey:e.ctrlKey })); // prettier-ignore await new Promise(res => { setTimeout(() => { res(this._interactionLock = undefined)})}); }); // prettier-ignore @@ -526,7 +517,7 @@ export class DocumentDecorations extends ObservableReactComponent DocumentManager.Instance.getDocumentView(member, docView)!) + .map(member => DocumentView.getDocumentView(member, docView)!) .forEach(member => this.resizeView(member, refPt, scale, opts)); doc.xPadding = NumCast(doc.xPadding) * scale.x; doc.yPadding = NumCast(doc.yPadding) * scale.y; @@ -607,7 +598,7 @@ export class DocumentDecorations extends ObservableReactComponent { + DocumentView.Selected().forEach(view => { NumCast(view.Document._height) < 20 && (view.layoutDoc._layout_autoHeight = true); }); // need to change points for resize, or else rotation/control points will fail. @@ -626,18 +617,18 @@ export class DocumentDecorations extends ObservableReactComponent 1 ? '-multiple-' : '-unset-'; + return DocumentView.Selected().length > 1 ? '-multiple-' : '-unset-'; } @computed get rotCenter() { - const lastView = SelectionManager.Views.lastElement(); + const lastView = DocumentView.Selected().lastElement(); if (lastView) { const invXf = lastView.screenToContentsTransform().inverse(); const seldoc = lastView.layoutDoc; @@ -649,7 +640,7 @@ export class DocumentDecorations extends ObservableReactComponent { @@ -678,7 +669,7 @@ export class DocumentDecorations extends ObservableReactComponent docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) || + DocumentView.Selected().some(docView => docView.Document._dragOnlyWithinContainer || docView.Document.isGroup || docView.Document.layout_hideOpenButton) || this._isRounding || this._isRotating; const hideDeleteButton = @@ -688,7 +679,7 @@ export class DocumentDecorations extends ObservableReactComponent { + DocumentView.Selected().some(docView => { const collectionAcl = docView.containerViewPath?.()?.lastElement() ? GetEffectiveAcl(docView.containerViewPath?.().lastElement().dataDoc) : AclEdit; return collectionAcl !== AclAdmin && collectionAcl !== AclEdit && GetEffectiveAcl(docView.Document) !== AclAdmin; }); @@ -703,7 +694,7 @@ export class DocumentDecorations extends ObservableReactComponent 135; const useRotation = !hideResizers && seldocview.Document.type !== DocumentType.EQUATION && CollectionFreeFormDocumentView.from(seldocview); // when do we want an object to not rotate? - const rotation = SelectionManager.Views.length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0; + const rotation = DocumentView.Selected().length === 1 ? seldocview.screenToContentsTransform().inverse().RotateDeg : 0; // Radius constants const useRounding = seldocview.ComponentView instanceof ImageBox || seldocview.ComponentView instanceof FormattedTextBox || seldocview.ComponentView instanceof CollectionFreeFormView; @@ -773,7 +764,7 @@ export class DocumentDecorations extends ObservableReactComponent CollectionFreeFormDocumentView.from(v)); + const freeformDoc = DocumentView.Selected().some(v => CollectionFreeFormDocumentView.from(v)); return (
- SelectionManager.Views} /> + DocumentView.Selected()} />
)}
diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx index 3cb7848c2..0ea0ebd83 100644 --- a/src/client/views/FieldsDropdown.tsx +++ b/src/client/views/FieldsDropdown.tsx @@ -13,7 +13,7 @@ import Select from 'react-select'; import { Doc } from '../../fields/Doc'; import { DocOptions, FInfo } from '../documents/Documents'; import { SearchUtil } from '../util/SearchUtil'; -import { SettingsManager } from '../util/SettingsManager'; +import { SnappingManager } from '../util/SnappingManager'; import './FilterPanel.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -68,36 +68,36 @@ export class FieldsDropdown extends ObservableReactComponent ({ ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, + color: SnappingManager.userColor, + background: SnappingManager.userBackgroundColor, }), placeholder: (baseStyles /* , state */) => ({ ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, + color: SnappingManager.userColor, + background: SnappingManager.userBackgroundColor, }), input: (baseStyles /* , state */) => ({ ...baseStyles, padding: 0, margin: 0, - color: SettingsManager.userColor, + color: SnappingManager.userColor, background: 'transparent', }), option: (baseStyles, state) => ({ ...baseStyles, - color: SettingsManager.userColor, - background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor, + color: SnappingManager.userColor, + background: !state.isFocused ? SnappingManager.userBackgroundColor : SnappingManager.userVariantColor, }), menuList: (baseStyles /* , state */) => ({ ...baseStyles, - backgroundColor: SettingsManager.userBackgroundColor, + backgroundColor: SnappingManager.userBackgroundColor, }), }} placeholder={typeof this._props.placeholder === 'string' ? this._props.placeholder : this._props.placeholder?.()} diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index ed9fc8dfb..c97edd7f0 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -11,12 +11,12 @@ import { Doc, DocListCast, Field, FieldType, LinkedTo, StrListCast } from '../.. import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { RichTextField } from '../../fields/RichTextField'; -import { DocumentManager } from '../util/DocumentManager'; import { SearchUtil } from '../util/SearchUtil'; -import { SettingsManager } from '../util/SettingsManager'; +import { SnappingManager } from '../util/SnappingManager'; import { undoable } from '../util/UndoManager'; import { FieldsDropdown } from './FieldsDropdown'; import './FilterPanel.scss'; +import { DocumentView } from './nodes/DocumentView'; import { Handle, Tick, TooltipRail, Track } from './nodes/SliderBox-components'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -40,7 +40,7 @@ export class FilterPanel extends ObservableReactComponent { return this._props.Document; } @computed get targetDocChildKey() { - const targetView = DocumentManager.Instance.getFirstDocumentView(this.Document); + const targetView = DocumentView.getFirstDocumentView(this.Document); return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data'; } @computed get targetDocChildren() { @@ -299,7 +299,7 @@ export class FilterPanel extends ObservableReactComponent { .find(filter => filter.split(Doc.FilterSep)[0] === facetHeader) ?.split(Doc.FilterSep)[1] } - style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} + style={{ color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} onKeyDown={e => e.key === 'Enter' && undoable(() => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')()} /> diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 0d1573ea3..7246f0ba0 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -65,8 +65,6 @@ export class GestureOverlay extends ObservableReactComponent { - GestureOverlay.DownDocView = undefined; + DocumentView.DownDocView = undefined; if (this._points.length > 1) { const B = this.svgBounds; const points = this._points.map(p => ({ X: p.X - B.left, Y: p.Y - B.top })); @@ -388,7 +386,7 @@ export class GestureOverlay extends ObservableReactComponent KeyControlInfo; export class KeyManager { - public static Instance = new KeyManager(); + // eslint-disable-next-line no-use-before-define + public static Instance: KeyManager; private router = new Map(); constructor() { + KeyManager.Instance = this; const isMac = navigator.platform.toLowerCase().indexOf('mac') >= 0; // SHIFT CONTROL ALT META @@ -46,6 +48,16 @@ export class KeyManager { this.router.set(isMac ? '0100' : '0010', this.alt); this.router.set(isMac ? '1001' : '1100', this.ctrl_shift); this.router.set('1000', this.shift); + + window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true); + window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true); + window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers); + window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers); + window.removeEventListener('keydown', KeyManager.Instance.handle); + window.addEventListener('keydown', KeyManager.Instance.handle); + window.removeEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('keyup', KeyManager.Instance.unhandle); + window.addEventListener('paste', KeyManager.Instance.paste as any); } public unhandle = action((/* e: KeyboardEvent */) => { @@ -89,8 +101,8 @@ export class KeyManager { private handleGreedy = action((/* keyname: string */) => {}); nudge = (x: number, y: number, label: string) => { - const nudgeable = SelectionManager.Views.some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge); - nudgeable && UndoManager.RunInBatch(() => SelectionManager.Views.map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label); + const nudgeable = DocumentView.Selected().some(dv => CollectionFreeFormDocumentView.from(dv)?.nudge); + nudgeable && UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => CollectionFreeFormDocumentView.from(dv)?.nudge(x, y)), label); return { stopPropagation: nudgeable, preventDefault: nudgeable }; }; @@ -98,16 +110,16 @@ export class KeyManager { switch (keyname) { case 'u': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - const ungroupings = SelectionManager.Views; + const ungroupings = DocumentView.Selected(); undoable(() => () => ungroupings.forEach(dv => { dv.layoutDoc.group = undefined; }), 'ungroup'); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); } break; case 'g': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - const selected = SelectionManager.Views; + const selected = DocumentView.Selected(); const cv = selected.reduce((col, dv) => (!col || CollectionFreeFormView.from(dv) === col ? CollectionFreeFormView.from(dv) : undefined), undefined as undefined | CollectionFreeFormView); - cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, SelectionManager.Docs), 'grouping'); + cv && undoable(() => cv._marqueeViewRef.current?.collection(e, true, DocumentView.SelectedDocs()), 'grouping'); } break; case ' ': @@ -133,7 +145,7 @@ export class KeyManager { doDeselect = !ContextMenu.Instance.closeMenu(); } if (doDeselect) { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); LightboxView.Instance.SetLightboxDoc(undefined); } // DictationManager.Controls.stop(); @@ -153,7 +165,7 @@ export class KeyManager { if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { if (LightboxView.LightboxDoc) { LightboxView.Instance.SetLightboxDoc(undefined); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true); return { stopPropagation: true, preventDefault: true }; } @@ -179,15 +191,15 @@ export class KeyManager { case 'arrowdown': return this.nudge(0, 10, 'nudge down'); case 'u' : if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = undefined}), 'unggroup'); - SelectionManager.DeselectAll(); + UndoManager.RunInBatch(() => DocumentView.Selected().map(dv => dv.Document).forEach(doc => {doc.group = undefined}), 'unggroup'); + DocumentView.DeselectAll(); } break; case 'g': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { const randomGroup = random(0, 1000); - UndoManager.RunInBatch(() => SelectionManager.Docs.forEach(doc => {doc.group = randomGroup}), 'group'); - SelectionManager.DeselectAll(); + UndoManager.RunInBatch(() => DocumentView.Selected().forEach(dv => {dv.Document.group = randomGroup}), 'group'); + DocumentView.DeselectAll(); } break; default: @@ -206,7 +218,7 @@ export class KeyManager { switch (keyname) { case 'ƒ': case 'f': - UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(SelectionManager.Views?.[0])?.float(), 'float'); + UndoManager.RunInBatch(() => CollectionFreeFormDocumentView.from(DocumentView.Selected()?.[0])?.float(), 'float'); break; default: } @@ -259,8 +271,8 @@ export class KeyManager { } break; case 'f': - if (SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView?.search) { - SelectionManager.Views[0].ComponentView?.search?.('', false, false); + if (DocumentView.Selected().length === 1 && DocumentView.Selected()[0].ComponentView?.search) { + DocumentView.Selected()[0].ComponentView?.search?.('', false, false); } else { const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher); if (searchBtn) { @@ -279,14 +291,14 @@ export class KeyManager { break; case 'y': if (Doc.ActivePage !== 'home') { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); UndoManager.Redo(); } stopPropagation = false; break; case 'z': if (Doc.ActivePage !== 'home') { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); UndoManager.Undo(); } stopPropagation = false; @@ -302,11 +314,17 @@ export class KeyManager { preventDefault = false; break; case 'x': - if (SelectionManager.Views.length) { + if (DocumentView.Selected().length) { const bds = DocumentDecorations.Instance.Bounds; - const pt = SelectionManager.Views[0].screenToViewTransform().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); + const pt = DocumentView.Selected()[0] // + .screenToViewTransform() + .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); + const text = + `__DashDocId(${pt?.[0] || 0},${pt?.[1] || 0}):` + // + DocumentView.Selected() + .map(dv => dv.Document[Id]) + .join(':'); // prettier-ignore + DocumentView.Selected().length && navigator.clipboard.writeText(text); DocumentDecorations.Instance.onCloseClick(true); stopPropagation = false; preventDefault = false; @@ -315,9 +333,15 @@ export class KeyManager { case 'c': if ((document.activeElement as any)?.type !== 'text' && !AnchorMenu.Instance.Active && DocumentDecorations.Instance.Bounds.r - DocumentDecorations.Instance.Bounds.x > 2) { const bds = DocumentDecorations.Instance.Bounds; - const pt = SelectionManager.Views[0].screenToViewTransform().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); + const pt = DocumentView.Selected()[0] + .screenToViewTransform() + .transformPoint(bds.x + (bds.r - bds.x) / 2, bds.y + (bds.b - bds.y) / 2); // prettier-ignore + const text = + `__DashCloneId(${pt?.[0] || 0},${pt?.[1] || 0}):` + + DocumentView.SelectedDocs() + .map(doc => doc[Id]) + .join(':'); // prettier-ignore + DocumentView.Selected().length && navigator.clipboard.writeText(text); stopPropagation = false; } preventDefault = false; @@ -336,7 +360,7 @@ export class KeyManager { if (!plain) return; const clone = plain.startsWith('__DashCloneId('); const docids = plain.split(':'); // hack! docids[0] is the top left of the selection rectangle - const addDocument = SelectionManager.Views.lastElement()?.ComponentView?.addDocument; + const addDocument = DocumentView.Selected().lastElement()?.ComponentView?.addDocument; if (addDocument && (plain.startsWith('__DashDocId(') || clone)) { Doc.Paste(docids.slice(1), clone, addDocument); } diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index b4fe44733..a49923ffb 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -7,7 +7,6 @@ import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { Cast } from '../../fields/Types'; import { returnFalse, setupMoveUpEvents } from '../../ClientUtils'; -import { SelectionManager } from '../util/SelectionManager'; import { UndoManager } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; @@ -15,6 +14,7 @@ import { InkStrokeProperties } from './InkStrokeProperties'; import { SnappingManager } from '../util/SnappingManager'; import { ObservableReactComponent } from './ObservableReactComponent'; import { PointData } from '../../pen-gestures/GestureTypes'; +import { DocumentView } from './nodes/DocumentView'; export interface InkControlProps { inkDoc: Doc; @@ -224,8 +224,8 @@ export class InkEndPtHandles extends ObservableReactComponent { const v1n = { X: v1.X / v1len, Y: v1.Y / v1len }; const v2n = { X: v2.X / v2len, Y: v2.Y / v2len }; const angle = Math.acos(v1n.X * v2n.X + v1n.Y * v2n.Y) * Math.sign(v1.X * v2.Y - v2.X * v1.Y); - InkStrokeProperties.Instance.stretchInk(SelectionManager.Views, scaling, p2, v1n, moveEv.shiftKey); - InkStrokeProperties.Instance.rotateInk(SelectionManager.Views, angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call + InkStrokeProperties.Instance.stretchInk(DocumentView.Selected(), scaling, p2, v1n, moveEv.shiftKey); + InkStrokeProperties.Instance.rotateInk(DocumentView.Selected(), angle, pt2()); // bcz: call pt2() func here because pt2 will have changed from previous stretchInk call return false; }), action(() => { diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 109a9cad4..3920ecc2a 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -9,7 +9,6 @@ import { Cast, NumCast } from '../../fields/Types'; import { PointData } from '../../pen-gestures/GestureTypes'; import { Point } from '../../pen-gestures/ndollar'; import { DocumentType } from '../documents/DocumentTypes'; -import { DocumentManager } from '../util/DocumentManager'; import { undoBatch } from '../util/UndoManager'; import { FitOneCurve } from '../util/bezierFit'; import { InkingStroke } from './InkingStroke'; @@ -386,7 +385,7 @@ export class InkStrokeProperties { containingCollection?.childDocs .filter(doc => doc.type === DocumentType.INK) .forEach(doc => { - const testInkView = DocumentManager.Instance.getDocumentView(doc, containingDocView); + const testInkView = DocumentView.getDocumentView(doc, containingDocView); const snapped = testInkView?.ComponentView?.snapPt?.(screenDragPt, doc === inkView.Document ? this.excludeSelfSnapSegs(ink, controlIndex) : []); if (snapped && snapped.distance < snapData.distance) { const snappedInkPt = doc === inkView.Document ? 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 diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 7e2b334af..1ed8de1be 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -6,7 +6,6 @@ // import { Cast, DateCast, NumCast } from '../../fields/Types'; // import { aggregateBounds } from '../../Utils'; // import { DocumentType } from '../documents/DocumentTypes'; -// import { DocumentManager } from '../util/DocumentManager'; // import { CollectionFreeFormView } from './collections/collectionFreeForm'; // import { InkingStroke } from './InkingStroke'; // import './InkTranscription.scss'; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 884512539..62fc73c78 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -44,7 +44,7 @@ import { InkTangentHandles } from './InkTangentHandles'; import { FieldView, FieldViewProps } from './nodes/FieldView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { PinDocView, PinProps } from './PinFuncs'; -import { StyleProp } from './StyleProvider'; +import { StyleProp } from './StyleProp'; const { INK_MASK_SIZE } = require('./global/globalCssVariables.module.scss'); // prettier-ignore diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 020525ef8..12d899388 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -13,10 +13,6 @@ import { CreateLinkToActiveAudio, Doc, DocListCast, FieldResult, Opt } from '../ import { Id } from '../../fields/FieldSymbols'; import { InkTool } from '../../fields/InkField'; import { Cast, NumCast, toList } from '../../fields/Types'; -import { DocumentManager } from '../util/DocumentManager'; -import { LinkManager } from '../util/LinkManager'; -import { SelectionManager } from '../util/SelectionManager'; -import { SettingsManager } from '../util/SettingsManager'; import { SnappingManager } from '../util/SnappingManager'; import { Transform } from '../util/Transform'; import { GestureOverlay } from './GestureOverlay'; @@ -24,8 +20,6 @@ import './LightboxView.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; import { CollectionDockingView } from './collections/CollectionDockingView'; -import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; -import { TabDocView } from './collections/TabDocView'; import { DocumentView } from './nodes/DocumentView'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; @@ -90,7 +84,7 @@ export class LightboxView extends ObservableReactComponent { }); const l = CreateLinkToActiveAudio(() => doc).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); - CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); + DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); this._history.push({ doc, target }); } else { this._future = []; @@ -98,14 +92,14 @@ export class LightboxView extends ObservableReactComponent { Doc.ActiveTool = InkTool.None; SnappingManager.SetExploreMode(false); } - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); if (future) { this._future.push( ...(this._doc ? [this._doc] : []), ...future .slice() .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)) - .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length) + .sort((a, b) => Doc.Links(a).length - Doc.Links(b).length) ); } this._doc = doc; @@ -134,11 +128,11 @@ export class LightboxView extends ObservableReactComponent { const lightDoc = this._doc; if (!lightDoc) return; const target = (this._docTarget = this._future.pop()); - const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); + const targetDocView = target && DocumentView.getLightboxDocumentView(target); if (targetDocView && target) { const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); - DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); + DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); if (this._history.lastElement().target !== target) this._history.push({ doc: lightDoc, target }); } else if (!target && this._path.length) { savedKeys.forEach(key => { @@ -157,10 +151,10 @@ export class LightboxView extends ObservableReactComponent { return; } const { doc, target } = this._history.lastElement(); - const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); + const docView = DocumentView.getLightboxDocumentView(target || doc); if (docView) { this._docTarget = target; - target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); + target && DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); } else { this.SetLightboxDoc(doc, target); } @@ -178,8 +172,8 @@ export class LightboxView extends ObservableReactComponent { if (this._docTarget) { const fieldKey = Doc.LayoutFieldKey(this._docTarget); const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])]; - const links = LinkManager.Links(this._docTarget) - .map(link => LinkManager.getOppositeAnchor(link, this._docTarget!)!) + const links = Doc.Links(this._docTarget) + .map(link => Doc.getOppositeAnchor(link, this._docTarget!)!) .filter(doc => doc); this.SetLightboxDoc(this._docTarget, undefined, contents.length ? contents : links); } @@ -220,7 +214,7 @@ export class LightboxView extends ObservableReactComponent {
{ e.stopPropagation(); click(); @@ -237,8 +231,8 @@ export class LightboxView extends ObservableReactComponent {
} @@ -252,7 +246,7 @@ export class LightboxView extends ObservableReactComponent { return !this._doc ? null : (
{ downx = e.clientX; downy = e.clientY; @@ -266,7 +260,7 @@ export class LightboxView extends ObservableReactComponent { width: this.lightboxWidth(), height: this.lightboxHeight(), clipPath: `path('${Doc.UserDoc().renderStyle === 'comic' ? wavyBorderPath(this.lightboxWidth(), this.lightboxHeight()) : undefined}')`, - background: SettingsManager.userBackgroundColor, + background: SnappingManager.userBackgroundColor, }}> { removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} addDocTab={this.AddDocTab} - pinToPres={TabDocView.PinDoc} + pinToPres={DocumentView.PinDoc} focus={emptyFunction} /> diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 01f3b032e..b6dfed687 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -8,14 +8,17 @@ import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import { AssignAllExtensions } from '../../extensions/Extensions'; import { FieldLoader } from '../../fields/FieldLoader'; +import { BranchingTrailManager } from '../util/BranchingTrailManager'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { PingManager } from '../util/PingManager'; import { ReplayMovements } from '../util/ReplayMovements'; import { TrackMovements } from '../util/TrackMovements'; +import { KeyManager } from './GlobalKeyHandler'; +import { MainView } from './MainView'; import { CollectionView } from './collections/CollectionView'; +import { CollectionFreeFormInfoUI } from './collections/collectionFreeForm/CollectionFreeFormInfoUI'; import './global/globalScripts'; -import { MainView } from './MainView'; -import { BranchingTrailManager } from '../util/BranchingTrailManager'; +import { LinkFollower } from '../util/LinkFollower'; dotenv.config(); @@ -58,6 +61,11 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; new ReplayMovements(); new BranchingTrailManager({}); new PingManager(); + new KeyManager(); + + // iniitialize plugin apis + CollectionFreeFormInfoUI.Init(); + LinkFollower.Init(); root.render(); }, 0); })(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index aec553baa..2c7d3d32a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -12,7 +12,8 @@ import '../../../node_modules/browndash-components/dist/styles/global.min.css'; import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc'; -import { DocData } from '../../fields/DocSymbols'; +import { DocData, DocViews } from '../../fields/DocSymbols'; +import { Id } from '../../fields/FieldSymbols'; import { DocCast, StrCast, toList } from '../../fields/Types'; import { DocServer } from '../DocServer'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; @@ -29,7 +30,6 @@ import { Hypothesis } from '../util/HypothesisUtils'; import { UPDATE_SERVER_CACHE } from '../util/LinkManager'; import { RTFMarkup } from '../util/RTFMarkup'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { SelectionManager } from '../util/SelectionManager'; import { ServerStats } from '../util/ServerStats'; import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; @@ -42,7 +42,6 @@ import { DashboardView } from './DashboardView'; import { DictationOverlay } from './DictationOverlay'; import { DocumentDecorations } from './DocumentDecorations'; import { GestureOverlay } from './GestureOverlay'; -import { KeyManager } from './GlobalKeyHandler'; import { LightboxView } from './LightboxView'; import './MainView.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; @@ -160,7 +159,7 @@ export class MainView extends ObservableReactComponent<{}> { leftMenuHeight = () => this._dashUIHeight; leftMenuFlyoutWidth = () => this._leftMenuFlyoutWidth; leftMenuFlyoutHeight = () => this._dashUIHeight; - propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SettingsManager.Instance?.propertiesWidth || 0)); + propertiesWidth = () => Math.max(0, Math.min(this._dashUIWidth - 50, SnappingManager.PropertiesWidth || 0)); propertiesHeight = () => this._dashUIHeight; mainDocViewWidth = () => this._dashUIWidth - this.propertiesWidth() - this.leftMenuWidth() - this.leftMenuFlyoutWidth(); mainDocViewHeight = () => this._dashUIHeight - this.headerBarDocHeight(); @@ -169,7 +168,7 @@ export class MainView extends ObservableReactComponent<{}> { // Utils.TraceConsoleLog(); reaction( // when a multi-selection occurs, remove focus from all active elements to allow keyboad input to go only to global key manager to act upon selection - () => SelectionManager.Views.slice(), + () => DocumentView.Selected().slice(), views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur() ); const scriptTag = document.createElement('script'); @@ -231,37 +230,29 @@ export class MainView extends ObservableReactComponent<{}> { tag.src = 'https://www.youtube.com/iframe_api'; const firstScriptTag = document.getElementsByTagName('script')[0]; firstScriptTag.parentNode!.insertBefore(tag, firstScriptTag); - window.removeEventListener('keydown', KeyManager.Instance.handleModifiers, true); - window.addEventListener('keydown', KeyManager.Instance.handleModifiers, true); - window.removeEventListener('keyup', KeyManager.Instance.unhandleModifiers); - window.addEventListener('keyup', KeyManager.Instance.unhandleModifiers); - window.removeEventListener('keydown', KeyManager.Instance.handle); - window.addEventListener('keydown', KeyManager.Instance.handle); - window.removeEventListener('keyup', KeyManager.Instance.unhandle); - window.addEventListener('keyup', KeyManager.Instance.unhandle); - window.addEventListener('paste', KeyManager.Instance.paste as any); document.addEventListener('dash', (e: any) => { // event used by chrome plugin to tell Dash which document to focus on const id = GetDocFromUrl(e.detail); - DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentManager.Instance.showDocument(doc, { willPan: false }) : null)); + DocServer.GetRefField(id).then(doc => (doc instanceof Doc ? DocumentView.showDocument(doc, { willPan: false }) : null)); }); document.addEventListener('linkAnnotationToDash', Hypothesis.linkListener); this.initEventListeners(); } componentWillUnMount() { - window.removeEventListener('keyup', KeyManager.Instance.unhandle); - window.removeEventListener('keydown', KeyManager.Instance.handle); - window.removeEventListener('pointerdown', this.globalPointerDown, true); - window.removeEventListener('pointermove', this.globalPointerMove, true); - window.removeEventListener('pointerup', this.globalPointerClick, true); - window.removeEventListener('paste', KeyManager.Instance.paste as any); - document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); + // window.removeEventListener('keyup', KeyManager.Instance.unhandle); + // window.removeEventListener('keydown', KeyManager.Instance.handle); + // window.removeEventListener('pointerdown', this.globalPointerDown, true); + // window.removeEventListener('pointermove', this.globalPointerMove, true); + // window.removeEventListener('pointerup', this.globalPointerClick, true); + // window.removeEventListener('paste', KeyManager.Instance.paste as any); + // document.removeEventListener('linkAnnotationToDash', Hypothesis.linkListener); } constructor(props: any) { super(props); makeObservable(this); + CollectionDockingView.setTabJSXComponent(TabDocView); DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl; MainView.Instance = this; DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); @@ -592,7 +583,7 @@ export class MainView extends ObservableReactComponent<{}> { if (!e.cancelBubble) { const pathstr = (e as any)?.path?.map((p: any) => p.classList?.toString()).join(); if (pathstr?.includes('libraryFlyout')) { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); } } }, @@ -719,14 +710,14 @@ export class MainView extends ObservableReactComponent<{}> { this, e, action(() => { - SettingsManager.Instance.propertiesWidth = Math.max(0, this._dashUIWidth - e.clientX); - return !SettingsManager.Instance.propertiesWidth; + SnappingManager.SetPropertiesWidth(Math.max(0, this._dashUIWidth - e.clientX)); + return !SnappingManager.PropertiesWidth; }), action(() => { - SettingsManager.Instance.propertiesWidth < 5 && (SettingsManager.Instance.propertiesWidth = 0); + SnappingManager.PropertiesWidth < 5 && SnappingManager.SetPropertiesWidth(0); }), action(() => { - SettingsManager.Instance.propertiesWidth = this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0; + SnappingManager.SetPropertiesWidth(this.propertiesWidth() < 15 ? Math.min(this._dashUIWidth - 50, 250) : 0); }), false ); @@ -776,7 +767,7 @@ export class MainView extends ObservableReactComponent<{}> { Document={this._sidebarContent.proto || this._sidebarContent} addDocument={undefined} addDocTab={DocumentViewInternal.addDocTabFunc} - pinToPres={TabDocView.PinDoc} + pinToPres={DocumentView.PinDoc} containerViewPath={returnEmptyDoclist} styleProvider={this._sidebarContent.proto === Doc.MyDashboards || this._sidebarContent.proto === Doc.MyFilesystem || this._sidebarContent.proto === Doc.MyTrails ? DashboardStyleProvider : DefaultStyleProvider} removeDocument={returnFalse} @@ -799,7 +790,7 @@ export class MainView extends ObservableReactComponent<{}> { @computed get leftMenuPanel() { return ( -
+
{ {this.flyout}
- +
{this.dockingContent} @@ -864,11 +855,11 @@ export class MainView extends ObservableReactComponent<{}> { className={`mainView-propertiesDragger${this.propertiesWidth() < 10 ? '-minified' : ''}`} key="props" onPointerDown={this.onPropertiesPointerDown} - style={{ background: SettingsManager.userVariantColor, right: this.propertiesWidth() - 1 }}> - + style={{ background: SnappingManager.userVariantColor, right: this.propertiesWidth() - 1 }}> +
)} -
+
@@ -911,11 +902,11 @@ export class MainView extends ObservableReactComponent<{}> { // setTimeout(action(() => (this._leftMenuFlyoutWidth += 0.5))); this._sidebarContent.proto = DocCast(button.target); - SettingsManager.Instance.LastPressedBtn = button; + SnappingManager.SetLastPressedBtn(button[Id]); }); closeFlyout = action(() => { - SettingsManager.Instance.LastPressedBtn = undefined; + SnappingManager.SetLastPressedBtn(''); this._panelContent = 'none'; this._sidebarContent.proto = undefined; this._leftMenuFlyoutWidth = 0; @@ -933,7 +924,7 @@ export class MainView extends ObservableReactComponent<{}> { @computed get docButtons() { return !Doc.MyDockedBtns ? null : ( -
+
{ ); } @computed get snapLines() { - const dragged = DragManager.docsBeingDragged.lastElement() ?? SelectionManager.Docs.lastElement(); - const dragPar = dragged ? CollectionFreeFormView.from(DocumentManager.Instance.getDocumentView(dragged)) : undefined; + const dragged = DragManager.docsBeingDragged.lastElement() ?? DocumentView.SelectedDocs().lastElement(); + const dragPar = dragged ? CollectionFreeFormView.from(Array.from(dragged[DocViews]).lastElement()) : undefined; return !dragPar?.layoutDoc.freeform_snapLines ? null : (
@@ -1010,14 +1001,22 @@ export class MainView extends ObservableReactComponent<{}> { ); } + togglePropertiesFlyout = () => { + if (MainView.Instance.propertiesWidth() > 0) { + SnappingManager.SetPropertiesWidth(0); + } else { + SnappingManager.SetPropertiesWidth(300); + } + }; + lightboxMaxBorder = [200, 50]; render() { return (
(ele => { @@ -1067,7 +1066,7 @@ export class MainView extends ObservableReactComponent<{}> { case 'dashboard': default: return (<>
- +
{this.mainDashboardArea} ); @@ -1086,7 +1085,7 @@ export class MainView extends ObservableReactComponent<{}> { {/* */} {this.snapLines} - + {LightboxView.LightboxDoc ? null : } diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 0f13de9e5..a917a7d57 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -227,7 +227,6 @@ export class MarqueeAnnotator extends ObservableReactComponent }; render() { - return LightboxView.LightboxDoc ? null : ( + return (
{this._props.overlayOptions.title || 'Untitled'} @@ -181,7 +180,7 @@ export class OverlayView extends ObservableReactComponent<{}> { docScreenToLocalXf = computedFn((doc: Doc) => () => new Transform(-NumCast(doc.overlayX), -NumCast(doc.overlayY), 1)); @computed get overlayDocs() { - return Doc.MyOverlayDocs.filter(d => !LightboxView.LightboxDoc || d.type === DocumentType.PRES).map(d => { + return Doc.MyOverlayDocs.map(d => { let [offsetx, offsety] = [0, 0]; const dref = React.createRef(); const onPointerMove = action((e: PointerEvent, down: number[]) => { diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index cbbf48c75..edf6df2b9 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -20,11 +20,8 @@ import { DocData } from '../../fields/DocSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, ScriptCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; -import { DocUtils } from '../documents/DocUtils'; +import { DocUtils, IsFollowLinkScript } from '../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; -import { IsFollowLinkScript } from '../util/LinkFollower'; -import { LinkManager } from '../util/LinkManager'; -import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { undoBatch, undoable } from '../util/UndoManager'; import { InkingStroke } from './InkingStroke'; @@ -40,13 +37,13 @@ export class PropertiesButtons extends React.Component<{}, {}> { @observable public static Instance: PropertiesButtons; @computed get selectedDoc() { - return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.Document; + return DocumentView.SelectedSchemaDoc() || DocumentView.Selected().lastElement()?.Document; } @computed get selectedLayoutDoc() { - return SelectionManager.SelectedSchemaDoc || SelectionManager.Views.lastElement()?.layoutDoc; + return DocumentView.SelectedSchemaDoc() || DocumentView.Selected().lastElement()?.layoutDoc; } @computed get selectedTabView() { - return !SelectionManager.SelectedSchemaDoc && SelectionManager.Views.lastElement()?.topMost; + return !DocumentView.SelectedSchemaDoc() && DocumentView.Selected().lastElement()?.topMost; } @computed get titleButton() { @@ -164,7 +161,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { // on => `${on ? 'Set' : 'Remove'} lightbox flag`, // on => 'window-restore', // onClick => { - // SelectionManager.Views.forEach(dv => { + // DocumentView.Selected().forEach(dv => { // const containerDoc = dv.Document; // //containerDoc.followAllLinks = // // containerDoc.noShadow = @@ -320,14 +317,14 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch handlePerspectiveChange = (e: any) => { this.selectedDoc && (this.selectedDoc._type_collection = e.target.value); - SelectionManager.Views.forEach(docView => { + DocumentView.Selected().forEach(docView => { docView.layoutDoc._type_collection = e.target.value; }); }; @computed get onClickVal() { const linkButton = IsFollowLinkScript(this.selectedDoc.onClick); const followLoc = this.selectedDoc._followLinkLocation; - const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); + const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); if (followLoc === OpenWhere.lightbox && !linkedToLightboxView()) return 'linkInPlace'; if (linkButton && followLoc === OpenWhere.addRight) return 'linkOnRight'; @@ -381,7 +378,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action handleOptionChange = (onClick: string) => { - SelectionManager.Views.forEach(docView => { + DocumentView.Selected().forEach(docView => { const linkButton = IsFollowLinkScript(docView.Document.onClick); docView.noOnClick(); switch (onClick) { @@ -417,7 +414,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const click = () => this.handleOptionChange(value[0]); const linkButton = IsFollowLinkScript(this.selectedDoc.onClick); const followLoc = this.selectedDoc._followLinkLocation; - const linkedToLightboxView = () => LinkManager.Links(this.selectedDoc).some(link => LinkManager.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); + const linkedToLightboxView = () => Doc.Links(this.selectedDoc).some(link => Doc.getOppositeAnchor(link, this.selectedDoc)?._isLightbox); let active = false; // prettier-ignore @@ -451,7 +448,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { } @undoBatch editOnClickScript = () => { - if (SelectionManager.Views.length) SelectionManager.Views.forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick')); + if (DocumentView.Selected().length) DocumentView.Selected().forEach(dv => DocUtils.makeCustomViewClicked(dv.Document, undefined, 'onClick')); else this.selectedDoc && DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, 'onClick'); }; @@ -472,8 +469,8 @@ export class PropertiesButtons extends React.Component<{}, {}> { fillWidth toggleType={ToggleType.BUTTON} onClick={undoable(() => { - if (SelectionManager.Views.length > 1) { - SelectionManager.Views.forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property)); + if (DocumentView.Selected().length > 1) { + DocumentView.Selected().forEach(dv => (onClick ?? onPropToggle)(dv, dv.Document, property)); } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property); }, property)} /> @@ -482,7 +479,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { render() { const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)]; - const isText = SelectionManager.Views.lastElement()?.ComponentView instanceof FormattedTextBox; + const isText = DocumentView.Selected().lastElement()?.ComponentView instanceof FormattedTextBox; const isInk = this.selectedDoc?.layout_isSvg; const isImage = layoutField instanceof ImageField; const isMap = this.selectedDoc?.type === DocumentType.MAP; diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index a806995f2..edb55f341 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -7,12 +7,12 @@ import { Doc } from '../../fields/Doc'; import { Cast } from '../../fields/Types'; import { DocumentType } from '../documents/DocumentTypes'; import { LinkManager } from '../util/LinkManager'; -import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import './PropertiesDocBacklinksSelector.scss'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { LinkMenu } from './linking/LinkMenu'; import { OpenWhere } from './nodes/OpenWhere'; +import { DocumentView } from './nodes/DocumentView'; type PropertiesDocBacklinksSelectorProps = { Document: Doc; @@ -25,7 +25,7 @@ type PropertiesDocBacklinksSelectorProps = { export class PropertiesDocBacklinksSelector extends React.Component { getOnClick = action((link: Doc) => { const linkAnchor = this.props.Document; - const other = LinkManager.getOppositeAnchor(link, linkAnchor); + const other = Doc.getOppositeAnchor(link, linkAnchor); const otherdoc = !other ? undefined : other.annotationOn && other.type !== DocumentType.RTF ? Cast(other.annotationOn, Doc, null) : other; LinkManager.Instance.currentLink = link; if (otherdoc) { @@ -36,10 +36,10 @@ export class PropertiesDocBacklinksSelector extends React.Component {this.props.hideTitle ? null :

Contexts:

} - +
); } diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 722b1f90a..7465c727a 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -7,11 +7,10 @@ import * as React from 'react'; import { Doc } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { Cast, StrCast } from '../../fields/Types'; -import { DocFocusOrOpen } from '../util/DocumentManager'; import { ObservableReactComponent } from './ObservableReactComponent'; import './PropertiesDocContextSelector.scss'; import { CollectionDockingView } from './collections/CollectionDockingView'; -import { DocumentView } from './nodes/DocumentView'; +import { DocFocusOrOpen, DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; type PropertiesDocContextSelectorProps = { diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index ff5dcd1b8..7c16e0ddb 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -23,10 +23,8 @@ import { ComputedField } from '../../fields/ScriptField'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl, SharingPermissions, normalizeEmail } from '../../fields/util'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; -import { DocumentManager } from '../util/DocumentManager'; import { GroupManager } from '../util/GroupManager'; import { LinkManager } from '../util/LinkManager'; -import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { SharingManager } from '../util/SharingManager'; import { Transform } from '../util/Transform'; @@ -40,7 +38,7 @@ import { PropertiesDocBacklinksSelector } from './PropertiesDocBacklinksSelector import { PropertiesDocContextSelector } from './PropertiesDocContextSelector'; import { PropertiesSection } from './PropertiesSection'; import './PropertiesView.scss'; -import { DefaultStyleProvider } from './StyleProvider'; +import { DefaultStyleProvider, SetFilterOpener as SetPropertiesFilterOpener } from './StyleProvider'; import { DocumentView } from './nodes/DocumentView'; import { StyleProviderFuncType } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; @@ -66,6 +64,12 @@ export class PropertiesView extends ObservableReactComponent { + this.CloseAll(); + this.openFilters = true; + }) + ); } @computed get MAX_EMBED_HEIGHT() { @@ -73,7 +77,7 @@ export class PropertiesView extends ObservableReactComponent(reqdKeys); - const docs: Doc[] = SelectionManager.Views.length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); + const docs: Doc[] = + DocumentView.Selected().length < 2 // + ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] + : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); docs.forEach(doc => Object.keys(doc) .filter(filter) @@ -237,7 +242,12 @@ export class PropertiesView extends ObservableReactComponent { - 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 = + DocumentView.Selected().length < 2 && this.selectedDoc + ? [this.layoutFields + ? Doc.Layout(this.selectedDoc) // + : this.dataDoc!] + : DocumentView.Selected().map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); // prettier-ignore docs.forEach(doc => { if (value.indexOf(':') !== -1) { const newVal = value[0].toUpperCase() + value.substring(1, value.length); @@ -295,13 +305,13 @@ export class PropertiesView extends ObservableReactComponent counter++); + Doc.Links(selAnchor).forEach(() => counter++); return counter; } @computed get layoutPreview() { - if (SelectionManager.Views.length > 1) { + if (DocumentView.Selected().length > 1) { return '-- multiple selected --'; } if (this.selectedDoc) { @@ -348,7 +358,7 @@ export class PropertiesView extends ObservableReactComponent { - const docs = SelectionManager.Views.length < 2 ? [this.selectedDoc] : SelectionManager.Views.map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc)); + const docs = DocumentView.Selected().length < 2 ? [this.selectedDoc] : DocumentView.Selected().map(dv => (this.layoutDocAcls ? dv.layoutDoc : dv.dataDoc)); SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs, this.layoutDocAcls); }; @@ -462,7 +472,7 @@ export class PropertiesView extends ObservableReactComponent docView.Document); + const docs = DocumentView.Selected().length < 2 && this.selectedDoc ? [this.selectedDoc] : DocumentView.SelectedDocs(); const target = docs[0]; const showAdmin = GetEffectiveAcl(target) === AclAdmin; @@ -580,7 +590,7 @@ export class PropertiesView extends ObservableReactComponent(); - SelectionManager.Views.forEach(dv => titles.add(StrCast(dv.Document.title))); + DocumentView.Selected().forEach(dv => titles.add(StrCast(dv.Document.title))); const title = Array.from(titles.keys()).length > 1 ? '--multiple selected--' : StrCast(this.selectedDoc?.title); return (
@@ -631,8 +641,8 @@ export class PropertiesView extends ObservableReactComponent { - if (SelectionManager.Views.length > 1) { - SelectionManager.Views.map(dv => Doc.SetInPlace(dv.Document, 'title', value, true)); + if (DocumentView.Selected().length > 1) { + DocumentView.Selected().map(dv => Doc.SetInPlace(dv.Document, 'title', value, true)); } else if (this.dataDoc) { if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, 'title', value, true); else KeyValueBox.SetField(this.dataDoc, 'title', value as string, true); @@ -1365,14 +1375,14 @@ export class PropertiesView extends ObservableReactComponent any = val => val) => { @@ -1435,7 +1445,7 @@ export class PropertiesView extends ObservableReactComponent diff --git a/src/client/views/ScriptBox.tsx b/src/client/views/ScriptBox.tsx index 8de359e07..9c36e6d26 100644 --- a/src/client/views/ScriptBox.tsx +++ b/src/client/views/ScriptBox.tsx @@ -122,14 +122,14 @@ export class ScriptBox extends React.Component { if (!text) { doc[DocData][fieldKey] = undefined; } else { - const script = CompileScript(text, { + const compScript = CompileScript(text, { params: { this: Doc.name, ...contextParams }, typecheck: false, editable: true, transformer: DocumentIconContainer.getTransformer(), }); - if (!script.compiled) { - onError(script.errors.map(error => error.messageText).join('\n')); + if (!compScript.compiled) { + onError(compScript.errors.map(error => error.messageText).join('\n')); return; } @@ -143,7 +143,7 @@ export class ScriptBox extends React.Component { div.innerHTML = 'button'; params.length && DragManager.StartButtonDrag([div], text, doc.title + '-instance', {}, params, () => {}, clientX, clientY); - doc[DocData][fieldKey] = new ScriptField(script); + doc[DocData][fieldKey] = new ScriptField(compScript); overlayDisposer(); } }} diff --git a/src/client/views/ScriptingRepl.tsx b/src/client/views/ScriptingRepl.tsx index ba2e22b3b..1a2eb460f 100644 --- a/src/client/views/ScriptingRepl.tsx +++ b/src/client/views/ScriptingRepl.tsx @@ -5,15 +5,15 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { DocumentManager } from '../util/DocumentManager'; import { CompileScript, Transformer, ts } from '../util/Scripting'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { SettingsManager } from '../util/SettingsManager'; +import { SnappingManager } from '../util/SnappingManager'; import { undoable } from '../util/UndoManager'; import { ObservableReactComponent } from './ObservableReactComponent'; import { OverlayView } from './OverlayView'; import './ScriptingRepl.scss'; import { DocumentIconContainer } from './nodes/DocumentIcon'; +import { DocumentView } from './nodes/DocumentView'; interface replValueProps { scrollToBottom: () => void; @@ -169,7 +169,7 @@ export class ScriptingRepl extends ObservableReactComponent<{}> { case 'Enter': { e.stopPropagation(); const docGlobals: { [name: string]: any } = {}; - DocumentManager.Instance.DocumentViews.forEach((dv, i) => { + DocumentView.allViews().forEach((dv, i) => { docGlobals[`d${i}`] = dv.Document; }); const globals = ScriptingGlobals.makeMutableGlobalsCopy(docGlobals); @@ -262,13 +262,13 @@ export class ScriptingRepl extends ObservableReactComponent<{}> { render() { return (
-
+
{this.commands.map(({ command, result }, i) => ( -
-
+
+
{command ||
}
-
+
@@ -276,7 +276,7 @@ export class ScriptingRepl extends ObservableReactComponent<{}> {
data.split(':')[0]) @@ -187,8 +185,8 @@ export class SidebarAnnos extends ObservableReactComponent 'title'; setHeightCallback = (height: number) => this._props.setHeight?.(height + this.filtersHeight()); sortByLinkAnchorY = (a: Doc, b: Doc) => { - const ay = LinkManager.Links(a).length && DocCast(LinkManager.Links(a)[0].link_anchor_1).y; - const by = LinkManager.Links(b).length && DocCast(LinkManager.Links(b)[0].link_anchor_1).y; + const ay = Doc.Links(a).length && DocCast(Doc.Links(a)[0].link_anchor_1).y; + const by = Doc.Links(b).length && DocCast(Doc.Links(b)[0].link_anchor_1).y; return NumCast(ay) - NumCast(by); }; render() { diff --git a/src/client/views/StyleProp.ts b/src/client/views/StyleProp.ts new file mode 100644 index 000000000..dd5b98cfe --- /dev/null +++ b/src/client/views/StyleProp.ts @@ -0,0 +1,24 @@ +export enum StyleProp { + TreeViewIcon = 'treeView_Icon', + TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items + DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view + Opacity = 'opacity', // opacity of the document view + BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views + BorderRounding = 'borderRounding', // border radius of the document view + Color = 'color', // foreground color of Document view items + BackgroundColor = 'backgroundColor', // background color of a document view + FillColor = 'fillColor', // fill color of an ink stroke or shape + WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note + PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified + Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background + HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that + ShowCaption = 'layout_showCaption', + TitleHeight = 'titleHeight', // Height of Title area + ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix) + BorderPath = 'customBorder', // border path for document view + FontColor = 'fontColor', // color o tet + FontSize = 'fontSize', // size of text font + FontFamily = 'fontFamily', // font family of text + FontWeight = 'fontWeight', // font weight of text + Highlighting = 'highlighting', // border highlighting +} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 6d4f69e6e..3ecfc8ba3 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -13,53 +13,20 @@ import { FaFilter } from 'react-icons/fa'; import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; import { DocViews } from '../../fields/DocSymbols'; +import { Id } from '../../fields/FieldSymbols'; +import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; +import { AudioAnnoState } from '../../server/SharedMediaTypes'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; -import { DocFocusOrOpen, DocumentManager } from '../util/DocumentManager'; -import { IsFollowLinkScript } from '../util/LinkFollower'; -import { LinkManager } from '../util/LinkManager'; -import { SettingsManager } from '../util/SettingsManager'; +import { IsFollowLinkScript } from '../documents/DocUtils'; import { SnappingManager } from '../util/SnappingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; -import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView'; import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; -import { DocumentViewProps } from './nodes/DocumentView'; +import { DocFocusOrOpen, DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; -import { KeyValueBox } from './nodes/KeyValueBox'; -import { PropertiesView } from './PropertiesView'; +import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; -import { ScriptField } from '../../fields/ScriptField'; - -export enum StyleProp { - TreeViewIcon = 'treeView_Icon', - TreeViewSortings = 'treeView_Sortings', // options for how to sort tree view items - DocContents = 'docContents', // when specified, the JSX returned will replace the normal rendering of the document view - Opacity = 'opacity', // opacity of the document view - BoxShadow = 'boxShadow', // box shadow - used for making collections standout and for showing clusters in free form views - BorderRounding = 'borderRounding', // border radius of the document view - Color = 'color', // foreground color of Document view items - BackgroundColor = 'backgroundColor', // background color of a document view - FillColor = 'fillColor', // fill color of an ink stroke or shape - WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note - PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified - Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background - HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that - ShowCaption = 'layout_showCaption', - TitleHeight = 'titleHeight', // Height of Title area - ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix) - BorderPath = 'customBorder', // border path for document view - FontColor = 'fontColor', // color o tet - FontSize = 'fontSize', // size of text font - FontFamily = 'fontFamily', // font family of text - FontWeight = 'fontWeight', // font weight of text - Highlighting = 'highlighting', // border highlighting -} - -export enum AudioAnnoState { - stopped = 'stopped', - playing = 'playing', -} function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); @@ -80,7 +47,7 @@ export function styleFromLayoutString(doc: Doc, props: FieldViewProps, scale: nu const divKeys = ['width', 'height', 'fontSize', 'transform', 'left', 'backgroundColor', 'left', 'right', 'top', 'bottom', 'pointerEvents', 'position']; const replacer = (match: any, expr: string) => // bcz: this executes a script to convert a property expression string: { script } into a value - ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ this: doc, self: doc, scale }).result?.toString() ?? ''; + ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: doc, scale }).result?.toString() ?? ''; divKeys.forEach((prop: string) => { const p = (props as any)[prop]; typeof p === 'string' && (style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer)); @@ -98,6 +65,11 @@ export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { } ${ph * inset}`; } +let _filterOpener: () => void; +export function SetFilterOpener(func: () => void) { + _filterOpener = func; +} + // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab // export function DefaultStyleProvider(doc: Opt, props: Opt, property: string): any { @@ -124,7 +96,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt, props: Opt = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color)); if (docColor) return docColor; const backColor = backgroundCol(); @@ -243,7 +214,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt, props: Opt, props: Opt, props: Opt ); const paint = () => !doc?.onPaint ? null : ( -
togglePaintView(e, doc, props)}> +
togglePaintView(e, doc, props)}>
); const filter = () => { - const dashView = untracked(() => DocumentManager.Instance.getDocumentView(Doc.ActiveDashboard)); + const dashView = untracked(() => DocumentView.getDocumentView(Doc.ActiveDashboard)); const showFilterIcon = StrListCast(doc?._childFilters).length || StrListCast(doc?._childFiltersByRanges).length ? 'green' // #18c718bd' //'hasFilter' @@ -357,17 +328,11 @@ export function DefaultStyleProvider(doc: Opt, props: Opt
} closeOnSelect - setSelectedVal={ - action((dv) => { - (dv as any).select(false); - (SettingsManager.Instance.propertiesWidth = 250); - setTimeout(action(() => { - if (PropertiesView.Instance) { - PropertiesView.Instance.CloseAll(); - PropertiesView.Instance.openFilters = true; - } - })); - }) + setSelectedVal={((dv: DocumentView) => { + dv.select(false); + SnappingManager.SetPropertiesWidth(250); + _filterOpener?.(); + }) as any // Dropdown assumes values are strings or numbers.. } size={Size.XSMALL} width={15} @@ -375,14 +340,14 @@ export function DefaultStyleProvider(doc: Opt, props: Opt StrListCast(dv?.Document.childFilters).length || StrListCast(dv?.Document.childRangeFilters).length) .map(dv => ({ text: StrCast(dv?.Document.title), val: dv as any, - style: {color:SettingsManager.userColor, background:SettingsManager.userBackgroundColor}, + style: {color:SnappingManager.userColor, background:SnappingManager.userBackgroundColor}, } as IListItemProps)) } />
@@ -395,7 +360,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}
}> -
DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}> +
DocumentView.getFirstDocumentView(doc)?.playAnnotation()}>
@@ -415,7 +380,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt void) { - const color = SettingsManager.userColor; + const color = SnappingManager.userColor; return ( { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.carouselItems.length) % this.carouselItems.length; }; diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index fda320077..9d3657995 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -11,7 +11,7 @@ import { Doc, Opt } from '../../../fields/Doc'; import { DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index b98aceb16..fc9e2e39b 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -16,27 +16,33 @@ import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import * as GoldenLayout from '../../goldenLayout'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; -import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; import { LightboxView } from '../LightboxView'; +import { DocumentView } from '../nodes/DocumentView'; import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; import { OverlayView } from '../OverlayView'; import { ScriptingRepl } from '../ScriptingRepl'; import { UndoStack } from '../UndoStack'; import './CollectionDockingView.scss'; import { CollectionSubView } from './CollectionSubView'; -import { TabDocView } from './TabDocView'; const _global = (window /* browser */ || global) /* node */ as any; @observer export class CollectionDockingView extends CollectionSubView() { + static tabClass: JSX.Element | null = null; + /** + * Configure golden layout to render its documents using the specified React component + * @param ele - typically would be set to TabDocView + */ + static setTabJSXComponent(ele: any) { + this.tabClass = ele; + } // eslint-disable-next-line no-use-before-define @observable public static Instance: CollectionDockingView | undefined = undefined; @@ -295,7 +301,7 @@ export class CollectionDockingView extends CollectionSubView() { glay.on('tabCreated', this.tabCreated); glay.on('tabDestroyed', this.tabDestroyed); glay.on('stackCreated', this.stackCreated); - glay.registerComponent('DocumentFrameRenderer', TabDocView); + glay.registerComponent('DocumentFrameRenderer', CollectionDockingView.tabClass); glay.container = this._containerRef.current; glay.init(); glay.root.layoutManager.on('itemDropped', this.tabItemDropped); @@ -431,8 +437,8 @@ export class CollectionDockingView extends CollectionSubView() { } else { const tabTarget = (e.target as HTMLElement)?.parentElement?.className.includes('lm_tab') ? (e.target as HTMLElement).parentElement : (e.target as HTMLElement); const map = Array.from(this.tabMap).find(tab => tab.element[0] === tabTarget); - if (map?.DashDoc && DocumentManager.Instance.getFirstDocumentView(map.DashDoc)) { - SelectionManager.SelectView(DocumentManager.Instance.getFirstDocumentView(map.DashDoc), false); + if (map?.DashDoc && DocumentView.getFirstDocumentView(map.DashDoc)) { + DocumentView.SelectView(DocumentView.getFirstDocumentView(map.DashDoc), false); } } } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index f945a7aa4..e53071584 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -21,13 +21,11 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { SelectionManager } from '../../util/SelectionManager'; -import { SettingsManager } from '../../util/SettingsManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu } from '../AntimodeMenu'; import { EditableView } from '../EditableView'; -import { MainView } from '../MainView'; import { DefaultStyleProvider } from '../StyleProvider'; import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView'; import './CollectionMenu.scss'; @@ -38,6 +36,7 @@ interface CollectionMenuProps { panelWidth: () => number; toggleTopBar: () => void; topBarHeight: () => number; + togglePropertiesFlyout: () => void; } @observer @@ -59,7 +58,7 @@ export class CollectionMenu extends AntimodeMenu { componentDidMount() { reaction( - () => SelectionManager.Views.lastElement(), + () => DocumentView.Selected().lastElement(), view => view && this.SetSelection(view) ); } @@ -77,15 +76,6 @@ export class CollectionMenu extends AntimodeMenu { } }; - @action - toggleProperties = () => { - if (MainView.Instance.propertiesWidth() > 0) { - SettingsManager.Instance.propertiesWidth = 0; - } else { - SettingsManager.Instance.propertiesWidth = 300; - } - }; - buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); const { scale, translateX, translateY } = ClientUtils.GetScreenTransform(this._docBtnRef.current); @@ -128,15 +118,15 @@ export class CollectionMenu extends AntimodeMenu { render() { const headerIcon = this.props.topBarHeight() > 0 ? 'angle-double-up' : 'angle-double-down'; const headerTitle = this.props.topBarHeight() > 0 ? 'Close Header Bar' : 'Open Header Bar'; - const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; - const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; + const propIcon = SnappingManager.PropertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = SnappingManager.PropertiesWidth > 0 ? 'Close Properties' : 'Open Properties'; const hardCodedButtons = (
0} icon={} @@ -145,9 +135,9 @@ export class CollectionMenu extends AntimodeMenu { 0} + color={SnappingManager.userColor} + onClick={this._props.togglePropertiesFlyout} + toggleStatus={SnappingManager.PropertiesWidth > 0} icon={} tooltip={propTitle} /> @@ -159,7 +149,7 @@ export class CollectionMenu extends AntimodeMenu {
{this.contMenuButtons} @@ -245,7 +235,7 @@ export class CollectionViewBaseChrome extends React.Component { this.target._freeform_panX = 0; this.target._freeform_panY = 0; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index e940a1aef..3f9eed1d6 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -27,7 +27,7 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import './CollectionNoteTakingView.scss'; import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn'; import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider'; diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index e02570d3e..5b3f625db 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -10,13 +10,13 @@ import { NumCast, StrCast, toList } from '../../../fields/Types'; import { emptyFunction } from '../../../Utils'; import { DocUtils } from '../../documents/DocUtils'; import { dropActionType } from '../../util/DropActionTypes'; -import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { OpenWhere } from '../nodes/OpenWhere'; import { computePassLayout, computeStarburstLayout } from './collectionFreeForm'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import './CollectionPileView.scss'; import { CollectionSubView } from './CollectionSubView'; +import { DocumentView } from '../nodes/DocumentView'; @observer export class CollectionPileView extends CollectionSubView() { @@ -150,7 +150,7 @@ export class CollectionPileView extends CollectionSubView() { @undoBatch onClick = (e: React.MouseEvent) => { if (e.button === 0) { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); this.toggleStarburst(); e.stopPropagation(); } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 48d24c910..f9b123bb6 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -19,11 +19,8 @@ import { ImageField } from '../../../fields/URLField'; import { emptyFunction, formatTime } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; -import { FollowLinkScript } from '../../documents/DocUtils'; -import { DocumentManager } from '../../util/DocumentManager'; +import { FollowLinkScript, IsFollowLinkScript } from '../../documents/DocUtils'; import { DragManager } from '../../util/DragManager'; -import { IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower'; -import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -87,7 +84,6 @@ export class CollectionStackedTimeline extends CollectionSubView> => new Promise>(res => { if (doc.hidden) options.didMove = !(doc.hidden = false); - const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); + const findDoc = (finish: (dv: DocumentView) => void) => DocumentView.addViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -296,7 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView 15 && !this.IsTrimming) { const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true); - setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); + setTimeout(() => DocumentView.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); this._timelineWrapper && (this._timelineWrapper.style.cursor = ''); @@ -481,7 +477,7 @@ export class CollectionStackedTimeline extends CollectionSubView { if (IsFollowLinkScript(anchorDoc.onClick)) { - LinkFollower.FollowLink(undefined, anchorDoc, false); + DocumentView.FollowLink(undefined, anchorDoc, false); } const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); @@ -761,12 +757,12 @@ class StackedTimelineAnchor extends ObservableReactComponent NumCast(this._props.mark[this._props.startTag]) && time < NumCast(this._props.mark[this._props.endTag]) && this._lastTimecode < NumCast(this._props.mark[this._props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this._props.mark, false); + DocumentView.FollowLink(undefined, this._props.mark, false); } this._lastTimecode = time; } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 8ae0f2832..3f12a281e 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -31,7 +31,7 @@ import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocum import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 7c08aedb1..a4708bed5 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -21,12 +21,11 @@ import { Docs, DocumentOptions } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; -import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { FieldViewProps } from '../nodes/FieldView'; -import { LoadingBox } from '../nodes/LoadingBox'; +import { DocumentView } from '../nodes/DocumentView'; export interface CollectionViewProps extends React.PropsWithChildren { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) @@ -377,7 +376,7 @@ export function CollectionSubView() { } }); } else { - const srcWeb = SelectionManager.Views.lastElement(); + const srcWeb = DocumentView.Selected().lastElement(); const srcUrl = (srcWeb?.Document.data as WebField)?.url?.href?.match(/https?:\/\/[^/]*/)?.[0]; const reg = new RegExp(ClientUtils.prepend(''), 'g'); const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; @@ -386,7 +385,7 @@ export function CollectionSubView() { Doc.GetProto(htmlDoc)['data-text'] = Doc.GetProto(htmlDoc).text = text; addDocument(htmlDoc); if (srcWeb) { - const iframe = SelectionManager.Views[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; + const iframe = DocumentView.Selected()[0].ContentDiv?.getElementsByTagName('iframe')?.[0]; const focusNode = iframe?.contentDocument?.getSelection()?.focusNode as any; if (focusNode) { const anchor = srcWeb?.ComponentView?.getAnchor?.(true); @@ -496,13 +495,13 @@ export function CollectionSubView() { if (typeof files === 'string') { const loading = Docs.Create.LoadingDocument(files, options); generatedDocuments.push(loading); - LoadingBox.addCurrentlyLoading(loading); + Doc.addCurrentlyLoading(loading); DocUtils.uploadYoutubeVideoLoading(files, {}, loading); } else { generatedDocuments.push( ...files.map(file => { const loading = Docs.Create.LoadingDocument(file, options); - LoadingBox.addCurrentlyLoading(loading); + Doc.addCurrentlyLoading(loading); DocUtils.uploadFileToDoc(file, {}, loading); return loading; }) diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index b3760d4af..0369e4a2a 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -12,7 +12,6 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { Docs } from '../../documents/Documents'; -import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; @@ -257,7 +256,7 @@ ScriptingGlobals.add(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBou action(() => { const filterVals = bounds.payload as string[]; filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, pivotField, filterVal, 'check')); - const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc); + const pivotView = DocumentView.getDocumentView(pivotDoc); if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) { if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) { // eslint-disable-next-line prefer-destructuring diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index d015e73ad..c1247f5b0 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -14,11 +14,9 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocUtils } from '../../documents/DocUtils'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; -import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; @@ -27,10 +25,11 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; +import { TreeViewType } from './CollectionTreeViewType'; import { TreeView } from './TreeView'; const _global = (window /* browser */ || global) /* node */ as any; @@ -49,12 +48,6 @@ export type collectionTreeViewProps = { hierarchyIndex?: number[]; }; -export enum TreeViewType { - outline = 'outline', - fileSystem = 'fileSystem', - default = 'default', -} - @observer export class CollectionTreeView extends CollectionSubView>() { public static AddTreeFunc = 'addTreeFolder(this.embedContainer)'; @@ -183,7 +176,7 @@ export class CollectionTreeView extends CollectionSubView !docs.includes(v)); - if (docs.some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll(); + if (docs.some(doc => DocumentView.Selected().some(dv => Doc.AreProtosEqual(dv.Document, doc)))) DocumentView.DeselectAll(); if (result.length !== value.length) { if (docIn instanceof Doc) { const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(docIn); @@ -191,7 +184,7 @@ export class CollectionTreeView extends CollectionSubView 0 && prev) { Doc.SetSelectOnLoad(prev); - DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false); + DocumentView.getDocumentView(prev, this.DocumentView?.())?.select(false); } return true; } @@ -468,7 +461,7 @@ export class CollectionTreeView extends CollectionSubView e.stopPropagation()} - onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : SelectionManager.DeselectAll())} + onClick={() => (!this.layoutDoc.forceActive ? this._props.select(false) : DocumentView.DeselectAll())} onDrop={this.onTreeDrop}>
    {this.treeViewElements}
diff --git a/src/client/views/collections/CollectionTreeViewType.ts b/src/client/views/collections/CollectionTreeViewType.ts new file mode 100644 index 000000000..53b88160e --- /dev/null +++ b/src/client/views/collections/CollectionTreeViewType.ts @@ -0,0 +1,5 @@ +export enum TreeViewType { + outline = 'outline', + fileSystem = 'fileSystem', + default = 'default', +} diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 2d900bb73..2d8b2564d 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -9,7 +9,7 @@ import * as ReactDOM from 'react-dom/client'; import { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; +import { DocData, DocViews } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; @@ -18,10 +18,8 @@ import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types'; import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { UndoManager, undoable } from '../../util/UndoManager'; @@ -29,7 +27,8 @@ import { DashboardView } from '../DashboardView'; import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { PinDocView, PinProps } from '../PinFuncs'; -import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; +import { DefaultStyleProvider } from '../StyleProvider'; import { Colors } from '../global/globalEnums'; import { DocumentView, returnEmptyDocViewList } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; @@ -194,9 +193,101 @@ export class TabDocView extends ObservableReactComponent { _mainCont: HTMLDivElement | null = null; _tabReaction: IReactionDisposer | undefined; + /** + * Adds a document to the presentation view + * */ + @action + public static PinDoc(docIn: Doc | Doc[], pinProps: PinProps) { + const docs = toList(docIn); + + const batch = UndoManager.StartBatch('Pin doc to pres trail'); + const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); + + if (!Doc.ActivePresentation) { + Doc.AddDocToList(Doc.MyTrails, 'data', curPres); + Doc.ActivePresentation = curPres; + } + + docs.forEach(doc => { + // Edge Case 1: Cannot pin document to itself + if (doc === curPres) { + alert('Cannot pin presentation document to itself'); + return; + } + const anchorDoc = DocumentView.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps); + const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({}); + const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc); + pinDoc.title = doc.title + ' - Slide'; + pinDoc.data = targDoc.type === DocumentType.PRES ? ComputedField.MakeFunction('copyField(this.presentation_targetDoc.data') : new List(); // the children of the embedding's layout are the presentation slide children. the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data + pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; + pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000; + pinDoc.presentation_groupWithUp = false; + Doc.SetContainer(pinDoc, curPres); + // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time + pinDoc.treeView = ''; // not really needed, but makes key value pane look better + pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area + pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. + pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field + pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view + pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header + const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null); + + if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc); + if (!pinProps?.audioRange && duration !== undefined) { + pinDoc.presentation_mediaStart = 'manual'; + pinDoc.presentation_mediaStop = 'manual'; + } + if (pinProps?.activeFrame !== undefined) { + pinDoc.config_activeFrame = pinProps?.activeFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presentation_movement = PresMovement.Pan; + } + if (pinProps?.currentFrame !== undefined) { + pinDoc.config_currentFrame = pinProps?.currentFrame; + pinDoc.title = doc.title + ' (move)'; + pinDoc.presentation_movement = PresMovement.Pan; + } + if (pinDoc.stroke_isInkMask) { + pinDoc.presentation_hideAfter = true; + pinDoc.presentation_hideBefore = true; + pinDoc.presentation_movement = PresMovement.None; + } + if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true; + Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement()); + PresBox.Instance?.clearSelectedArray(); + pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array + }); + if ( + // open the presentation trail if it's not already opened + !Array.from(CollectionDockingView.Instance?.tabMap ?? []) + .map(d => d.DashDoc) + .includes(curPres) + ) { + if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres); + CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); + setTimeout(() => DocumentView.showDocument(docs.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things + } + setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs + } + + static Activate = (tabDoc: Doc) => { + const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue); + tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) + return tab !== undefined; + }; + // static ActivateTabView(doc: Doc) { + // const tabView = Array.from(TabDocView._allTabs).find(view => view._document === doc); + // if (!tabView?._activated && tabView?._document) { + // TabDocView.Activate(tabView?._document); + // return tabView; + // } + // return undefined; + // } constructor(props: any) { super(props); makeObservable(this); + DocumentView.activateTabView = TabDocView.Activate; + DocumentView.PinDoc = TabDocView.PinDoc; } @observable _activated: boolean = false; @@ -205,8 +296,14 @@ export class TabDocView extends ObservableReactComponent { @observable _hovering = false; @observable _isActive: boolean = false; @observable _isAnyChildContentActive = false; + public static IsSelected = (doc?: Doc) => { + if (Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected)) { + return true; + } + return false; + }; @computed get _isUserActivated() { - return SelectionManager.IsSelected(this._document) || this._isAnyChildContentActive; + return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } get _isContentActive() { return this._isUserActivated || this._hovering; @@ -279,12 +376,12 @@ export class TabDocView extends ObservableReactComponent { returnFalse, action(clickEv => { if (this.view) { - SelectionManager.SelectView(this.view, false); + DocumentView.SelectView(this.view, false); const child = getChild(); simulateMouseClick(child, clickEv.clientX, clickEv.clientY + 30, clickEv.screenX, clickEv.screenY + 30); } else { this._activated = true; - setTimeout(() => this.view && SelectionManager.SelectView(this.view, false)); + setTimeout(() => this.view && DocumentView.SelectView(this.view, false)); } }) ); @@ -350,7 +447,7 @@ export class TabDocView extends ObservableReactComponent { // select the tab document when the tab is directly clicked and activate the tab whenver the tab document is selected titleEle.onpointerdown = action((e: any) => { if (e.target.className !== 'lm_iconWrap') { - if (this.view) SelectionManager.SelectView(this.view, false); + if (this.view) DocumentView.SelectView(this.view, false); else this._activated = true; if (Date.now() - titleEle.lastClick < 1000) titleEle.select(); titleEle.lastClick = Date.now(); @@ -358,7 +455,7 @@ export class TabDocView extends ObservableReactComponent { } }); tab._disposers.selectionDisposer = reaction( - () => SelectionManager.IsSelected(this._document), + () => TabDocView.IsSelected(this._document), action(selected => { if (selected) this._activated = true; if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) { @@ -382,89 +479,12 @@ export class TabDocView extends ObservableReactComponent { .off('click') // unbind the current click handler .click(() => { Object.values(tab._disposers).forEach((disposer: any) => disposer?.()); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); UndoManager.RunInBatch(() => tab.contentItem.remove(), 'delete tab'); }); } }; - /** - * Adds a document to the presentation view - * */ - @action - public static PinDoc(docIn: Doc | Doc[], pinProps: PinProps) { - const docs = toList(docIn); - - const batch = UndoManager.StartBatch('Pin doc to pres trail'); - const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); - - if (!Doc.ActivePresentation) { - Doc.AddDocToList(Doc.MyTrails, 'data', curPres); - Doc.ActivePresentation = curPres; - } - - docs.forEach(doc => { - // Edge Case 1: Cannot pin document to itself - if (doc === curPres) { - alert('Cannot pin presentation document to itself'); - return; - } - const anchorDoc = DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.getAnchor?.(false, pinProps); - const pinDoc = anchorDoc?.type === DocumentType.CONFIG ? anchorDoc : Docs.Create.ConfigDocument({}); - const targDoc = (pinDoc.presentation_targetDoc = anchorDoc ?? doc); - pinDoc.title = doc.title + ' - Slide'; - pinDoc.data = targDoc.type === DocumentType.PRES ? ComputedField.MakeFunction('copyField(this.presentation_targetDoc.data') : new List(); // the children of the embedding's layout are the presentation slide children. the embedding's data field might be children of a collection, PDF data, etc -- in any case we don't want the tree view to "see" this data - pinDoc.presentation_movement = doc.type === DocumentType.SCRIPTING || pinProps?.pinDocLayout ? PresMovement.None : PresMovement.Zoom; - pinDoc.presentation_duration = pinDoc.presentation_duration ?? 1000; - pinDoc.presentation_groupWithUp = false; - Doc.SetContainer(pinDoc, curPres); - // these should potentially all be props passed down by the CollectionTreeView to the TreeView elements. That way the PresBox could configure all of its children at render time - pinDoc.treeView = ''; // not really needed, but makes key value pane look better - pinDoc.treeView_RenderAsBulletHeader = true; // forces a tree view to render the document next to the bullet in the header area - pinDoc.treeView_HeaderWidth = '100%'; // forces the header to grow to be the same size as its largest sibling. - pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field - pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view - pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header - const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null); - - if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc); - if (!pinProps?.audioRange && duration !== undefined) { - pinDoc.presentation_mediaStart = 'manual'; - pinDoc.presentation_mediaStop = 'manual'; - } - if (pinProps?.activeFrame !== undefined) { - pinDoc.config_activeFrame = pinProps?.activeFrame; - pinDoc.title = doc.title + ' (move)'; - pinDoc.presentation_movement = PresMovement.Pan; - } - if (pinProps?.currentFrame !== undefined) { - pinDoc.config_currentFrame = pinProps?.currentFrame; - pinDoc.title = doc.title + ' (move)'; - pinDoc.presentation_movement = PresMovement.Pan; - } - if (pinDoc.stroke_isInkMask) { - pinDoc.presentation_hideAfter = true; - pinDoc.presentation_hideBefore = true; - pinDoc.presentation_movement = PresMovement.None; - } - if (curPres.expandBoolean) pinDoc.presentation_expandInlineButton = true; - Doc.AddDocToList(curPres, 'data', pinDoc, PresBox.Instance?.sortArray()?.lastElement()); - PresBox.Instance?.clearSelectedArray(); - pinDoc && PresBox.Instance?.addToSelectedArray(pinDoc); // Update selected array - }); - if ( - // open the presentation trail if it's not already opened - !Array.from(CollectionDockingView.Instance?.tabMap ?? []) - .map(d => d.DashDoc) - .includes(curPres) - ) { - if (Doc.IsInMyOverlay(curPres)) Doc.RemFromMyOverlay(curPres); - CollectionDockingView.AddSplit(curPres, OpenWhereMod.right); - setTimeout(() => DocumentManager.Instance.showDocument(docs.lastElement(), { willPan: true }), 100); // keeps the pinned doc in view since the sidebar shifts things - } - setTimeout(batch.end, 500); // need to wait until dockingview (goldenlayout) updates all its structurs - } - componentDidMount() { new _global.ResizeObserver( action((entries: any) => { @@ -484,12 +504,12 @@ export class TabDocView extends ObservableReactComponent { } componentDidUpdate(prevProps: Readonly) { super.componentDidUpdate(prevProps); - this._view && DocumentManager.Instance.AddView(this._view); + this._view && DocumentView.addView(this._view); } componentWillUnmount() { this._tabReaction?.(); - this._view && DocumentManager.Instance.RemoveView(this._view); + this._view && DocumentView.removeView(this._view); runInAction(() => TabDocView._allTabs.delete(this)); this._props.glContainer.layoutManager.off('activeContentItemChanged', this.onActiveContentItemChanged); @@ -503,7 +523,7 @@ export class TabDocView extends ObservableReactComponent { private onActiveContentItemChanged(contentItem: any) { if (!contentItem || (this.stack === contentItem.parent && ((contentItem?.tab === this.tab && !this._isActive) || (contentItem?.tab !== this.tab && this._isActive)))) { this._activated = this._isActive = !contentItem || contentItem?.tab === this.tab; - if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => SelectionManager.SelectView(this._view, false)); + if (!this._view && this.tab?.contentItem?.config?.props?.panelName !== TabDocView.DontSelectOnActivate) setTimeout(() => DocumentView.SelectView(this._view, false)); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } } @@ -517,7 +537,7 @@ export class TabDocView extends ObservableReactComponent { // lightbox - will add the document to any collection along the path from the document to the docking view that has a field isLightbox. if none is found, it adds to the full screen lightbox addDocTab = (docsIn: Doc | Doc[], location: OpenWhere) => { const docs = toList(docsIn); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); const whereFields = location.split(':'); const keyValue = whereFields.includes(OpenWhereMod.keyvalue); const whereMods = whereFields.length > 1 ? (whereFields[1] as OpenWhereMod) : OpenWhereMod.none; @@ -527,7 +547,7 @@ export class TabDocView extends ObservableReactComponent { switch (whereFields[0]) { case undefined: case OpenWhere.lightbox: if (this.layoutDoc?._isLightbox) { - const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentManager.Instance.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined; + const lightboxView = !docs[0].annotationOn && DocCast(docs[0].embedContainer) ? DocumentView.getFirstDocumentView(DocCast(docs[0].embedContainer)) : undefined; const data = lightboxView?.dataDoc[Doc.LayoutFieldKey(lightboxView.Document)]; if (lightboxView && (!data || data instanceof List)) { lightboxView.layoutDoc[Doc.LayoutFieldKey(lightboxView.Document)] = new List(docs); @@ -543,7 +563,7 @@ export class TabDocView extends ObservableReactComponent { }; remDocTab = (doc: Doc | Doc[]) => { if (doc === this._document) { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); CollectionDockingView.CloseSplit(this._document); return true; } @@ -551,11 +571,7 @@ export class TabDocView extends ObservableReactComponent { }; getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); - static Activate = (tabDoc: Doc) => { - const tab = Array.from(CollectionDockingView.Instance?.tabMap!).find(findTab => findTab.DashDoc === tabDoc && !findTab.contentItem.config.props.keyValue); - tab?.header.parent.setActiveContentItem(tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) - return tab !== undefined; - }; + @action focusFunc = () => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { @@ -586,7 +602,7 @@ export class TabDocView extends ObservableReactComponent { { - this._lastView && DocumentManager.Instance.RemoveView(this._lastView); + this._lastView && DocumentView.removeView(this._lastView); this._view = r; this._lastView = this._view; })} @@ -635,7 +651,7 @@ export class TabDocView extends ObservableReactComponent { this._mainCont = ref; if (this._mainCont) { if (this._lastTab) { - this._view && DocumentManager.Instance.RemoveView(this._view); + this._view && DocumentView.removeView(this._view); } this._lastTab = this.tab; (this._mainCont as any).InitTab = (tab: any) => this.init(tab, this._document); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 2f5af0564..969b98d91 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -21,24 +21,23 @@ import { TraceMobx } from '../../../fields/util'; import { DocUtils } from '../../documents/DocUtils'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { LinkManager } from '../../util/LinkManager'; import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { FieldViewProps, StyleProviderFuncType } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { OpenWhere } from '../nodes/OpenWhere'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { CollectionTreeView, TreeViewType } from './CollectionTreeView'; +import { CollectionTreeView } from './CollectionTreeView'; +import { TreeViewType } from './CollectionTreeViewType'; import { CollectionView } from './CollectionView'; import { TreeSort } from './TreeSort'; import './TreeView.scss'; @@ -208,7 +207,7 @@ export class TreeView extends ObservableReactComponent { const ind = DocListCast(this.dataDoc[key]).indexOf(docs.lastElement()); const res = docs.reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); - res && ind > 0 && DocumentManager.Instance.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false); + res && ind > 0 && DocumentView.getDocumentView(DocListCast(this.dataDoc[key])[ind - 1], this.treeView.DocumentView?.())?.select(false); return res; }; @@ -379,7 +378,7 @@ export class TreeView extends ObservableReactComponent { const bulletData = bullet[DocData]; bulletData.title = ComputedField.MakeFunction('this.text?.Text'); bulletData.data = new List([]); - DocumentManager.Instance.AddViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.()); + DocumentView.addViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.()); return bullet; } @@ -823,7 +822,7 @@ export class TreeView extends ObservableReactComponent { @computed get validExpandViewTypes() { const annos = () => (DocListCast(this.Document[this.fieldKey + '_annotations']).length && !this.treeView.dashboardMode ? 'annotations' : ''); - const links = () => (LinkManager.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : ''); + const links = () => (Doc.Links(this.Document).length && !this.treeView.dashboardMode ? 'links' : ''); const data = () => (this.childDocs || this.treeView.dashboardMode ? this.fieldKey : ''); const embeddings = () => (this.treeView.dashboardMode ? '' : 'embeddings'); const fields = () => (Doc.noviceMode ? '' : 'fields'); @@ -1210,7 +1209,7 @@ export class TreeView extends ObservableReactComponent { className={`treeView-container${this._props.isContentActive() ? '-active' : ''}`} ref={this.createTreeDropTarget} onDrop={this.onTreeDrop} - // 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 + // onPointerDown={e => this._props.isContentActive(true) && DocumentView.DeselectAll()} // bcz: this breaks entering a text filter in a filterBox since it deselects the filter's target document // onKeyDown={this.onKeyDown} >
  • diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts index 6fc295e27..26a52cd2a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts @@ -1,17 +1,16 @@ import { action, observable } from 'mobx'; +import { CollectionFreeFormView } from '.'; +import { intersectRect } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; -import { intersectRect } from '../../../../Utils'; import { DocumentType } from '../../../documents/DocumentTypes'; -import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; -import { SelectionManager } from '../../../util/SelectionManager'; +import { StyleProp } from '../../StyleProp'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; +import { DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; -import { StyleProp } from '../../StyleProvider'; import './CollectionFreeFormView.scss'; -import { CollectionFreeFormView } from '.'; export class CollectionFreeFormClusters { private _view: CollectionFreeFormView; @@ -44,7 +43,7 @@ export class CollectionFreeFormClusters { const h = NumCast(doc1Layout._height) + clusterDistance; return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 }); } - pickCluster(probe: number[]) { + handlePointerDown(probe: number[]) { this._hitCluster = this.childLayoutPairs .map(pair => pair.layout) .reduce((cluster, cd) => { @@ -62,13 +61,13 @@ export class CollectionFreeFormClusters { return this._hitCluster; } - tryDragCluster(e: PointerEvent) { + tryToDrag(e: PointerEvent) { const cluster = this._hitCluster; if (cluster !== -1) { const ptsParent = e; if (ptsParent) { const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === cluster); - const clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.DocumentView)!); + const clusterDocs = eles.map(ele => DocumentView.getDocumentView(ele, this.DocumentView)!); const { left, top } = clusterDocs[0].getBounds || { left: 0, top: 0 }; const de = new DragManager.DocumentDragData(eles, e.ctrlKey || e.altKey ? dropActionType.embed : undefined); de.moveDocument = this.viewMoveDocument; @@ -87,7 +86,7 @@ export class CollectionFreeFormClusters { return false; } - initClusters() { + initLayout() { if (this.Document._freeform_useClusters && !this._clusterSets.length && this.childDocs.length) { return this.updateClusters(true); } @@ -97,11 +96,11 @@ export class CollectionFreeFormClusters { updateClusters(useClusters: boolean) { this.Document._freeform_useClusters = useClusters; this._clusterSets.length = 0; - this.childLayoutPairs.map(pair => pair.layout).map(c => this.updateCluster(c)); + this.childLayoutPairs.map(pair => pair.layout).map(c => this.addDocument(c)); } @action - updateClusterDocs(docs: Doc[]) { + addDocuments(docs: Doc[]) { const childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.Document._freeform_useClusters) { const docFirst = docs[0]; @@ -143,12 +142,12 @@ export class CollectionFreeFormClusters { this._clusterSets[(doc.layout_cluster = NumCast(docFirst.layout_cluster))].push(doc); }); } - childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.updateCluster(child)); + childLayouts.map(child => !this._clusterSets.some((set, i) => Doc.IndexOf(child, set) !== -1 && child.layout_cluster === i) && this.addDocument(child)); } } @action - updateCluster = (doc: Doc) => { + addDocument = (doc: Doc) => { const childLayouts = this.childLayoutPairs.map(pair => pair.layout); if (this.Document._freeform_useClusters) { this._clusterSets.forEach(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); @@ -188,7 +187,7 @@ export class CollectionFreeFormClusters { const cluster = NumCast(doc?.layout_cluster); if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) { if (this._clusterSets.length <= cluster) { - setTimeout(() => doc && this.updateCluster(doc)); + setTimeout(() => doc && this.addDocument(doc)); } else { // choose a cluster color from a palette const colors = ['#da42429e', '#31ea318c', 'rgba(197, 87, 20, 0.55)', '#4a7ae2c4', 'rgba(216, 9, 255, 0.5)', '#ff7601', '#1dffff', 'yellow', 'rgba(27, 130, 49, 0.55)', 'rgba(0, 0, 0, 0.268)']; @@ -212,9 +211,9 @@ export class CollectionFreeFormClusters { return styleProp; }; - trySelectCluster = (addToSel: boolean) => { + tryToSelect = (addToSel: boolean) => { if (addToSel && this._hitCluster !== -1) { - !addToSel && SelectionManager.DeselectAll(); + !addToSel && DocumentView.DeselectAll(); const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); this.selectDocuments(eles); return true; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 65a529d62..5d8373fc7 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -4,13 +4,13 @@ import * as React from 'react'; import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { StrCast } from '../../../../fields/Types'; -import { LinkManager } from '../../../util/LinkManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { TopBar } from '../../topbar/TopBar'; import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState'; import './CollectionFreeFormView.scss'; import { DocData } from '../../../../fields/DocSymbols'; +import { CollectionFreeFormView } from './CollectionFreeFormView'; export interface CollectionFreeFormInfoUIProps { Document: Doc; @@ -21,6 +21,12 @@ export interface CollectionFreeFormInfoUIProps { @observer export class CollectionFreeFormInfoUI extends ObservableReactComponent { + public static Init() { + CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => ( + // + + )); + } _firstDocPos = { x: 0, y: 0 }; constructor(props: any) { @@ -63,7 +69,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent DocumentLinksButton.StartLink; const linkUnstart = () => !DocumentLinksButton.StartLink; - const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length; + const numDocLinks = () => Doc.Links(firstDoc())?.length; const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView; const activeTool = () => Doc.ActiveTool; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index 90977d955..65a2fe0aa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -3,9 +3,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; -import { PresBox } from '../../nodes/trails/PresBox'; -import './CollectionFreeFormView.scss'; import { ObservableReactComponent } from '../../ObservableReactComponent'; +import './CollectionFreeFormView.scss'; export interface CollectionFreeFormPannableContentsProps { Document: Doc; @@ -20,12 +19,16 @@ export interface CollectionFreeFormPannableContentsProps { @observer export class CollectionFreeFormPannableContents extends ObservableReactComponent { + static _overlayPlugin: ((fform: Doc) => React.JSX.Element) | null = null; + public static SetOverlayPlugin(plugin: ((fform: Doc) => React.JSX.Element) | null) { + CollectionFreeFormPannableContents._overlayPlugin = plugin; + } constructor(props: CollectionFreeFormPannableContentsProps) { super(props); makeObservable(this); } @computed get presPaths() { - return this._props.showPresPaths() ? PresBox.Instance.pathLines(this._props.Document) : null; + return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Document) : null; } // rectangle highlight used when following trail/link to a region of a collection that isn't a document showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) => diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f55d5a23f..6d901119e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -26,15 +26,12 @@ import { aggregateBounds, emptyFunction, intersectRect, Utils } from '../../../. import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocUtils } from '../../../documents/DocUtils'; -import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { ReplayMovements } from '../../../util/ReplayMovements'; import { CompileScript } from '../../../util/Scripting'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; -import { SelectionManager } from '../../../util/SelectionManager'; -import { freeformScrollMode } from '../../../util/SettingsManager'; -import { SnappingManager } from '../../../util/SnappingManager'; +import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; import { Timeline } from '../../animationtimeline/Timeline'; @@ -50,12 +47,11 @@ import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { OpenWhere } from '../../nodes/OpenWhere'; import { PinDocView, PinProps } from '../../PinFuncs'; -import { StyleProp } from '../../StyleProvider'; +import { StyleProp } from '../../StyleProp'; import { CollectionSubView } from '../CollectionSubView'; -import { TreeViewType } from '../CollectionTreeView'; +import { TreeViewType } from '../CollectionTreeViewType'; import { CollectionFreeFormBackgroundGrid } from './CollectionFreeFormBackgroundGrid'; import { CollectionFreeFormClusters } from './CollectionFreeFormClusters'; -import { CollectionFreeFormInfoUI } from './CollectionFreeFormInfoUI'; import { computePassLayout, computePivotLayout, computeStarburstLayout, computeTimelineLayout, PoolData, ViewDefBounds, ViewDefResult } from './CollectionFreeFormLayoutEngines'; import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannableContents'; import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; @@ -87,8 +83,9 @@ export class CollectionFreeFormView extends CollectionSubView(); - public static from(dv?: DocumentView) { - return CollectionFreeFormDocumentView.from(dv)?.CollectionFreeFormView; + public static from(dv?: DocumentView): CollectionFreeFormView | undefined { + const parent = CollectionFreeFormDocumentView.from(dv)?._props.parent; + return parent instanceof CollectionFreeFormView ? parent : undefined; } _oldWheel: any; @@ -227,25 +224,6 @@ export class CollectionFreeFormView extends CollectionSubView { - CollectionFreeFormDocumentView.animFields.forEach(val => { - const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); - }); - CollectionFreeFormDocumentView.animStringFields.forEach(val => { - const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); - }); - CollectionFreeFormDocumentView.animDataFields(doc).forEach(val => { - const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null); - findexed?.length <= timecode + 1 && findexed.push(undefined as any); - }); - }); - return newTimer; - } changeKeyFrame = (back = false) => { const currentFrame = Cast(this.Document._currentFrame, 'number', null); if (currentFrame === undefined) { @@ -256,7 +234,7 @@ export class CollectionFreeFormView extends CollectionSubView { - SelectionManager.DeselectAll(); - docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())).forEach(dv => dv && SelectionManager.SelectView(dv, true)); + DocumentView.DeselectAll(); + docs.map(doc => DocumentView.getDocumentView(doc, this.DocumentView?.())).forEach(dv => dv && DocumentView.SelectView(dv, true)); }; addDocument = (newBox: Doc | Doc[]) => { let retVal = false; @@ -301,7 +279,7 @@ export class CollectionFreeFormView extends CollectionSubView void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); + const findDoc = (finish: (dv: DocumentView) => void) => DocumentView.addViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -459,7 +437,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this._clusters.tryDragCluster(e)) { + if (this._clusters.tryToDrag(e)) { e.stopPropagation(); // we're moving a cluster, so stop propagation and return true to end panning and let the document drag take over return true; } @@ -675,7 +653,7 @@ export class CollectionFreeFormView extends CollectionSubView DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())) + .map(doc => DocumentView.getDocumentView(doc, this.DocumentView?.())) .filter(inkView => inkView?.ComponentView instanceof InkingStroke) .map(inkView => inkView!) .map(inkView => ({ inkViewBounds: inkView.getBounds, inkStroke: inkView.ComponentView as InkingStroke, inkView })) @@ -776,7 +754,7 @@ export class CollectionFreeFormView extends CollectionSubView doc.type === DocumentType.INK && !doc.dontIntersect) .forEach(doc => { - const otherInk = DocumentManager.Instance.getDocumentView(doc, this.DocumentView?.())?.ComponentView as InkingStroke; + const otherInk = DocumentView.getDocumentView(doc, this.DocumentView?.())?.ComponentView as InkingStroke; const { inkData: otherInkData } = otherInk?.inkScaledData() ?? { inkData: [] }; const otherScreenPts = otherInkData.map(point => otherInk.ptToScreen(point)); const otherCtrlPts = otherScreenPts.map(spt => (ink.ComponentView as InkingStroke).ptFromScreen(spt)); @@ -1078,17 +1056,18 @@ export class CollectionFreeFormView extends CollectionSubView this.childDocs; closeInfo = action(() => { Doc.IsInfoUIDisabled = true }); // prettier-ignore - infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : ); + static _infoUI: ((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => JSX.Element) | null = null; + static SetInfoUICreator(func: (doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => JSX.Element) { + CollectionFreeFormView._infoUI = func; + } + infoUI = () => + Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth + ? null // + : CollectionFreeFormView._infoUI?.(this.Document, this.layoutDoc, this.childDocsFunc, this.closeInfo) || null; componentDidMount() { this._props.setContentViewBox?.(this); @@ -1551,9 +1537,7 @@ export class CollectionFreeFormView extends CollectionSubView intersectRect(docDims(doc), rect); const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to - activeDocs - .filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)) - .forEach(doc => DocumentManager.Instance.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); + activeDocs.filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)).forEach(doc => DocumentView.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); const horizLines: number[] = []; const vertLines: number[] = []; @@ -1654,7 +1638,7 @@ export class CollectionFreeFormView extends CollectionSubView 0 ? undefined : this.nudge} addDocTab={this.addDocTab} slowLoadDocuments={this.slowLoadDocuments} - trySelectCluster={this._clusters.trySelectCluster} + trySelectCluster={this._clusters.tryToSelect} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument} @@ -1744,22 +1728,22 @@ export class CollectionFreeFormView extends CollectionSubView selView[0].ComponentView?.setKeyFrameEditing?.(!selView[0].ComponentView?.getKeyFrameEditing?.())); return undefined; }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function pinWithView(pinContent: boolean) { - SelectionManager.Views.forEach(view => + DocumentView.Selected().forEach(view => view._props.pinToPres(view.Document, { currentFrame: Cast(view.Document.currentFrame, 'number', null), pinData: { @@ -1772,16 +1756,16 @@ ScriptingGlobals.add(function pinWithView(pinContent: boolean) { }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function bringToFront() { - SelectionManager.Views.forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document)); + DocumentView.Selected().forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document)); }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function sendToBack() { - SelectionManager.Views.forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document, true)); + DocumentView.Selected().forEach(view => CollectionFreeFormView.from(view)?.bringToFront(view.Document, true)); }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function datavizFromSchema() { // creating a dataviz doc to represent the schema table - SelectionManager.Views.forEach(viewIn => { + DocumentView.Selected().forEach(viewIn => { const view = viewIn; if (!view.layoutDoc.schema_columnKeys) { view.layoutDoc.schema_columnKeys = new List(['title', 'type', 'author', 'author_date']); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3ab04f403..b96444024 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -17,15 +17,14 @@ import { CognitiveServices } from '../../../cognitive_services/CognitiveServices import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs, DocumentOptions } from '../../../documents/Documents'; -import { SelectionManager } from '../../../util/SelectionManager'; -import { freeformScrollMode } from '../../../util/SettingsManager'; -import { SnappingManager } from '../../../util/SnappingManager'; +import { SnappingManager, freeformScrollMode } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { UndoManager, undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { MarqueeViewBounds } from '../../PinFuncs'; import { PreviewCursor } from '../../PreviewCursor'; +import { DocumentView } from '../../nodes/DocumentView'; import { OpenWhere } from '../../nodes/OpenWhere'; import { pasteImageBitmap } from '../../nodes/WebBoxRenderer'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -175,7 +174,7 @@ export class MarqueeView extends ObservableReactComponent | KeyboardEvent | undefined, hide?: boolean) => { const selected = this.marqueeSelect(false); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); selected.forEach(doc => { hide ? (doc.hidden = true) : this._props.removeDocument?.(doc); }); @@ -380,7 +379,7 @@ export class MarqueeView extends ObservableReactComponent { const selected = this.marqueeSelect(false); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); selected.forEach(d => this._props.removeDocument?.(d)); const newCollection = DocUtils.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2)!; this._props.addDocument?.(newCollection); @@ -390,7 +389,7 @@ export class MarqueeView extends ObservableReactComponent ); getCurrentlyPlayingUI = () => - !CollectionStackedTimeline.CurrentlyPlaying?.length ? null : ( + !DocumentView.CurrentlyPlaying?.length ? null : ( Currently playing: - {CollectionStackedTimeline.CurrentlyPlaying.map((clip, i) => ( + {DocumentView.CurrentlyPlaying.map((clip, i) => ( <> - DocumentManager.Instance.showDocument(clip.Document, { willZoomCentered: true })}> - {clip.Document.title + (i === CollectionStackedTimeline.CurrentlyPlaying.length - 1 ? ' ' : ',')} + DocumentView.showDocument(clip.Document, { willZoomCentered: true })}> + {clip.Document.title + (i === DocumentView.CurrentlyPlaying.length - 1 ? ' ' : ',')} clip.ComponentView?.TogglePause?.()} />{' '} clip.ComponentView?.Pause?.()} />{' '} diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index 49ba85524..931e2c5e0 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { UndoManager } from '../../../util/UndoManager'; -import { StyleProp } from '../../StyleProvider'; +import { StyleProp } from '../../StyleProp'; import { StyleProviderFuncType } from '../../nodes/FieldView'; import { DimUnit } from './CollectionMulticolumnView'; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index ad77c327d..cff0a8b4c 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { UndoManager } from '../../../util/UndoManager'; -import { StyleProp } from '../../StyleProvider'; +import { StyleProp } from '../../StyleProp'; import { StyleProviderFuncType } from '../../nodes/FieldView'; import { DimUnit } from './CollectionMultirowView'; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 406a7d626..b30954ffd 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -15,16 +15,15 @@ import { ColumnType } from '../../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { DocUtils } from '../../../documents/DocUtils'; import { Docs, DocumentOptions, FInfo } from '../../../documents/Documents'; -import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; -import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { DefaultStyleProvider, StyleProp } from '../../StyleProvider'; +import { StyleProp } from '../../StyleProp'; +import { DefaultStyleProvider } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; @@ -92,12 +91,12 @@ export class CollectionSchemaView extends CollectionSubView() { } @computed get _selectedDocs() { - const selected = SelectionManager.Docs.filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document)); + const selected = DocumentView.SelectedDocs().filter(doc => Doc.AreProtosEqual(DocCast(doc.embedContainer), this.Document)); if (!selected.length) { // if no schema doc is directly selected, test if a child of a schema doc is selected (such as in the preview window) - const childOfSchemaDoc = SelectionManager.Docs.find(sel => DocumentManager.GetContextPath(sel, true).includes(this.Document)); + const childOfSchemaDoc = DocumentView.SelectedDocs().find(sel => DocumentView.getContextPath(sel, true).includes(this.Document)); if (childOfSchemaDoc) { - const contextPath = DocumentManager.GetContextPath(childOfSchemaDoc, true); + const contextPath = DocumentView.getContextPath(childOfSchemaDoc, true); return [contextPath[contextPath.indexOf(childOfSchemaDoc) - 1]]; // the schema doc that is "selected" by virtue of one of its children being selected } } @@ -175,6 +174,7 @@ export class CollectionSchemaView extends CollectionSubView() { document.removeEventListener('keydown', this.onKeyDown); } + isUnstyledView = returnTrue; // used by style provide via ViewBoxInterface rowIndex = (doc: Doc) => this.sortedDocs.docs.indexOf(doc); @action @@ -190,7 +190,7 @@ export class CollectionSchemaView extends CollectionSubView() { !e.shiftKey && this.clearSelection(); const newDoc = this.sortedDocs.docs[lastIndex + 1]; if (this._selectedDocs.includes(newDoc)) { - SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); + DocumentView.DeselectView(DocumentView.getFirstDocumentView(curDoc)); } else { this.addDocToSelection(newDoc, e.shiftKey); this._selectedCell && (this._selectedCell[0] = newDoc); @@ -209,7 +209,7 @@ export class CollectionSchemaView extends CollectionSubView() { if (firstIndex > 0 && firstIndex < this.childDocs.length) { !e.shiftKey && this.clearSelection(); const newDoc = this.sortedDocs.docs[firstIndex - 1]; - if (this._selectedDocs.includes(newDoc)) SelectionManager.DeselectView(DocumentManager.Instance.getFirstDocumentView(curDoc)); + if (this._selectedDocs.includes(newDoc)) DocumentView.DeselectView(DocumentView.getFirstDocumentView(curDoc)); else { this.addDocToSelection(newDoc, e.shiftKey); this._selectedCell && (this._selectedCell[0] = newDoc); @@ -416,12 +416,12 @@ export class CollectionSchemaView extends CollectionSubView() { @action addDocToSelection = (doc: Doc, extendSelection: boolean) => { - const rowDocView = DocumentManager.Instance.getDocumentView(doc); - if (rowDocView) SelectionManager.SelectView(rowDocView, extendSelection); + const rowDocView = DocumentView.getDocumentView(doc); + if (rowDocView) DocumentView.SelectView(rowDocView, extendSelection); }; @action - clearSelection = () => SelectionManager.DeselectAll(); + clearSelection = () => DocumentView.DeselectAll(); selectRows = (doc: Doc, lastSelected: Doc) => { const index = this.rowIndex(doc); @@ -476,9 +476,9 @@ export class CollectionSchemaView extends CollectionSubView() { this.dataDoc[this.fieldKey ?? 'data'] = new List([...removed, ...draggedDocs, ...pushedDocs]); this.clearSelection(); draggedDocs.forEach(doc => { - const draggedView = DocumentManager.Instance.getFirstDocumentView(doc); - if (draggedView) DocumentManager.Instance.RemoveView(draggedView); - DocumentManager.Instance.AddViewRenderedCb(doc, dv => dv.select(true)); + const draggedView = DocumentView.getFirstDocumentView(doc); + if (draggedView) DocumentView.removeView(draggedView); + DocumentView.addViewRenderedCb(doc, dv => dv.select(true)); }); return true; } diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index dfef3aa48..ee6987e89 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -19,9 +19,7 @@ import { ColumnType } from '../../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast, toList } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { FInfo, FInfoFieldType } from '../../../documents/Documents'; -import { DocFocusOrOpen } from '../../../util/DocumentManager'; import { dropActionType } from '../../../util/DropActionTypes'; -import { SettingsManager } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; @@ -29,7 +27,7 @@ import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; -import { returnEmptyDocViewList } from '../../nodes/DocumentView'; +import { DocFocusOrOpen, returnEmptyDocViewList } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; @@ -300,8 +298,8 @@ export class SchemaDateCell extends ObservableReactComponent} size={Size.XSMALL} type={Type.TERT} - color={SettingsManager.userColor} - background={SettingsManager.userBackgroundColor} + color={SnappingManager.userColor} + background={SnappingManager.userBackgroundColor} popup={
    diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index e75b01ab6..c595681b7 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -10,7 +10,6 @@ import { Gestures } from '../../../pen-gestures/GestureTypes'; import { DocumentType } from '../../documents/DocumentTypes'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; -import { SelectionManager } from '../../util/SelectionManager'; import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; import { InkingStroke } from '../InkingStroke'; @@ -26,14 +25,14 @@ import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function IsNoneSelected() { - return SelectionManager.Views.length <= 0; + return DocumentView.Selected().length <= 0; }, 'are no document selected'); // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setView(view: string, getSelected: boolean) { - if (getSelected) return SelectionManager.Docs; - const selected = SelectionManager.Docs.lastElement(); + if (getSelected) return DocumentView.SelectedDocs(); + const selected = DocumentView.SelectedDocs().lastElement(); selected ? (selected._type_collection = view) : console.log('[FontIconBox.tsx] changeView failed'); return undefined; }); @@ -41,7 +40,7 @@ ScriptingGlobals.add(function setView(view: string, getSelected: boolean) { // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { - const selectedViews = SelectionManager.Views; + const selectedViews = DocumentView.Selected(); if (Doc.ActiveTool !== InkTool.None) { if (checkResult) { return ActiveFillColor(); @@ -71,7 +70,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b } }); } else { - const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : []; + const selected = DocumentView.SelectedDocs().length ? DocumentView.SelectedDocs() : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : []; if (checkResult) { return selected.lastElement()?._backgroundColor ?? 'transparent'; } @@ -90,10 +89,10 @@ ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { if (checkResult) { - return SelectionManager.Views.length ? StrCast(SelectionManager.Docs.lastElement().layout_headingColor) : Doc.SharingDoc().headingColor; + return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor; } - if (SelectionManager.Views.length) { - SelectionManager.Docs.forEach(doc => { + if (DocumentView.Selected().length) { + DocumentView.SelectedDocs().forEach(doc => { doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color; doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title'); }); @@ -108,7 +107,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { - const selected = SelectionManager.Views.length ? SelectionManager.Views[0] : undefined; + const selected = DocumentView.Selected().length ? DocumentView.Selected()[0] : undefined; if (checkResult) { if (NumCast(selected?.Document.z) >= 1) return true; return false; @@ -119,7 +118,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { - const selected = SelectionManager.Docs.lastElement(); + const selected = DocumentView.SelectedDocs().lastElement(); // prettier-ignore const map: Map<'center' |'grid' | 'snaplines' | 'clusters' | 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ ['grid', { @@ -153,7 +152,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'center' | 'grid' | 'snaplines' return map.get(attr)?.checkResult(selected); } const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; - SelectionManager.Views.map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv)); + DocumentView.Selected().map(dv => map.get(attr)?.setDoc(dv.layoutDoc, dv)); setTimeout(() => batch.end(), 100); return undefined; }); @@ -347,7 +346,7 @@ ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); // toggle: Set overlay status of selected document // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { - const selected = SelectionManager.Docs.lastElement() ?? Doc.UserDoc(); + const selected = DocumentView.SelectedDocs().lastElement() ?? Doc.UserDoc(); // prettier-ignore const map: Map<'inkMask' | 'labels' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ ['inkMask', { @@ -381,7 +380,20 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil return map.get(option)?.checkResult(); } map.get(option)?.setMode(); - SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc)); + DocumentView.SelectedDocs() + .filter(doc => doc._layout_isSvg) + .map(doc => map.get(option)?.setInk(doc)); + return undefined; +}); + +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { + if (readOnly) { + return DocumentView.Selected().some(dv => dv.Document.keepZWhenDragged); + } + DocumentView.Selected().forEach(dv => { + dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged; + }); return undefined; }); @@ -390,7 +402,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'labels' | 'fil * */ // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) { - const selected = SelectionManager.Views.lastElement(); + const selected = DocumentView.Selected().lastElement(); if (selected?.Document.type === DocumentType.WEB) { if (checkResult) { return StrCast(selected.Document.data, Cast(selected.Document.data, WebField, null)?.url?.href); @@ -401,7 +413,7 @@ ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) { }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function webForward(checkResult?: boolean) { - const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox; + const selected = DocumentView.Selected().lastElement()?.ComponentView as WebBox; if (checkResult) { return selected?.forward(checkResult) ? undefined : 'lightGray'; } @@ -410,24 +422,24 @@ ScriptingGlobals.add(function webForward(checkResult?: boolean) { }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function webBack() { - const selected = SelectionManager.Views.lastElement()?.ComponentView as WebBox; + const selected = DocumentView.Selected().lastElement()?.ComponentView as WebBox; selected?.back(); }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function videoSnapshot() { - const selected = SelectionManager.Views.lastElement()?.ComponentView as VideoBox; + const selected = DocumentView.Selected().lastElement()?.ComponentView as VideoBox; selected?.Snapshot(); }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function imageSetPixelSize() { - const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox; + const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox; selected?.setNativeSize(); }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function imageRotate90() { - const selected = SelectionManager.Views.lastElement()?.ComponentView as ImageBox; + const selected = DocumentView.Selected().lastElement()?.ComponentView as ImageBox; selected?.rotate(); }); @@ -436,7 +448,7 @@ ScriptingGlobals.add(function imageRotate90() { * */ // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { - const selected = SelectionManager.Docs.lastElement(); + const selected = DocumentView.SelectedDocs().lastElement(); if (checkResult && selected) { const result: boolean = NumCast(selected.schema_previewWidth) > 0; if (result) return Colors.MEDIUM_BLUE; @@ -453,7 +465,7 @@ ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { }); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { - const selected = SelectionManager.Docs.lastElement(); + const selected = DocumentView.SelectedDocs().lastElement(); if (checkResult && selected) { return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent'; } @@ -468,7 +480,7 @@ ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { */ // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) { - SelectionManager.Docs.forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore + DocumentView.SelectedDocs().forEach(doc => { doc._text_fontFamily = key; }); // prettier-ignore const editorView = RichTextMenu.Instance?.TextView?.EditorView; if (checkResult) { return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc())?.fontFamily); diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index f99a18db2..cd735318e 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -8,7 +8,6 @@ import { Doc, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { Cast, DocCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; -import { LinkManager } from '../../util/LinkManager'; import { DocumentView } from '../nodes/DocumentView'; import './LinkMenu.scss'; import { LinkMenuItem } from './LinkMenuItem'; @@ -59,8 +58,7 @@ export class LinkMenuGroup extends React.Component { ? this.props.docView._props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(linkDoc.link_anchor_2) : DocCast(linkDoc.link_anchor_1) - : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || - LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null)); + : Doc.getOppositeAnchor(linkDoc, sourceDoc) || Doc.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null)); return !destDoc || !sourceDoc ? null : ( LinkManager.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; + const draggedDocs = (specificLinks || LinkManager.Links(sourceDoc)).map(link => Doc.getOppositeAnchor(link, sourceDoc)).filter(l => l) as Doc[]; if (draggedDocs.length) { const moddrag: Doc[] = []; @@ -81,7 +78,7 @@ export class LinkMenuItem extends ObservableReactComponent { onIconDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, () => { - const ancestor = DocumentManager.LinkCommonAncestor(this._props.linkDoc); + const ancestor = DocumentView.linkCommonAncestor(this._props.linkDoc); if (!ancestor?.ComponentView?.removeDocument?.(this._props.linkDoc)) { ancestor?.ComponentView?.addDocument?.(this._props.linkDoc); } @@ -105,14 +102,14 @@ export class LinkMenuItem extends ObservableReactComponent { Doc.ActivePresentation = trail; DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight); } else { - SelectionManager.SelectView(this._props.docView, false); + DocumentView.SelectView(this._props.docView, false); LinkManager.Instance.currentLink = this._props.linkDoc === LinkManager.Instance.currentLink ? undefined : this._props.linkDoc; LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined; - if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { + if ((SnappingManager.PropertiesWidth ?? 0) < 100) { setTimeout( action(() => { - SettingsManager.Instance.propertiesWidth = 250; + SnappingManager.SetPropertiesWidth(250); }) ); } @@ -126,10 +123,12 @@ export class LinkMenuItem extends ObservableReactComponent { this, e, moveEv => { - const eleClone: any = this._drag.current!.cloneNode(true); - eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`; - StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]); - this._props.clearLinkEditor?.(); + const eleClone: any = this._drag.current?.cloneNode(true); + if (eleClone) { + eleClone.style.transform = `translate(${moveEv.x}px, ${moveEv.y}px)`; + StartLinkTargetsDrag(eleClone, this._props.docView, moveEv.x, moveEv.y, this._props.sourceDoc, [this._props.linkDoc]); + this._props.clearLinkEditor?.(); + } return true; }, emptyFunction, @@ -146,13 +145,13 @@ export class LinkMenuItem extends ObservableReactComponent { : undefined; if (focusDoc) this._props.docView._props.focus(focusDoc, { instant: true }); - LinkFollower.FollowLink(this._props.linkDoc, this._props.sourceDoc, false); + DocumentView.FollowLink(this._props.linkDoc, this._props.sourceDoc, false); } } ); }; - deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc)))); + deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => Doc.DeleteLink?.(this._props.linkDoc)))); @observable _hover = false; docView = () => this._props.docView; render() { @@ -181,7 +180,7 @@ export class LinkMenuItem extends ObservableReactComponent { style={{ fontSize: this._hover ? 'larger' : undefined, fontWeight: this._hover ? 'bold' : undefined, - background: LinkManager.Instance.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + background: LinkManager.Instance.currentLink === this._props.linkDoc ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, }}>
    { onClick={e => { e.stopPropagation(); CollectionDockingView.AddSplit(NewLightboxView.LightboxDoc || NewLightboxView.LightboxDoc!, OpenWhereMod.none); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); NewLightboxView.SetNewLightboxDoc(undefined); }}>
    { } } public static AddDocTab = (docsIn: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => { - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); const doc = toList(docsIn).lastElement(); return ( doc && @@ -110,8 +105,8 @@ export class NewLightboxView extends React.Component { } else { const l = CreateLinkToActiveAudio(() => doc).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); - CollectionStackedTimeline.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); - // TabDocView.PinDoc(doc, { hidePresBox: true }); + DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); + // DocumentView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); if (doc !== LightboxView.LightboxDoc) { this._savedState = { @@ -130,7 +125,7 @@ export class NewLightboxView extends React.Component { ...future .slice() .sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)) - .sort((a, b) => LinkManager.Links(a).length - LinkManager.Links(b).length), + .sort((a, b) => Doc.Links(a).length - Doc.Links(b).length), ]; } this._doc = doc; @@ -148,11 +143,11 @@ export class NewLightboxView extends React.Component { @action public static Next() { const doc = NewLightboxView._doc!; const target = (NewLightboxView._docTarget = this._future?.pop()); - const targetDocView = target && DocumentManager.Instance.getLightboxDocumentView(target); + const targetDocView = target && DocumentView.getLightboxDocumentView(target); if (targetDocView && target) { const l = CreateLinkToActiveAudio(() => targetDocView.ComponentView?.getAnchor?.(true) || target).lastElement(); l && (Cast(l.link_anchor_2, Doc, null).backgroundColor = 'lightgreen'); - DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); + DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target }); } else if (!target && NewLightboxView.path.length) { const saved = NewLightboxView._savedState; @@ -182,10 +177,10 @@ export class NewLightboxView extends React.Component { return; } const { doc, target } = NewLightboxView._history?.lastElement(); - const docView = DocumentManager.Instance.getLightboxDocumentView(target || doc); + const docView = DocumentView.getLightboxDocumentView(target || doc); if (docView) { NewLightboxView._docTarget = target; - target && DocumentManager.Instance.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); + target && DocumentView.showDocument(target, { willZoomCentered: true, zoomScale: 0.9 }); } else { NewLightboxView.SetNewLightboxDoc(doc, target); } @@ -277,7 +272,7 @@ export class NewLightboxView extends React.Component { removeDocument={undefined} whenChildContentsActiveChanged={emptyFunction} addDocTab={this.addDocTab} - pinToPres={TabDocView.PinDoc} + pinToPres={DocumentView.PinDoc} focus={emptyFunction} /> @@ -316,8 +311,8 @@ export class NewLightboxView extends React.Component { if (coll) { const fieldKey = Doc.LayoutFieldKey(coll); const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '_annotations'])]; - const links = LinkManager.Links(coll) - .map(link => LinkManager.getOppositeAnchor(link, coll)) + const links = Doc.Links(coll) + .map(link => Doc.getOppositeAnchor(link, coll)) .filter(doc => doc) .map(doc => doc!); NewLightboxView.SetNewLightboxDoc(coll, undefined, contents.length ? contents : links); diff --git a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx index 23027808f..375408d01 100644 --- a/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx +++ b/src/client/views/newlightbox/components/Recommendation/Recommendation.tsx @@ -1,12 +1,12 @@ import * as React from 'react'; -import { IRecommendation } from './utils'; -import './Recommendation.scss'; -import { getType } from '../../utils'; import { FaEyeSlash } from 'react-icons/fa'; -import { NewLightboxView } from '../../NewLightboxView'; -import { DocumentManager } from '../../../../util/DocumentManager'; import { Doc } from '../../../../../fields/Doc'; import { Docs } from '../../../../documents/Documents'; +import { DocumentView } from '../../../nodes/DocumentView'; +import { NewLightboxView } from '../../NewLightboxView'; +import { getType } from '../../utils'; +import './Recommendation.scss'; +import { IRecommendation } from './utils'; export const Recommendation = (props: IRecommendation) => { const { title, data, type, text, transcript, loading, source, previewUrl, related_concepts, distance, docId } = props; @@ -17,7 +17,7 @@ export const Recommendation = (props: IRecommendation) => { onClick={() => { let doc: Doc | null = null; if (source == 'Dash' && docId) { - const docView = DocumentManager.Instance.getDocumentViewsById(docId).lastElement(); + const docView = DocumentView.getDocumentViewsById(docId).lastElement(); if (docView) { doc = docView.Document; } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 4697491e0..9deed4de4 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -18,14 +18,15 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils } from '../../documents/DocUtils'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; -import { LinkManager } from '../../util/LinkManager'; import { undoBatch } from '../../util/UndoManager'; import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { DocViewUtils } from '../DocViewUtils'; import { PinDocView, PinProps } from '../PinFuncs'; import './AudioBox.scss'; +import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { OpenWhere } from './OpenWhere'; @@ -99,7 +100,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { return this._props.PanelHeight() < 50; } // used to collapse timeline when node is shrunk @computed get links() { - return LinkManager.Links(this.dataDoc); + return Doc.Links(this.dataDoc); } @computed get mediaState() { return this.dataDoc.mediaState as mediaState; @@ -218,9 +219,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { @action removeCurrentlyPlaying = () => { const docView = this.DocumentView?.(); - if (CollectionStackedTimeline.CurrentlyPlaying && docView) { - const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView); - index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1); + if (DocumentView.CurrentlyPlaying && docView) { + const index = DocumentView.CurrentlyPlaying.indexOf(docView); + index !== -1 && DocumentView.CurrentlyPlaying.splice(index, 1); } }; @@ -228,11 +229,11 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { @action addCurrentlyPlaying = () => { const docView = this.DocumentView?.(); - if (!CollectionStackedTimeline.CurrentlyPlaying) { - CollectionStackedTimeline.CurrentlyPlaying = []; + if (!DocumentView.CurrentlyPlaying) { + DocumentView.CurrentlyPlaying = []; } - if (docView && CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView) === -1) { - CollectionStackedTimeline.CurrentlyPlaying.push(docView); + if (docView && DocumentView.CurrentlyPlaying.indexOf(docView) === -1) { + DocumentView.CurrentlyPlaying.push(docView); } }; @@ -251,7 +252,7 @@ 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(); - DocUtils.ActiveRecordings.push(this); + DocViewUtils.ActiveRecordings.push(this); this._recorder.ondataavailable = async (e: any) => { const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); if (!(result instanceof Error)) { @@ -278,8 +279,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { this.dataDoc[this.fieldKey + '_duration'] = (now - this._recordStart - this._pausedTime) / 1000; this.mediaState = mediaState.Paused; this._stream?.getAudioTracks()[0].stop(); - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + const ind = DocViewUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1); } }; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 691d07e31..685a5aca4 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -5,18 +5,16 @@ import { OmitKeys } from '../../../ClientUtils'; import { numberRange } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { TransitionTimer } from '../../../fields/DocSymbols'; +import { InkField } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; -import { SelectionManager } from '../../util/SelectionManager'; import { DocComponent } from '../DocComponent'; -import { StyleProp } from '../StyleProvider'; -import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; +import { StyleProp } from '../StyleProp'; import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps } from './DocumentView'; import { FieldViewProps } from './FieldView'; @@ -43,7 +41,8 @@ interface freeFormProps { export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { RenderCutoffProvider: (doc: Doc) => boolean; - CollectionFreeFormView: CollectionFreeFormView; + isAnyChildContentActive: () => boolean; + parent: any; } @observer export class CollectionFreeFormDocumentView extends DocComponent() { @@ -66,7 +65,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames - public static from(dv?: DocumentView) { + public static from(dv?: DocumentView): CollectionFreeFormDocumentView | undefined { return dv?._props.parent instanceof CollectionFreeFormDocumentView ? dv._props.parent : undefined; } @@ -120,7 +119,6 @@ export class CollectionFreeFormDocumentView extends DocComponent this.Transition || StrCast(this.Document.dataTransition); // prettier-ignore RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking @@ -185,13 +183,32 @@ export class CollectionFreeFormDocumentView extends DocComponent { + this.animFields.forEach(val => { + const findexed = Cast(doc[`${val.key}_indexed`], listSpec('number'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as number); + }); + this.animStringFields.forEach(val => { + const findexed = Cast(doc[`${val}_indexed`], listSpec('string'), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any as string); + }); + this.animDataFields(doc).forEach(val => { + const findexed = Cast(doc[`${val}_indexed`], listSpec(InkField), null); + findexed?.length <= timecode + 1 && findexed.push(undefined as any); + }); + }); + return newTimer; + } public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) { docs.forEach(doc => { if (doc.appearFrame === undefined) doc.appearFrame = currTimecode; @@ -220,7 +237,7 @@ export class CollectionFreeFormDocumentView extends DocComponent SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(topDoc, containerDocView), false), 0); + setTimeout(() => DocumentView.SelectView(DocumentView.getDocumentView(topDoc, containerDocView), false), 0); } }; @@ -255,7 +272,7 @@ export class CollectionFreeFormDocumentView extends DocComponent { - if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined; + if (this._props.isAnyChildContentActive()) return undefined; const backColor = this.BackgroundColor; const isGroup = this.dataDoc.isGroup && (!backColor || backColor === 'transparent'); return isGroup ? (this._props.isDocumentActive?.() ? 'group' : this._props.isGroupActive?.() ? 'child' : 'inactive') : this._props.isGroupActive?.() ? 'child' : undefined; diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index c42ca4468..6ae6bb228 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -15,7 +15,7 @@ import { dropActionType } from '../../util/DropActionTypes'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index ecfdcc229..15187b4e4 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -16,7 +16,6 @@ import { TraceMobx } from '../../../../fields/util'; import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; -import { DocumentManager } from '../../../util/DocumentManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; @@ -254,7 +253,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() im this.toggleSidebar(); } return new Promise>(res => { - DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + DocumentView.addViewRenderedCb(doc, dv => res(dv)); }); }; @computed get sidebarWidthPercent() { diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 1c3134185..bc35ab8c8 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -8,7 +8,6 @@ import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; import { Cast, DocCast, StrCast } from '../../../../../fields/Types'; import { Docs } from '../../../../documents/Documents'; -import { DocumentManager } from '../../../../util/DocumentManager'; import { undoable } from '../../../../util/UndoManager'; import {} from '../../../DocComponent'; import { ObservableReactComponent } from '../../../ObservableReactComponent'; @@ -16,6 +15,7 @@ import { PinProps, PinDocView } from '../../../PinFuncs'; import { DataVizBox } from '../DataVizBox'; import { createLineGenerator, drawLine, minMaxRange, scaleCreatorNumerical, xAxisCreator, xGrid, yAxisCreator, yGrid } from '../utils/D3Utils'; import './Chart.scss'; +import { DocumentView } from '../../DocumentView'; export interface DataPoint { x: number; @@ -71,7 +71,7 @@ export class LineChart extends ObservableReactComponent { } @computed get parentViz() { return DocCast(this._props.Document.dataViz_parentViz); - // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links + // return LinkManager.Links(this._props.Document) // out of all links // .filter(link => { // return link.link_anchor_1 == this._props.Document.dataViz_parentViz; // }) // get links where this chart doc is the target of the link @@ -80,7 +80,7 @@ export class LineChart extends ObservableReactComponent { @computed get incomingHighlited() { // return selected x and y axes // otherwise, use the selection of whatever is linked to us - const incomingVizBox = DocumentManager.Instance.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox; + const incomingVizBox = DocumentView.getFirstDocumentView(this.parentViz)?.ComponentView as DataVizBox; const highlitedRowIds = NumListCast(incomingVizBox?.layoutDoc?.dataViz_highlitedRows); return this._tableData.filter((record, i) => highlitedRowIds.includes(this._tableDataIds[i])); // get all the datapoints they have selected field set by incoming anchor } diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index ffb2f528a..ef6d1d412 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -84,7 +84,7 @@ export class PieChart extends ObservableReactComponent { @computed get parentViz() { return DocCast(this._props.Document.dataViz_parentViz); - // return LinkManager.Instance.getAllRelatedLinks(this._props.Document) // out of all links + // return LinkManager.Links(this._props.Document) // out of all links // .filter(link => link.link_anchor_1 == this._props.Document.dataViz_parentViz) // get links where this chart doc is the target of the link // .map(link => DocCast(link.link_anchor_1)); // then return the source of the link } diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 8038b2cd4..5cd77e274 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -7,6 +7,7 @@ import * as React from 'react'; import { ClientUtils, setupMoveUpEvents } from '../../../../../ClientUtils'; import { emptyFunction } from '../../../../../Utils'; import { Doc, Field, NumListCast } from '../../../../../fields/Doc'; +import { DocData } from '../../../../../fields/DocSymbols'; import { List } from '../../../../../fields/List'; import { listSpec } from '../../../../../fields/Schema'; import { Cast, DocCast } from '../../../../../fields/Types'; @@ -152,11 +153,9 @@ export class TableBox extends ObservableReactComponent { DragManager.StartAnchorAnnoDrag(moveEv.target instanceof HTMLElement ? [moveEv.target] : [], new DragManager.AnchorAnnoDragData(this._props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, { dragComplete: completeEv => { if (!completeEv.aborted && completeEv.annoDragData && completeEv.annoDragData.linkSourceDoc && completeEv.annoDragData.dropDocument && completeEv.linkDocument) { - completeEv.linkDocument.link_displayLine = true; - completeEv.linkDocument.link_matchEmbeddings = true; - completeEv.linkDocument.link_displayArrow = true; - // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this._props.Document; - // e.annoDragData.linkSourceDoc.followLinkZoom = false; + completeEv.linkDocument[DocData].link_matchEmbeddings = true; + completeEv.linkDocument[DocData].stroke_startMarker = true; + this._props.docView?.()?._props.addDocument?.(completeEv.linkDocument); } }, }); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e902d1792..ec9db8480 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -31,7 +31,6 @@ import { FunctionPlotBox } from './FunctionPlotBox'; import { ImageBox } from './ImageBox'; import { KeyValueBox } from './KeyValueBox'; import { LabelBox } from './LabelBox'; -import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkBox } from './LinkBox'; import { LoadingBox } from './LoadingBox'; import { MapBox } from './MapBox/MapBox'; @@ -87,18 +86,18 @@ interface HTMLtagProps { export class HTMLtag extends React.Component { click = () => { const clickScript = (this.props as any).onClick as Opt; - clickScript?.script.run({ this: this.props.Document, self: this.props.Document, scale: this.props.scaling }); + clickScript?.script.run({ this: this.props.Document, scale: this.props.scaling }); }; onInput = (e: React.FormEvent) => { const onInputScript = (this.props as any).onInput as Opt; - onInputScript?.script.run({ this: this.props.Document, self: this.props.Document, value: (e.target as any).textContent }); + onInputScript?.script.run({ this: this.props.Document, value: (e.target as any).textContent }); }; render() { const style: { [key: string]: any } = {}; const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; const replacer = (match: any, expr: string) => // bcz: this executes a script to convert a property expression string: { script } into a value - (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.Document, this: this.props.Document, scale: this.props.scaling }).result as string) || ''; + (ScriptField.MakeFunction(expr, { this: Doc.name, scale: 'number' })?.script.run({ this: this.props.Document, scale: this.props.scaling }).result as string) || ''; Object.keys(divKeys).forEach((prop: string) => { const p = (this.props as any)[prop] as string; style[prop] = p?.replace(/{([^.'][^}']+)}/g, replacer); @@ -121,7 +120,6 @@ export class DocumentContentsView extends ObservableReactComponent{content}< as in {this.title} - const replacer = (match: any, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix; + const replacer = (match: any, prefix: string, expr: string, postfix: string) => prefix + ((ScriptField.MakeFunction(expr, { this: Doc.name })?.script.run({ this: this._props.Document }).result as string) || '') + postfix; layoutFrame = layoutFrame.replace(/(>[^{]*)[^=]\{([^.'][^<}]+)\}([^}]*<)/g, replacer); // replace HTML with corresponding HTML tag as in: becomes @@ -205,7 +203,7 @@ export class DocumentContentsView extends ObservableReactComponent { makeObservable(this); } - static get DocViews() { - return LightboxView.LightboxDoc ? DocumentManager.Instance.DocumentViews.filter(v => LightboxView.Contains(v)) : DocumentManager.Instance.DocumentViews; - } render() { const { view } = this._props; const { left, top, right } = view.getBounds || { left: 0, top: 0, right: 0, bottom: 0 }; @@ -69,7 +64,7 @@ export class DocumentIconContainer extends React.Component { const match = node.text.match(/d([0-9]+)/); if (match) { const m = parseInt(match[1]); - const doc = DocumentIcon.DocViews[m].Document; + const doc = DocumentView.allViews()[m].Document; usedDocuments.add(m); return factory.createIdentifier(`idToDoc("${doc[Id]}")`); } @@ -81,7 +76,7 @@ export class DocumentIconContainer extends React.Component { return ts.visitNode(root, visit); }, getVars() { - const docs = DocumentIcon.DocViews; + const docs = DocumentView.allViews(); const capturedVariables: { [name: string]: FieldType } = {}; usedDocuments.forEach(index => { capturedVariables[`d${index}`] = docs.length > index ? docs[index].Document : `d${index}`; @@ -91,6 +86,6 @@ export class DocumentIconContainer extends React.Component { }; } render() { - return DocumentIcon.DocViews.map((dv, i) => ); + return DocumentView.allViews().map((dv, i) => ); } } diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 977899589..0c5156339 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -8,11 +8,8 @@ import * as React from 'react'; import { StopEvent, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; -import { StrCast } from '../../../fields/Types'; import { DocUtils } from '../../documents/DocUtils'; import { DragManager } from '../../util/DragManager'; -import { Hypothesis } from '../../util/HypothesisUtils'; import { LinkManager } from '../../util/LinkManager'; import { UndoManager, undoBatch } from '../../util/UndoManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; @@ -169,16 +166,6 @@ export class DocumentLinksButton extends ObservableReactComponent (link.link_matchEmbeddings ? link.link_anchor_1 === this.Document : Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.Document)) || (link.link_matchEmbeddings ? link.link_anchor_2 === this.Document : Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.Document)) || @@ -197,11 +193,11 @@ export class DocumentViewInternal extends DocComponent !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document); + return Doc.Links(this.Document).filter(link => !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document); } @computed get filteredLinks() { - return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []).filter(d => d.link_displayLine || Doc.UserDoc().showLinkLines); + return DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []); } componentWillUnmount() { @@ -259,7 +255,7 @@ export class DocumentViewInternal extends DocComponent dv.ContentDiv); + const views = DocumentView.Selected().filter(dv => dv.ContentDiv); const selected = views.length > 1 && views.some(dv => dv.Document === this.Document) ? views : [docView]; const dragData = new DragManager.DocumentDragData(selected.map(dv => dv.Document)); const screenXf = docView.screenToViewTransform(); @@ -289,8 +285,8 @@ export class DocumentViewInternal extends DocComponent { const browseTransitionTime = 500; - SelectionManager.DeselectAll(); - DocumentManager.Instance.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { + DocumentView.DeselectAll(); + DocumentView.showDocument(this.Document, { zoomScale: 0.8, willZoomCentered: true }, (focused: boolean) => { const options: FocusViewOptions = { pointFocus: { X: e.clientX, Y: e.clientY }, zoomTime: browseTransitionTime }; if (!focused && this._docView) { this._docView @@ -327,7 +323,7 @@ export class DocumentViewInternal extends DocComponent this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title); } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') { UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap'); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); Doc.UnBrushDoc(this.Document); } else { this._singleClickFunc?.(); @@ -377,7 +373,7 @@ export class DocumentViewInternal extends DocComponent DocumentView.LongPress && this._props.select(false), 1000); - if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this._docView; + if (!DocumentView.DownDocView) DocumentView.DownDocView = this._docView; this._downX = e.clientX; this._downY = e.clientY; @@ -463,7 +459,7 @@ export class DocumentViewInternal extends DocComponent { - if (this._props.dontRegisterView || this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false; + if (this._props.dontRegisterView) return false; if (this.Document === Doc.ActiveDashboard) { e.stopPropagation(); e.preventDefault(); @@ -484,7 +480,7 @@ export class DocumentViewInternal extends DocComponent SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); - zorderItems.push({ description: 'Send to Back', event: () => SelectionManager.Views.forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' }); + zorderItems.push({ description: 'Bring to Front', event: () => DocumentView.Selected().forEach(dv => dv._props.bringToFront?.(dv.Document, false)), icon: 'arrow-up' }); + zorderItems.push({ description: 'Send to Back', event: () => DocumentView.Selected().forEach(dv => dv._props.bringToFront?.(dv.Document, true)), icon: 'arrow-down' }); zorderItems.push({ description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged', event: undoBatch( @@ -601,7 +597,7 @@ export class DocumentViewInternal extends DocComponent this.toggleFollowLink(false, false), icon: 'link' }); !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); - } else if (LinkManager.Links(this.Document).length) { + } else if (Doc.Links(this.Document).length) { onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' }); onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' }); @@ -718,7 +714,7 @@ export class DocumentViewInternal extends DocComponent d.link_displayLine || Doc.UserDoc().showLinkLines); + const filtered = DocUtils.FilterDocs(this.directLinks, this._props.childFilters?.() ?? [], []); return filtered.some(link => link._link_displayArrow) ? 0 : undefined; } default: @@ -726,34 +722,6 @@ export class DocumentViewInternal extends DocComponent () => link.link_displayLine = false; // prettier-ignore - @computed get allLinkEndpoints() { - // the small blue dots that mark the endpoints of links - if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null; - return this.filteredLinks.map(link => ( -
    - -
    - )); - } - @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -780,7 +748,6 @@ export class DocumentViewInternal extends DocComponent - {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
    ); } @@ -1008,50 +975,45 @@ export class DocumentViewInternal extends DocComponent void) => void, onEnd?: () => void) { - let gumStream: any; - let recorder: any; - navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => { - let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); - if (audioTextAnnos) audioTextAnnos.push(''); - else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List(['']); - DictationManager.Controls.listen({ - interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore - continuous: { indefinite: false }, - }).then(results => { - if (results && [DictationManager.Controls.Infringed].includes(results)) { - DictationManager.Controls.stop(); - } - onEnd?.(); - }); - - gumStream = stream; - recorder = new MediaRecorder(stream); - recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); - if (!(result instanceof Error)) { - const audioField = new AudioField(result.accessPaths.agnostic.client); - const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); - if (audioAnnos) audioAnnos.push(audioField); - else dataDoc[field + '_audioAnnotations'] = new List([audioField]); - } - }; - recorder.start(); - const stopFunc = () => { - recorder.stop(); - DictationManager.Controls.stop(/* false */); - dataDoc.audioAnnoState = AudioAnnoState.stopped; - gumStream.getAudioTracks()[0].stop(); - }; - if (onRecording) onRecording(stopFunc); - else setTimeout(stopFunc, 5000); - }); - } } @observer export class DocumentView extends DocComponent() { public static ROOT_DIV = 'documentView-effectsWrapper'; + // LinkFollower + public static FollowLink: (linkDoc: Opt, sourceDoc: Doc, altKey: boolean) => boolean; + // selection funcs + public static DeselectAll: (except?: Doc) => void | undefined; + public static DeselectView: (dv: DocumentView | undefined) => void | undefined; + public static SelectView: (dv: DocumentView | undefined, extendSelection: boolean) => void | undefined; + public static Selected: () => DocumentView[]; + public static SelectedDocs: () => Doc[]; + public static SelectSchemaDoc: (doc: Doc, deselectAllFirst?: boolean) => void; + public static SelectedSchemaDoc: () => Opt; + // view mgr funcs + public static activateTabView: (tabDoc: Doc) => boolean; + public static allViews: () => DocumentView[]; + public static addView: (dv: DocumentView) => void | undefined; + public static removeView: (dv: DocumentView) => void | undefined; + public static addViewRenderedCb: (doc: Opt, func: (dv: DocumentView) => any) => boolean; + public static getFirstDocumentView: (toFind: Doc) => DocumentView | undefined; + public static getDocumentView: (target: Doc | undefined, preferredCollection?: DocumentView) => Opt; + public static getContextPath: (doc: Opt, includeExistingViews?: boolean) => Doc[]; + public static getLightboxDocumentView: (toFind: Doc) => Opt; + public static showDocumentView: (targetDocView: DocumentView, options: FocusViewOptions) => Promise; + public static showDocument: ( + targetDoc: Doc, // document to display + optionsIn: FocusViewOptions, // options for how to navigate to target + finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done. + ) => Promise; + public static linkCommonAncestor: (link: Doc) => DocumentView | undefined; + // pin func + public static PinDoc: (docIn: Doc | Doc[], pinProps: PinProps) => void; + // gesture + public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to. + // media playing + @observable public static CurrentlyPlaying: DocumentView[] = []; + public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore public ContentRef = React.createRef(); private _htmlOverlayEffect: Opt; @@ -1138,14 +1100,14 @@ export class DocumentView extends DocComponent() { componentDidMount() { runInAction(() => this.Document[DocViews].add(this)); this._disposers.onViewMounted = reaction(() => ScriptCast(this.Document.onViewMounted)?.script?.run({ this: this.Document }).result, emptyFunction); - !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.AddView(this); + !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.addView(this); } componentWillUnmount() { this._viewTimer && clearTimeout(this._viewTimer); runInAction(() => this.Document[DocViews].delete(this)); Object.values(this._disposers).forEach(disposer => disposer?.()); - !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); + !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.removeView(this); } public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore @@ -1176,10 +1138,6 @@ export class DocumentView extends DocComponent() { const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse(); const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; - if (this._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { - const docuBox = this.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); - if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined }; - } // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.()); return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() }; @@ -1289,7 +1247,7 @@ export class DocumentView extends DocComponent() { if (checkResult) { return Doc.UserDoc().defaultTextLayout; } - const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined; + const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined; undoable(() => { let tempDoc: Opt; if (view) { @@ -1359,11 +1317,11 @@ export class DocumentView extends DocComponent() { screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale; isSelected = () => this.IsSelected; select = (extendSelection: boolean, focusSelection?: boolean) => { - if (this.IsSelected && SelectionManager.Views.length > 1) SelectionManager.DeselectView(this); + if (this.IsSelected && DocumentView.Selected().length > 1) DocumentView.DeselectView(this); else { - SelectionManager.SelectView(this, extendSelection); + DocumentView.SelectView(this, extendSelection); if (focusSelection) { - DocumentManager.Instance.showDocument(this.Document, { + DocumentView.showDocument(this.Document, { willZoomCentered: true, zoomScale: 0.9, zoomTime: 500, @@ -1506,6 +1464,37 @@ export function returnEmptyDocViewList() { return emptyPath; } +// eslint-disable-next-line default-param-last +export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { + let doc = docIn; + const options = optionsIn; + const func = () => { + const cv = DocumentView.getDocumentView(containingDoc); + const dv = DocumentView.getDocumentView(doc, cv); + if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) { + DocumentView.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document)); + } else { + const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc)); + const showDoc = !Doc.IsSystem(container) && !cv ? container : doc; + options.toggleTarget = undefined; + DocumentView.showDocument(showDoc, options, () => DocumentView.showDocument(doc, { ...options, openLocation: undefined })).then(() => { + const cvFound = DocumentView.getDocumentView(containingDoc); + const dvFound = DocumentView.getDocumentView(doc, cvFound); + dvFound && Doc.linkFollowHighlight(dvFound.Document); + }); + } + }; + if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) { + doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!; + } + if (doc.hidden) { + doc.hidden = false; + options.toggleTarget = false; + setTimeout(func); + } else func(); +} +ScriptingGlobals.add(DocFocusOrOpen); + // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { documentView.iconify(); @@ -1527,9 +1516,9 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour const collectedLinks = DocListCast(linkCollection[DocData].data); let wid = NumCast(linkSource._width); let embedding: Doc | undefined; - const links = LinkManager.Links(linkSource); + const links = Doc.Links(linkSource); links.forEach(link => { - const other = LinkManager.getOppositeAnchor(link, linkSource); + const other = Doc.getOppositeAnchor(link, linkSource); const otherdoc = DocCast(other?.annotationOn ?? other); if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) { embedding = Doc.MakeEmbedding(otherdoc); diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 8d1617e66..5e3bb9fec 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -17,7 +17,7 @@ import { ContextMenu } from '../../ContextMenu'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { SelectedDocView } from '../../selectedDoc'; -import { StyleProp } from '../../StyleProvider'; +import { StyleProp } from '../../StyleProp'; import { FieldView, FieldViewProps } from '../FieldView'; import { OpenWhere } from '../OpenWhere'; import './FontIconBox.scss'; @@ -116,7 +116,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { default: type = 'slider'; break; } // prettier-ignore - const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, self: this.Document, value, _readOnly_: value === undefined }); + const numScript = (value?: number) => ScriptCast(this.Document.script).script.run({ this: this.Document, value, _readOnly_: value === undefined }); const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); // Script for checking the outcome of the toggle const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3))); @@ -142,7 +142,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { setupMoveUpEvents( this, e, - () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, self: this.Document, value: { doc: value, e } }).result, + () => ScriptCast(this.Document.onDragScript)?.script.run({ this: this.Document, value: { doc: value, e } }).result, emptyFunction, emptyFunction ); // prettier-ignore @@ -162,7 +162,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { const isViewDropdown = script?.script.originalScript.startsWith('{ return setView'); if (isViewDropdown) { const selected = Array.from(script?.script.run({ _readOnly_: true }).result) as Doc[]; - // const selected = SelectionManager.Docs; + // const selected = DocumentView.SelectedDocs(); if (selected.lastElement()) { if (StrCast(selected.lastElement().type) === DocumentType.COL) { text = StrCast(selected.lastElement()._type_collection); @@ -190,7 +190,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { } noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Carousel3D, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; } else { - text = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result; + text = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result; // text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); getStyle = (val: string) => ({ fontFamily: val }); } @@ -208,7 +208,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { return ( script.script.run({ this: this.Document, self: this.Document, value }), `dropdown select ${this.label}`)} + setSelectedVal={undoable(value => script.script.run({ this: this.Document, value }), `dropdown select ${this.label}`)} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} type={Type.TERT} @@ -232,17 +232,17 @@ export class FontIconBox extends ViewBoxBaseComponent() { */ @computed get colorButton() { const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); - const curColor = this.colorScript?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent'; + const curColor = this.colorScript?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result ?? 'transparent'; const tooltip: string = StrCast(this.Document.toolTip); return ( { if (!this.colorBatch) this.colorBatch = UndoManager.StartBatch(`Set ${tooltip} color`); - this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false }); + this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false }); }} setFinalColor={value => { - this.colorScript?.script.run({ this: this.Document, self: this.Document, value: value, _readOnly_: false }); + this.colorScript?.script.run({ this: this.Document, value: value, _readOnly_: false }); this.colorBatch?.end(); this.colorBatch = undefined; }} @@ -262,7 +262,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { const tooltip: string = StrCast(this.Document.toolTip); // const script = ScriptCast(this.Document.onClick); - // const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false; + // const toggleStatus = script ? script.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result : false; // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); const items = DocListCast(this.dataDoc.data); @@ -278,10 +278,10 @@ export class FontIconBox extends ViewBoxBaseComponent() { tooltip: StrCast(item.toolTip), val: StrCast(item.toolType), }))} - selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)} + selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)} setSelectedVal={(val: string | number) => { const itemDoc = items.find(item => item.toolType === val); - itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false }); + itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, value: val, _readOnly_: false }); }} /> ); @@ -296,7 +296,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { const script = ScriptCast(this.Document.onClick); const double = ScriptCast(this.Document.onDoubleClick); - const toggleStatus = script?.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result ?? false; + const toggleStatus = script?.script.run({ this: this.Document, value: undefined, _readOnly_: true }).result ?? false; // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); // const backgroundColor = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor); @@ -319,8 +319,8 @@ export class FontIconBox extends ViewBoxBaseComponent() { returnTrue, emptyFunction, action((clickEv, doubleTap) => { - (!doubleTap || !double) && script?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false }); - doubleTap && double?.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false }); + (!doubleTap || !double) && script?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false }); + doubleTap && double?.script.run({ this: this.Document, value: !toggleStatus, _readOnly_: false }); this._hackToRecompute += 1; }) ) @@ -341,15 +341,15 @@ export class FontIconBox extends ViewBoxBaseComponent() { @computed get editableText() { const script = ScriptCast(this.Document.script); - const checkResult = script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result; + const checkResult = script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result; - const setValue = (value: string): boolean => script?.script.run({ this: this.Document, self: this.Document, value, _readOnly_: false }).result; + const setValue = (value: string): boolean => script?.script.run({ this: this.Document, value, _readOnly_: false }).result; return (
    - script?.script.run({ this: this.Document, self: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine contents={checkResult} /> + script?.script.run({ this: this.Document, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine contents={checkResult} />
    ); @@ -358,7 +358,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { renderButton = () => { const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); const tooltip = StrCast(this.Document.toolTip); - const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, self: this.Document, _readOnly_: false }); + const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, _readOnly_: false }); const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label }; // prettier-ignore switch (this.type) { diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index f32d39aaf..3d1bd7563 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -2,7 +2,7 @@ import functionPlot from 'function-plot'; import { computed, makeObservable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { Cast, StrCast } from '../../../fields/Types'; @@ -11,7 +11,6 @@ import { DocUtils } from '../../documents/DocUtils'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; -import { LinkManager } from '../../util/LinkManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; @@ -49,8 +48,8 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent return anchor; }; @computed get graphFuncs() { - const links = LinkManager.Instance.getAllRelatedLinks(this.Document) - .map(d => LinkManager.getOppositeAnchor(d, this.Document)) + const links = Doc.Links(this.Document) + .map(d => Doc.getOppositeAnchor(d, this.Document)) .filter(d => d) .map(d => d!); const funcs = links.concat(DocListCast(this.dataDoc[this.fieldKey])).map(doc => diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index d317f46bb..3945da104 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -19,7 +19,6 @@ import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils } from '../../documents/DocUtils'; import { Networking } from '../../Network'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; @@ -30,7 +29,8 @@ import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { OverlayView } from '../OverlayView'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { PinDocView, PinProps } from '../PinFuncs'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; +import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; import './ImageBox.scss'; @@ -254,7 +254,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() impl cropping.y = NumCast(this.Document.y); this._props.addDocTab(cropping, OpenWhere.inParent); } - DocumentManager.Instance.AddViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200)); + DocumentView.addViewRenderedCb(cropping, dv => setTimeout(() => (dv.ComponentView as ImageBox).setNativeSize(), 200)); this._props.bringToFront?.(cropping); return cropping; }; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 46bb16e50..cd591fa42 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -59,6 +59,8 @@ export class KeyValueBox extends ObservableReactComponent { get fieldDocToLayout() { return DocCast(this._props.Document); } + isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface + dontRegisterView = returnTrue; // used by ViewBoxInterface @action onEnterKey = (e: React.KeyboardEvent): void => { @@ -111,7 +113,7 @@ export class KeyValueBox extends ObservableReactComponent { if (setResult) setResult?.(value); else target[key] = field; }; - const res = script.run({ this: Doc.Layout(doc), self: doc, _setCacheResult_ }, console.log); + const res = script.run({ this: Doc.Layout(doc), _setCacheResult_ }, console.log); if (!res.success) { if (key) target[key] = script.originalScript; return false; diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 4dec3506c..f80ff5f94 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -13,7 +13,7 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxBaseComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { FieldView, FieldViewProps } from './FieldView'; import BigText from './LabelBigText'; import './LabelBox.scss'; diff --git a/src/client/views/nodes/LinkAnchorBox.scss b/src/client/views/nodes/LinkAnchorBox.scss deleted file mode 100644 index caff369df..000000000 --- a/src/client/views/nodes/LinkAnchorBox.scss +++ /dev/null @@ -1,34 +0,0 @@ -.linkAnchorBox-cont, -.linkAnchorBox-cont-small { - cursor: default; - position: absolute; - width: 15; - height: 15; - border-radius: 20px; - user-select: none; - pointer-events: all; - - .linkAnchorBox-linkCloser { - position: absolute; - width: 18; - height: 18; - background: rgb(219, 21, 21); - top: -1px; - left: -1px; - border-radius: 5px; - display: flex; - justify-content: center; - align-items: center; - padding-left: 2px; - padding-top: 1px; - } - .linkAnchorBox-button { - position: relative; - display: inline-block; - } -} - -.linkAnchorBox-cont-small { - width: 5px; - height: 5px; -} \ No newline at end of file diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx deleted file mode 100644 index d43241de0..000000000 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ /dev/null @@ -1,118 +0,0 @@ -import { action, computed, makeObservable } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { setupMoveUpEvents } from '../../../ClientUtils'; -import { Utils, emptyFunction } from '../../../Utils'; -import { Doc } from '../../../fields/Doc'; -import { NumCast, StrCast } from '../../../fields/Types'; -import { TraceMobx } from '../../../fields/util'; -import { DragManager } from '../../util/DragManager'; -import { dropActionType } from '../../util/DropActionTypes'; -import { LinkFollower } from '../../util/LinkFollower'; -import { SelectionManager } from '../../util/SelectionManager'; -import { ViewBoxBaseComponent } from '../DocComponent'; -import { StyleProp } from '../StyleProvider'; -import { FieldView, FieldViewProps } from './FieldView'; -import './LinkAnchorBox.scss'; -import { LinkInfo } from './LinkDocPreview'; - -const { MEDIUM_GRAY } = require('../global/globalCssVariables.module.scss'); // prettier-ignore - -@observer -export class LinkAnchorBox extends ViewBoxBaseComponent() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(LinkAnchorBox, fieldKey); - } - _doubleTap = false; - _lastTap: number = 0; - _ref = React.createRef(); - _isOpen = false; - _timeout: NodeJS.Timeout | undefined; - - constructor(props: FieldViewProps) { - super(props); - makeObservable(this); - } - - componentDidMount() { - this._props.setContentViewBox?.(this); - } - - @computed get linkSource() { - return this.DocumentView?.().containerViewPath?.().lastElement().Document; // this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.LinkSource); - } - - onPointerDown = (e: React.PointerEvent) => { - const { linkSource } = this; - linkSource && - setupMoveUpEvents(this, e, this.onPointerMove, emptyFunction, (clickEv, doubleTap) => { - if (doubleTap) LinkFollower.FollowLink(this.Document, linkSource, false); - else this._props.select(false); - }); - }; - onPointerMove = action((e: PointerEvent) => { - const cdiv = this._ref?.current?.parentElement; - if (!this._isOpen && cdiv) { - const bounds = cdiv.getBoundingClientRect(); - const pt = Utils.getNearestPointInPerimeter(bounds.left, bounds.top, bounds.width, bounds.height, e.clientX, e.clientY); - const separation = Math.sqrt((pt[0] - e.clientX) * (pt[0] - e.clientX) + (pt[1] - e.clientY) * (pt[1] - e.clientY)); - if (separation > 100) { - const dragData = new DragManager.DocumentDragData([this.Document]); - dragData.dropAction = dropActionType.embed; - dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick']; - DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]); - return true; - } - this.layoutDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100; - this.layoutDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100; - this.layoutDoc.link_autoMoveAnchors = false; - } - return false; - }); - - specificContextMenu = (): void => {}; - - render() { - TraceMobx(); - const small = this._props.PanelWidth() <= 1; // this happens when rendered in a treeView - const x = NumCast(this.layoutDoc[this.fieldKey + '_x'], 100); - const y = NumCast(this.layoutDoc[this.fieldKey + '_y'], 100); - const background = this._props.styleProvider?.(this.dataDoc, this._props, StyleProp.BackgroundColor + ':anchor'); - const anchor = this.fieldKey === 'link_anchor_1' ? 'link_anchor_2' : 'link_anchor_1'; - const anchorScale = !this.dataDoc[this.fieldKey + '_useSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25; - const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title); - const selView = SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_1') - ? 'link_anchor_1' - : SelectionManager.Views.lastElement()?._props.LayoutTemplateString?.includes('link_anchor_2') - ? 'link_anchor_2' - : ''; - return ( -
    - LinkInfo.SetLinkInfo({ - DocumentView: this.DocumentView, - styleProvider: this._props.styleProvider, - linkSrc: this.linkSource, - linkDoc: this.Document, - showHeader: true, - location: [e.clientX, e.clientY + 20], - noPreview: false, - }) - } - onPointerDown={this.onPointerDown} - onContextMenu={this.specificContextMenu} - style={{ - border: selView && this.dataDoc[selView] === this.dataDoc[this.fieldKey] ? `solid ${MEDIUM_GRAY} 2px` : undefined, - background, - left: `calc(${x}% - ${small ? 2.5 : 7.5}px)`, - top: `calc(${y}% - ${small ? 2.5 : 7.5}px)`, - transform: `scale(${anchorScale})`, - cursor: 'grab', - }} - /> - ); - } -} diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 559b1fcae..6caa38a7f 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -13,12 +13,11 @@ import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; -import { DocumentManager } from '../../util/DocumentManager'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { ComparisonBox } from './ComparisonBox'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; @@ -43,7 +42,7 @@ export class LinkBox extends ViewBoxBaseComponent() { anchor = (which: number) => { const anch = DocCast(this.dataDoc['link_anchor_' + which]); const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch; - return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement()); + return DocumentView.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement()); }; _hackToSeeIfDeleted: any; componentWillUnmount() { diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 23cb25962..ff95f8547 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -2,16 +2,17 @@ import { action, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DocData } from '../../../fields/DocSymbols'; +import { StrCast } from '../../../fields/Types'; import { LinkManager } from '../../util/LinkManager'; import './LinkDescriptionPopup.scss'; import { TaskCompletionBox } from './TaskCompletedBox'; -import { StrCast } from '../../../fields/Types'; @observer export class LinkDescriptionPopup extends React.Component<{}> { // eslint-disable-next-line no-use-before-define public static Instance: LinkDescriptionPopup; @observable public display: boolean = false; + // eslint-disable-next-line react/no-unused-class-component-methods @observable public showDescriptions: string = 'ON'; @observable public popupX: number = 700; @observable public popupY: number = 350; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 0936acc15..508008569 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -11,12 +11,10 @@ import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/T import { DocServer } from '../../DocServer'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; -import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; -import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { SearchUtil } from '../../util/SearchUtil'; -import { SettingsManager } from '../../util/SettingsManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentView } from './DocumentView'; @@ -134,17 +132,17 @@ export class LinkDocPreview extends ObservableReactComponent { - if (anchor instanceof Doc && LinkManager.Links(anchor).length) { - this._linkDoc = this._linkDoc ?? LinkManager.Links(anchor)[0]; + if (anchor instanceof Doc && Doc.Links(anchor).length) { + this._linkDoc = this._linkDoc ?? Doc.Links(anchor)[0]; const automaticLink = this._linkDoc.link_relationship === LinkManager.AutoKeywords; if (automaticLink) { // automatic links specify the target in the link info, not the source const linkTarget = anchor; - this._linkSrc = LinkManager.getOppositeAnchor(this._linkDoc, linkTarget); + this._linkSrc = Doc.getOppositeAnchor(this._linkDoc, linkTarget); this._markerTargetDoc = this._targetDoc = linkTarget; } else { this._linkSrc = anchor; - const linkTarget = LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + const linkTarget = Doc.getOppositeAnchor(this._linkDoc, this._linkSrc); this._markerTargetDoc = linkTarget; this._targetDoc = /* linkTarget?.type === DocumentType.MARKER && */ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } @@ -169,8 +167,8 @@ export class LinkDocPreview extends ObservableReactComponent { LinkInfo.Clear(); if (this._linkDoc && this._linkSrc) { - LinkFollower.FollowLink(this._linkDoc, this._linkSrc, false); + DocumentView.FollowLink(this._linkDoc, this._linkSrc, false); } else if (this._props.hrefs?.length) { const webDoc = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, this._props.hrefs[0], false).keys()) .filter(doc => doc.type === DocumentType.WEB) .lastElement() ?? Docs.Create.WebDocument(this._props.hrefs[0], { title: this._props.hrefs[0], _nativeWidth: 850, _width: 200, _height: 400, data_useCors: true }); - DocumentManager.Instance.showDocument(webDoc, { + DocumentView.showDocument(webDoc, { openLocation: OpenWhere.lightbox, willPan: true, zoomTime: 500, @@ -229,7 +227,7 @@ export class LinkDocPreview extends ObservableReactComponent +
    Edit Link
    } placement="top">
    @@ -282,7 +280,7 @@ export class LinkDocPreview extends ObservableReactComponent { - const targetanchor = this._linkDoc && this._linkSrc && LinkManager.getOppositeAnchor(this._linkDoc, this._linkSrc); + const targetanchor = this._linkDoc && this._linkSrc && Doc.getOppositeAnchor(this._linkDoc, this._linkSrc); targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {}); }} Document={this._targetDoc!} @@ -325,7 +323,7 @@ export class LinkDocPreview extends ObservableReactComponent + style={{ borderColor: SnappingManager.userColor, left: this._props.location[0], top: this._props.location[1], width: this.width() + borders, height: this.height() + borders + (this._props.showHeader ? 37 : 0) }}> {this.docPreview}
    ); diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index aa89398f3..5f343bdfe 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -6,12 +6,11 @@ import { Doc } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { StrCast } from '../../../fields/Types'; import { Networking } from '../../Network'; -import { DocumentManager } from '../../util/DocumentManager'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { Docs } from '../../documents/Documents'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; import './LoadingBox.scss'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { Docs } from '../../documents/Documents'; /** * LoadingBox Class represents a placeholder doc for documents that are currently @@ -39,25 +38,11 @@ export class LoadingBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LoadingBox, fieldKey); } - // removes from currently loading display - public static removeCurrentlyLoading(doc: Doc) { - if (DocumentManager.Instance.CurrentlyLoading) { - const index = DocumentManager.Instance.CurrentlyLoading.indexOf(doc); - runInAction(() => index !== -1 && DocumentManager.Instance.CurrentlyLoading.splice(index, 1)); - } - } - - // adds doc to currently loading display - public static addCurrentlyLoading(doc: Doc) { - if (DocumentManager.Instance.CurrentlyLoading.indexOf(doc) === -1) { - runInAction(() => DocumentManager.Instance.CurrentlyLoading.push(doc)); - } - } _timer: any; @observable progress = ''; componentDidMount() { - if (!DocumentManager.Instance.CurrentlyLoading?.includes(this.Document)) { + if (!Doc.CurrentlyLoading?.includes(this.Document)) { this.Document.loadingError = 'Upload interrupted, please try again'; } else { const updateFunc = async () => { diff --git a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx index f176317d2..b8fd8ac6a 100644 --- a/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/DirectionsAnchorMenu.tsx @@ -8,9 +8,9 @@ import { returnFalse } from '../../../../ClientUtils'; import { unimplementedFunction } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; -import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { DocumentView } from '../DocumentView'; @observer export class DirectionsAnchorMenu extends AntimodeMenu { @@ -56,7 +56,7 @@ export class DirectionsAnchorMenu extends AntimodeMenu { componentDidMount() { this._disposer = reaction( - () => SelectionManager.Views.slice(), + () => DocumentView.Selected().slice(), () => DirectionsAnchorMenu.Instance.fadeOut(true) ); } diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index 174511e1a..103a35434 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -13,9 +13,9 @@ import { unimplementedFunction } from '../../../../Utils'; import { Doc, Opt } from '../../../../fields/Doc'; import { NumCast, StrCast } from '../../../../fields/Types'; import { CalendarManager } from '../../../util/CalendarManager'; -import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; +import { DocumentView } from '../DocumentView'; import './MapAnchorMenu.scss'; import { MapboxApiUtility, TransportationType } from './MapboxApiUtility'; import { MarkerIcons } from './MarkerIcons'; @@ -126,7 +126,7 @@ export class MapAnchorMenu extends AntimodeMenu { componentDidMount() { this._disposer = reaction( - () => SelectionManager.Views.slice(), + () => DocumentView.Selected().slice(), () => MapAnchorMenu.Instance.fadeOut(true) ); } diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 50831f8ea..ac8010f11 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -21,9 +21,7 @@ import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types'; import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; -import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; -import { LinkManager } from '../../../util/LinkManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; import { PinDocView, PinProps } from '../../PinFuncs'; @@ -249,7 +247,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem if (existingPin) { setTimeout(() => { // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet - if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) { + if (!Doc.Links(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) { const anchor = this.getAnchor(true, undefined, existingPin); anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' }); doc.latitude = existingPin?.latitude; @@ -438,7 +436,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem options.didMove = true; } return new Promise>(res => { - DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + DocumentView.addViewRenderedCb(doc, dv => res(dv)); }); }; diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx index 9825824bd..7697fd295 100644 --- a/src/client/views/nodes/MapBox/MapBox2.tsx +++ b/src/client/views/nodes/MapBox/MapBox2.tsx @@ -509,9 +509,9 @@ // // TODO: auto center on select a document in the sidebar // private handleMapCenter = (map: google.maps.Map) => { -// // console.log("print the selected views in selectionManager:") -// // if (SelectionManager.Views.lastElement()) { -// // console.log(SelectionManager.Views.lastElement()); +// // console.log("print the selected views in Document.Selected:") +// // if (DocumentView.Selected().lastElement()) { +// // console.log(DocumentView.Selected().lastElement()); // // } // }; diff --git a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx index 3b4ffd4bd..bfd40692b 100644 --- a/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx +++ b/src/client/views/nodes/MapboxMapBox/MapboxContainer.tsx @@ -13,9 +13,7 @@ import { DocCast, NumCast, StrCast, toList } from '../../../../fields/Types'; import { DocUtils } from '../../../documents/DocUtils'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; -import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; -import { LinkManager } from '../../../util/LinkManager'; import { Transform } from '../../../util/Transform'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; @@ -130,7 +128,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent if (existingPin) { setTimeout(() => { // we use a timeout in case this is called from the sidebar which may have just added a link that hasn't made its way into th elink manager yet - if (!LinkManager.Instance.getAllRelatedLinks(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) { + if (!Doc.Links(doc).some(link => DocCast(link.link_anchor_1)?.mapPin === existingPin || DocCast(link.link_anchor_2)?.mapPin === existingPin)) { const anchor = this.getAnchor(true, undefined, existingPin); anchor && DocUtils.MakeLink(anchor, doc, { link_relationship: 'link to map location' }); doc.latitude = existingPin?.latitude; @@ -390,7 +388,7 @@ export class MapBoxContainer extends ViewBoxAnnotatableComponent options.didMove = true; } return new Promise>(res => { - DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + DocumentView.addViewRenderedCb(doc, dv => res(dv)); }); }; /* diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 5d1874aca..9038ed27a 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -19,9 +19,7 @@ import { emptyFunction } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocUtils } from '../../documents/DocUtils'; -import { DocumentManager } from '../../util/DocumentManager'; import { KeyCodes } from '../../util/KeyCodes'; -import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionStackingView } from '../collections/CollectionStackingView'; @@ -251,7 +249,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent() implem this.toggleSidebar(false); } return new Promise>(res => { - DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + DocumentView.addViewRenderedCb(doc, dv => res(dv)); }); }; @@ -561,7 +559,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent() implem removeDocument={this.removeDocument} /> ) : ( -
    setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}> +
    setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}> () { @@ -102,7 +102,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { screengrabber.overlayY = 590; // was 0 screengrabber[DocData][Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true; Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay - DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => { + DocumentView.addViewRenderedCb(screengrabber, docView => { RecordingBox.screengrabber = docView.ComponentView as RecordingBox; RecordingBox.screengrabber.Record?.(); }); @@ -119,7 +119,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { value.overlayX = 70; value.overlayY = window.innerHeight - 180; Doc.AddToMyOverlay(value); - DocumentManager.Instance.AddViewRenderedCb(value, docView => { + DocumentView.addViewRenderedCb(value, docView => { Doc.UserDoc().currentRecording = docView.Document; docView.select(false); RecordingBox.resumeWorkspaceReplaying(value); @@ -132,7 +132,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { */ @undoBatch public static addRecToWorkspace(value: RecordingBox) { - const ffView = Array.from(DocumentManager.Instance.DocumentViews).find(view => view.ComponentView instanceof CollectionFreeFormView); + const ffView = DocumentView.allViews().find(view => view.ComponentView instanceof CollectionFreeFormView); (ffView?.ComponentView as CollectionFreeFormView)._props.addDocument?.(value.Document); Doc.RemoveDocFromList(Doc.UserDoc(), 'workspaceRecordings', value.Document); Doc.RemFromMyOverlay(value.Document); @@ -142,7 +142,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { } public static resumeWorkspaceReplaying(doc: Doc) { - const docView = DocumentManager.Instance.getDocumentView(doc); + const docView = DocumentView.getDocumentView(doc); if (docView?.ComponentView instanceof VideoBox) { docView.ComponentView.Play(); } @@ -150,7 +150,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { } public static pauseWorkspaceReplaying(doc: Doc) { - const docView = DocumentManager.Instance.getDocumentView(doc); + const docView = DocumentView.getDocumentView(doc); const videoBox = docView?.ComponentView as VideoBox; if (videoBox) { videoBox.Pause(); diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 8eb55b2f8..3be50f5e6 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -24,6 +24,7 @@ import { SettingsManager } from '../../util/SettingsManager'; import { TrackMovements } from '../../util/TrackMovements'; import { ContextMenu } from '../ContextMenu'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { DocViewUtils } from '../DocViewUtils'; import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { mediaState } from './AudioBox'; @@ -161,8 +162,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent() // }); } componentWillUnmount() { - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + const ind = DocViewUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1); } specificContextMenu = (): void => { @@ -265,7 +266,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent() this._screenCapture = true; this.dataDoc.mediaState = 'recording'; }); - DocUtils.ActiveRecordings.push(this); + DocViewUtils.ActiveRecordings.push(this); } else { this._audioRec?.stop(); this._videoRec?.stop(); @@ -273,8 +274,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent() this._screenCapture = false; this.dataDoc.mediaState = 'paused'; }); - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + const ind = DocViewUtils.ActiveRecordings.indexOf(this); + ind !== -1 && DocViewUtils.ActiveRecordings.splice(ind, 1); CaptureManager.Instance.open(this.Document); } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index f32d00386..afd73cfe8 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -16,9 +16,7 @@ import { emptyFunction, formatTime } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils, FollowLinkScript } from '../../documents/DocUtils'; -import { DocumentManager } from '../../util/DocumentManager'; import { dropActionType } from '../../util/DropActionTypes'; -import { LinkManager } from '../../util/LinkManager'; import { ReplayMovements } from '../../util/ReplayMovements'; import { undoBatch } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; @@ -26,16 +24,16 @@ import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionS import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { VideoThumbnails } from '../global/globalEnums'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { PinDocView, PinProps } from '../PinFuncs'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; import { RecordingBox } from './RecordingBox'; import './VideoBox.scss'; -import { VideoThumbnails } from '../global/globalEnums'; /** * VideoBox @@ -88,7 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl @observable _scrubbing: boolean = false; @computed get links() { - return LinkManager.Links(this.dataDoc); + return Doc.Links(this.dataDoc); } @computed get heightPercent() { return NumCast(this.layoutDoc._layout_timelineHeightPercent, 100); @@ -347,7 +345,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl this._props.addDocument?.(imageSnapshot); DocUtils.MakeLink(imageSnapshot, this.getAnchor(true), { link_relationship: 'video snapshot' }); // link && (DocCast(link.link_anchor_2)[DocData].timecodeToHide = NumCast(DocCast(link.link_anchor_2).timecodeToShow) + 3); // do we need to set an end time? should default to +0.1 - setTimeout(() => downX !== undefined && downY !== undefined && DocumentManager.Instance.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true)); + setTimeout(() => downX !== undefined && downY !== undefined && DocumentView.getFirstDocumentView(imageSnapshot)?.startDragging(downX, downY, dropActionType.move, true)); }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { @@ -392,7 +390,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl return this._stackedTimeline.getView(doc, options); } return new Promise>(res => { - DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + DocumentView.addViewRenderedCb(doc, dv => res(dv)); }); }; @@ -446,7 +444,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl { fireImmediately: true } ); - (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoBox.numThumbnails) && this.getVideoThumbnails(); + (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length !== VideoThumbnails.DENSE) && this.getVideoThumbnails(); } }; @@ -652,20 +650,20 @@ export class VideoBox extends ViewBoxAnnotatableComponent() impl @action removeCurrentlyPlaying = () => { const docView = this.DocumentView?.(); - if (CollectionStackedTimeline.CurrentlyPlaying && docView) { - const index = CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView); - index !== -1 && CollectionStackedTimeline.CurrentlyPlaying.splice(index, 1); + if (DocumentView.CurrentlyPlaying && docView) { + const index = DocumentView.CurrentlyPlaying.indexOf(docView); + index !== -1 && DocumentView.CurrentlyPlaying.splice(index, 1); } }; // adds doc to currently playing display @action addCurrentlyPlaying = () => { const docView = this.DocumentView?.(); - if (!CollectionStackedTimeline.CurrentlyPlaying) { - CollectionStackedTimeline.CurrentlyPlaying = []; + if (!DocumentView.CurrentlyPlaying) { + DocumentView.CurrentlyPlaying = []; } - if (docView && CollectionStackedTimeline.CurrentlyPlaying.indexOf(docView) === -1) { - CollectionStackedTimeline.CurrentlyPlaying.push(docView); + if (docView && DocumentView.CurrentlyPlaying.indexOf(docView) === -1) { + DocumentView.CurrentlyPlaying.push(docView); } }; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 54f246b20..1003bc473 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -21,7 +21,6 @@ import { emptyFunction, stringHash } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils } from '../../documents/DocUtils'; -import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; @@ -38,7 +37,7 @@ import { Annotation } from '../pdf/Annotation'; import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { PinDocView, PinProps } from '../PinFuncs'; import { SidebarAnnos } from '../SidebarAnnos'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; @@ -324,7 +323,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href); if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); return new Promise>(res => { - DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + DocumentView.addViewRenderedCb(doc, dv => res(dv)); }); }; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 1c5ea2dd4..5d53a2a5f 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -293,7 +293,6 @@ export class DashFieldView { unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview); constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { makeObservable(this); - const self = this; this.node = node; this.tbox = tbox; this.getpos = getPos; @@ -305,14 +304,14 @@ export class DashFieldView { this.dom.onkeypress = function (e: KeyboardEvent) { e.stopPropagation(); }; - this.dom.onkeydown = function (e: KeyboardEvent) { + this.dom.onkeydown = (e: KeyboardEvent) => { e.stopPropagation(); if (e.key === 'Tab') { e.preventDefault(); const editor = tbox.EditorView; if (editor) { const { state } = editor; - for (let i = self.getpos() + 1; i < state.doc.content.size; i++) { + for (let i = this.getpos() + 1; i < state.doc.content.size; i++) { if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) { editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i)))); return; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index ad6629fc9..8a4a7718a 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -32,16 +32,13 @@ import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocUtils } from '../../../documents/DocUtils'; import { DictationManager } from '../../../util/DictationManager'; -import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { MakeTemplate } from '../../../util/DropConverter'; import { LinkManager } from '../../../util/LinkManager'; import { RTFMarkup } from '../../../util/RTFMarkup'; -import { SelectionManager } from '../../../util/SelectionManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; -import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackingView } from '../../collections/CollectionStackingView'; import { CollectionTreeView } from '../../collections/CollectionTreeView'; import { ContextMenu } from '../../ContextMenu'; @@ -53,9 +50,10 @@ import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup'; import { PinDocView, PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; -import { styleFromLayoutString, StyleProp } from '../../StyleProvider'; +import { StyleProp } from '../../StyleProp'; +import { styleFromLayoutString } from '../../StyleProvider'; import { mediaState } from '../AudioBox'; -import { DocumentView, DocumentViewInternal } from '../DocumentView'; +import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { FocusViewOptions } from '../FocusViewOptions'; import { LinkInfo } from '../LinkDocPreview'; @@ -260,7 +258,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { stopFunc = stop }); // prettier-ignore + DictationManager.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => { stopFunc = stop }); // prettier-ignore const reactionDisposer = reaction( () => target.mediaState, @@ -271,7 +269,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { let linkTime; let linkAnchor; - LinkManager.Links(this.dataDoc).forEach(l => { + Doc.Links(this.dataDoc).forEach(l => { const anchor = DocCast(l.link_anchor_1)?.annotationOn ? DocCast(l.link_anchor_1) : DocCast(l.link_anchor_2)?.annotationOn ? DocCast(l.link_anchor_2) : undefined; if (anchor && (anchor.annotationOn as Doc).mediaState === mediaState.Recording) { linkTime = NumCast(anchor._timecodeToShow /* audioStart */); @@ -423,7 +421,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const newAutoLinks = new Set(); - const oldAutoLinks = LinkManager.Links(this.Document).filter( + const oldAutoLinks = Doc.Links(this.Document).filter( link => ((!Doc.isTemplateForField(this.Document) && (!Doc.isTemplateForField(DocCast(link.link_anchor_1)) || !Doc.AreProtosEqual(DocCast(link.link_anchor_1), this.Document)) && @@ -442,7 +440,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(LinkManager.Instance.deleteLink); + oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.Document).forEach(doc => Doc.DeleteLink?.(doc)); }; updateTitle = () => { @@ -496,7 +494,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { alink = alink ?? - (LinkManager.Links(this.Document).find( + (Doc.Links(this.Document).find( link => Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.Document) && // Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target) @@ -787,7 +785,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const batch = UndoManager.StartBatch('delete link'); - LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]); + Doc.DeleteLink?.(Doc.Links(anchor)[0]); // const docAnnotations = DocListCast(this._props.dataDoc[this.fieldKey]); // this._props.dataDoc[this.fieldKey] = new List(docAnnotations.filter(a => a !== this.annoTextRegion)); // AnchorMenu.Instance.fadeOut(true); @@ -837,7 +835,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc); + anchor && DocumentView.SelectSchemaDoc(anchor as Doc); AnchorMenu.Instance.Status = 'annotation'; AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc); AnchorMenu.Instance.Pinned = false; @@ -1124,7 +1122,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._sidebarRef?.current?.makeDocUnfiltered(doc)); } return new Promise>(res => { - DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv)); + DocumentView.addViewRenderedCb(doc, dv => res(dv)); }); }; focus = (textAnchor: Doc, options: FocusViewOptions) => { @@ -1206,7 +1204,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.RecordingEvent, this.breakupDictation); this._disposers.layout_autoHeight = reaction( () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }), @@ -1244,7 +1242,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks + () => Doc.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { this._cachedLinks.forEach(l => !newLinks.includes(l) && this.RemoveLinkFromDoc(l)); this._cachedLinks = newLinks; @@ -1556,7 +1554,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const docView = DocumentManager.Instance.getDocumentView(audiodoc); + const docView = DocumentView.getDocumentView(audiodoc); if (!docView) { this._props.addDocTab(audiodoc, OpenWhere.addBottom); setTimeout(func); @@ -1769,7 +1767,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const ComponentTag: any = tag === CollectionViewType.Freeform ? CollectionFreeFormView : tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; + const ComponentTag: any = tag === CollectionViewType.Tree ? CollectionTreeView : tag === 'translation' ? FormattedTextBox : CollectionStackingView; return ComponentTag === CollectionStackingView ? ( ) : ( -
    setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => SelectionManager.SelectView(this.DocumentView?.()!, false), true)}> +
    setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => DocumentView.SelectView(this.DocumentView?.()!, false), true)}> { if (e.clientX > this.ProseRef!.getBoundingClientRect().right) { - if (this.dataDoc[this.SidebarKey + '_type_collection'] === CollectionViewType.Freeform) { - // if the scrolled freeform is a child of the sidebar component, we need to let the event go through - // so react can let the freeform view handle it. We prevent default to stop any containing views from scrolling - e.preventDefault(); - } return; } @@ -2037,7 +2030,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent !this._props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); const paddingX = NumCast(this.layoutDoc._xMargin, this._props.xPadding || 0); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index bc6454146..7a8b72be0 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -11,7 +11,7 @@ import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSy import { GetEffectiveAcl } from '../../../../fields/util'; import { Docs } from '../../../documents/Documents'; import { RTFMarkup } from '../../../util/RTFMarkup'; -import { SelectionManager } from '../../../util/SelectionManager'; +import { DocumentView } from '../DocumentView'; import { OpenWhere } from '../OpenWhere'; const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; @@ -172,7 +172,7 @@ export function buildKeymap>(schema: S, props: any): KeyMa bind('Escape', (state: EditorState, dispatch: (tx: Transaction) => void) => { dispatch(state.tr.setSelection(TextSelection.create(state.doc, state.selection.from, state.selection.from))); (document.activeElement as any).blur?.(); - SelectionManager.DeselectAll(); + DocumentView.DeselectAll(); }); bind('Alt-Enter', () => onKey() || true); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 6c12b9991..a612f3c65 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -11,11 +11,10 @@ import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { BoolCast, Cast, StrCast } from '../../../../fields/Types'; import { DocServer } from '../../../DocServer'; -import { LinkManager } from '../../../util/LinkManager'; -import { SelectionManager } from '../../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { DocumentView } from '../DocumentView'; import { EquationBox } from '../EquationBox'; import { FieldViewProps } from '../FieldView'; import { FormattedTextBox } from './FormattedTextBox'; @@ -116,7 +115,7 @@ export class RichTextMenu extends AntimodeMenu { _disposer: IReactionDisposer | undefined; componentDidMount() { this._disposer = reaction( - () => SelectionManager.Views.slice(), + () => DocumentView.Selected().slice(), () => this.updateMenu(undefined, undefined, undefined, undefined) ); } @@ -144,8 +143,8 @@ export class RichTextMenu extends AntimodeMenu { const { activeSizes } = active; const { activeColors } = active; const { activeHighlights } = active; - const refDoc = SelectionManager.Views.lastElement()?.layoutDoc ?? Doc.UserDoc(); - const refField = (pfx => (pfx ? pfx + '_' : ''))(SelectionManager.Views.lastElement()?.LayoutFieldKey); + const refDoc = DocumentView.Selected().lastElement()?.layoutDoc ?? Doc.UserDoc(); + const refField = (pfx => (pfx ? pfx + '_' : ''))(DocumentView.Selected().lastElement()?.LayoutFieldKey); const refVal = (field: string, dflt: string) => StrCast(refDoc[refField + field], StrCast(Doc.UserDoc()[field], dflt)); this._activeListType = this.getActiveListStyle(); @@ -237,8 +236,8 @@ export class RichTextMenu extends AntimodeMenu { m.type === state.schema.marks.pFontSize && activeSizes.add(m.attrs.fontSize); m.type === state.schema.marks.pFontHighlight && activeHighlights.add(String(m.attrs.fontHighlight)); }); - } else if (SelectionManager.Views.some(dv => dv.ComponentView instanceof EquationBox)) { - SelectionManager.Views.forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize))); + } else if (DocumentView.Selected().some(dv => dv.ComponentView instanceof EquationBox)) { + DocumentView.Selected().forEach(dv => StrCast(dv.Document._text_fontSize) && activeSizes.add(StrCast(dv.Document._text_fontSize))); } return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) }; } @@ -566,7 +565,7 @@ export class RichTextMenu extends AntimodeMenu { if (linkDoc instanceof Doc) { const linkAnchor1 = await Cast(linkDoc.link_anchor_1, Doc); const linkAnchor2 = await Cast(linkDoc.link_anchor_2, Doc); - const currentDoc = SelectionManager.Docs.lastElement(); + const currentDoc = DocumentView.Selected().lastElement().Document; if (currentDoc && linkAnchor1 && linkAnchor2) { if (Doc.AreProtosEqual(currentDoc, linkAnchor1)) { return StrCast(linkAnchor2.title); @@ -605,7 +604,7 @@ export class RichTextMenu extends AntimodeMenu { .filter((aref: any) => aref?.href.indexOf(Doc.localServerPath()) === 0) .forEach((aref: any) => { const anchorId = aref.href.replace(Doc.localServerPath(), '').split('?')[0]; - anchorId && DocServer.GetRefField(anchorId).then(linkDoc => LinkManager.Instance.deleteLink(linkDoc as Doc)); + anchorId && DocServer.GetRefField(anchorId).then(linkDoc => Doc.DeleteLink?.(linkDoc as Doc)); }); } } diff --git a/src/client/views/nodes/formattedText/SummaryView.tsx b/src/client/views/nodes/formattedText/SummaryView.tsx index 2c366b49b..238267f6e 100644 --- a/src/client/views/nodes/formattedText/SummaryView.tsx +++ b/src/client/views/nodes/formattedText/SummaryView.tsx @@ -21,11 +21,10 @@ export class SummaryView { root: any; constructor(node: any, view: any, getPos: any) { - const self = this; this.dom = document.createElement('span'); this.dom.className = this.className(node.attrs.visibility); - this.dom.onpointerdown = function (e: any) { - self.onPointerDown(e, node, view, getPos); + this.dom.onpointerdown = (e: any) => { + this.onPointerDown(e, node, view, getPos); }; this.dom.onkeypress = function (e: any) { e.stopPropagation(); diff --git a/src/client/views/nodes/generativeFill/GenerativeFill.tsx b/src/client/views/nodes/generativeFill/GenerativeFill.tsx index d5fad296f..91b0ebd5c 100644 --- a/src/client/views/nodes/generativeFill/GenerativeFill.tsx +++ b/src/client/views/nodes/generativeFill/GenerativeFill.tsx @@ -16,7 +16,6 @@ import { NumCast } from '../../../../fields/Types'; import { Networking } from '../../../Network'; import { DocUtils } from '../../../documents/DocUtils'; import { Docs } from '../../../documents/Documents'; -import { DocumentManager } from '../../../util/DocumentManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; import { ImageEditorData } from '../ImageBox'; @@ -28,6 +27,7 @@ import { APISuccess, ImageUtility } from './generativeFillUtils/ImageHandler'; import { PointerHandler } from './generativeFillUtils/PointerHandler'; import { activeColor, canvasSize, eraserColor, freeformRenderSize, newCollectionSize, offsetDistanceY, offsetX } from './generativeFillUtils/generativeFillConstants'; import { CursorData, ImageDimensions, Point } from './generativeFillUtils/generativeFillInterfaces'; +import { DocumentView } from '../DocumentView'; // enum BrushStyle { // ADD, @@ -295,7 +295,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD _height: newCollectionSize, title: 'Image edit collection', }); - DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History', link_displayLine: false }); + DocUtils.MakeLink(imageRootDoc, newCollectionRef.current, { link_relationship: 'Image Edit Version History' }); // opening new tab CollectionDockingView.AddSplit(newCollectionRef.current, OpenWhereMod.right); @@ -400,7 +400,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD parentDoc.current.gen_fill_children = new List([newImg]); } - DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}`, link_displayLine: true }); + DocUtils.MakeLink(parentDoc.current, newImg, { link_relationship: `Image edit; Prompt: ${input}` }); adjustImgPositions(); if (isNewCollection && newCollectionRef.current) { @@ -430,7 +430,7 @@ const GenerativeFill = ({ imageEditorOpen, imageEditorSource, imageRootDoc, addD ImageEditorData.Open = false; ImageEditorData.Source = ''; if (newCollectionRef.current) { - DocumentManager.Instance.AddViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce()); + DocumentView.addViewRenderedCb(newCollectionRef.current, dv => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce()); } setEdits([]); }; diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 485ba7367..6b4f5e073 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -19,17 +19,14 @@ import { emptyFunction, emptyPath, stringHash } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; -import { DocumentManager } from '../../../util/DocumentManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; -import { SelectionManager } from '../../../util/SelectionManager'; import { SerializationHelper } from '../../../util/SerializationHelper'; -import { SettingsManager } from '../../../util/SettingsManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; -import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; +import { CollectionFreeFormPannableContents } from '../../collections/collectionFreeForm/CollectionFreeFormPannableContents'; import { CollectionView } from '../../collections/CollectionView'; import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; @@ -55,8 +52,9 @@ export class PresBox extends ViewBoxBaseComponent() { super(props); makeObservable(this); if (!PresBox.navigateToDocScript) { - PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!; + PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, this)')!; } + CollectionFreeFormPannableContents.SetOverlayPlugin((fform: Doc) => PresBox.Instance.pathLines(fform)); } private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -122,8 +120,8 @@ export class PresBox extends ViewBoxBaseComponent() { return false; } @computed get selectedDocumentView() { - if (SelectionManager.Views.length) return SelectionManager.Views[0]; - if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document); + if (DocumentView.Selected().length) return DocumentView.Selected()[0]; + if (this.selectedArray.size) return DocumentView.getDocumentView(this.Document); return undefined; } @computed get isPres() { @@ -175,7 +173,7 @@ export class PresBox extends ViewBoxBaseComponent() { this._unmounting = false; this.turnOffEdit(true); this._disposers.selection = reaction( - () => SelectionManager.Views.slice(), + () => DocumentView.Selected().slice(), views => (!PresBox.Instance || views.some(view => view.Document === this.Document)) && this.updateCurrentPresentation(), { fireImmediately: true } ); @@ -200,7 +198,7 @@ export class PresBox extends ViewBoxBaseComponent() { startTempMedia = (targetDoc: Doc, activeItem: Doc) => { const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart); if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) { - const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); + const targMedia = DocumentView.getDocumentView(targetDoc); targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.config_clipStart), NumCast(activeItem.config_clipStart) + duration); } }; @@ -208,7 +206,7 @@ export class PresBox extends ViewBoxBaseComponent() { stopTempMedia = (targetDocField: FieldResult) => { const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField); if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) { - const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); + const targMedia = DocumentView.getDocumentView(targetDoc); targMedia?.ComponentView?.Pause?.(); } }; @@ -260,7 +258,7 @@ export class PresBox extends ViewBoxBaseComponent() { // go to documents chain runSubroutines = (childrenToRun: Opt, normallyNextSlide: Doc) => { if (childrenToRun && childrenToRun[0] !== normallyNextSlide) { - childrenToRun.forEach(child => DocumentManager.Instance.showDocument(child, {})); + childrenToRun.forEach(child => DocumentView.showDocument(child, {})); } }; @@ -280,7 +278,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (listItems && presIndexed < listItems.length) { if (!first) { const listItemDoc = listItems[presIndexed]; - const targetView = listItems && DocumentManager.Instance.getFirstDocumentView(listItemDoc); + const targetView = listItems && DocumentView.getFirstDocumentView(listItemDoc); Doc.linkFollowUnhighlight(); Doc.HighlightDoc(listItemDoc); listItemDoc.presentation_effect = this.activeItem.presBulletEffect; @@ -425,7 +423,7 @@ export class PresBox extends ViewBoxBaseComponent() { const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc); const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext; if (context) { - const ffview = CollectionFreeFormView.from(DocumentManager.Instance.getFirstDocumentView(context)); + const ffview = CollectionFreeFormView.from(DocumentView.getFirstDocumentView(context)); if (ffview?.childDocs) { PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime); ffview.layoutDoc._currentFrame = NumCast(activeFrame); @@ -605,7 +603,7 @@ export class PresBox extends ViewBoxBaseComponent() { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; bestTarget._freeform_panX = viewport.panX; bestTarget._freeform_panY = viewport.panY; - const dv = DocumentManager.Instance.getDocumentView(bestTarget); + const dv = DocumentView.getDocumentView(bestTarget); if (dv) { changed = true; const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv._props.PanelWidth() / viewport.width, dv._props.PanelHeight() / viewport.height); @@ -644,8 +642,8 @@ export class PresBox extends ViewBoxBaseComponent() { const eleViewCache = Array.from(this._eleArray); const resetSelection = action(() => { if (!this._props.isSelected()) { - const presDocView = DocumentManager.Instance.getDocumentView(this.Document); - if (presDocView) SelectionManager.SelectView(presDocView, false); + const presDocView = DocumentView.getDocumentView(this.Document); + if (presDocView) DocumentView.SelectView(presDocView, false); this.clearSelectedArray(); selViewCache.forEach(doc => this.addToSelectedArray(doc)); this._dragArray.splice(0, this._dragArray.length, ...dragViewCache); @@ -660,7 +658,7 @@ export class PresBox extends ViewBoxBaseComponent() { static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) { if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { - (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); + (DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); return; } const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined; @@ -680,19 +678,19 @@ export class PresBox extends ViewBoxBaseComponent() { }; if (activeItem.presentation_openInLightbox) { const context = DocCast(targetDoc.annotationOn) ?? targetDoc; - if (!DocumentManager.Instance.getLightboxDocumentView(context)) { + if (!DocumentView.getLightboxDocumentView(context)) { LightboxView.Instance.SetLightboxDoc(context); } } if (targetDoc) { if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined; - DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, () => { + DocumentView.addViewRenderedCb(LightboxView.LightboxDoc, () => { // if target or the doc it annotates is not in the lightbox, then close the lightbox - if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { + if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { LightboxView.Instance.SetLightboxDoc(undefined); } - DocumentManager.Instance.showDocument(targetDoc, options, finished); + DocumentView.showDocument(targetDoc, options, finished); }); } else finished?.(); } @@ -1032,8 +1030,8 @@ export class PresBox extends ViewBoxBaseComponent() { @action selectPres = () => { - const presDocView = DocumentManager.Instance.getDocumentView(this.Document); - presDocView && SelectionManager.SelectView(presDocView, false); + const presDocView = DocumentView.getDocumentView(this.Document); + presDocView && DocumentView.SelectView(presDocView, false); }; focusElement = (doc: Doc) => { @@ -1044,7 +1042,7 @@ export class PresBox extends ViewBoxBaseComponent() { // Regular click @action selectElement = (doc: Doc, noNav = false) => { - CollectionStackedTimeline.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.()); + DocumentView.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.()); if (noNav) { const index = this.childDocs.indexOf(doc); if (index >= 0 && index < this.childDocs.length) { @@ -1200,7 +1198,7 @@ export class PresBox extends ViewBoxBaseComponent() { const order: JSX.Element[] = []; const docs = new Set(); const presCollection = collection; - const dv = DocumentManager.Instance.getDocumentView(presCollection); + const dv = DocumentView.getDocumentView(presCollection); this.childDocs.forEach((doc, index) => { const tagDoc = PresBox.targetRenderedDoc(doc); const srcContext = Cast(tagDoc.embedContainer, Doc, null); @@ -2206,7 +2204,7 @@ export class PresBox extends ViewBoxBaseComponent() { if (doc) { const tabMap = CollectionDockingView.Instance?.tabMap; const docTab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc; - const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab; + const presCollection = DocumentView.getContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab; const data = Cast(presCollection?.data, listSpec(Doc)); const configData = Cast(this.Document.data, listSpec(Doc)); if (data && configData) { @@ -2282,12 +2280,12 @@ export class PresBox extends ViewBoxBaseComponent() { @action toggleProperties = () => { - SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250; + SnappingManager.SetPropertiesWidth(SnappingManager.PropertiesWidth > 0 ? 0 : 250); }; @computed get toolbar() { - const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; - const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; + const propIcon = SnappingManager.PropertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = SnappingManager.PropertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; const mode = StrCast(this.Document._type_collection) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; const activeColor = SnappingManager.userVariantColor; @@ -2316,7 +2314,7 @@ export class PresBox extends ViewBoxBaseComponent() { {propTitle}
    }>
    - 0 ? activeColor : inactiveColor }} /> + 0 ? activeColor : inactiveColor }} />
    diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index af0ab3b53..306b98190 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -13,9 +13,8 @@ import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Ty import { emptyFunction } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; -import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; -import { SettingsManager } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoable, undoBatch } from '../../../util/UndoManager'; import { TreeView } from '../../collections/TreeView'; @@ -23,7 +22,7 @@ import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; import { PinDocView } from '../../PinFuncs'; -import { StyleProp } from '../../StyleProvider'; +import { StyleProp } from '../../StyleProp'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { PresBox } from './PresBox'; @@ -282,8 +281,8 @@ export class PresElementBox extends ViewBoxBaseComponent() { @action toggleProperties = () => { - if (SettingsManager.Instance.propertiesWidth < 5) { - SettingsManager.Instance.propertiesWidth = 250; + if (SnappingManager.PropertiesWidth < 5) { + SnappingManager.SetPropertiesWidth(250); } }; @@ -417,7 +416,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { @computed get toolbarWidth(): number { - const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox); + const presBoxDocView = DocumentView.getDocumentView(this.presBox); const width = NumCast(this.presBox?._width); return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300; } diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index f02c56471..a837969aa 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -8,12 +8,12 @@ import { ClientUtils, returnFalse, setupMoveUpEvents } from '../../../ClientUtil import { emptyFunction, unimplementedFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocumentType } from '../../documents/DocumentTypes'; -import { SelectionManager } from '../../util/SelectionManager'; import { SettingsManager } from '../../util/SettingsManager'; import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu'; import { LinkPopup } from '../linking/LinkPopup'; import './AnchorMenu.scss'; import { GPTPopup } from './GPTPopup/GPTPopup'; +import { DocumentView } from '../nodes/DocumentView'; @observer export class AnchorMenu extends AntimodeMenu { @@ -66,7 +66,7 @@ export class AnchorMenu extends AntimodeMenu { componentDidMount() { this._disposer = reaction( - () => SelectionManager.Views.slice(), + () => DocumentView.Selected().slice(), () => AnchorMenu.Instance.fadeOut(true) ); } @@ -145,7 +145,7 @@ export class AnchorMenu extends AntimodeMenu { * all selected text available to summarize but its only supported for pdf and web ATM. * @returns Whether the GPT icon for summarization should appear */ - canSummarize = () => SelectionManager.Docs.some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any)); + canSummarize = () => DocumentView.SelectedDocs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any)); render() { const buttons = diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 8b2b179d3..7dd4047c1 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -5,10 +5,10 @@ import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; import { Highlight } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; import { BoolCast, DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { undoable } from '../../util/UndoManager'; import { ObservableReactComponent } from '../ObservableReactComponent'; +import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { OpenWhere } from '../nodes/OpenWhere'; import { AnchorMenu } from './AnchorMenu'; @@ -56,7 +56,7 @@ export class Annotation extends ObservableReactComponent { @computed get linkHighlighted() { const found = LinkManager.Instance.getAllDirectLinks(this._props.annoDoc).find(link => { - const a1 = LinkManager.getOppositeAnchor(link, this._props.annoDoc); + const a1 = Doc.getOppositeAnchor(link, this._props.annoDoc); return a1 && Doc.GetBrushStatus(DocCast(a1.annotationOn, a1)); }); return found; @@ -103,7 +103,7 @@ export class Annotation extends ObservableReactComponent { e.preventDefault(); } else if (e.button === 0) { e.stopPropagation(); - LinkFollower.FollowLink(undefined, this._props.annoDoc, false); + DocumentView.FollowLink(undefined, this._props.annoDoc, false); } }; brushed = () => this._props.annoDoc && Doc.GetBrushHighlightStatus(this._props.annoDoc); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 15ac73f78..45b1d727e 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -15,17 +15,17 @@ import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; import { DocUtils } from '../../documents/DocUtils'; -import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; +import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { LinkInfo } from '../nodes/LinkDocPreview'; import { PDFBox } from '../nodes/PDFBox'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { StyleProp } from '../StyleProvider'; +import { StyleProp } from '../StyleProp'; import { AnchorMenu } from './AnchorMenu'; import { Annotation } from './Annotation'; import { GPTPopup } from './GPTPopup/GPTPopup'; @@ -122,7 +122,7 @@ export class PDFViewer extends ObservableReactComponent { this._disposers.selected = reaction( () => this._props.isSelected(), - () => SelectionManager.Views.length === 1 && this.setupPdfJsViewer(), + () => DocumentView.Selected().length === 1 && this.setupPdfJsViewer(), { fireImmediately: true } ); this._disposers.curPage = reaction( diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 5df934231..56552c952 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -12,16 +12,15 @@ import { DocCast, StrCast } from '../../../fields/Types'; import { DocUtils } from '../../documents/DocUtils'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; -import { DocumentManager } from '../../util/DocumentManager'; -import { LinkManager } from '../../util/LinkManager'; import { SearchUtil } from '../../util/SearchUtil'; -import { SettingsManager } from '../../util/SettingsManager'; +import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { CollectionDockingView } from '../collections/CollectionDockingView'; import { IRecommendation, Recommendation } from '../newlightbox/components'; import { fetchRecommendations } from '../newlightbox/utils'; +import { DocumentView } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './SearchBox.scss'; @@ -54,7 +53,7 @@ export class SearchBoxItem extends ObservableReactComponent * or opening it in a new tab. */ selectElement = async (doc: Doc, finishFunc: () => void) => { - await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, finishFunc); + await DocumentView.showDocument(doc, { willZoomCentered: true }, finishFunc); }; /** @@ -65,12 +64,12 @@ export class SearchBoxItem extends ObservableReactComponent */ onResultClick = action(async (doc: Doc) => { this._props.selectItem(doc); - this.selectElement(doc, () => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false)); + this.selectElement(doc, () => DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.(this._props.searchString, undefined, false)); }); componentWillUnmount(): void { const doc = this._props.Document; - DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true); + DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true); } @undoBatch @@ -99,20 +98,18 @@ export class SearchBoxItem extends ObservableReactComponent } } style={{ - fontWeight: LinkManager.Links(this._props.linkFrom).find( - link => - Doc.AreProtosEqual(LinkManager.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || - Doc.AreProtosEqual(DocCast(LinkManager.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document) + fontWeight: Doc.Links(this._props.linkFrom).find( + link => Doc.AreProtosEqual(Doc.getOppositeAnchor(link, this._props.linkFrom!), this._props.Document) || Doc.AreProtosEqual(DocCast(Doc.getOppositeAnchor(link, this._props.linkFrom!)?.annotationOn), this._props.Document) ) ? 'bold' : '', }} className={this._props.className}>
    {title as string}
    -
    +
    {formattedType}
    -
    +
    {this._props.matchedKeys.join(', ')}
    @@ -373,7 +370,7 @@ export class SearchBox extends ViewBoxBaseComponent() { const query = StrCast(this._searchString); Doc.SetSearchQuery(query); - if (!this._props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); + if (!this._props.linkSearch) Array.from(this._results.keys()).forEach(doc => DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.(this._searchString, undefined, true)); this._results.clear(); if (query) { @@ -412,7 +409,7 @@ export class SearchBox extends ViewBoxBaseComponent() { this._timeout && clearTimeout(this._timeout); this._timeout = undefined; this._results.forEach((_, doc) => { - DocumentManager.Instance.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true); + DocumentView.getFirstDocumentView(doc)?.ComponentView?.search?.('', undefined, true); Doc.UnBrushDoc(doc); Doc.UnHighlightDoc(doc); Doc.ClearSearchMatches(); @@ -476,7 +473,7 @@ export class SearchBox extends ViewBoxBaseComponent() { const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => ); return ( -
    +
    {isLinkSearch ? null : ( e.stopPropagation()} /> diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index ee6987e89..3df7ecdbe 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -29,7 +29,6 @@ import { DefaultStyleProvider } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; import { DocFocusOrOpen, returnEmptyDocViewList } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; -import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; @@ -144,7 +143,7 @@ export class SchemaTableCell extends ObservableReactComponent | undefined) => { if ((value?.nativeEvent as any).shiftKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); - } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); + } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); })} /> KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} + onChange={val => Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} />
    diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 6ae6bb228..474d54119 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -19,11 +19,10 @@ import { StyleProp } from '../StyleProp'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; -import { KeyValueBox } from './KeyValueBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; @observer -export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class ComparisonBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); } @@ -188,9 +187,9 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field // The GPT call will put the "answer" in the second slot of the comparison (eg., text_2) if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) { - const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... + const queryText = altText?.replace('(this)', subjectText); // TODO: this should be done in Doc.setField but it doesn't know about the fieldKey ... if (queryText?.match(/\(\(.*\)\)/)) { - KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt + Doc.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt } } return layoutTemplateString; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index f8ce50c98..4a5c32b4c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -31,7 +31,6 @@ import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter'; import { UPDATE_SERVER_CACHE } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SearchUtil } from '../../util/SearchUtil'; -import { SharingManager } from '../../util/SharingManager'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; @@ -47,7 +46,6 @@ import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; import { FieldViewProps, FieldViewSharedProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; -import { KeyValueBox } from './KeyValueBox'; import { OpenWhere } from './OpenWhere'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; @@ -115,7 +113,7 @@ export class DocumentViewInternal extends DocComponent = undefined; // needs to be accessed from DocumentView wrapper class + @observable _componentView: Opt> = undefined; // needs to be accessed from DocumentView wrapper class @observable _animateScaleTime: Opt = undefined; // milliseconds for animating between views. defaults to 300 if not uset @observable _animateScalingTo = 0; @@ -627,7 +625,7 @@ export class DocumentViewInternal extends DocComponent DocUtils.Zip(this.Document) }); - (this.Document._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this._docView), icon: 'users' }); + constantItems.push({ description: 'Share', event: () => DocumentView.ShareOpen(this._docView), icon: 'users' }); if (this._props.removeDocument && Doc.ActiveDashboard !== this.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); @@ -699,7 +697,7 @@ export class DocumentViewInternal extends DocComponent this._props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHandler; setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height)); } // prettier-ignore - setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore + setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore isContentActive = (): boolean | undefined => this._isContentActive; childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; @@ -725,7 +723,7 @@ export class DocumentViewInternal extends DocComponent u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor)) + // StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, + StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor) + // ) ); const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0; const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); @@ -839,7 +839,7 @@ export class DocumentViewInternal extends DocComponent - {this._componentView instanceof KeyValueBox ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])} + {this._componentView?.isUnstyledView?.() ? renderDoc : DocumentViewInternal.AnimationEffect(renderDoc, this.Document[Animation])} {borderPath?.jsx}
    ); @@ -980,6 +980,8 @@ export class DocumentViewInternal extends DocComponent() { public static ROOT_DIV = 'documentView-effectsWrapper'; + // Sharing Manager + public static ShareOpen: (target?: DocumentView, targetDoc?: Doc) => void; // LinkFollower public static FollowLink: (linkDoc: Opt, sourceDoc: Doc, altKey: boolean) => boolean; // selection funcs @@ -1049,7 +1051,7 @@ export class DocumentView extends DocComponent() { @observable public static LongPress = false; @computed private get shouldNotScale() { - return (this.layout_fitWidth && !this.nativeWidth) || this._props.LayoutTemplateString?.includes(KeyValueBox.name) || [CollectionViewType.Docking].includes(this.Document._type_collection as any); + return (this.layout_fitWidth && !this.nativeWidth) || this.ComponentView?.isUnstyledView?.(); } @computed private get effectiveNativeWidth() { return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width); @@ -1144,10 +1146,10 @@ export class DocumentView extends DocComponent() { } @computed get nativeWidth() { - return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); + return returnVal(this._props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); } @computed get nativeHeight() { - return this._props.LayoutTemplateString?.includes(KeyValueBox.name) ? 0 : returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); + return returnVal(this._props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this._props.TemplateDataDocument, !this.layout_fitWidth)); } @computed public get centeringX() { return this._props.dontCenter?.includes('x') ? 0 : this.Xshift; } // prettier-ignore @computed public get centeringY() { return this._props.dontCenter?.includes('y') ? 0 : this.Yshift; } // prettier-ignore diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index c6c77d8d2..8a37000f7 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -45,7 +45,7 @@ export interface FieldViewSharedProps { containerViewPath?: () => DocumentView[]; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document isGroupActive?: () => string | undefined; // is this document part of a group that is active - setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox + setContentViewBox?: (view: ViewBoxInterface) => any; // called by rendered field's viewBox so that DocumentView can make direct calls to the viewBox PanelWidth: () => number; PanelHeight: () => number; isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index cd591fa42..66e210c03 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -13,10 +13,10 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; import { CompiledScript } from '../../util/Scripting'; -import { undoBatch } from '../../util/UndoManager'; +import { undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ObservableReactComponent } from '../ObservableReactComponent'; +import { ViewBoxBaseComponent } from '../DocComponent'; import { DocumentIconContainer } from './DocumentIcon'; import { FieldView, FieldViewProps } from './FieldView'; import { ImageBox } from './ImageBox'; @@ -31,7 +31,7 @@ export type KVPScript = { onDelegate: boolean; }; @observer -export class KeyValueBox extends ObservableReactComponent { +export class KeyValueBox extends ViewBoxBaseComponent() { public static LayoutString() { return FieldView.LayoutString(KeyValueBox, 'data'); } @@ -48,19 +48,17 @@ export class KeyValueBox extends ObservableReactComponent { componentDidMount() { this._props.setContentViewBox?.(this); } - isKeyValueBox = returnTrue; - able = returnAlways; - layout_fitWidth = returnTrue; - onClickScriptDisable = returnAlways; + // ViewBoxInterface overrides + override isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface - ignore opacity, anim effects, titles + override dontRegisterView = returnTrue; // don't want to follow links to this view + override onClickScriptDisable = returnAlways; @observable private rows: KeyValuePair[] = []; @observable _splitPercentage = 50; get fieldDocToLayout() { - return DocCast(this._props.Document); + return DocCast(this.Document); } - isUnstyledView = returnTrue; // used by style provider via ViewBoxInterface - dontRegisterView = returnTrue; // used by ViewBoxInterface @action onEnterKey = (e: React.KeyboardEvent): void => { @@ -83,7 +81,7 @@ export class KeyValueBox extends ObservableReactComponent { * @param value * @returns */ - public static CompileKVPScript(rawvalueIn: string): KVPScript | undefined { + public static CompileKVPScript = (rawvalueIn: string): KVPScript | undefined => { let rawvalue = rawvalueIn; const onDelegate = rawvalue.startsWith('='); rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue; @@ -97,9 +95,9 @@ export class KeyValueBox extends ObservableReactComponent { script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer()); } return !script.compiled ? undefined : { script, type, onDelegate }; - } + }; - public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) { + public static ApplyKVPScript = (doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => { const { script, type, onDelegate } = kvpScript; // const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc); @@ -127,14 +125,13 @@ export class KeyValueBox extends ObservableReactComponent { return true; } return false; - } + }; - @undoBatch - public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) { - const script = this.CompileKVPScript(value); + public static SetField = undoable((doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => { + const script = KeyValueBox.CompileKVPScript(value); if (!script) return false; - return this.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult); - } + return KeyValueBox.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult); + }, 'Set Doc Field'); onPointerDown = (e: React.PointerEvent): void => { if (e.buttons === 1 && this._props.isSelected()) { @@ -249,15 +246,15 @@ export class KeyValueBox extends ObservableReactComponent { getFieldView = () => { const rows = this.rows.filter(row => row.isChecked); if (rows.length > 1) { - const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this._props.Document).title}`, _chromeHidden: true }); + const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.Document).title}`, _chromeHidden: true }); rows.forEach(row => { - const field = this.createFieldView(DocCast(this._props.Document), row); + const field = this.createFieldView(DocCast(this.Document), row); field && Doc.AddDocToList(parent, 'data', field); row.uncheck(); }); return parent; } - return rows.length ? this.createFieldView(DocCast(this._props.Document), rows.lastElement()) : undefined; + return rows.length ? this.createFieldView(DocCast(this.Document), rows.lastElement()) : undefined; }; createFieldView = (templateDoc: Doc, row: KeyValuePair) => { @@ -305,7 +302,7 @@ export class KeyValueBox extends ObservableReactComponent { openItems.push({ description: 'Default Perspective', event: () => { - this._props.addDocTab(this._props.Document, OpenWhere.close); + this._props.addDocTab(this.Document, OpenWhere.close); this._props.addDocTab(this.fieldDocToLayout, OpenWhere.addRight); }, icon: 'image', @@ -341,6 +338,9 @@ export class KeyValueBox extends ObservableReactComponent {
    ); } + public static Init() { + Doc.SetField = KeyValueBox.SetField; + } } Docs.Prototypes.TemplateMap.set(DocumentType.KVP, { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 878b0e54c..397cc15ed 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -15,7 +15,6 @@ import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider } from '../StyleProvider'; import { returnEmptyDocViewList } from './DocumentView'; -import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; import { OpenWhere } from './OpenWhere'; @@ -136,7 +135,7 @@ export class KeyValuePair extends ObservableReactComponent { pinToPres: returnZero, }} GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)} - SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)} + SetValue={(value: string) => Doc.SetField(this._props.doc, this._props.keyName, value)} />
    diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 9038ed27a..b8b9f63a9 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -25,7 +25,7 @@ import { CollectionFreeFormView } from '../collections/collectionFreeForm'; import { CollectionStackingView } from '../collections/CollectionStackingView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { Colors } from '../global/globalEnums'; import { PDFViewer } from '../pdf/PDFViewer'; import { PinDocView, PinProps } from '../PinFuncs'; @@ -39,7 +39,7 @@ import './PDFBox.scss'; import { CreateImage } from './WebBoxRenderer'; @observer -export class PDFBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class PDFBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } @@ -283,7 +283,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent() implem !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw)); }; - public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { + override search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { if (!this._searching && !clear) { this._searching = true; setTimeout(() => { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 1003bc473..198652310 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -48,7 +48,7 @@ import './WebBox.scss'; const { CreateImage } = require('./WebBoxRenderer'); @observer -export class WebBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class WebBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } @@ -112,7 +112,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem } @action - search = (searchString: string, bwd?: boolean, clear: boolean = false) => { + override search = (searchString: string, bwd?: boolean, clear: boolean = false) => { if (!this._searching && !clear) { this._searching = true; setTimeout(() => { @@ -1000,7 +1000,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() implem }; _innerCollectionView: CollectionFreeFormView | undefined; zoomScaling = () => this._innerCollectionView?.zoomScaling() ?? 1; - setInnerContent = (component: ViewBoxInterface) => { + setInnerContent = (component: ViewBoxInterface) => { this._innerCollectionView = component as CollectionFreeFormView; }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 8a4a7718a..321fdbb91 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -43,7 +43,7 @@ import { CollectionStackingView } from '../../collections/CollectionStackingView import { CollectionTreeView } from '../../collections/CollectionTreeView'; import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; @@ -73,12 +73,12 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; // import * as applyDevTools from 'prosemirror-dev-tools'; -interface FormattedTextBoxProps extends FieldViewProps { +export interface FormattedTextBoxProps extends FieldViewProps { onBlur?: () => void; // callback when text loses focus autoFocus?: boolean; // whether text should get input focus when created } @observer -export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 1ff862859..bf11dfe62 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -7,13 +7,11 @@ import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { NumCast, StrCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; -import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; -import { DocUtils } from '../../../documents/DocUtils'; import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { DocUtils } from '../../../documents/DocUtils'; import { CollectionView } from '../../collections/CollectionView'; import { ContextMenu } from '../../ContextMenu'; -import { KeyValueBox } from '../KeyValueBox'; import { FormattedTextBox } from './FormattedTextBox'; import { wrappingInputRule } from './prosemirrorPatches'; import { RichTextMenu } from './RichTextMenu'; @@ -311,10 +309,10 @@ export class RichTextRules { } }; const getTitledDoc = (title: string) => { - if (!DocServer.FindDocByTitle(title)) { + if (!Doc.FindDocByTitle(title)) { Docs.Create.TextDocument('', { title: title, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true }); } - const titledDoc = DocServer.FindDocByTitle(title); + const titledDoc = Doc.FindDocByTitle(title); return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; }; const target = getTitledDoc(docTitle); @@ -337,14 +335,14 @@ export class RichTextRules { const assign = match[4] === ':' ? (match[4] = '') : match[4]; const value = match[5]; const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('='); - const getTitledDoc = (title: string) => DocServer.FindDocByTitle(title); + const getTitledDoc = (title: string) => Doc.FindDocByTitle(title); // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' ) if (value?.includes(',') && !value.startsWith('((')) { const values = value.split(','); const strs = values.some(v => !v.match(/^[-]?[0-9.]$/)); this.Document[DocData][fieldKey] = strs ? new List(values) : new List(values.map(v => Number(v))); } else if (value) { - KeyValueBox.SetField( + Doc.SetField( this.Document, fieldKey, assign + value, @@ -367,7 +365,7 @@ export class RichTextRules { // pass the contents between '((' and '))' to chatGPT and append the result new InputRule(/(^|[^=])(\(\(.*\)\))$/, (state, match, start, end) => { let count = 0; // ignore first return value which will be the notation that chat is pending a result - KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { + Doc.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { if (count) { const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string)); tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length)))); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index bb6995398..725221a66 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -225,7 +225,17 @@ export class Doc extends RefField { @observable public static GuestTarget: Doc | undefined = undefined; @observable public static GuestMobile: Doc | undefined = undefined; @observable.shallow public static CurrentlyLoading: Doc[] = observable([]); - // removes from currently loading display + // DocServer api + public static FindDocByTitle(title: string) { + const foundDocId = + title && + Array.from(Object.keys(DocServer.Cache())) + .filter(key => DocServer.Cache()[key] instanceof Doc) + .find(key => (DocServer.Cache()[key] as Doc).title === title); + + return foundDocId ? (DocServer.Cache()[foundDocId] as Doc) : undefined; + } + // removes from currently loading doc set public static removeCurrentlyLoading(doc: Doc) { if (Doc.CurrentlyLoading) { const index = Doc.CurrentlyLoading.indexOf(doc); @@ -238,12 +248,14 @@ export class Doc extends RefField { runInAction(() => Doc.CurrentlyLoading.push(doc)); } } - + // LinkManager api public static AddLink: (link: Doc, checkExists?: boolean) => void; public static DeleteLink: (link: Doc) => void; public static Links: (link: Doc | undefined) => Doc[]; public static getOppositeAnchor: (linkDoc: Doc, anchor: Doc) => Doc | undefined; - + // KeyValue SetField + public static SetField: (doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) => boolean; + // UserDoc "API" public static get MySharedDocs() { return DocCast(Doc.UserDoc().mySharedDocs); } // prettier-ignore public static get MyUserDocView() { return DocCast(Doc.UserDoc().myUserDocView); } // prettier-ignore public static get MyDockedBtns() { return DocCast(Doc.UserDoc().myDockedBtns); } // prettier-ignore -- cgit v1.2.3-70-g09d2 From b858bd3cad81da41e63b9f8e807e41421ca4aa34 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 8 May 2024 21:03:08 -0400 Subject: lots of api cleanup and cycle removal --- .../apis/google_docs/GooglePhotosClientUtils.ts | 2 +- src/client/documents/DocFromField.ts | 59 +++------ src/client/util/CaptureManager.tsx | 5 +- src/client/util/DocumentManager.ts | 15 ++- src/client/util/ReplayMovements.ts | 51 +++++--- src/client/util/SnappingManager.ts | 3 + src/client/util/UndoManager.ts | 10 +- src/client/views/DashboardView.tsx | 5 - src/client/views/DocComponent.tsx | 62 +--------- src/client/views/DocumentDecorations.tsx | 5 +- src/client/views/GestureOverlay.tsx | 17 +-- src/client/views/GlobalKeyHandler.ts | 7 +- src/client/views/InkingStroke.tsx | 3 +- src/client/views/LightboxView.tsx | 33 +++-- src/client/views/Main.tsx | 99 +++++++++++++-- src/client/views/MainView.tsx | 29 +++-- src/client/views/ObservableReactComponent.tsx | 11 ++ src/client/views/PropertiesDocContextSelector.tsx | 4 +- src/client/views/StyleProvider.tsx | 12 +- src/client/views/TemplateMenu.tsx | 4 +- src/client/views/ViewBoxInterface.ts | 60 +++++++++ .../views/collections/CollectionDockingView.tsx | 13 +- src/client/views/collections/CollectionMenu.tsx | 4 +- .../views/collections/CollectionNoteTakingView.tsx | 3 +- .../collections/CollectionNoteTakingViewColumn.tsx | 54 +-------- .../collections/CollectionStackedTimeline.tsx | 5 +- .../views/collections/CollectionStackingView.tsx | 3 +- .../views/collections/CollectionTreeView.tsx | 2 +- src/client/views/collections/CollectionView.tsx | 4 +- src/client/views/collections/TabDocView.tsx | 16 ++- .../CollectionFreeFormPannableContents.tsx | 5 + .../collectionFreeForm/CollectionFreeFormView.tsx | 25 ++-- .../collectionSchema/SchemaTableCell.tsx | 70 +++++------ src/client/views/linking/LinkPopup.tsx | 3 +- .../views/newlightbox/ButtonMenu/ButtonMenu.tsx | 22 ++-- .../views/newlightbox/ExploreView/ExploreView.tsx | 30 ++--- .../views/newlightbox/Header/LightboxHeader.tsx | 8 +- src/client/views/newlightbox/NewLightboxView.tsx | 62 ++-------- .../RecommendationList/RecommendationList.tsx | 134 ++++++++++----------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/nodes/ComparisonBox.tsx | 2 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 4 +- src/client/views/nodes/DocumentContentsView.tsx | 96 +++------------ src/client/views/nodes/DocumentView.tsx | 120 ++++++++++-------- src/client/views/nodes/EquationBox.tsx | 4 +- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/KeyValuePair.tsx | 3 +- src/client/views/nodes/LinkBox.tsx | 5 +- src/client/views/nodes/MapBox/MapBox.tsx | 4 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 13 +- src/client/views/nodes/VideoBox.tsx | 31 ++--- src/client/views/nodes/WebBox.tsx | 6 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 35 ++---- src/client/views/nodes/trails/PresBox.tsx | 21 ++-- src/client/views/search/SearchBox.tsx | 54 ++++----- src/client/views/topbar/TopBar.tsx | 4 +- src/fields/Doc.ts | 2 +- src/fields/InkField.ts | 11 ++ src/fields/Proxy.ts | 2 +- src/fields/RichTextUtils.ts | 2 +- src/fields/util.ts | 2 +- 61 files changed, 664 insertions(+), 720 deletions(-) create mode 100644 src/client/views/ViewBoxInterface.ts (limited to 'src/client/views/Main.tsx') diff --git a/src/client/apis/google_docs/GooglePhotosClientUtils.ts b/src/client/apis/google_docs/GooglePhotosClientUtils.ts index fdc185a8e..b238f07e9 100644 --- a/src/client/apis/google_docs/GooglePhotosClientUtils.ts +++ b/src/client/apis/google_docs/GooglePhotosClientUtils.ts @@ -331,7 +331,7 @@ export namespace GooglePhotos { if (typeof target === 'string') { description = target; } else if (target instanceof RichTextField) { - description = RichTextUtils.ToPlainText(EditorState.fromJSON(FormattedTextBox.Instance.config, JSON.parse(target.Data))); + description = RichTextUtils.ToPlainText(EditorState.fromJSON(new FormattedTextBox({} as any).config, JSON.parse(target.Data))); } return description; }; diff --git a/src/client/documents/DocFromField.ts b/src/client/documents/DocFromField.ts index 1c0a9755b..b65bbbdf5 100644 --- a/src/client/documents/DocFromField.ts +++ b/src/client/documents/DocFromField.ts @@ -1,50 +1,31 @@ -/* eslint-disable prefer-destructuring */ -/* eslint-disable default-param-last */ -/* eslint-disable no-use-before-define */ import { Doc, DocListCast } from '../../fields/Doc'; import { InkField } from '../../fields/InkField'; import { List } from '../../fields/List'; +import { StrCast } from '../../fields/Types'; import { AudioField, ImageField, PdfField, VideoField } from '../../fields/URLField'; -import { InkingStroke } from '../views/InkingStroke'; -import { CollectionView } from '../views/collections/CollectionView'; -import { AudioBox } from '../views/nodes/AudioBox'; -import { ImageBox } from '../views/nodes/ImageBox'; -import { PDFBox } from '../views/nodes/PDFBox'; -import { VideoBox } from '../views/nodes/VideoBox'; -import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; import { Docs, DocumentOptions } from './Documents'; +/** + * Changes the field key in the doc's layout string to be the specified field + */ +export function ResetLayoutFieldKey(doc: Doc, fieldKey: string) { + doc.layout = StrCast(doc.layout).replace(/={'.*'}/, `={'${fieldKey}'}`); + return doc; +} export function DocumentFromField(target: Doc, fieldKey: string, proto?: Doc, options?: DocumentOptions): Doc | undefined { - let created: Doc | undefined; const field = target[fieldKey]; const resolved = options ?? {}; - if (field instanceof ImageField) { - created = Docs.Create.ImageDocument(field.url.href, resolved); - created.layout = ImageBox.LayoutString(fieldKey); - } else if (field instanceof Doc) { - created = field; - } else if (field instanceof VideoField) { - created = Docs.Create.VideoDocument(field.url.href, resolved); - created.layout = VideoBox.LayoutString(fieldKey); - } else if (field instanceof PdfField) { - created = Docs.Create.PdfDocument(field.url.href, resolved); - created.layout = PDFBox.LayoutString(fieldKey); - } else if (field instanceof AudioField) { - created = Docs.Create.AudioDocument(field.url.href, resolved); - created.layout = AudioBox.LayoutString(fieldKey); - } else if (field instanceof InkField) { - created = Docs.Create.InkDocument(field.inkData, resolved); - created.layout = InkingStroke.LayoutString(fieldKey); - } else if (field instanceof List && field[0] instanceof Doc) { - created = Docs.Create.StackingDocument(DocListCast(field), resolved); - created.layout = CollectionView.LayoutString(fieldKey); - } else { - created = Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved }); - created.layout = FormattedTextBox.LayoutString(fieldKey); - } - if (created) { - created.title = fieldKey; - proto && created.proto && (created.proto = Doc.GetProto(proto)); - } + const nonDocFieldToDoc = () => { + if (field instanceof ImageField) return Docs.Create.ImageDocument(field.url.href, resolved); + if (field instanceof VideoField) return Docs.Create.VideoDocument(field.url.href, resolved); + if (field instanceof PdfField) return Docs.Create.PdfDocument(field.url.href, resolved); + if (field instanceof AudioField) return Docs.Create.AudioDocument(field.url.href, resolved); + if (field instanceof InkField) return Docs.Create.InkDocument(field.inkData, resolved); + if (field instanceof List && field[0] instanceof Doc) return Docs.Create.StackingDocument(DocListCast(field), resolved); + return Docs.Create.TextDocument('', { ...{ _width: 200, _height: 25, _layout_autoHeight: true }, ...resolved }); + }; + const created = field instanceof Doc ? field : ResetLayoutFieldKey(nonDocFieldToDoc(), fieldKey); + created.title = fieldKey; + proto && created.proto && (created.proto = Doc.GetProto(proto)); return created; } diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx index 4fd934774..253cdd8b5 100644 --- a/src/client/util/CaptureManager.tsx +++ b/src/client/util/CaptureManager.tsx @@ -7,10 +7,9 @@ import * as React from 'react'; import { addStyleSheet } from '../../ClientUtils'; import { Doc } from '../../fields/Doc'; import { DocCast, StrCast } from '../../fields/Types'; -import { LightboxView } from '../views/LightboxView'; import { MainViewModal } from '../views/MainViewModal'; -import './CaptureManager.scss'; import { DocumentView } from '../views/nodes/DocumentView'; +import './CaptureManager.scss'; @observer export class CaptureManager extends React.Component<{}> { @@ -79,7 +78,7 @@ export class CaptureManager extends React.Component<{}> {
    { - LightboxView.Instance.SetLightboxDoc(this._document); + DocumentView.SetLightboxDoc(this._document); this.close(); }}> Save diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 5bcac7330..97051207b 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -7,7 +7,6 @@ import { listSpec } from '../../fields/Schema'; import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { AudioField } from '../../fields/URLField'; import { CollectionViewType } from '../documents/DocumentTypes'; -import { LightboxView } from '../views/LightboxView'; import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView'; import { FocusViewOptions } from '../views/nodes/FocusViewOptions'; import { OpenWhere } from '../views/nodes/OpenWhere'; @@ -25,7 +24,7 @@ export class DocumentManager { // global holds all of the nodes (regardless of which collection they're in) @observable private _documentViews = new Set(); @computed public get DocumentViews() { - return Array.from(this._documentViews).filter(view => (!view.ComponentView?.dontRegisterView?.() && !LightboxView.LightboxDoc) || LightboxView.Contains(view)); + return Array.from(this._documentViews).filter(view => (!view.ComponentView?.dontRegisterView?.() && !DocumentView.LightboxDoc()) || DocumentView.LightboxContains(view)); } public AddDocumentView(dv: DocumentView) { this._documentViews.add(dv); @@ -68,7 +67,7 @@ export class DocumentManager { private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; public AddViewRenderedCb = (doc: Opt, func: (dv: DocumentView) => any) => { if (doc) { - const dv = LightboxView.LightboxDoc ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc); + const dv = DocumentView.LightboxDoc() ? this.getLightboxDocumentView(doc) : this.getDocumentView(doc); this._viewRenderedCbs.push({ doc, func }); if (dv) { this.callAddViewFuncs(dv); @@ -141,18 +140,18 @@ export class DocumentManager { public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => { const views: DocumentView[] = []; - DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.Contains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view)); + DocumentManager.Instance.DocumentViews.forEach(view => DocumentView.LightboxContains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view)); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined); }; public getFirstDocumentView = (toFind: Doc): DocumentView | undefined => { - if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind); + if (DocumentView.LightboxDoc()) return DocumentManager.Instance.getLightboxDocumentView(toFind); const views = this.getDocumentViews(toFind); // .filter(view => view.Document !== originatingDoc); return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined); }; public getDocumentViews(toFind: Doc): DocumentView[] { const toReturn: DocumentView[] = []; - const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.Contains(view)); - const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.Contains(view)); + const docViews = DocumentManager.Instance.DocumentViews.filter(view => !DocumentView.LightboxContains(view)); + const lightViews = DocumentManager.Instance.DocumentViews.filter(view => DocumentView.LightboxContains(view)); // heuristic to return the "best" documents first: // choose a document in the lightbox first @@ -262,7 +261,7 @@ export class DocumentManager { return; } options.didMove = true; - (!LightboxView.LightboxDoc && docContextPath.some(doc => DocumentView.activateTabView(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); + (!DocumentView.LightboxDoc() && docContextPath.some(doc => DocumentView.activateTabView(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight); this.AddViewRenderedCb(docContextPath[0], dv => res(dv)); })); if (options.openLocation === OpenWhere.lightbox) { diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts index 530fcf211..c5afe549c 100644 --- a/src/client/util/ReplayMovements.ts +++ b/src/client/util/ReplayMovements.ts @@ -1,16 +1,17 @@ import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { Doc, IdToDoc } from '../../fields/Doc'; -import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { CollectionFreeFormView } from '../views/collections/collectionFreeForm'; +import { DocumentView } from '../views/nodes/DocumentView'; import { OpenWhereMod } from '../views/nodes/OpenWhere'; -import { VideoBox } from '../views/nodes/VideoBox'; +import { SnappingManager } from './SnappingManager'; import { Movement, Presentation } from './TrackMovements'; -import { DocumentView } from '../views/nodes/DocumentView'; +import { ViewBoxInterface } from '../views/ViewBoxInterface'; +import { StrCast } from '../../fields/Types'; export class ReplayMovements { private timers: NodeJS.Timeout[] | null; private videoBoxDisposeFunc: IReactionDisposer | null; - private videoBox: VideoBox | null; + private videoBox: ViewBoxInterface | null; private isPlaying: boolean; // create static instance and getter for global use @@ -29,6 +30,22 @@ export class ReplayMovements { this.videoBoxDisposeFunc = null; this.videoBox = null; this.isPlaying = false; + + reaction( + () => SnappingManager.UserPanned, + () => { + if (Doc.UserDoc()?.presentationMode === 'watching') this.pauseFromInteraction(); + } + ); + reaction( + () => DocumentView.Selected().slice(), + selviews => { + const selVideo = selviews.find(dv => dv.ComponentView?.playFrom); + if (selVideo?.ComponentView?.Play) { + this.setVideoBox(selVideo.ComponentView); + } else this.removeVideoBox(); + } + ); } // pausing movements will dispose all timers that are planned to replay the movements @@ -45,18 +62,16 @@ export class ReplayMovements { this.timers?.map(timer => clearTimeout(timer)); }; - setVideoBox = async (videoBox: VideoBox) => { - // console.info('setVideoBox', videoBox); + setVideoBox = async (videoBox: ViewBoxInterface) => { if (this.videoBox !== null) { console.warn('setVideoBox on already videoBox'); } - if (this.videoBoxDisposeFunc !== null) { - console.warn('setVideoBox on already videoBox dispose func'); - this.videoBoxDisposeFunc(); - } + this.videoBoxDisposeFunc?.(); + + const data = StrCast(videoBox.dataDoc?.[videoBox.fieldKey + '_presentation']); + const presentation = data ? JSON.parse(data) : null; - const { presentation } = videoBox; - if (presentation == null) { + if (presentation === null) { console.warn('setVideoBox on null videoBox presentation'); return; } @@ -64,18 +79,14 @@ export class ReplayMovements { this.loadPresentation(presentation); this.videoBoxDisposeFunc = reaction( - () => ({ playing: videoBox._playing, timeViewed: videoBox.player?.currentTime || 0 }), + () => ({ playing: videoBox.IsPlaying?.(), timeViewed: videoBox.PlayerTime?.() || 0 }), ({ playing, timeViewed }) => (playing ? this.playMovements(presentation, timeViewed) : this.pauseMovements()) ); this.videoBox = videoBox; }; removeVideoBox = () => { - if (this.videoBoxDisposeFunc == null) { - console.warn('removeVideoBox on null videoBox'); - return; - } - this.videoBoxDisposeFunc(); + this.videoBoxDisposeFunc?.(); this.videoBox = null; this.videoBoxDisposeFunc = null; @@ -83,7 +94,7 @@ export class ReplayMovements { // should be called from interacting with the screen pauseFromInteraction = () => { - this.videoBox?.Pause(); + this.videoBox?.Pause?.(); this.pauseMovements(); }; @@ -117,7 +128,7 @@ export class ReplayMovements { return undefined; } // console.log('openTab', docId, doc); - CollectionDockingView.AddSplit(doc, OpenWhereMod.right); + DocumentView.addSplit(doc, OpenWhereMod.right); const docView = DocumentView.getDocumentView(doc); // BUG - this returns undefined if the doc is already open return docView?.ComponentView as CollectionFreeFormView; diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 6789c2ab8..1337d271f 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -26,6 +26,7 @@ export class SnappingManager { @observable _serverVersion: string = ''; @observable _lastBtnId: string = ''; @observable _propertyWid: number = 0; + @observable _printToConsole: boolean = false; private constructor() { SnappingManager._manager = this; @@ -55,6 +56,7 @@ export class SnappingManager { public static get ServerVersion() { return this.Instance._serverVersion; } // prettier-ignore public static get LastPressedBtn() { return this.Instance._lastBtnId; } // prettier-ignore public static get PropertiesWidth(){ return this.Instance._propertyWid; } // prettier-ignore + public static get PrintToConsole() { return this.Instance._printToConsole; } // prettier-ignore public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore @@ -69,6 +71,7 @@ export class SnappingManager { public static SetServerVersion = (version:string) =>runInAction(() => {this.Instance._serverVersion = version}); // prettier-ignore public static SetLastPressedBtn = (id:string) =>runInAction(() => {this.Instance._lastBtnId = id}); // prettier-ignore public static SetPropertiesWidth= (wid:number) =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore + public static SetPrintToConsole = (state:boolean) =>runInAction(() => {this.Instance._printToConsole = state}); // prettier-ignore public static userColor: string | undefined; public static userVariantColor: string | undefined; diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 956c0e674..534ffd2c8 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -3,9 +3,7 @@ import { action, observable, runInAction } from 'mobx'; import { Without } from '../../Utils'; import { RichTextField } from '../../fields/RichTextField'; - -// eslint-disable-next-line prefer-const -let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen +import { SnappingManager } from './SnappingManager'; function getBatchName(target: any, key: string | symbol): string { const keyName = key.toString(); @@ -108,7 +106,7 @@ export namespace UndoManager { export function AddEvent(event: UndoEvent, value?: any): void { if (currentBatch && batchCounter.get() && !undoing) { - printToConsole && + SnappingManager.PrintToConsole && console.log( ' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + @@ -183,7 +181,7 @@ export namespace UndoManager { } export function StartBatch(batchName: string): Batch { - printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName); + SnappingManager.PrintToConsole && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName); runInAction(() => batchCounter.set(batchCounter.get() + 1)); if (currentBatch === undefined) { currentBatch = []; @@ -193,7 +191,7 @@ export namespace UndoManager { const EndBatch = action((batchName: string, cancel: boolean = false) => { runInAction(() => batchCounter.set(batchCounter.get() - 1)); - printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + (currentBatch?.length ?? 0) + ')'); + SnappingManager.PrintToConsole && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + (currentBatch?.length ?? 0) + ')'); if (batchCounter.get() === 0 && currentBatch?.length) { if (!cancel) { undoStack.push(currentBatch); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index fc6a330f7..b7383a37e 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -305,11 +305,6 @@ export class DashboardView extends ObservableReactComponent<{}> { }); 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) { /* empty */ } else if (doc.readOnly) { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index f01666d62..94e84e647 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -2,66 +2,16 @@ import { action, computed, makeObservable, observable } from 'mobx'; import * as React from 'react'; import { returnFalse } from '../../ClientUtils'; import { DateField } from '../../fields/DateField'; -import { Doc, DocListCast, FieldType, Opt } from '../../fields/Doc'; -import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData, DocViews } from '../../fields/DocSymbols'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, DocData } from '../../fields/DocSymbols'; import { List } from '../../fields/List'; -import { RefField } from '../../fields/RefField'; import { toList } from '../../fields/Types'; import { GetEffectiveAcl, inheritParentAcls } from '../../fields/util'; import { DocumentType } from '../documents/DocumentTypes'; -import { DragManager } from '../util/DragManager'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { PinProps } from './PinFuncs'; -import { DocumentView } from './nodes/DocumentView'; -import { FocusViewOptions } from './nodes/FocusViewOptions'; +import { ViewBoxInterface } from './ViewBoxInterface'; import { FieldViewProps } from './nodes/FieldView'; -import { OpenWhere } from './nodes/OpenWhere'; -// import { DocUtils } from '../documents/Documents'; -/** - * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc) - * Many of these methods only make sense for specific viewBox'es, but they should be written to - * be as general as possible - */ -export class ViewBoxInterface

    extends ObservableReactComponent> { - promoteCollection?: () => void; // moves contents of collection to parent - updateIcon?: () => void; // updates the icon representation of the document - getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) - restoreView?: (viewSpec: Doc) => boolean; - scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt; // returns the duration of the focus - brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms) - getView?: (doc: Doc, options: FocusViewOptions) => Promise>; // returns a nested DocumentView for the specified doc or undefined - addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox - addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) - removeDocument?: (doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean) => boolean; // add a document (used only by collections) - select?: (ctrlKey: boolean, shiftKey: boolean) => void; - focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt; - viewTransition?: () => Opt; // duration of a view transition animation - isAnyChildContentActive?: () => boolean; // is any child content of the document active - onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected - getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) - setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) - playTrail?: (docs: Doc[]) => void; - playFrom?: (time: number, endTime?: number) => void; - Pause?: () => void; // pause a media document (eg, audio/video) - IsPlaying?: () => boolean; // is a media document playing - TogglePause?: (keep?: boolean) => void; // toggle media document playing state - setFocus?: () => void; // sets input focus to the componentView - setData?: (data: FieldType | Promise) => boolean; - componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; - dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set) => void; - dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag) - incrementalRendering?: () => void; - infoUI?: () => JSX.Element | null; - contentBounds?: () => undefined | { bounds: { x: number; y: number; r: number; b: number }; cx: number; cy: number; width: number; height: number }; // bounds of contents in collection coordinate space (used by TabDocViewThumb) - screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }>; - ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; - ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; - snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; - search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; - dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views - isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations -} /** * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's * (note: this should not be used for the 'Box' views that render the contents of Doc views) @@ -238,11 +188,7 @@ export function ViewBoxAnnotatableComponent

    () { Doc.RemoveEmbedding(rdoc, rdoc); } }); - if (targetDataDoc.isGroup && DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]).length < 2) { - Array.from(targetDataDoc[DocViews])[0]?.ComponentView?.promoteCollection?.(); - } else { - this.isAnyChildContentActive() && this._props.select(false); - } + this.isAnyChildContentActive() && this._props.select(false); return true; } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4262c2d57..fd44909c0 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -25,7 +25,6 @@ import { DocumentButtonBar } from './DocumentButtonBar'; import './DocumentDecorations.scss'; import { InkStrokeProperties } from './InkStrokeProperties'; import { InkingStroke } from './InkingStroke'; -import { LightboxView } from './LightboxView'; import { ObservableReactComponent } from './ObservableReactComponent'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; @@ -95,7 +94,7 @@ export class DocumentDecorations extends ObservableReactComponent !embedding.embedContainer) ?? Doc.MakeEmbedding(openDoc); Doc.deiconifyView(openDoc); } - LightboxView.Instance.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1)); + DocumentView.SetLightboxDoc(openDoc, undefined, selectedDocs.slice(1)); } } DocumentView.DeselectAll(); diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 7246f0ba0..2f26bdaef 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -21,7 +21,7 @@ import { SetActiveInkColor, SetActiveInkWidth, } from '../../fields/Doc'; -import { InkData, InkTool } from '../../fields/InkField'; +import { InkData, InkField, InkTool } from '../../fields/InkField'; import { NumCast } from '../../fields/Types'; // import MobileInkOverlay from '../../mobile/MobileInkOverlay'; import { Gestures } from '../../pen-gestures/GestureTypes'; @@ -362,7 +362,7 @@ export class GestureOverlay extends ObservableReactComponent { - const padding = pad ? [-20000, 20000] : []; - const xs = [...padding, ...stroke.map(p => p.X)]; - const ys = [...padding, ...stroke.map(p => p.Y)]; - const right = Math.max(...xs); - const left = Math.min(...xs); - 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 GestureOverlay.getBounds(this._points); + return InkField.getBounds(this._points); } get elements() { diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index c73da35bc..18ec0b6a9 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -15,7 +15,6 @@ import { UndoManager, undoable } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; import { DocumentDecorations } from './DocumentDecorations'; import { InkStrokeProperties } from './InkStrokeProperties'; -import { LightboxView } from './LightboxView'; import { MainView } from './MainView'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionStackedTimeline } from './collections/CollectionStackedTimeline'; @@ -146,7 +145,7 @@ export class KeyManager { } if (doDeselect) { DocumentView.DeselectAll(); - LightboxView.Instance.SetLightboxDoc(undefined); + DocumentView.SetLightboxDoc(undefined); } // DictationManager.Controls.stop(); GoogleAuthenticationManager.Instance.cancel(); @@ -163,8 +162,8 @@ export class KeyManager { case 'delete': case 'backspace': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - if (LightboxView.LightboxDoc) { - LightboxView.Instance.SetLightboxDoc(undefined); + if (DocumentView.LightboxDoc()) { + DocumentView.SetLightboxDoc(undefined); DocumentView.DeselectAll(); } else if (!window.getSelection()?.toString()) DocumentDecorations.Instance.onCloseClick(true); return { stopPropagation: true, preventDefault: true }; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index f497ca447..55f28f415 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -35,7 +35,8 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; import { ContextMenu } from './ContextMenu'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from './DocComponent'; +import { ViewBoxInterface } from './ViewBoxInterface'; +import { ViewBoxAnnotatableComponent } from './DocComponent'; import { Colors } from './global/globalEnums'; import { InkControlPtHandles, InkEndPtHandles } from './InkControlPtHandles'; import './InkStroke.scss'; diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 12d899388..269f4fa83 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -19,14 +19,16 @@ import { GestureOverlay } from './GestureOverlay'; import './LightboxView.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; import { DefaultStyleProvider, wavyBorderPath } from './StyleProvider'; -import { CollectionDockingView } from './collections/CollectionDockingView'; import { DocumentView } from './nodes/DocumentView'; import { OpenWhere, OpenWhereMod } from './nodes/OpenWhere'; +import { ScriptingGlobals } from '../util/ScriptingGlobals'; +import { OverlayView } from './OverlayView'; interface LightboxViewProps { PanelWidth: number; PanelHeight: number; maxBorder: number[]; + addSplit: (document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string | undefined, keyValue?: boolean | undefined) => boolean; } const savedKeys = ['freeform_panX', 'freeform_panY', 'freeform_scale', 'layout_scrollTop', 'layout_fieldKey']; @@ -39,7 +41,7 @@ export class LightboxView extends ObservableReactComponent { * @returns true if a DocumentView is descendant of the lightbox view */ public static Contains(view?:DocumentView) { return view && LightboxView.Instance?._docView && (view.containerViewPath?.() ?? []).concat(view).includes(LightboxView.Instance?._docView); } // prettier-ignore - public static get LightboxDoc() { return LightboxView.Instance?._doc; } // prettier-ignore + public static LightboxDoc = () => LightboxView.Instance?._doc; // eslint-disable-next-line no-use-before-define static Instance: LightboxView; private _path: { @@ -65,10 +67,20 @@ export class LightboxView extends ObservableReactComponent { super(props); makeObservable(this); LightboxView.Instance = this; + DocumentView._setLightboxDoc = this.SetLightboxDoc; + DocumentView._lightboxContains = LightboxView.Contains; + DocumentView._lightboxDoc = LightboxView.LightboxDoc; } - + /** + * Sets the root Doc to render in the lightbox view. + * @param doc + * @param target a Doc within 'doc' to focus on (useful for freeform collections) + * @param future a list of Docs to step through with the arrow buttons of the lightbox + * @param layoutTemplate a template to apply to 'doc' to render it. + * @returns success flag which is currently always true + */ @action - public SetLightboxDoc(doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) { + public SetLightboxDoc = (doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) => { const lightDoc = this._doc; lightDoc && lightDoc !== doc && @@ -110,7 +122,7 @@ export class LightboxView extends ObservableReactComponent { this._docTarget = target ?? doc; return true; - } + }; public AddDocTab = (docs: Doc | Doc[], location: OpenWhere, layoutTemplate?: Doc | string) => { const doc = toList(docs).lastElement(); @@ -183,7 +195,7 @@ export class LightboxView extends ObservableReactComponent { const lightDoc = this._docTarget ?? this._doc; if (lightDoc) { Doc.RemoveDocFromList(Doc.MyRecentlyClosed, 'data', lightDoc); - CollectionDockingView.AddSplit(lightDoc, OpenWhereMod.none); + this._props.addSplit(lightDoc, OpenWhereMod.none); this.SetLightboxDoc(undefined); } }; @@ -243,7 +255,9 @@ export class LightboxView extends ObservableReactComponent { />

    ); - return !this._doc ? null : ( + return !this._doc ? ( + + ) : (
    { return this.props.navBtn('50%', 0, 0, 'chevron-down', this.props.lightboxDoc(), this.props.stepInto, ''); } } + +// eslint-disable-next-line prefer-arrow-callback +ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) { + LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); // , 0); +}); diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 259ffbbc5..8968acbbb 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -7,7 +7,6 @@ import * as dotenv from 'dotenv'; // see https://github.com/motdotla/dotenv#how- import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import { AssignAllExtensions } from '../../extensions/Extensions'; -import { Doc } from '../../fields/Doc'; import { FieldLoader } from '../../fields/FieldLoader'; import { BranchingTrailManager } from '../util/BranchingTrailManager'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; @@ -16,11 +15,49 @@ import { PingManager } from '../util/PingManager'; import { ReplayMovements } from '../util/ReplayMovements'; import { TrackMovements } from '../util/TrackMovements'; import { KeyManager } from './GlobalKeyHandler'; +import { InkingStroke } from './InkingStroke'; import { MainView } from './MainView'; +import { CollectionCalendarView } from './collections/CollectionCalendarView'; +import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionView } from './collections/CollectionView'; +import { TabDocView } from './collections/TabDocView'; +import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { CollectionFreeFormInfoUI } from './collections/collectionFreeForm/CollectionFreeFormInfoUI'; +import { CollectionSchemaView } from './collections/collectionSchema/CollectionSchemaView'; +import { SchemaRowBox } from './collections/collectionSchema/SchemaRowBox'; import './global/globalScripts'; +import { AudioBox } from './nodes/AudioBox'; +import { ComparisonBox } from './nodes/ComparisonBox'; +import { DataVizBox } from './nodes/DataVizBox/DataVizBox'; +import { DocumentContentsView, HTMLtag } from './nodes/DocumentContentsView'; +import { EquationBox } from './nodes/EquationBox'; +import { FieldView } from './nodes/FieldView'; +import { FontIconBox } from './nodes/FontIconBox/FontIconBox'; +import { FunctionPlotBox } from './nodes/FunctionPlotBox'; +import { ImageBox } from './nodes/ImageBox'; import { KeyValueBox } from './nodes/KeyValueBox'; +import { LabelBox } from './nodes/LabelBox'; +import { LinkBox } from './nodes/LinkBox'; +import { LoadingBox } from './nodes/LoadingBox'; +import { MapBox } from './nodes/MapBox/MapBox'; +import { MapPushpinBox } from './nodes/MapBox/MapPushpinBox'; +import { PDFBox } from './nodes/PDFBox'; +import { PhysicsSimulationBox } from './nodes/PhysicsBox/PhysicsSimulationBox'; +import { RecordingBox } from './nodes/RecordingBox'; +import { ScreenshotBox } from './nodes/ScreenshotBox'; +import { ScriptingBox } from './nodes/ScriptingBox'; +import { VideoBox } from './nodes/VideoBox'; +import { WebBox } from './nodes/WebBox'; +import { DashDocCommentView } from './nodes/formattedText/DashDocCommentView'; +import { DashDocView } from './nodes/formattedText/DashDocView'; +import { DashFieldView } from './nodes/formattedText/DashFieldView'; +import { EquationView } from './nodes/formattedText/EquationView'; +import { FootnoteView } from './nodes/formattedText/FootnoteView'; +import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; +import { SummaryView } from './nodes/formattedText/SummaryView'; +import { ImportElementBox } from './nodes/importBox/ImportElementBox'; +import { PresBox, PresElementBox } from './nodes/trails'; +import { SearchBox } from './search/SearchBox'; dotenv.config(); @@ -36,22 +73,14 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; root.render(); window.location.search.includes('safe') && CollectionView.SetSafeMode(true); const info = await CurrentUserUtils.loadCurrentUser(); - // if (info.email === 'guest') DocServer.Control.makeReadOnly(); if (!info.userDocumentId) { alert('Fatal Error: user not found in database'); return; } await CurrentUserUtils.loadUserDocument(info); setTimeout(() => { - document.getElementById('root')!.addEventListener( - 'wheel', - event => { - if (event.ctrlKey) { - event.preventDefault(); - } - }, - true - ); + // prevent zooming browser + document.getElementById('root')!.addEventListener('wheel', event => event.ctrlKey && event.preventDefault(), true); const startload = (document as any).startLoad; const loading = Date.now() - (startload ? Number(startload) : Date.now() - 3000); console.log('Loading Time = ' + loading); @@ -65,10 +94,56 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; new PingManager(); new KeyManager(); - // iniitialize plugin apis + // initialize plugins and classes that require plugins + CollectionDockingView.Init(TabDocView); + FormattedTextBox.Init((tbox: FormattedTextBox) => ({ + dashComment(node: any, view: any, getPos: any) { return new DashDocCommentView(node, view, getPos); }, // prettier-ignore + dashDoc(node: any, view: any, getPos: any) { return new DashDocView(node, view, getPos, tbox); }, // prettier-ignore + dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, tbox); }, // prettier-ignore + equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, tbox); }, // prettier-ignore + summary(node: any, view: any, getPos: any) { return new SummaryView(node, view, getPos); }, // prettier-ignore + footnote(node: any, view: any, getPos: any) { return new FootnoteView(node, view, getPos); }, // prettier-ignore + })); CollectionFreeFormInfoUI.Init(); LinkFollower.Init(); KeyValueBox.Init(); + PresBox.Init(TabDocView.AllTabDocs); + DocumentContentsView.Init(KeyValueBox.LayoutString(), { + FormattedTextBox, + ImageBox, + FontIconBox, + LabelBox, + EquationBox, + FieldView, + CollectionFreeFormView, + CollectionDockingView, + CollectionSchemaView, + CollectionCalendarView, + CollectionView, + WebBox, + KeyValueBox, + PDFBox, + VideoBox, + AudioBox, + RecordingBox, + PresBox, + PresElementBox, + SearchBox, + FunctionPlotBox, + InkingStroke, + LinkBox, + ScriptingBox, + MapBox, + ScreenshotBox, + DataVizBox, + HTMLtag, + ComparisonBox, + LoadingBox, + PhysicsSimulationBox, + SchemaRowBox, + ImportElementBox, + MapPushpinBox, + }); root.render(); }, 0); })(); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 637824c31..e4a18dcea 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -12,7 +12,7 @@ import '../../../node_modules/browndash-components/dist/styles/global.min.css'; import { ClientUtils, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../ClientUtils'; import { emptyFunction } from '../../Utils'; import { Doc, DocListCast, GetDocFromUrl, Opt } from '../../fields/Doc'; -import { DocData, DocViews } from '../../fields/DocSymbols'; +import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { DocCast, StrCast, toList } from '../../fields/Types'; import { DocServer } from '../DocServer'; @@ -45,10 +45,9 @@ import { GestureOverlay } from './GestureOverlay'; import { LightboxView } from './LightboxView'; import './MainView.scss'; import { ObservableReactComponent } from './ObservableReactComponent'; -import { OverlayView } from './OverlayView'; import { PreviewCursor } from './PreviewCursor'; import { PropertiesView } from './PropertiesView'; -import { DashboardStyleProvider, DefaultStyleProvider } from './StyleProvider'; +import { DashboardStyleProvider, DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider'; import { TimelineMenu } from './animationtimeline/TimelineMenu'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionMenu } from './collections/CollectionMenu'; @@ -61,7 +60,7 @@ import { LinkMenu } from './linking/LinkMenu'; import { AudioBox } from './nodes/AudioBox'; import { SchemaCSVPopUp } from './nodes/DataVizBox/SchemaCSVPopUp'; import { DocButtonState } from './nodes/DocumentLinksButton'; -import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from './nodes/DocumentView'; +import { DocumentView, DocumentViewInternal } from './nodes/DocumentView'; import { ImageEditorData as ImageEditor } from './nodes/ImageBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; import { LinkDocPreview, LinkInfo } from './nodes/LinkDocPreview'; @@ -102,7 +101,7 @@ export class MainView extends ObservableReactComponent<{}> { return this._hideUI ? 0 : 27; } // 27 comes form lm.config.defaultConfig.dimensions.headerHeight in goldenlayout.js @computed private get topOfDashUI() { - return this._hideUI || LightboxView.LightboxDoc ? 0 : Number(TOPBAR_HEIGHT.replace('px', '')); + return this._hideUI || DocumentView.LightboxDoc() ? 0 : Number(TOPBAR_HEIGHT.replace('px', '')); } @computed private get topOfHeaderBarDoc() { return this.topOfDashUI; @@ -171,6 +170,10 @@ export class MainView extends ObservableReactComponent<{}> { () => DocumentView.Selected().slice(), views => views.length > 1 && (document.activeElement as any)?.blur !== undefined && (document.activeElement as any)!.blur() ); + reaction( + () => Doc.MyDockedBtns.linearView_IsOpen, + open => SnappingManager.SetPrintToConsole(!!open) + ); const scriptTag = document.createElement('script'); scriptTag.setAttribute('type', 'text/javascript'); scriptTag.setAttribute('src', 'https://www.bing.com/api/maps/mapcontrol?callback=makeMap'); @@ -252,7 +255,6 @@ export class MainView extends ObservableReactComponent<{}> { constructor(props: any) { super(props); makeObservable(this); - CollectionDockingView.setTabJSXComponent(TabDocView); DocumentViewInternal.addDocTabFunc = MainView.addDocTabFunc_impl; MainView.Instance = this; DashboardView._urlState = HistoryUtil.parseUrl(window.location) || ({} as any); @@ -686,7 +688,7 @@ export class MainView extends ObservableReactComponent<{}> { @computed get dockingContent() { return ( - +
    { style={{ width: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, minWidth: `calc(100% - ${this._leftMenuFlyoutWidth + this.leftMenuWidth() + this.propertiesWidth()}px)`, - transform: LightboxView.LightboxDoc ? 'scale(0.0001)' : undefined, + transform: DocumentView.LightboxDoc() ? 'scale(0.0001)' : undefined, }}> {!this.mainContainer ? null : this.mainDocView}
    @@ -791,7 +793,7 @@ export class MainView extends ObservableReactComponent<{}> { @computed get leftMenuPanel() { return ( -
    +
    { } @computed get snapLines() { const dragged = DragManager.docsBeingDragged.lastElement() ?? DocumentView.SelectedDocs().lastElement(); - const dragPar = dragged ? CollectionFreeFormView.from(Array.from(dragged[DocViews]).lastElement()) : undefined; + const dragPar = dragged ? CollectionFreeFormView.from(DocumentView.getViews(dragged).lastElement()) : undefined; return !dragPar?.layoutDoc.freeform_snapLines ? null : (
    @@ -1066,7 +1068,7 @@ export class MainView extends ObservableReactComponent<{}> { case 'home': return ; case 'dashboard': default: return (<> -
    +
    {this.mainDashboardArea} @@ -1083,14 +1085,11 @@ export class MainView extends ObservableReactComponent<{}> { - {/* */} {this.snapLines} - - {LightboxView.LightboxDoc ? null : } + - {/* */}
    ); } diff --git a/src/client/views/ObservableReactComponent.tsx b/src/client/views/ObservableReactComponent.tsx index 266411770..34da82b6c 100644 --- a/src/client/views/ObservableReactComponent.tsx +++ b/src/client/views/ObservableReactComponent.tsx @@ -1,6 +1,8 @@ import { action, makeObservable, observable } from 'mobx'; import * as React from 'react'; import './AntimodeMenu.scss'; +import { observer } from 'mobx-react'; +import JsxParser from 'react-jsx-parser'; /** * This is an abstract class that serves as the base for a PDF-style or Marquee-style @@ -21,3 +23,12 @@ export abstract class ObservableReactComponent extends React.Component })); // prettier-ignore } } + +class ObserverJsxParser1 extends JsxParser { + constructor(props: any) { + super(props); + observer(this as any); + } +} + +export const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; diff --git a/src/client/views/PropertiesDocContextSelector.tsx b/src/client/views/PropertiesDocContextSelector.tsx index 7465c727a..1fea36d16 100644 --- a/src/client/views/PropertiesDocContextSelector.tsx +++ b/src/client/views/PropertiesDocContextSelector.tsx @@ -10,7 +10,7 @@ import { Cast, StrCast } from '../../fields/Types'; import { ObservableReactComponent } from './ObservableReactComponent'; import './PropertiesDocContextSelector.scss'; import { CollectionDockingView } from './collections/CollectionDockingView'; -import { DocFocusOrOpen, DocumentView } from './nodes/DocumentView'; +import { DocumentView } from './nodes/DocumentView'; import { OpenWhere } from './nodes/OpenWhere'; type PropertiesDocContextSelectorProps = { @@ -58,7 +58,7 @@ export class PropertiesDocContextSelector extends ObservableReactComponent { if (!this._props.DocView) return; const col = Doc.IsDataProto(clickCol) ? Doc.MakeDelegate(clickCol) : clickCol; - DocFocusOrOpen(Doc.GetProto(this._props.DocView.Document), undefined, col); + DocumentView.FocusOrOpen(Doc.GetProto(this._props.DocView.Document), undefined, col); }; render() { diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index e6e95e86c..0b8201903 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -12,7 +12,6 @@ import { BsArrowDown, BsArrowDownUp, BsArrowUp } from 'react-icons/bs'; import { FaFilter } from 'react-icons/fa'; import { ClientUtils, DashColor, lightOrDark } from '../../ClientUtils'; import { Doc, Opt, StrListCast } from '../../fields/Doc'; -import { DocViews } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../fields/Types'; @@ -23,10 +22,11 @@ import { SnappingManager } from '../util/SnappingManager'; import { undoBatch, UndoManager } from '../util/UndoManager'; import { TreeSort } from './collections/TreeSort'; import { Colors } from './global/globalEnums'; -import { DocFocusOrOpen, DocumentView, DocumentViewProps } from './nodes/DocumentView'; +import { DocumentView, DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { StyleProp } from './StyleProp'; import './StyleProvider.scss'; +import { emptyPath } from '../../Utils'; function toggleLockedPosition(doc: Doc) { UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground'); @@ -134,7 +134,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt dv.IsSelected).length; + const selected = DocumentView.getViews(doc).filter(dv => dv.IsSelected).length; const highlightIndex = Doc.GetBrushHighlightStatus(doc) || (selected ? Doc.DocBrushStatus.selfBrushed : 0); const highlightColor = ['transparent', 'rgb(68, 118, 247)', selected ? "black" : 'rgb(68, 118, 247)', 'orange', 'lightBlue'][highlightIndex]; const highlightStyle = ['solid', 'dashed', 'solid', 'solid', 'solid'][highlightIndex]; @@ -403,7 +403,11 @@ export function DashboardStyleProvider(doc: Opt, props: Opt if (doc && property.split(':')[0] === StyleProp.Decorations) { return doc._type_collection === CollectionViewType.Docking || Doc.IsSystem(doc) ? null - : DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.embedContainer ?? doc?.annotationOn))); + : DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => DocumentView.FocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.embedContainer ?? doc?.annotationOn))); } return DefaultStyleProvider(doc, props, property); } + +export function returnEmptyDocViewList() { + return emptyPath; +} diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 5a7c2ef5b..cff32a557 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -13,8 +13,8 @@ import { DocUtils } from '../documents/DocUtils'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; import { CollectionTreeView } from './collections/CollectionTreeView'; -import { DocumentView, returnEmptyDocViewList } from './nodes/DocumentView'; -import { DefaultStyleProvider } from './StyleProvider'; +import { DocumentView } from './nodes/DocumentView'; +import { DefaultStyleProvider, returnEmptyDocViewList } from './StyleProvider'; import './TemplateMenu.scss'; @observer diff --git a/src/client/views/ViewBoxInterface.ts b/src/client/views/ViewBoxInterface.ts new file mode 100644 index 000000000..c633f34fb --- /dev/null +++ b/src/client/views/ViewBoxInterface.ts @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { Doc, FieldType, Opt } from '../../fields/Doc'; +import { RefField } from '../../fields/RefField'; +import { DragManager } from '../util/DragManager'; +import { ObservableReactComponent } from './ObservableReactComponent'; +import { PinProps } from './PinFuncs'; +import { DocumentView } from './nodes/DocumentView'; +import { FocusViewOptions } from './nodes/FocusViewOptions'; +import { OpenWhere } from './nodes/OpenWhere'; +// import { DocUtils } from '../documents/Documents'; + +/** + * Shared interface among all viewBox'es (ie, react classes that render the contents of a Doc) + * Many of these methods only make sense for specific viewBox'es, but they should be written to + * be as general as possible + */ +export abstract class ViewBoxInterface

    extends ObservableReactComponent> { + abstract get Document(): Doc; + abstract get dataDoc(): Doc; + abstract get fieldKey(): string; + promoteCollection?: () => void; // moves contents of collection to parent + updateIcon?: () => void; // updates the icon representation of the document + getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) + restoreView?: (viewSpec: Doc) => boolean; + scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: FocusViewOptions) => Opt; // returns the duration of the focus + brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms) + getView?: (doc: Doc, options: FocusViewOptions) => Promise>; // returns a nested DocumentView for the specified doc or undefined + addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox + addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) + removeDocument?: (doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean) => boolean; // add a document (used only by collections) + select?: (ctrlKey: boolean, shiftKey: boolean) => void; + focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt; + viewTransition?: () => Opt; // duration of a view transition animation + isAnyChildContentActive?: () => boolean; // is any child content of the document active + onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected + getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) + setKeyFrameEditing?: (set: boolean) => void; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) + playTrail?: (docs: Doc[]) => void; + playFrom?: (time: number, endTime?: number, fullPlay?: boolean) => void; // play a range of a media document + Play?: () => void; // play a media documents + Pause?: () => void; // pause a media document (eg, audio/video) + IsPlaying?: () => boolean; // is a media document playing + PlayerTime?: () => number | undefined; // current timecode of player + TogglePause?: (keep?: boolean) => void; // toggle media document playing state + setFocus?: () => void; // sets input focus to the componentView + setData?: (data: FieldType | Promise) => boolean; + componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; + dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set) => void; + dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag) + incrementalRendering?: () => void; + infoUI?: () => JSX.Element | null; + contentBounds?: () => undefined | { bounds: { x: number; y: number; r: number; b: number }; cx: number; cy: number; width: number; height: number }; // bounds of contents in collection coordinate space (used by TabDocViewThumb) + screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; transition?: string }>; + ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; + ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; + snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; + search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; + dontRegisterView?: () => boolean; // KeyValueBox's don't want to register their views + isUnstyledView?: () => boolean; // SchemaView and KeyValue are unstyled -- not titles, no opacity, no animations +} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 0ee3575f3..8fb2b30f1 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -22,7 +22,6 @@ import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; -import { LightboxView } from '../LightboxView'; import { DocumentView } from '../nodes/DocumentView'; import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; import { OverlayView } from '../OverlayView'; @@ -37,11 +36,13 @@ const _global = (window /* browser */ || global) /* node */ as any; export class CollectionDockingView extends CollectionSubView() { static tabClass: JSX.Element | null = null; /** - * Configure golden layout to render its documents using the specified React component + * Initialize by assigning the add split method to DocumentView and by + * configuring golden layout to render its documents using the specified React component * @param ele - typically would be set to TabDocView */ - static setTabJSXComponent(ele: any) { + public static Init(ele: any) { this.tabClass = ele; + DocumentView.addSplit = CollectionDockingView.AddSplit; } // eslint-disable-next-line no-use-before-define @observable public static Instance: CollectionDockingView | undefined = undefined; @@ -336,7 +337,7 @@ export class CollectionDockingView extends CollectionSubView() { SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property if (this._containerRef.current) { this._lightboxReactionDisposer = reaction( - () => LightboxView.LightboxDoc, + () => DocumentView.LightboxDoc(), doc => setTimeout(() => !doc && this.onResize()) ); new _global.ResizeObserver(this.onResize).observe(this._containerRef.current); @@ -391,7 +392,7 @@ export class CollectionDockingView extends CollectionSubView() { onResize = () => { const cur = this._containerRef.current; // bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed - !LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height); + !DocumentView.LightboxDoc() && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height); }; endUndoBatch = () => { @@ -633,7 +634,7 @@ export class CollectionDockingView extends CollectionSubView() { ScriptingGlobals.add( // eslint-disable-next-line prefer-arrow-callback function openInLightbox(doc: any) { - LightboxView.Instance.AddDocTab(doc, OpenWhere.lightbox); + CollectionDockingView.Instance?._props.addDocTab(doc, OpenWhere.lightbox); }, 'opens up document in a lightbox', '(doc: any)' diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index e53071584..3eb3008c4 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -26,8 +26,8 @@ import { Transform } from '../../util/Transform'; import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu } from '../AntimodeMenu'; import { EditableView } from '../EditableView'; -import { DefaultStyleProvider } from '../StyleProvider'; -import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; +import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import './CollectionMenu.scss'; import { CollectionLinearView } from './collectionLinear'; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 53211be77..16c474996 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -22,7 +22,6 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { FieldsDropdown } from '../FieldsDropdown'; import { Colors } from '../global/globalEnums'; -import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; @@ -177,7 +176,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { ); this._disposers.refList = reaction( - () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }), + () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !DocumentView.LightboxContains(this.DocumentView?.()) }), ({ refList, autoHeight }) => { if (autoHeight) { refList.forEach(r => this.observer.observe(r)); diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index c098c033b..44ab1968d 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -4,14 +4,11 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { lightOrDark, returnEmptyString } from '../../../ClientUtils'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { RichTextField } from '../../../fields/RichTextField'; +import { Doc, Opt } from '../../../fields/Doc'; import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { Cast, NumCast } from '../../../fields/Types'; -import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { DocumentFromField } from '../../documents/DocFromField'; import { DocUtils } from '../../documents/DocUtils'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; @@ -19,11 +16,10 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; -import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import './CollectionNoteTakingView.scss'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import './CollectionNoteTakingView.scss'; interface CSVFieldColumnProps { Document: Doc; @@ -171,9 +167,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent { ContextMenu.Instance.clearItems(); - const layoutItems: ContextMenuProps[] = []; - const docItems: ContextMenuProps[] = []; - const dataDoc = this._props.TemplateDataDocument || this._props.Document; + const { pivotField } = this._props; const pivotValue = this.getValue(this._props.heading); DocUtils.addDocumentCreatorMenuItems( @@ -187,50 +181,10 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent dataDoc[fieldKey] instanceof RichTextField || dataDoc[fieldKey] instanceof ImageField || typeof dataDoc[fieldKey] === 'string') - .map(fieldKey => - docItems.push({ - description: ':' + fieldKey, - event: () => { - const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document)); - if (created) { - if (this._props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this._props.Document); - } - return this._props.addDocument?.(created); - } - return undefined; - }, - icon: 'compress-arrows-alt', - }) - ); - Array.from(Object.keys(Doc.GetProto(dataDoc))) - .filter(fieldKey => DocListCast(dataDoc[fieldKey]).length) - .map(fieldKey => - docItems.push({ - description: ':' + fieldKey, - event: () => { - const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); - if (created) { - const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document; - if (container.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, container); - return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); - } - return this._props.addDocument?.(created) || false; - } - return undefined; - }, - icon: 'compress-arrows-alt', - }) - ); - !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' }); - !Doc.UserDoc().noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' }); ContextMenu.Instance.setDefaultItem('::', (name: string): void => { Doc.GetProto(this._props.Document)[name] = ''; const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true }); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 1604920f6..fac885300 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -26,7 +26,6 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { VideoThumbnails } from '../global/globalEnums'; -import { LightboxView } from '../LightboxView'; import { AudioWaveform } from '../nodes/audio/AudioWaveform'; import { DocumentView } from '../nodes/DocumentView'; import { FocusFuncType, StyleProviderFuncType } from '../nodes/FieldView'; @@ -752,10 +751,10 @@ class StackedTimelineAnchor extends ObservableReactComponent Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); if ( - !LightboxView.LightboxDoc && + !DocumentView.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. // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video. - /* (isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc)) */ + /* (isDictation || !Doc.AreProtosEqual(DocumentView.LightboxDoc(), this._props.layoutDoc)) */ !this._props.layoutDoc.dontAutoFollowLinks && Doc.Links(this._props.mark).length && time > NumCast(this._props.mark[this._props.startTag]) && diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 07aa0f4f0..56d2a6c9c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -26,7 +26,6 @@ import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from '../EditableView'; -import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; @@ -229,7 +228,7 @@ export class CollectionStackingView extends CollectionSubView layoutAutoHeight && this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : this._refList.reduce((p, r) => p + DivHeight(r), 0))) ); this._disposers.refList = reaction( - () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !LightboxView.Contains(this.DocumentView?.()) }), + () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !DocumentView.LightboxContains(this.DocumentView?.()) }), ({ refList, autoHeight }) => { this.observer.disconnect(); if (autoHeight) refList.forEach(r => this.observer.observe(r)); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index c1247f5b0..beb8c0666 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -209,7 +209,7 @@ export class CollectionTreeView extends CollectionSubView { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout const layoutItems: ContextMenuProps[] = []; - const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc; + const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc; menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' }); if (!Doc.noviceMode) { layoutItems.push({ diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 7cadd072b..b52c7c54c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -13,7 +13,7 @@ import { Docs } from '../../documents/Documents'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView } from '../nodes/FieldView'; import { OpenWhere } from '../nodes/OpenWhere'; import { CollectionCalendarView } from './CollectionCalendarView'; @@ -35,7 +35,7 @@ import { CollectionMultirowView } from './collectionMulticolumn/CollectionMultir import { CollectionSchemaView } from './collectionSchema/CollectionSchemaView'; @observer -export class CollectionView extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class CollectionView extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 008ef6ab4..afd584154 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -9,7 +9,7 @@ import * as ReactDOM from 'react-dom/client'; import { ClientUtils, DashColor, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocData, DocViews } from '../../../fields/DocSymbols'; +import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; @@ -28,13 +28,14 @@ import { LightboxView } from '../LightboxView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; -import { DefaultStyleProvider } from '../StyleProvider'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { Colors } from '../global/globalEnums'; -import { DocumentView, returnEmptyDocViewList } from '../nodes/DocumentView'; +import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; -import { PresBox, PresMovement } from '../nodes/trails'; +import { PresBox } from '../nodes/trails'; +import { PresMovement } from '../nodes/trails/PresEnums'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionView } from './CollectionView'; import './TabDocView.scss'; @@ -187,6 +188,11 @@ interface TabDocViewProps { @observer export class TabDocView extends ObservableReactComponent { static _allTabs = new ObservableSet(); + public static AllTabDocs() { + return Array.from(TabDocView._allTabs) + .filter(tv => tv._document) + .map(tv => tv._document!); + } _mainCont: HTMLDivElement | null = null; _tabReaction: IReactionDisposer | undefined; @@ -294,7 +300,7 @@ export class TabDocView extends ObservableReactComponent { @observable _isActive: boolean = false; @observable _isAnyChildContentActive = false; public static IsSelected = (doc?: Doc) => { - if (Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected)) { + if (DocumentView.getViews(doc).some(dv => dv?.IsSelected)) { return true; } return false; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index 65a2fe0aa..e543b4008 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -20,6 +20,11 @@ export interface CollectionFreeFormPannableContentsProps { @observer export class CollectionFreeFormPannableContents extends ObservableReactComponent { static _overlayPlugin: ((fform: Doc) => React.JSX.Element) | null = null; + /** + * Setup a plugin function that returns components to display on a layer above the collection + * See PresBox which renders presenstation paths over the collection + * @param plugin a function that receives the collection Doc and returns JSX Elements + */ public static SetOverlayPlugin(plugin: ((fform: Doc) => React.JSX.Element) | null) { CollectionFreeFormPannableContents._overlayPlugin = plugin; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 74de6524b..dbd9fb11f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -28,7 +28,6 @@ import { CollectionViewType, DocumentType } from '../../../documents/DocumentTyp import { DocUtils } from '../../../documents/DocUtils'; import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; -import { ReplayMovements } from '../../../util/ReplayMovements'; import { CompileScript } from '../../../util/Scripting'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager'; @@ -36,9 +35,7 @@ import { Transform } from '../../../util/Transform'; import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; import { Timeline } from '../../animationtimeline/Timeline'; import { ContextMenu } from '../../ContextMenu'; -import { GestureOverlay } from '../../GestureOverlay'; import { InkingStroke } from '../../InkingStroke'; -import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; import { DocumentView } from '../../nodes/DocumentView'; @@ -367,7 +364,7 @@ export class CollectionFreeFormView extends CollectionSubView { - this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, GestureOverlay.getBounds(points), text)); + this.onGesture(e, new GestureUtils.GestureEvent(gesture, points, InkField.getBounds(points), text)); }; onPointerMove = (e: PointerEvent) => { @@ -838,8 +835,6 @@ export class CollectionFreeFormView extends CollectionSubView will talk with Bob about using mobx to do this to remove this line of code. - if (Doc.UserDoc()?.presentationMode === 'watching') ReplayMovements.Instance.pauseFromInteraction(); if (!this.isAnnotationOverlay && this.childDocs.length) { // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds @@ -849,7 +844,7 @@ export class CollectionFreeFormView extends CollectionSubView { const collectionDoc = this.Document; if (collectionDoc?._type_collection !== CollectionViewType.Freeform) { + SnappingManager.TriggerUserPanned(); this.setPan( NumCast(this.layoutDoc[this.panXFieldKey]) + ((this._props.PanelWidth() / 2) * x) / this.zoomScaling(), // nudge x,y as a function of panel dimension and scale NumCast(this.layoutDoc[this.panYFieldKey]) + ((this._props.PanelHeight() / 2) * -y) / this.zoomScaling(), @@ -1008,6 +1004,15 @@ export class CollectionFreeFormView extends CollectionSubView { + const ret = !!this._props.removeDocument?.(docs, annotationKey); + // if this is a group and we have fewer than 2 Docs, then just promote what's left to our parent and get rid of the group. + if (ret && DocListCast(this.dataDoc[annotationKey ?? this.fieldKey]).length < 2 && this.Document.isGroup) { + this.promoteCollection(); + } + return ret; + }; childPointerEventsFunc = () => this._childPointerEvents; childContentsActive = () => (this._props.childContentsActive ?? this.isContentActive() === false ? returnFalse : emptyFunction)(); getChildDocView(entry: PoolData) { @@ -1050,7 +1055,7 @@ export class CollectionFreeFormView extends CollectionSubView this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0; incrementalRender = action(() => { - if (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())) { + if (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.())) { const layoutUnrendered = this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])); const loadIncrement = this.Document.isTemplateDoc ? Number.MAX_VALUE : 5; for (let i = 0; i < Math.min(layoutUnrendered.length, loadIncrement); i++) { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index b017eb62b..5874364e0 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -25,9 +25,9 @@ import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { DefaultStyleProvider } from '../../StyleProvider'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; -import { DocFocusOrOpen, returnEmptyDocViewList } from '../../nodes/DocumentView'; +import { DocumentView } from '../../nodes/DocumentView'; import { FieldViewProps } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { FInfotoColType } from './CollectionSchemaView'; @@ -59,6 +59,14 @@ export interface SchemaTableCellProps { rootSelected?: () => boolean; } +function selectedCell(props: SchemaTableCellProps) { + return ( + props.isRowActive() && + props.selectedCol() === props.col && // + props.selectedCells()?.filter(d => d === props.Document)?.length + ); +} + @observer export class SchemaTableCell extends ObservableReactComponent { constructor(props: SchemaTableCellProps) { @@ -67,7 +75,7 @@ export class SchemaTableCell extends ObservableReactComponent { - DocFocusOrOpen(toList(docs)[0]); + DocumentView.FocusOrOpen(toList(docs)[0]); return true; }; public static renderProps(props: SchemaTableCellProps) { @@ -114,11 +122,6 @@ export class SchemaTableCell extends ObservableReactComponent doc === this._props.Document).length !== 0 && this._props.selectedCol() === this._props.col; - } - @computed get defaultCellContent() { const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props); @@ -132,12 +135,12 @@ export class SchemaTableCell extends ObservableReactComponent this.selected && this._props.autoFocus && r?.setIsFocused(true)} + ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} contents={undefined} fieldContents={fieldProps} - editing={this.selected ? undefined : false} + editing={selectedCell(this._props) ? undefined : false} GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)} SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { @@ -157,30 +160,27 @@ export class SchemaTableCell extends ObservableReactComponent; - case ColumnType.Boolean: return ; - case ColumnType.RTF: return ; + switch (this.getCellType) { + case ColumnType.Image: return ; + case ColumnType.Boolean: return ; + case ColumnType.RTF: return ; case ColumnType.Enumeration: return Field.toString(val))} />; - case ColumnType.Date: return ; - default: return this.defaultCellContent; + case ColumnType.Date: return ; + default: return this.defaultCellContent; } } @@ -193,13 +193,13 @@ export class SchemaTableCell extends ObservableReactComponent + style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> {this.content}

    ); @@ -329,20 +329,14 @@ export class SchemaRTFCell extends ObservableReactComponent doc === this._props.Document).length !== 0 && this._props.selectedCol() === this._props.col; - // return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; - } - // if the text box blurs and none of its contents are focused(), then the edit finishes - selectedFunc = () => this.selected; + selectedFunc = () => !!selectedCell(this._props); render() { const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); fieldProps.isContentActive = this.selectedFunc; return ( -
    - {this.selected ? this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} +
    + {selectedCell(this._props) ? this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
    ); } @@ -354,10 +348,6 @@ export class SchemaBoolCell extends ObservableReactComponent doc === this._props.Document).length !== 0 && this._props.selectedCol() === this._props.col; - } render() { const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); return ( @@ -375,7 +365,7 @@ export class SchemaBoolCell extends ObservableReactComponent Field.toKeyValueString(this._props.Document, this._props.fieldKey)} SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { @@ -399,10 +389,6 @@ export class SchemaEnumerationCell extends ObservableReactComponent doc === this._props.Document).length !== 0 && this._props.selectedCol() === this._props.col; - } render() { const { color, textDecoration, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); const options = this._props.options?.map(facet => ({ value: facet, label: facet })); diff --git a/src/client/views/linking/LinkPopup.tsx b/src/client/views/linking/LinkPopup.tsx index 9fb1c0fdc..76a8396ff 100644 --- a/src/client/views/linking/LinkPopup.tsx +++ b/src/client/views/linking/LinkPopup.tsx @@ -5,8 +5,7 @@ import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from ' import { emptyFunction } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { Transform } from '../../util/Transform'; -import { DefaultStyleProvider } from '../StyleProvider'; -import { returnEmptyDocViewList } from '../nodes/DocumentView'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { SearchBox } from '../search/SearchBox'; import './LinkPopup.scss'; diff --git a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx index e48e993cf..3eb99f47a 100644 --- a/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx +++ b/src/client/views/newlightbox/ButtonMenu/ButtonMenu.tsx @@ -1,24 +1,27 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action } from 'mobx'; import * as React from 'react'; import { Doc } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { SnappingManager } from '../../../util/SnappingManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; +import { DocumentView } from '../../nodes/DocumentView'; import { OpenWhereMod } from '../../nodes/OpenWhere'; import { NewLightboxView } from '../NewLightboxView'; import './ButtonMenu.scss'; -import { IButtonMenu } from './utils'; -export const ButtonMenu = (props: IButtonMenu) => { +export function ButtonMenu() { return ( -
    +
    { e.stopPropagation(); NewLightboxView.LightboxDoc!._fitWidth = !NewLightboxView.LightboxDoc!._fitWidth; - }}>
    + }} + />
    { CollectionDockingView.AddSplit(NewLightboxView.LightboxDoc || NewLightboxView.LightboxDoc!, OpenWhereMod.none); DocumentView.DeselectAll(); NewLightboxView.SetNewLightboxDoc(undefined); - }}>
    + }} + />
    { onClick={e => { e.stopPropagation(); Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; - }}>
    + }} + />
    { onClick={action(e => { e.stopPropagation(); SnappingManager.SetExploreMode(!SnappingManager.ExploreMode); - })}>
    + })} + />
    ); -}; +} diff --git a/src/client/views/newlightbox/ExploreView/ExploreView.tsx b/src/client/views/newlightbox/ExploreView/ExploreView.tsx index a1d6375c4..f8c07cc43 100644 --- a/src/client/views/newlightbox/ExploreView/ExploreView.tsx +++ b/src/client/views/newlightbox/ExploreView/ExploreView.tsx @@ -1,32 +1,34 @@ -import './ExploreView.scss'; -import { IBounds, IExploreView, emptyBounds } from './utils'; -import { IRecommendation } from '../components'; +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import * as React from 'react'; -import { NewLightboxView } from '../NewLightboxView'; import { StrCast } from '../../../../fields/Types'; +import { NewLightboxView } from '../NewLightboxView'; +import './ExploreView.scss'; +import { IExploreView, emptyBounds } from './utils'; -export const ExploreView = (props: IExploreView) => { +export function ExploreView(props: IExploreView) { const { recs, bounds = emptyBounds } = props; return ( -
    +
    {recs && recs.map(rec => { - const x_bound: number = Math.max(Math.abs(bounds.max_x), Math.abs(bounds.min_x)); - const y_bound: number = Math.max(Math.abs(bounds.max_y), Math.abs(bounds.min_y)); + const xBound: number = Math.max(Math.abs(bounds.max_x), Math.abs(bounds.min_x)); + const yBound: number = Math.max(Math.abs(bounds.max_y), Math.abs(bounds.min_y)); if (rec.embedding) { - const x = (rec.embedding.x / x_bound) * 50; - const y = (rec.embedding.y / y_bound) * 50; + const x = (rec.embedding.x / xBound) * 50; + const y = (rec.embedding.y / yBound) * 50; return ( -
    {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}> +
    {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}> {rec.title}
    ); - } else return null; + } + return null; })} -
    +
    {StrCast(NewLightboxView.LightboxDoc?.title)}
    ); -}; +} diff --git a/src/client/views/newlightbox/Header/LightboxHeader.tsx b/src/client/views/newlightbox/Header/LightboxHeader.tsx index 51bfaa4e5..882d28fba 100644 --- a/src/client/views/newlightbox/Header/LightboxHeader.tsx +++ b/src/client/views/newlightbox/Header/LightboxHeader.tsx @@ -4,8 +4,8 @@ import { BsBookmark, BsBookmarkFill } from 'react-icons/bs'; import { MdTravelExplore } from 'react-icons/md'; import { Doc } from '../../../../fields/Doc'; import { StrCast } from '../../../../fields/Types'; -import { LightboxView } from '../../LightboxView'; import { Colors } from '../../global/globalEnums'; +import { DocumentView } from '../../nodes/DocumentView'; import { NewLightboxView } from '../NewLightboxView'; import { EditableText } from '../components/EditableText'; import { getType } from '../utils'; @@ -14,11 +14,11 @@ import { INewLightboxHeader } from './utils'; export function NewLightboxHeader(props: INewLightboxHeader) { const { height = 100, width } = props; - const [doc, setDoc] = React.useState(LightboxView.LightboxDoc); + const [doc, setDoc] = React.useState(DocumentView.LightboxDoc()); const [editing, setEditing] = React.useState(false); const [title, setTitle] = React.useState(null); React.useEffect(() => { - const lbDoc = LightboxView.LightboxDoc; + const lbDoc = DocumentView.LightboxDoc(); setDoc(lbDoc); if (lbDoc) { setTitle( @@ -32,7 +32,7 @@ export function NewLightboxHeader(props: INewLightboxHeader) { /> ); } - }, [LightboxView.LightboxDoc]); + }, [DocumentView.LightboxDoc()]); const [saved, setSaved] = React.useState(false); diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx index 558ce7e38..c86ddb745 100644 --- a/src/client/views/newlightbox/NewLightboxView.tsx +++ b/src/client/views/newlightbox/NewLightboxView.tsx @@ -1,6 +1,5 @@ /* eslint-disable jsx-a11y/no-static-element-interactions */ /* eslint-disable jsx-a11y/click-events-have-key-events */ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -12,7 +11,6 @@ import { Cast, NumCast, StrCast, toList } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { GestureOverlay } from '../GestureOverlay'; -import { LightboxView } from '../LightboxView'; import { DefaultStyleProvider } from '../StyleProvider'; import { DocumentView } from '../nodes/DocumentView'; import { OpenWhere } from '../nodes/OpenWhere'; @@ -108,7 +106,7 @@ export class NewLightboxView extends React.Component { DocumentView.CurrentlyPlaying?.forEach(dv => dv.ComponentView?.Pause?.()); // DocumentView.PinDoc(doc, { hidePresBox: true }); this._history ? this._history.push({ doc, target }) : (this._history = [{ doc, target }]); - if (doc !== LightboxView.LightboxDoc) { + if (doc !== DocumentView.LightboxDoc()) { this._savedState = { layout_fieldKey: StrCast(doc.layout_fieldKey), panX: Cast(doc.freeform_panX, 'number', null), @@ -151,11 +149,12 @@ export class NewLightboxView extends React.Component { if (NewLightboxView._history?.lastElement().target !== target) NewLightboxView._history?.push({ doc, target }); } else if (!target && NewLightboxView.path.length) { const saved = NewLightboxView._savedState; - if (LightboxView.LightboxDoc && saved) { - LightboxView.LightboxDoc._freeform_panX = saved.panX; - LightboxView.LightboxDoc._freeform_panY = saved.panY; - LightboxView.LightboxDoc._freeform_scale = saved.scale; - LightboxView.LightboxDoc._layout_scrollTop = saved.scrollTop; + const lightboxDoc = DocumentView.LightboxDoc(); + if (lightboxDoc && saved) { + lightboxDoc._freeform_panX = saved.panX; + lightboxDoc._freeform_panY = saved.panY; + lightboxDoc._freeform_scale = saved.scale; + lightboxDoc._layout_scrollTop = saved.scrollTop; } const pop = NewLightboxView.path.pop(); if (pop) { @@ -176,7 +175,7 @@ export class NewLightboxView extends React.Component { NewLightboxView.SetNewLightboxDoc(undefined); return; } - const { doc, target } = NewLightboxView._history?.lastElement(); + const { doc, target } = NewLightboxView._history?.lastElement() ?? { doc: undefined, target: undefined }; const docView = DocumentView.getLightboxDocumentView(target || doc); if (docView) { NewLightboxView._docTarget = target; @@ -248,14 +247,15 @@ export class NewLightboxView extends React.Component { @computed get documentView() { - if (!LightboxView.LightboxDoc) return null; + const lightboxDoc = DocumentView.LightboxDoc(); + if (!lightboxDoc) return null; return ( { NewLightboxView._docView = r !== null ? r : undefined; })} - Document={LightboxView.LightboxDoc} + Document={lightboxDoc} PanelWidth={this.newLightboxWidth} PanelHeight={this.newLightboxHeight} LayoutTemplate={NewLightboxView.LightboxDocTemplate} @@ -281,50 +281,14 @@ export class NewLightboxView extends React.Component { newLightboxWidth = () => this.props.PanelWidth - 420; newLightboxHeight = () => this.props.PanelHeight - 140; newLightboxScreenToLocal = () => 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) => ( -
    -
    -
    {color}
    - -
    -
    - ); docFilters = () => NewLightboxView._docFilters || []; - @action - stepInto = () => { - NewLightboxView.path.push({ - doc: LightboxView.LightboxDoc, - target: NewLightboxView._docTarget, - future: NewLightboxView._future, - history: NewLightboxView._history, - saved: NewLightboxView._savedState, - }); - const coll = NewLightboxView._docTarget; - if (coll) { - const fieldKey = Doc.LayoutFieldKey(coll); - const contents = [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + '_annotations'])]; - const links = Doc.Links(coll) - .map(link => Doc.getOppositeAnchor(link, coll)) - .filter(doc => doc) - .map(doc => doc!); - NewLightboxView.SetNewLightboxDoc(coll, undefined, contents.length ? contents : links); - } - }; - future = () => NewLightboxView._future; render() { const newLightboxHeaderHeight = 100; let downx = 0; let downy = 0; - return !LightboxView.LightboxDoc ? null : ( + return !DocumentView.LightboxDoc() ? null : (
    { @@ -376,7 +340,7 @@ export class NewLightboxTourBtn extends React.Component 0, 0, 'chevron-down', - () => (LightboxView.LightboxDoc /* && this.props.future()?.length */ ? '' : 'none'), + () => (DocumentView.LightboxDoc() /* && this.props.future()?.length */ ? '' : 'none'), e => { e.stopPropagation(); this.props.stepInto(); diff --git a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx index 1d502b73f..dc3339cd3 100644 --- a/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx +++ b/src/client/views/newlightbox/RecommendationList/RecommendationList.tsx @@ -1,3 +1,7 @@ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ +/* eslint-disable guard-for-in */ import { IconButton, Size, Type } from 'browndash-components'; import * as React from 'react'; import { FaCaretDown, FaCaretUp } from 'react-icons/fa'; @@ -5,17 +9,15 @@ import { GrClose } from 'react-icons/gr'; import { DocListCast, StrListCast } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { StrCast } from '../../../../fields/Types'; -import { LightboxView } from '../../LightboxView'; import { Colors } from '../../global/globalEnums'; +import { DocumentView } from '../../nodes/DocumentView'; import { IBounds } from '../ExploreView/utils'; import { NewLightboxView } from '../NewLightboxView'; import { IRecommendation, Recommendation } from '../components'; import { IDocRequest, fetchKeywords, fetchRecommendations } from '../utils'; import './RecommendationList.scss'; -import { IRecommendationList } from './utils'; -export const RecommendationList = (props: IRecommendationList) => { - const { loading, keywords } = props; +export function RecommendationList() { const [loadingKeywords, setLoadingKeywords] = React.useState(true); const [showMore, setShowMore] = React.useState(false); const [keywordsLoc, setKeywordsLoc] = React.useState([]); @@ -25,21 +27,22 @@ export const RecommendationList = (props: IRecommendationList) => { React.useEffect(() => { const getKeywords = async () => { - let text = StrCast(LightboxView.LightboxDoc?.text); + const text = StrCast(DocumentView.LightboxDoc()?.text); console.log('[1] fetching keywords'); const response = await fetchKeywords(text, 5, true); console.log('[2] response:', response); const kw = response.keywords; console.log(kw); NewLightboxView.SetKeywords(kw); - if (LightboxView.LightboxDoc) { + const lightboxDoc = DocumentView.LightboxDoc(); + if (lightboxDoc) { console.log('setting keywords on doc'); - LightboxView.LightboxDoc.keywords = new List(kw); + lightboxDoc.keywords = new List(kw); setKeywordsLoc(NewLightboxView.Keywords); } setLoadingKeywords(false); }; - let keywordsList = StrListCast(LightboxView.LightboxDoc!.keywords); + const keywordsList = StrListCast(DocumentView.LightboxDoc()!.keywords); if (!keywordsList || keywordsList.length < 2) { setLoadingKeywords(true); getKeywords(); @@ -57,14 +60,14 @@ export const RecommendationList = (props: IRecommendationList) => { console.log('fetching recommendations'); let query = 'undefined'; if (keywordsLoc) query = keywordsLoc.join(','); - let src = StrCast(NewLightboxView.LightboxDoc?.text); - let dashDocs: IDocRequest[] = []; + const src = StrCast(NewLightboxView.LightboxDoc?.text); + const dashDocs: IDocRequest[] = []; // get linked docs - let linkedDocs = DocListCast(NewLightboxView.LightboxDoc?.links); + const linkedDocs = DocListCast(NewLightboxView.LightboxDoc?.links); console.log('linked docs', linkedDocs); // get context docs (docs that are also in the collection) - // let contextDocs: Doc[] = DocListCast(DocCast(LightboxView.LightboxDoc?.context).data) - // let docId = LightboxView.LightboxDoc && LightboxView.LightboxDoc[Id] + // let contextDocs: Doc[] = DocListCast(DocCast(DocumentView.LightboxDoc()?.context).data) + // let docId = DocumentView.LightboxDoc() && DocumentView.LightboxDoc()[Id] // console.log("context docs", contextDocs) // contextDocs.forEach((doc: Doc) => { // if (docId !== doc[Id]){ @@ -79,10 +82,8 @@ export const RecommendationList = (props: IRecommendationList) => { console.log('dash docs', dashDocs); if (query !== undefined) { const response = await fetchRecommendations(src, query, [], true); - const num_recs = response.num_recommendations; - const recs = response.recommendations; - const keywords = response.keywords; - const response_bounds: IBounds = { + const theRecs = response.recommendations; + const responseBounds: IBounds = { max_x: response.max_x, max_y: response.max_y, min_x: response.min_x, @@ -93,22 +94,23 @@ export const RecommendationList = (props: IRecommendationList) => { // setKeywordsLoc(NewLightboxView.Keywords); // } // console.log(response_bounds) - NewLightboxView.SetBounds(response_bounds); + NewLightboxView.SetBounds(responseBounds); const recommendations: IRecommendation[] = []; - for (const key in recs) { + // eslint-disable-next-line no-restricted-syntax + for (const key in theRecs) { console.log(key); - const title = recs[key].title; - const url = recs[key].url; - const type = recs[key].type; - const text = recs[key].text; - const transcript = recs[key].transcript; - const previewUrl = recs[key].previewUrl; - const embedding = recs[key].embedding; - const distance = recs[key].distance; - const source = recs[key].source; - const related_concepts = recs[key].related_concepts; - const docId = recs[key].doc_id; - related_concepts.length >= 1 && + const { title } = theRecs[key]; + const { url } = theRecs[key]; + const { type } = theRecs[key]; + const { text } = theRecs[key]; + const { transcript } = theRecs[key]; + const { previewUrl } = theRecs[key]; + const { embedding } = theRecs[key]; + const { distance } = theRecs[key]; + const { source } = theRecs[key]; + const { related_concepts: relatedConcepts } = theRecs[key]; + const docId = theRecs[key].doc_id; + relatedConcepts.length >= 1 && recommendations.push({ title: title, data: url, @@ -119,14 +121,15 @@ export const RecommendationList = (props: IRecommendationList) => { embedding: embedding, distance: Math.round(distance * 100) / 100, source: source, - related_concepts: related_concepts, + related_concepts: relatedConcepts, docId: docId, }); } recommendations.sort((a, b) => { if (a.distance && b.distance) { return a.distance - b.distance; - } else return 0; + } + return 0; }); console.log('[rec]: ', recommendations); NewLightboxView.SetRecs(recommendations); @@ -138,12 +141,12 @@ export const RecommendationList = (props: IRecommendationList) => { return (
    { e.stopPropagation(); }}> -
    -
    Recommendations
    +
    +
    Recommendations
    {NewLightboxView.LightboxDoc && (
    The recommendations are produced based on the text in the document{' '} @@ -153,65 +156,58 @@ export const RecommendationList = (props: IRecommendationList) => { . The following keywords are used to fetch the recommendations.
    )} -
    Keywords
    +
    Keywords
    {loadingKeywords ? ( -
    +
    ) : ( -
    +
    {keywordsLoc && - keywordsLoc.map((word, ind) => { - return ( -
    - {word} - } - onClick={() => { - let kw = keywordsLoc; - kw.splice(ind); - NewLightboxView.SetKeywords(kw); - }} - /> -
    - ); - })} + keywordsLoc.map((word, ind) => ( +
    + {word} + } + onClick={() => { + const kw = keywordsLoc; + kw.splice(ind); + NewLightboxView.SetKeywords(kw); + }} + /> +
    + ))}
    )} {!showMore ? (
    { setShowMore(true); }}> More
    ) : ( -
    +
    { setShowMore(false); }}> Less
    -
    Type
    -
    Sources
    +
    Type
    +
    Sources
    )}
    -
    - {recs && - recs.map((rec: IRecommendation) => { - return ; - })} -
    +
    {recs && recs.map((rec: IRecommendation) => )}
    ); -}; +} diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 685a5aca4..62c4cc61a 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -296,9 +296,9 @@ export class CollectionFreeFormDocumentView extends DocComponent ) : ( val.lower)).omit} // prettier-ignore + parent={this} DataTransition={this.DataTransition} LocalRotation={this.localRotation} CollectionFreeFormDocumentView={this.returnThis} diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 474d54119..e1d16549c 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -13,7 +13,7 @@ import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { dropActionType } from '../../util/DropActionTypes'; import { undoBatch } from '../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import './ComparisonBox.scss'; diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 15187b4e4..9ca63194c 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -18,7 +18,7 @@ import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { UndoManager, undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { MarqueeAnnotator } from '../../MarqueeAnnotator'; import { PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; @@ -41,7 +41,7 @@ export enum DataVizView { } @observer -export class DataVizBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class DataVizBox extends ViewBoxAnnotatableComponent() { private _mainCont: React.RefObject = React.createRef(); private _marqueeref = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index ec9db8480..18529a429 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -2,7 +2,6 @@ import { computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import JsxParser from 'react-jsx-parser'; import * as XRegExp from 'xregexp'; import { OmitKeys } from '../../../ClientUtils'; import { Without, emptyPath } from '../../../Utils'; @@ -11,56 +10,15 @@ import { AclPrivate, DocData } from '../../../fields/DocSymbols'; import { ScriptField } from '../../../fields/ScriptField'; import { Cast, DocCast, StrCast } from '../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; -import { InkingStroke } from '../InkingStroke'; -import { ObservableReactComponent } from '../ObservableReactComponent'; -import { CollectionCalendarView } from '../collections/CollectionCalendarView'; -import { CollectionDockingView } from '../collections/CollectionDockingView'; -import { CollectionView } from '../collections/CollectionView'; -import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; -import { CollectionSchemaView } from '../collections/collectionSchema/CollectionSchemaView'; -import { SchemaRowBox } from '../collections/collectionSchema/SchemaRowBox'; -import { SearchBox } from '../search/SearchBox'; -import { AudioBox } from './AudioBox'; -import { ComparisonBox } from './ComparisonBox'; -import { DataVizBox } from './DataVizBox/DataVizBox'; +import { ObservableReactComponent, ObserverJsxParser } from '../ObservableReactComponent'; import './DocumentView.scss'; -import { EquationBox } from './EquationBox'; -import { FieldView, FieldViewProps } from './FieldView'; -import { FontIconBox } from './FontIconBox/FontIconBox'; -import { FunctionPlotBox } from './FunctionPlotBox'; -import { ImageBox } from './ImageBox'; -import { KeyValueBox } from './KeyValueBox'; -import { LabelBox } from './LabelBox'; -import { LinkBox } from './LinkBox'; -import { LoadingBox } from './LoadingBox'; -import { MapBox } from './MapBox/MapBox'; -import { MapPushpinBox } from './MapBox/MapPushpinBox'; -import { PDFBox } from './PDFBox'; -import { PhysicsSimulationBox } from './PhysicsBox/PhysicsSimulationBox'; -import { RecordingBox } from './RecordingBox'; -import { ScreenshotBox } from './ScreenshotBox'; -import { ScriptingBox } from './ScriptingBox'; -import { VideoBox } from './VideoBox'; -import { WebBox } from './WebBox'; -import { FormattedTextBox } from './formattedText/FormattedTextBox'; -import { ImportElementBox } from './importBox/ImportElementBox'; -import { PresBox } from './trails/PresBox'; -import { PresElementBox } from './trails/PresElementBox'; +import { FieldViewProps } from './FieldView'; type BindingProps = Without; export interface JsxBindings { props: BindingProps; } -class ObserverJsxParser1 extends JsxParser { - constructor(props: any) { - super(props); - observer(this as any); - } -} - -export const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any; - interface HTMLtagProps { Document: Doc; htmltag: string; @@ -116,6 +74,15 @@ export interface DocumentContentsViewProps extends FieldViewProps { } @observer export class DocumentContentsView extends ObservableReactComponent { + private static DefaultLayoutString: string; + /** + * Set of all available rendering componets for Docs (e.g., ImageBox, CollectionFreeFormView, etc) + */ + private static Components: { [key: string]: any }; + public static Init(defaultLayoutString: string, components:{ [key: string]: any}) { + DocumentContentsView.DefaultLayoutString = defaultLayoutString; + DocumentContentsView.Components = components; + } constructor(props: any) { super(props); makeObservable(this); @@ -124,11 +91,11 @@ export class DocumentContentsView extends ObservableReactComponentawaiting layout

    '; - if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, KeyValueBox.LayoutString()); + if (this._props.layoutFieldKey === 'layout_keyValue') return StrCast(this._props.Document.layout_keyValue, DocumentContentsView.DefaultLayoutString); const tempLayout = DocCast(this.layoutDoc[this.layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(this.layoutDoc.layout_fieldKey, 'layout')]); const layoutDoc = tempLayout ?? this.layoutDoc; const layout = Cast(layoutDoc[layoutDoc === this._props.Document && this._props.layoutFieldKey ? this._props.layoutFieldKey : StrCast(layoutDoc.layout_fieldKey, 'layout')], 'string'); - if (layout === undefined) return this._props.Document.data ? "" : KeyValueBox.LayoutString(); + if (layout === undefined) return this._props.Document.data ? "" : DocumentContentsView.DefaultLayoutString; if (typeof layout === 'string') return layout; return '

    Loading layout

    '; } @@ -223,42 +190,7 @@ export class DocumentContentsView extends ObservableReactComponent void; dragEnding?: () => void; - parent?: any; + parent?: any; // parent React component view (see CollectionFreeFormDocumentView) } @observer export class DocumentViewInternal extends DocComponent() { @@ -320,7 +321,7 @@ export class DocumentViewInternal extends DocComponent this.onDoubleClickHandler.script.run(scriptProps, console.log).result?.select && this._props.select(false), 'on double click: ' + this.Document.title); } else if (!Doc.IsSystem(this.Document) && defaultDblclick !== 'ignore') { - UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.Document, OpenWhere.lightbox), 'double tap'); + UndoManager.RunInBatch(() => this._props.addDocTab(this.Document, OpenWhere.lightbox), 'double tap'); DocumentView.DeselectAll(); Doc.UnBrushDoc(this.Document); } else { @@ -561,7 +562,7 @@ export class DocumentViewInternal extends DocComponent LightboxView.Instance.SetLightboxDoc(this.Document), icon: 'external-link-alt' }); + appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => DocumentView.SetLightboxDoc(this.Document), icon: 'external-link-alt' }); } appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'eye' }); !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); @@ -980,6 +981,29 @@ export class DocumentViewInternal extends DocComponent() { public static ROOT_DIV = 'documentView-effectsWrapper'; + public static addSplit: (Doc: Doc, where: OpenWhereMod) => void; + // Lightbox + public static _lightboxDoc: () => Doc | undefined; + public static _lightboxContains: (view?: DocumentView) => boolean | undefined; + public static _setLightboxDoc: (doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) => boolean; + /** + * @returns The Doc, if any, being displayed in the lightbox + */ + public static readonly LightboxDoc = () => DocumentView._lightboxDoc?.(); + /** + * @param view + * @returns whether 'view' is anywhere in the rendering hierarchy of the lightbox + */ + public static readonly LightboxContains = (view?: DocumentView) => DocumentView._lightboxContains?.(view); + /** + * Sets the root Doc to render in the lightbox view. + * @param doc + * @param target a Doc within 'doc' to focus on (useful for freeform collections) + * @param future a list of Docs to step through with the arrow buttons of the lightbox + * @param layoutTemplate a template to apply to 'doc' to render it. + * @returns success flag which is currently always true + */ + public static readonly SetLightboxDoc = (doc: Opt, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) => DocumentView._setLightboxDoc(doc, target, future, layoutTemplate); // Sharing Manager public static ShareOpen: (target?: DocumentView, targetDoc?: Doc) => void; // LinkFollower @@ -998,6 +1022,7 @@ export class DocumentView extends DocComponent() { public static addView: (dv: DocumentView) => void | undefined; public static removeView: (dv: DocumentView) => void | undefined; public static addViewRenderedCb: (doc: Opt, func: (dv: DocumentView) => any) => boolean; + public static getViews = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []) as DocumentView[]; public static getFirstDocumentView: (toFind: Doc) => DocumentView | undefined; public static getDocumentView: (target: Doc | undefined, preferredCollection?: DocumentView) => Opt; public static getContextPath: (doc: Opt, includeExistingViews?: boolean) => Doc[]; @@ -1032,8 +1057,8 @@ export class DocumentView extends DocComponent() { public static UniquifyId(inLightbox: boolean | undefined, id: string) { return (inLightbox ? 'lightbox-' : '') + id; } - public ViewGuid = DocumentView.UniquifyId(LightboxView.Contains(this), Utils.GenerateGuid()); // a unique id associated with the main
    . used by LinkBox's Xanchor to find the arrowhead locations. - public DocUniqueId = DocumentView.UniquifyId(LightboxView.Contains(this), this.Document[Id]); + public ViewGuid = DocumentView.UniquifyId(DocumentView.LightboxContains(this), Utils.GenerateGuid()); // a unique id associated with the main
    . used by LinkBox's Xanchor to find the arrowhead locations. + public DocUniqueId = DocumentView.UniquifyId(DocumentView.LightboxContains(this), this.Document[Id]); constructor(props: DocumentViewProps) { super(props); @@ -1401,6 +1426,7 @@ export class DocumentView extends DocComponent() { }}> () { const docId = ClientUtils.CurrentUserEmail() + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; // prettier-ignore DocServer.GetRefField(docId).then(docx => - LightboxView.Instance.SetLightboxDoc( + DocumentView.SetLightboxDoc( (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, onViewMounted: ScriptField.MakeScript('updateLinkCollection(this, this.target)') }, docId) ) ); } -} - -export function returnEmptyDocViewList() { - return emptyPath; -} - -// eslint-disable-next-line default-param-last -export function DocFocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { - let doc = docIn; - const options = optionsIn; - const func = () => { - const cv = DocumentView.getDocumentView(containingDoc); - const dv = DocumentView.getDocumentView(doc, cv); - if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) { - DocumentView.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document)); - } else { - const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc)); - const showDoc = !Doc.IsSystem(container) && !cv ? container : doc; - options.toggleTarget = undefined; - DocumentView.showDocument(showDoc, options, () => DocumentView.showDocument(doc, { ...options, openLocation: undefined })).then(() => { - const cvFound = DocumentView.getDocumentView(containingDoc); - const dvFound = DocumentView.getDocumentView(doc, cvFound); - dvFound && Doc.linkFollowHighlight(dvFound.Document); - }); + // eslint-disable-next-line default-param-last + public static FocusOrOpen(docIn: Doc, optionsIn: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) { + let doc = docIn; + const options = optionsIn; + const func = () => { + const cv = DocumentView.getDocumentView(containingDoc); + const dv = DocumentView.getDocumentView(doc, cv); + if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) { + DocumentView.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document)); + } else { + const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc)); + const showDoc = !Doc.IsSystem(container) && !cv ? container : doc; + options.toggleTarget = undefined; + DocumentView.showDocument(showDoc, options, () => DocumentView.showDocument(doc, { ...options, openLocation: undefined })).then(() => { + const cvFound = DocumentView.getDocumentView(containingDoc); + const dvFound = DocumentView.getDocumentView(doc, cvFound); + dvFound && Doc.linkFollowHighlight(dvFound.Document); + }); + } + }; + if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) { + doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!; } - }; - if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) { - doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!; + if (doc.hidden) { + doc.hidden = false; + options.toggleTarget = false; + setTimeout(func); + } else func(); } - if (doc.hidden) { - doc.hidden = false; - options.toggleTarget = false; - setTimeout(func); - } else func(); } -ScriptingGlobals.add(DocFocusOrOpen); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { - documentView.iconify(); - documentView.select(false); +ScriptingGlobals.add(function DocFocusOrOpen(docIn: Doc, optionsIn?: FocusViewOptions, containingDoc?: Doc) { + return DocumentView.FocusOrOpen(docIn, optionsIn, containingDoc); }); // eslint-disable-next-line prefer-arrow-callback -ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) { - LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); // , 0); +ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { + documentView.iconify(); + documentView.select(false); }); // eslint-disable-next-line prefer-arrow-callback diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index 32d08fbe7..1f5c9b84b 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -11,7 +11,7 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; -import { LightboxView } from '../LightboxView'; +import { DocumentView } from './DocumentView'; import './EquationBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import EquationEditor from './formattedText/EquationEditor'; @@ -30,7 +30,7 @@ export class EquationBox extends ViewBoxBaseComponent() { componentDidMount() { this._props.setContentViewBox?.(this); - if (Doc.SelectOnLoad === this.Document && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) { + if (Doc.SelectOnLoad === this.Document && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()))) { this._props.select(false); this._ref.current!.mathField.focus(); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 66b134889..3f351a072 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -10,8 +10,8 @@ import { ScriptField } from '../../../fields/ScriptField'; import { WebField } from '../../../fields/URLField'; import { dropActionType } from '../../util/DropActionTypes'; import { Transform } from '../../util/Transform'; -import { ViewBoxInterface } from '../DocComponent'; import { PinProps } from '../PinFuncs'; +import { ViewBoxInterface } from '../ViewBoxInterface'; import { DocumentView } from './DocumentView'; import { FocusViewOptions } from './FocusViewOptions'; import { OpenWhere } from './OpenWhere'; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 397cc15ed..0956be3e9 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -13,8 +13,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DefaultStyleProvider } from '../StyleProvider'; -import { returnEmptyDocViewList } from './DocumentView'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; import { OpenWhere } from './OpenWhere'; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 6caa38a7f..8d6ae9f73 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -16,7 +16,6 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { SnappingManager } from '../../util/SnappingManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { EditableView } from '../EditableView'; -import { LightboxView } from '../LightboxView'; import { StyleProp } from '../StyleProp'; import { ComparisonBox } from './ComparisonBox'; import { DocumentView } from './DocumentView'; @@ -52,7 +51,7 @@ export class LinkBox extends ViewBoxBaseComponent() { componentDidMount() { this._props.setContentViewBox?.(this); this._disposers.deleting = reaction( - () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView!())), + () => !this.anchor1 && !this.anchor2 && this.DocumentView?.() && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView!())), empty => { if (empty) { this._hackToSeeIfDeleted = setTimeout(() => { @@ -110,7 +109,7 @@ export class LinkBox extends ViewBoxBaseComponent() { const getAnchor = (field: FieldResult): Element[] => { const docField = DocCast(field); const doc = docField?.layout_unrendered ? DocCast(docField.annotationOn, docField) : docField; - const ele = document.getElementById(DocumentView.UniquifyId(LightboxView.Contains(this.DocumentView?.()), doc[Id])); + const ele = document.getElementById(DocumentView.UniquifyId(DocumentView.LightboxContains(this.DocumentView?.()), doc[Id])); if (ele?.className === 'linkBox-label') foundParent = true; if (ele?.getBoundingClientRect().width) return [ele]; const eles = Array.from(document.getElementsByClassName(doc[Id])).filter(el => el?.getBoundingClientRect().width); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index ac8010f11..d7687e03e 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -23,7 +23,7 @@ import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { UndoManager, undoable } from '../../../util/UndoManager'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { PinDocView, PinProps } from '../../PinFuncs'; import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; @@ -63,7 +63,7 @@ type PopupInfo = { }; @observer -export class MapBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { +export class MapBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBox, fieldKey); } diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index e46e40bfe..07381c7d0 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -50,9 +50,9 @@ export class RecordingBox extends ViewBoxBaseComponent() { this.result = info; this.dataDoc.type = DocumentType.VID; - this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); + this.dataDoc[this.fieldKey + '_recorded'] = this.dataDoc.layout; // save the recording layout to allow re-recording later + this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey); // then convert the recording box to a video this.dataDoc[this._props.fieldKey] = new VideoField(this.result.accessPaths.client); - this.dataDoc[this.fieldKey + '_recorded'] = true; // stringify the presentation and store it if (presentation?.movements) { const presCopy = { ...presentation }; @@ -143,18 +143,13 @@ export class RecordingBox extends ViewBoxBaseComponent() { public static resumeWorkspaceReplaying(doc: Doc) { const docView = DocumentView.getDocumentView(doc); - if (docView?.ComponentView instanceof VideoBox) { - docView.ComponentView.Play(); - } + docView?.ComponentView?.Play?.(); Doc.UserDoc().workspaceReplayingState = mediaState.Playing; } public static pauseWorkspaceReplaying(doc: Doc) { const docView = DocumentView.getDocumentView(doc); - const videoBox = docView?.ComponentView as VideoBox; - if (videoBox) { - videoBox.Pause(); - } + docView?.ComponentView?.Pause?.(); Doc.UserDoc().workspaceReplayingState = mediaState.Paused; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 3b1815f8a..fe7600fa3 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -17,7 +17,6 @@ import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocUtils, FollowLinkScript } from '../../documents/DocUtils'; import { dropActionType } from '../../util/DropActionTypes'; -import { ReplayMovements } from '../../util/ReplayMovements'; import { undoBatch } from '../../util/UndoManager'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackedTimeline, TrimScope } from '../collections/CollectionStackedTimeline'; @@ -32,7 +31,6 @@ import { StyleProp } from '../StyleProp'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; -import { RecordingBox } from './RecordingBox'; import './VideoBox.scss'; /** @@ -101,18 +99,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { return field?.url.href ?? vfield?.url.href ?? ''; } - // returns the presentation data if it exists, null otherwise - @computed get presentation() { - const data = this.dataDoc[this.fieldKey + '_presentation']; - return data ? JSON.parse(StrCast(data)) : null; - } - @computed private get timeline() { return this._stackedTimeline; } private get transition() { return this._clicking ? 'left 0.5s, width 0.5s, height 0.5s' : ''; } // css transition for hiding/showing timeline + public get player(): HTMLVideoElement | null { return this._videoRef; } @@ -122,10 +115,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { this._props.setContentViewBox?.(this); // this tells the DocumentView that this VideoBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the VideoBox when making a link. this.player && this.setPlayheadTime(this.timeline?.clipStart || 0); document.addEventListener('keydown', this.keyEvents, true); - - if (this.presentation) { - ReplayMovements.Instance.setVideoBox(this); - } } componentWillUnmount() { @@ -134,12 +123,14 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { this.Pause(); Object.keys(this._disposers).forEach(d => this._disposers[d]?.()); document.removeEventListener('keydown', this.keyEvents, true); - - if (this.presentation) { - ReplayMovements.Instance.removeVideoBox(); - } } + override PlayerTime = () => this.player?.currentTime; + override Pause = (update: boolean = true) => { + this.pause(update); + !this._keepCurrentlyPlaying && this.removeCurrentlyPlaying(); + }; + // handles key events, when timeline scrubs fade controls @action keyEvents = (e: KeyboardEvent) => { @@ -230,10 +221,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { } this._playRegionTimer = undefined; }; - @action Pause = (update: boolean = true) => { - this.pause(update); - !this._keepCurrentlyPlaying && this.removeCurrentlyPlaying(); - }; // toggles video full screen @action public FullScreen = () => { @@ -518,11 +505,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent() { icon: 'expand-arrows-alt', }); // if the videobox was turned from a recording box - if (this.dataDoc[this.fieldKey + '_recorded'] === true) { + if (this.dataDoc[this.fieldKey + '_recorded']) { subitems.push({ description: 'Recreate recording', event: () => { - this.dataDoc.layout = RecordingBox.LayoutString(this.fieldKey); + this.dataDoc.layout = StrCast(this.dataDoc[this.fieldKey + '_recorded']); // restore the saed recording layout // delete assoicated video data this.dataDoc[this._props.fieldKey] = ''; this.dataDoc[this.fieldKey + '_duration'] = ''; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index b6ef36974..8835ea5e7 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -28,9 +28,8 @@ import { MarqueeOptionsMenu } from '../collections/collectionFreeForm'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent, ViewBoxInterface } from '../DocComponent'; +import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { Colors } from '../global/globalEnums'; -import { LightboxView } from '../LightboxView'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { AnchorMenu } from '../pdf/AnchorMenu'; import { Annotation } from '../pdf/Annotation'; @@ -38,6 +37,7 @@ import { GPTPopup } from '../pdf/GPTPopup/GPTPopup'; import { PinDocView, PinProps } from '../PinFuncs'; import { SidebarAnnos } from '../SidebarAnnos'; import { StyleProp } from '../StyleProp'; +import { ViewBoxInterface } from '../ViewBoxInterface'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; @@ -623,7 +623,7 @@ export class WebBox extends ViewBoxAnnotatableComponent() { clearStyleSheetRules(WebBox.webStyleSheet); this._scrollTimer = undefined; const newScrollTop = scrollTop > iframeHeight ? iframeHeight : scrollTop; - if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.()))) { + if (!LinkInfo.Instance?.LinkInfo && this._outerRef.current && newScrollTop !== this.layoutDoc.thumbScrollTop && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.()))) { this.layoutDoc.thumb = undefined; this.layoutDoc.thumbScrollTop = undefined; this.layoutDoc.thumbNativeWidth = undefined; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 321fdbb91..e354aedb7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,3 +1,4 @@ +/* eslint-disable no-use-before-define */ /* eslint-disable jsx-a11y/no-static-element-interactions */ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -10,7 +11,7 @@ import { inputRules } from 'prosemirror-inputrules'; import { keymap } from 'prosemirror-keymap'; import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, Selection, TextSelection, Transaction } from 'prosemirror-state'; -import { EditorView } from 'prosemirror-view'; +import { EditorView, NodeViewConstructor } from 'prosemirror-view'; import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, ClientUtils, DivWidth, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, StopEvent } from '../../../../ClientUtils'; @@ -45,7 +46,6 @@ import { ContextMenu } from '../../ContextMenu'; import { ContextMenuProps } from '../../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; -import { LightboxView } from '../../LightboxView'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup'; import { PinDocView, PinProps } from '../../PinFuncs'; @@ -58,11 +58,6 @@ import { FieldView, FieldViewProps } from '../FieldView'; import { FocusViewOptions } from '../FocusViewOptions'; import { LinkInfo } from '../LinkDocPreview'; import { OpenWhere } from '../OpenWhere'; -import { DashDocCommentView } from './DashDocCommentView'; -import { DashDocView } from './DashDocView'; -import { DashFieldView } from './DashFieldView'; -import { EquationView } from './EquationView'; -import { FootnoteView } from './FootnoteView'; import './FormattedTextBox.scss'; import { findLinkMark, FormattedTextBoxComment } from './FormattedTextBoxComment'; import { buildKeymap, updateBullets } from './ProsemirrorExampleTransfer'; @@ -70,7 +65,6 @@ import { removeMarkWithAttrs } from './prosemirrorPatches'; import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; -import { SummaryView } from './SummaryView'; // import * as applyDevTools from 'prosemirror-dev-tools'; export interface FormattedTextBoxProps extends FieldViewProps { @@ -82,10 +76,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent EditorState.create(FormattedTextBox.Instance.config); - // eslint-disable-next-line no-use-before-define - public static Instance: FormattedTextBox; - public static LiveTextUndo: UndoManager.Batch | undefined; + private static nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor }; + /** + * Initialize the class with all the plugin node view components + * @param nodeViews prosemirror plugins that render a custom UI for specific node types + */ + public static Init(nodeViews: (self: FormattedTextBox) => { [key: string]: NodeViewConstructor }) { + FormattedTextBox.nodeViews = nodeViews; + } + public static LiveTextUndo: UndoManager.Batch | undefined; // undo batch when typing a new text note into a collection static _globalHighlightsCache: string = ''; static _globalHighlights = new ObservableSet(['Audio Tags', 'Text from Others', 'Todo Items', 'Important Items', 'Disagree Items', 'Ignore Items']); static _highlightStyleSheet: any = addStyleSheet(); @@ -189,7 +188,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + private static _getTabDocs: () => Doc[]; + public static Init(tabDocs: () => Doc[]) { + PresBox._getTabDocs = tabDocs; + } static navigateToDocScript: ScriptField; constructor(props: FieldViewProps) { @@ -679,16 +681,16 @@ export class PresBox extends ViewBoxBaseComponent() { if (activeItem.presentation_openInLightbox) { const context = DocCast(targetDoc.annotationOn) ?? targetDoc; if (!DocumentView.getLightboxDocumentView(context)) { - LightboxView.Instance.SetLightboxDoc(context); + DocumentView.SetLightboxDoc(context); } } if (targetDoc) { if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined; - DocumentView.addViewRenderedCb(LightboxView.LightboxDoc, () => { + DocumentView.addViewRenderedCb(DocumentView.LightboxDoc(), () => { // if target or the doc it annotates is not in the lightbox, then close the lightbox if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { - LightboxView.Instance.SetLightboxDoc(undefined); + DocumentView.SetLightboxDoc(undefined); } DocumentView.showDocument(targetDoc, options, finished); }); @@ -789,7 +791,7 @@ export class PresBox extends ViewBoxBaseComponent() { default: } }); - LightboxView.Instance.SetLightboxDoc(undefined); + DocumentView.SetLightboxDoc(undefined); Doc.RemFromMyOverlay(this.Document); return PresStatus.Edit; }; @@ -892,7 +894,7 @@ export class PresBox extends ViewBoxBaseComponent() { exitMinimize = () => { if (Doc.IsInMyOverlay(this.layoutDoc)) { Doc.RemFromMyOverlay(this.Document); - CollectionDockingView.AddSplit(this.Document, OpenWhereMod.right); + DocumentView.addSplit(this.Document, OpenWhereMod.right); } return PresStatus.Edit; }; @@ -2202,9 +2204,8 @@ export class PresBox extends ViewBoxBaseComponent() { if (freeform && layout) doc = this.createTemplate(layout, title); if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title }); if (doc) { - const tabMap = CollectionDockingView.Instance?.tabMap; - const docTab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc; - const presCollection = DocumentView.getContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? docTab; + const docTab = PresBox._getTabDocs().find(tdoc => tdoc.type === DocumentType.COL); + const presCollection = DocCast(DocumentView.getContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc, docTab); const data = Cast(presCollection?.data, listSpec(Doc)); const configData = Cast(this.Document.data, listSpec(Doc)); if (data && configData) { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 56552c952..ae0838dd5 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -18,8 +18,8 @@ import { undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { CollectionDockingView } from '../collections/CollectionDockingView'; -import { IRecommendation, Recommendation } from '../newlightbox/components'; -import { fetchRecommendations } from '../newlightbox/utils'; +// import { IRecommendation, Recommendation } from '../newlightbox/components'; +// import { fetchRecommendations } from '../newlightbox/utils'; import { DocumentView } from '../nodes/DocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import './SearchBox.scss'; @@ -142,7 +142,7 @@ export class SearchBox extends ViewBoxBaseComponent() { @observable _searchString = ''; @observable _docTypeString = 'all'; @observable _results: Map = new Map(); - @observable _recommendations: IRecommendation[] = []; + // @observable _recommendations: IRecommendation[] = []; @observable _pageRanks: Map = new Map(); @observable _linkedDocsOut: Map> = new Map>(); @observable _linkedDocsIn: Map> = new Map>(); @@ -375,29 +375,29 @@ export class SearchBox extends ViewBoxBaseComponent() { if (query) { this.searchCollection(query); - const response = await fetchRecommendations('', query, [], true); - const recs = response.recommendations as any[]; - const recommendations: IRecommendation[] = []; - recs.forEach(rec => { - const { title, url, type, text, transcript, previewUrl, embedding, distance, source, related_concepts: relatedConcepts, doc_id: docId } = rec; - recommendations.push({ - title, - data: url, - type, - text, - transcript, - previewUrl, - embedding, - distance: Math.round(distance * 100) / 100, - source: source, - related_concepts: relatedConcepts, - docId, - }); - }); - const setRecommendations = action(() => { - this._recommendations = recommendations; - }); - setRecommendations(); + // const response = await fetchRecommendations('', query, [], true); + // const recs = response.recommendations as any[]; + // const recommendations: IRecommendation[] = []; + // recs.forEach(rec => { + // const { title, url, type, text, transcript, previewUrl, embedding, distance, source, related_concepts: relatedConcepts, doc_id: docId } = rec; + // recommendations.push({ + // title, + // data: url, + // type, + // text, + // transcript, + // previewUrl, + // embedding, + // distance: Math.round(distance * 100) / 100, + // source: source, + // related_concepts: relatedConcepts, + // docId, + // }); + // }); + // const setRecommendations = action(() => { + // this._recommendations = recommendations; + // }); + // setRecommendations(); } }; @@ -470,7 +470,7 @@ export class SearchBox extends ViewBoxBaseComponent() { }); // eslint-disable-next-line react/jsx-props-no-spreading - const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => ); + const recommendationsJSX: JSX.Element[] = []; // this._recommendations.map(props => ); return (
    diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 9f97efcce..e558e14e3 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -23,9 +23,9 @@ import { CollectionDockingView } from '../collections/CollectionDockingView'; import { CollectionLinearView } from '../collections/collectionLinear'; import { DashboardView } from '../DashboardView'; import { Colors } from '../global/globalEnums'; -import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView'; +import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DefaultStyleProvider } from '../StyleProvider'; +import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import './TopBar.scss'; /** diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 725221a66..fe044c035 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -102,7 +102,7 @@ export namespace Field { export function Copy(field: any) { return field instanceof ObjectField ? ObjectField.MakeCopy(field) : field; } - UndoManager.SetFieldPrinter(toJavascriptString); + UndoManager.SetFieldPrinter(toString); } export type FieldType = number | string | boolean | ObjectField | RefField; export type Opt = T | undefined; diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index c4f5f7a24..46704eb2b 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -89,6 +89,17 @@ export class InkField extends ObjectField { [ToString]() { return 'InkField'; } + + public static getBounds(stroke: InkData, pad?: boolean) { + const padding = pad ? [-20000, 20000] : []; + const xs = [...padding, ...stroke.map(p => p.X)]; + const ys = [...padding, ...stroke.map(p => p.Y)]; + const right = Math.max(...xs); + const left = Math.min(...xs); + const bottom = Math.max(...ys); + const top = Math.min(...ys); + return { right, left, bottom, top, width: right - left, height: bottom - top }; + } } ScriptingGlobals.add('InkField', InkField); diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index 4f8058ce4..83b5672b3 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -45,7 +45,7 @@ export class ProxyField extends ObjectField { return Field.toScriptString(this[ToValue]()?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ? } [ToString]() { - return 'ProxyField'; + return Field.toString(this[ToValue]()?.value); } @serializable(primitive()) diff --git a/src/fields/RichTextUtils.ts b/src/fields/RichTextUtils.ts index 5eb60a2f8..3763dcd2c 100644 --- a/src/fields/RichTextUtils.ts +++ b/src/fields/RichTextUtils.ts @@ -164,7 +164,7 @@ export namespace RichTextUtils { const inlineObjectMap = await parseInlineObjects(document); const title = document.title!; const { text, paragraphs } = GoogleApiClientUtils.Docs.Utils.extractText(document); - let state = FormattedTextBox.blankState(); + let state = EditorState.create(new FormattedTextBox({} as any).config); const structured = parseLists(paragraphs); let position = 3; diff --git a/src/fields/util.ts b/src/fields/util.ts index 9361430cb..a6499c3e3 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -443,7 +443,7 @@ export function containedFieldChangedHandler(container: List | Doc, p }); lastValue = ObjectField.MakeCopy((container as any)[prop as any]); }, - prop: 'remove ' + (diff.items?.length ?? 0) + ' items from list', + prop: 'remove ' + (diff.items?.length ?? 0) + ' items from list(' + ((container as any)?.title ?? '') + ':' + prop + ')', }, diff?.items ); -- cgit v1.2.3-70-g09d2 From 38a382a03675d6a50ec7de75f05025efd093f570 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 17 May 2024 10:39:25 -0400 Subject: manually added ChatBox from aj-starter --- package-lock.json | 359 ++++++++++++ package.json | 7 + src/client/documents/DocumentTypes.ts | 1 + src/client/documents/Documents.ts | 9 + src/client/util/CurrentUserUtils.ts | 2 + src/client/views/Main.tsx | 2 + src/client/views/nodes/ChatBox/ChatBox.scss | 228 ++++++++ src/client/views/nodes/ChatBox/ChatBox.tsx | 609 +++++++++++++++++++++ .../views/nodes/ChatBox/MessageComponent.tsx | 116 ++++ src/client/views/nodes/ChatBox/types.ts | 23 + src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/fields/Types.ts | 5 +- src/server/ApiManagers/AssistantManager.ts | 131 +++++ src/server/index.ts | 10 +- 14 files changed, 1498 insertions(+), 6 deletions(-) create mode 100644 src/client/views/nodes/ChatBox/ChatBox.scss create mode 100644 src/client/views/nodes/ChatBox/ChatBox.tsx create mode 100644 src/client/views/nodes/ChatBox/MessageComponent.tsx create mode 100644 src/client/views/nodes/ChatBox/types.ts create mode 100644 src/server/ApiManagers/AssistantManager.ts (limited to 'src/client/views/Main.tsx') diff --git a/package-lock.json b/package-lock.json index 60198132c..35ffc712c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -60,6 +60,7 @@ "babel": "^6.23.0", "babel-loader": "^9.1.3", "bcrypt-nodejs": "0.0.3", + "better-react-mathjax": "^2.0.3", "bezier-curve": "^1.0.0", "bezier-js": "^6.1.4", "bingmaps-react": "^1.2.10", @@ -86,6 +87,7 @@ "csstype": "^3.1.3", "csv-parser": "^3.0.0", "csv-stringify": "^6.4.4", + "csvtojson": "^2.0.10", "D": "^1.0.0", "d3": "^7.8.5", "depcheck": "^1.4.7", @@ -107,6 +109,7 @@ "fluent-ffmpeg": "^2.1.2", "forever-agent": "^0.6.1", "fork-ts-checker-webpack-plugin": "^9.0.2", + "form-data": "^4.0.0", "formidable": "3.5.1", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", @@ -133,6 +136,7 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "mapbox-gl": "^3.0.1", + "markdown-it": "^14.1.0", "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", @@ -182,6 +186,7 @@ "react-grid-layout": "^1.4.4", "react-icons": "^5.0.1", "react-jsx-parser": "^1.29.0", + "react-latex-next": "^3.0.0", "react-loading": "^2.0.3", "react-map-gl": "^7.1.6", "react-markdown": "^9.0.1", @@ -193,8 +198,10 @@ "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", + "rehype-katex": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^5.0.2", @@ -9347,6 +9354,11 @@ "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/katex": { + "version": "0.16.7", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.7.tgz", + "integrity": "sha512-HMwFiRujE5PjrgwHQ25+bsLJgowjGjm5Z8FVSf0N6PwgJrwxH0QxzHYDcKsTfV3wva0vzrpqMTJS2jXPr5BMEQ==" + }, "node_modules/@types/keygrip": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/keygrip/-/keygrip-1.0.6.tgz", @@ -11251,6 +11263,17 @@ "resolved": "https://registry.npmjs.org/before-after-hook/-/before-after-hook-3.0.2.tgz", "integrity": "sha512-Nik3Sc0ncrMK4UUdXQmAnRtzmNQTAAXmXIopizwZ1W1t8QmfJj+zL4OA2I7XPTPW5z5TDqv4hRo/JzouDJnX3A==" }, + "node_modules/better-react-mathjax": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/better-react-mathjax/-/better-react-mathjax-2.0.3.tgz", + "integrity": "sha512-wfifT8GFOKb1TWm2+E50I6DJpLZ5kLbch283Lu043EJtwSv0XvZDjr4YfR4d2MjAhqP6SH4VjjrKgbX8R00oCQ==", + "dependencies": { + "mathjax-full": "^3.2.2" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, "node_modules/bezier-curve": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bezier-curve/-/bezier-curve-1.0.0.tgz", @@ -15907,6 +15930,33 @@ "resolved": "https://registry.npmjs.org/csv-stringify/-/csv-stringify-6.4.6.tgz", "integrity": "sha512-h2V2XZ3uOTLilF5dPIptgUfN/o2ia/80Ie0Lly18LAnw5s8Eb7kt8rfxSUy24AztJZas9f6DPZpVlzDUtFt/ag==" }, + "node_modules/csvtojson": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz", + "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==", + "dependencies": { + "bluebird": "^3.5.1", + "lodash": "^4.17.3", + "strip-bom": "^2.0.0" + }, + "bin": { + "csvtojson": "bin/csvtojson" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/csvtojson/node_modules/strip-bom": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", + "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", + "dependencies": { + "is-utf8": "^0.2.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/custom-event": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/custom-event/-/custom-event-1.0.1.tgz", @@ -18913,6 +18963,14 @@ "node": ">=8" } }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "engines": { + "node": ">=6" + } + }, "node_modules/espree": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", @@ -20825,6 +20883,52 @@ "node": ">= 0.4" } }, + "node_modules/hast-util-from-dom": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.0.tgz", + "integrity": "sha512-d6235voAp/XR3Hh5uy7aGLbM3S4KamdW0WEgOaU1YoewnuYw4HXb5eRtv9g65m/RFGEfUY1Mw4UqCc5Y8L4Stg==", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^8.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.1.tgz", + "integrity": "sha512-RXQBLMl9kjKVNkJTIO6bZyb2n+cUH8LFaSSzo82jiLT6Tfc+Pt7VQCS+/h3YwG4jaNE2TA2sdJisGWR+aJrp0g==", + "dependencies": { + "@types/hast": "^3.0.0", + "devlop": "^1.1.0", + "hast-util-from-parse5": "^8.0.0", + "parse5": "^7.0.0", + "vfile": "^6.0.0", + "vfile-message": "^4.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-parse5": { "version": "8.0.1", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.1.tgz", @@ -20844,6 +20948,18 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-is-element": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-is-element/-/hast-util-is-element-3.0.0.tgz", + "integrity": "sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/hast-util-parse-selector/-/hast-util-parse-selector-4.0.0.tgz", @@ -20924,6 +21040,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-to-text": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/hast-util-to-text/-/hast-util-to-text-4.0.2.tgz", + "integrity": "sha512-KK6y/BN8lbaq654j7JgBydev7wuNMcID54lkRav1P0CaE1e47P72AWWPiGKXTJU271ooYzcvTAn/Zt0REnvc7A==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/unist": "^3.0.0", + "hast-util-is-element": "^3.0.0", + "unist-util-find-after": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-whitespace": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz", @@ -22592,6 +22723,11 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/is-utf8": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", + "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" + }, "node_modules/is-weakmap": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", @@ -23214,6 +23350,29 @@ "node": ">=12.0.0" } }, + "node_modules/katex": { + "version": "0.16.10", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.10.tgz", + "integrity": "sha512-ZiqaC04tp2O5utMsl2TEZTXxa6WSC4yo0fv5ML++D3QZv/vx2Mct0mTlRx3O+uUkjfuAgOkzsCmq5MiUEsDDdA==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, + "node_modules/katex/node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "engines": { + "node": ">= 12" + } + }, "node_modules/kdbush": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/kdbush/-/kdbush-4.0.2.tgz", @@ -23429,6 +23588,14 @@ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/linkify-it": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-5.0.0.tgz", + "integrity": "sha512-5aHCbzQRADcdP+ATqnDuhhJ/MRIqDkZX5pyjFHRRysS8vZ5AbqGEoFIb6pYHPZ+L/OC2Lc+xT8uHVVR5CAK/wQ==", + "dependencies": { + "uc.micro": "^2.0.0" + } + }, "node_modules/load-bmfont": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/load-bmfont/-/load-bmfont-1.4.1.tgz", @@ -23796,6 +23963,27 @@ "vt-pbf": "^3.1.3" } }, + "node_modules/markdown-it": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz", + "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==", + "dependencies": { + "argparse": "^2.0.1", + "entities": "^4.4.0", + "linkify-it": "^5.0.0", + "mdurl": "^2.0.0", + "punycode.js": "^2.3.1", + "uc.micro": "^2.1.0" + }, + "bin": { + "markdown-it": "bin/markdown-it.mjs" + } + }, + "node_modules/markdown-it/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/markdown-table": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-3.0.3.tgz", @@ -23819,6 +24007,17 @@ "mr-parser": "^0.2.1" } }, + "node_modules/mathjax-full": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/mathjax-full/-/mathjax-full-3.2.2.tgz", + "integrity": "sha512-+LfG9Fik+OuI8SLwsiR02IVdjcnRCy5MufYLi0C3TdMT56L/pjB0alMVGgoWJF8pN9Rc7FESycZB9BMNWIid5w==", + "dependencies": { + "esm": "^3.2.25", + "mhchemparser": "^4.1.0", + "mj-context-menu": "^0.6.1", + "speech-rule-engine": "^4.0.6" + } + }, "node_modules/mathquill": { "version": "0.10.1-a", "resolved": "https://registry.npmjs.org/mathquill/-/mathquill-0.10.1-a.tgz", @@ -23987,6 +24186,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx-expression": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx-expression/-/mdast-util-mdx-expression-2.0.0.tgz", @@ -24109,6 +24326,11 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", + "integrity": "sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==" + }, "node_modules/media-typer": { "version": "0.3.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", @@ -24172,6 +24394,11 @@ "node": ">= 0.6" } }, + "node_modules/mhchemparser": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/mhchemparser/-/mhchemparser-4.2.1.tgz", + "integrity": "sha512-kYmyrCirqJf3zZ9t/0wGgRZ4/ZJw//VwaRVGA75C4nhE60vtnIzhl9J9ndkX/h6hxSN7pjg/cE0VxbnNM+bnDQ==" + }, "node_modules/micromark": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/micromark/-/micromark-4.0.0.tgz", @@ -24353,6 +24580,24 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.0.0.tgz", + "integrity": "sha512-iJ2Q28vBoEovLN5o3GO12CpqorQRYDPT+p4zW50tGwTfJB+iv/VnB6Ini+gqa24K97DwptMBBIvVX6Bjk49oyQ==", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/micromark-factory-destination": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/micromark-factory-destination/-/micromark-factory-destination-2.0.0.tgz", @@ -24840,6 +25085,11 @@ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, + "node_modules/mj-context-menu": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/mj-context-menu/-/mj-context-menu-0.6.1.tgz", + "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" + }, "node_modules/mkdirp": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", @@ -29576,6 +29826,14 @@ "node": ">=6" } }, + "node_modules/punycode.js": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode.js/-/punycode.js-2.3.1.tgz", + "integrity": "sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA==", + "engines": { + "node": ">=6" + } + }, "node_modules/q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", @@ -30099,6 +30357,22 @@ "@types/react": "^17" } }, + "node_modules/react-latex-next": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/react-latex-next/-/react-latex-next-3.0.0.tgz", + "integrity": "sha512-x70f1b1G7TronVigsRgKHKYYVUNfZk/3bciFyYX1lYLQH2y3/TXku3+5Vap8MDbJhtopePSYBsYWS6jhzIdz+g==", + "dependencies": { + "katex": "^0.16.0" + }, + "engines": { + "node": ">=12", + "npm": ">=5" + }, + "peerDependencies": { + "react": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0", + "react-dom": "^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/react-loading": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/react-loading/-/react-loading-2.0.3.tgz", @@ -30622,6 +30896,24 @@ "jsesc": "bin/jsesc" } }, + "node_modules/rehype-katex": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.0.tgz", + "integrity": "sha512-h8FPkGE00r2XKU+/acgqwWUlyzve1IiOKwsEkg4pDL3k48PiE0Pt+/uLtVHDVkN1yA4iurZN6UES8ivHVEQV6Q==", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-raw": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/rehype-raw/-/rehype-raw-7.0.0.tgz", @@ -30661,6 +30953,21 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-parse": { "version": "11.0.0", "resolved": "https://registry.npmjs.org/remark-parse/-/remark-parse-11.0.0.tgz", @@ -32129,6 +32436,27 @@ "node": ">= 6" } }, + "node_modules/speech-rule-engine": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", + "dependencies": { + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" + }, + "bin": { + "sre": "bin/sre" + } + }, + "node_modules/speech-rule-engine/node_modules/commander": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, "node_modules/splaytree": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/splaytree/-/splaytree-3.1.2.tgz", @@ -34036,6 +34364,11 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-2.6.1.tgz", "integrity": "sha512-ofhi8kjIje6npGozTip9Fr8iecmYfEbS06i0JnIg+rh51KakryWF4+jX8lLKZVhy6N+ID45WYSFCxPOdTWCzNg==" }, + "node_modules/uc.micro": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-2.1.0.tgz", + "integrity": "sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A==" + }, "node_modules/uid-safe": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz", @@ -34164,6 +34497,19 @@ "node": ">=0.10.0" } }, + "node_modules/unist-util-find-after": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-find-after/-/unist-util-find-after-5.0.0.tgz", + "integrity": "sha512-amQa0Ep2m6hE2g72AugUItjbuM8X8cGQnFoHk0pGfrFeT9GZhzN5SW8nRsiGKK7Aif4CrACPENkA6P/Lw6fHGQ==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/unist-util-is": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", @@ -35165,6 +35511,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wicked-good-xpath": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", + "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==" + }, "node_modules/wide-align": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", @@ -35481,6 +35832,14 @@ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", "dev": true }, + "node_modules/xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==", + "engines": { + "node": ">=0.1" + } + }, "node_modules/xmlhttprequest-ssl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz", diff --git a/package.json b/package.json index 833bebf44..aa0874714 100644 --- a/package.json +++ b/package.json @@ -145,6 +145,7 @@ "babel": "^6.23.0", "babel-loader": "^9.1.3", "bcrypt-nodejs": "0.0.3", + "better-react-mathjax": "^2.0.3", "bezier-curve": "^1.0.0", "bezier-js": "^6.1.4", "bingmaps-react": "^1.2.10", @@ -171,6 +172,7 @@ "csstype": "^3.1.3", "csv-parser": "^3.0.0", "csv-stringify": "^6.4.4", + "csvtojson": "^2.0.10", "D": "^1.0.0", "d3": "^7.8.5", "depcheck": "^1.4.7", @@ -192,6 +194,7 @@ "fluent-ffmpeg": "^2.1.2", "forever-agent": "^0.6.1", "fork-ts-checker-webpack-plugin": "^9.0.2", + "form-data": "^4.0.0", "formidable": "3.5.1", "function-plot": "^1.23.3", "golden-layout": "^2.6.0", @@ -218,6 +221,7 @@ "jszip": "^3.10.1", "lodash": "^4.17.21", "mapbox-gl": "^3.0.1", + "markdown-it": "^14.1.0", "mathquill": "^0.10.1-a", "md5-file": "^5.0.0", "memorystream": "^0.3.1", @@ -267,6 +271,7 @@ "react-grid-layout": "^1.4.4", "react-icons": "^5.0.1", "react-jsx-parser": "^1.29.0", + "react-latex-next": "^3.0.0", "react-loading": "^2.0.3", "react-map-gl": "^7.1.6", "react-markdown": "^9.0.1", @@ -278,8 +283,10 @@ "react-xarrows": "^2.0.2", "readline": "^1.3.0", "recharts": "^2.10.3", + "rehype-katex": "^7.0.0", "rehype-raw": "^7.0.0", "remark-gfm": "^4.0.0", + "remark-math": "^6.0.0", "request": "^2.88.2", "request-promise": "^4.2.6", "reveal.js": "^5.0.2", diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts index b4ad9c17d..0520dc375 100644 --- a/src/client/documents/DocumentTypes.ts +++ b/src/client/documents/DocumentTypes.ts @@ -20,6 +20,7 @@ export enum DocumentType { WEBCAM = 'webcam', // webcam CONFIG = 'config', // configuration document intended to specify a view layout configuration, but not be directly rendered (e.g., for saving the page# of a PDF, or view transform of a collection) SCRIPTING = 'script', // script editor + CHAT = 'chat', // chat with GPT about files EQUATION = 'equation', // equation editor FUNCPLOT = 'funcplot', // function plotter MAP = 'map', diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 24dd5cf37..ef1c709f0 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -180,6 +180,12 @@ export class DocumentOptions { date_range?: STRt = new StrInfo('date range for calendar', false); + chat?: STRt = new StrInfo('fields related to chatBox', false); + chat_history?: STRt = new StrInfo('chat history for chatbox', false); + chat_thread_id?: STRt = new StrInfo('thread id for chatbox', false); + chat_assistant_id?: STRt = new StrInfo('assistant id for chatbox', false); + chat_vector_store_id?: STRt = new StrInfo('assistant id for chatbox', false); + wikiData?: STRt = new StrInfo('WikiData ID related to map location'); description?: STRt = new StrInfo('description of document'); _timecodeToShow?: NUMt = new NumInfo('media timecode when document should appear (e.g., when an annotation shows up as a video plays)', false); @@ -735,6 +741,9 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.SCRIPTING), script || undefined, { ...options, layout: fieldKey ? `` /* ScriptingBox.LayoutString(fieldKey) */ : undefined }); } + export function ChatDocument(options?: DocumentOptions) { + return InstanceFromProto(Prototypes.get(DocumentType.CHAT), undefined, { ...(options || {}) }); + } // eslint-disable-next-line default-param-last export function VideoDocument(url: string, options: DocumentOptions = {}, overwriteDoc?: Doc) { return InstanceFromProto(Prototypes.get(DocumentType.VID), new VideoField(url), options, undefined, undefined, undefined, overwriteDoc); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ffc1304bf..09250d29d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -368,6 +368,7 @@ pie title Minerals in my tap water {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title_custom: true, waitForDoubleClickToClick: 'never'}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}}, {key: "Script", creator: opts => Docs.Create.ScriptingDocument(null, opts), opts: { _width: 200, _height: 250, }}, {key: "DataViz", creator: opts => Docs.Create.DataVizDocument("/users/rz/Downloads/addresses.csv", opts), opts: { _width: 300, _height: 300 }}, + {key: "Chat", creator: Docs.Create.ChatDocument, opts: { _width: 300, _height: 300, }}, {key: "Header", creator: headerTemplate, opts: { _width: 300, _height: 120, _header_pointerEvents: "all", _header_height: 50, _header_fontSize: 9,_layout_autoHeightMargins: 50, _layout_autoHeight: true, treeView_HideUnrendered: true}}, {key: "ViewSlide", creator: slideView, opts: { _width: 400, _height: 300, _xMargin: 3, _yMargin: 3,}}, {key: "Trail", creator: Docs.Create.PresDocument, opts: { _width: 400, _height: 30, _type_collection: CollectionViewType.Stacking, dropAction: dropActionType.embed, treeView_HideTitle: true, _layout_fitWidth:true, layout_boxShadow: "0 0" }}, @@ -397,6 +398,7 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, + { toolTip: "Tap or drag to create a chat assistant", title: "Assistant Chat", icon: "book",dragFactory: doc.emptyChat as Doc, clickFactory: DocCast(doc.emptyChat)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 8968acbbb..149de59b2 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -28,6 +28,7 @@ import { SchemaRowBox } from './collections/collectionSchema/SchemaRowBox'; import './global/globalScripts'; import { AudioBox } from './nodes/AudioBox'; import { ComparisonBox } from './nodes/ComparisonBox'; +import { ChatBox } from './nodes/ChatBox/ChatBox'; import { DataVizBox } from './nodes/DataVizBox/DataVizBox'; import { DocumentContentsView, HTMLtag } from './nodes/DocumentContentsView'; import { EquationBox } from './nodes/EquationBox'; @@ -136,6 +137,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; MapBox, ScreenshotBox, DataVizBox, + ChatBox, HTMLtag, ComparisonBox, LoadingBox, diff --git a/src/client/views/nodes/ChatBox/ChatBox.scss b/src/client/views/nodes/ChatBox/ChatBox.scss new file mode 100644 index 000000000..f1ad3d074 --- /dev/null +++ b/src/client/views/nodes/ChatBox/ChatBox.scss @@ -0,0 +1,228 @@ +$background-color: #f8f9fa; +$text-color: #333; +$input-background: #fff; +$button-color: #007bff; +$button-hover-color: darken($button-color, 10%); +$shadow-color: rgba(0, 0, 0, 0.075); +$border-radius: 8px; + +.chatBox { + display: flex; + flex-direction: column; + width: 100%; /* Adjust the width as needed, could be in percentage */ + height: 100%; /* Adjust the height as needed, could be in percentage */ + background-color: $background-color; + font-family: 'Helvetica Neue', Arial, sans-serif; + //margin: 20px auto; + //overflow: hidden; + + .scroll-box { + flex-grow: 1; + overflow-y: scroll; + overflow-x: hidden; + height: 100%; + padding: 10px; + display: flex; + flex-direction: column-reverse; + + &::-webkit-scrollbar { + width: 8px; + } + &::-webkit-scrollbar-thumb { + background-color: darken($background-color, 10%); + border-radius: $border-radius; + } + + + .chat-content { + display: flex; + flex-direction: column; + } + + .messages { + display: flex; + flex-direction: column; + .message { + padding: 10px; + margin-bottom: 10px; + border-radius: $border-radius; + background-color: lighten($background-color, 5%); + box-shadow: 0 2px 5px $shadow-color; + //display: flex; + align-items: center; + max-width: 70%; + word-break: break-word; + .message-footer { // Assuming this is the container for the toggle button + //max-width: 70%; + + + .toggle-logs-button { + margin-top: 10px; // Padding on sides to align with the text above + width: 95%; + //display: block; // Ensures the button extends the full width of its container + text-align: center; // Centers the text inside the button + //padding: 8px 0; // Adequate padding for touch targets + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + //transition: background-color 0.3s; + //margin-top: 10px; // Adds space above the button + box-shadow: 0 2px 4px $shadow-color; // Consistent shadow with other elements + &:hover { + background-color: $button-hover-color; + } + } + .tool-logs { + width: 100%; + background-color: $input-background; + color: $text-color; + margin-top: 5px; + //padding: 10px; + //border-radius: $border-radius; + //box-shadow: inset 0 2px 4px $shadow-color; + //transition: opacity 1s ease-in-out; + font-family: monospace; + overflow-x: auto; + max-height: 150px; // Ensuring it does not grow too large + overflow-y: auto; + } + + } + + .custom-link { + color: lightblue; + text-decoration: underline; + cursor: pointer; + } + &.user { + align-self: flex-end; + background-color: $button-color; + color: #fff; + } + + &.chatbot { + align-self: flex-start; + background-color: $input-background; + color: $text-color; + } + + span { + flex-grow: 1; + padding-right: 10px; + } + + img { + max-width: 50px; + max-height: 50px; + border-radius: 50%; + } + } + } + padding-bottom: 0; + } + + .chat-form { + display: flex; + flex-grow: 1; + //height: 50px; + bottom: 0; + width: 100%; + padding: 10px; + background-color: $input-background; + box-shadow: inset 0 -1px 2px $shadow-color; + + input[type="text"] { + flex-grow: 1; + border: 1px solid darken($input-background, 10%); + border-radius: $border-radius; + padding: 8px 12px; + margin-right: 10px; + } + + button { + padding: 8px 16px; + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + transition: background-color 0.3s; + + &:hover { + background-color: $button-hover-color; + } + } + margin-bottom: 0; + } +} + +.initializing-overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba($background-color, 0.95); + display: flex; + justify-content: center; + align-items: center; + font-size: 1.5em; + color: $text-color; + z-index: 10; // Ensure it's above all other content (may be better solution) + + &::before { + content: 'Initializing...'; + font-weight: bold; + } +} + + +.modal { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + display: flex; + justify-content: center; + align-items: center; + background-color: rgba(0, 0, 0, 0.4); + + .modal-content { + background-color: $input-background; + color: $text-color; + padding: 20px; + border-radius: $border-radius; + box-shadow: 0 2px 10px $shadow-color; + display: flex; + flex-direction: column; + align-items: center; + width: auto; + min-width: 300px; + + h4 { + margin-bottom: 15px; + } + + p { + margin-bottom: 20px; + } + + button { + padding: 10px 20px; + background-color: $button-color; + color: #fff; + border: none; + border-radius: $border-radius; + cursor: pointer; + margin: 5px; + transition: background-color 0.3s; + + &:hover { + background-color: $button-hover-color; + } + } + } +} diff --git a/src/client/views/nodes/ChatBox/ChatBox.tsx b/src/client/views/nodes/ChatBox/ChatBox.tsx new file mode 100644 index 000000000..880c332ac --- /dev/null +++ b/src/client/views/nodes/ChatBox/ChatBox.tsx @@ -0,0 +1,609 @@ +import { MathJaxContext } from 'better-react-mathjax'; +import { action, makeObservable, observable, observe, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import OpenAI, { ClientOptions } from 'openai'; +import { ImageFile, Message } from 'openai/resources/beta/threads/messages'; +import { RunStep } from 'openai/resources/beta/threads/runs/steps'; +import * as React from 'react'; +import { Doc } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { CsvCast, DocCast, PDFCast, StrCast } from '../../../../fields/Types'; +import { CsvField } from '../../../../fields/URLField'; +import { Networking } from '../../../Network'; +import { DocUtils } from '../../../documents/DocUtils'; +import { DocumentType } from '../../../documents/DocumentTypes'; +import { Docs } from '../../../documents/Documents'; +import { DocumentManager } from '../../../util/DocumentManager'; +import { LinkManager } from '../../../util/LinkManager'; +import { ViewBoxAnnotatableComponent } from '../../DocComponent'; +import { FieldView, FieldViewProps } from '../FieldView'; +import './ChatBox.scss'; +import MessageComponent from './MessageComponent'; +import { ANNOTATION_LINK_TYPE, ASSISTANT_ROLE, AssistantMessage, DOWNLOAD_TYPE } from './types'; + +@observer +export class ChatBox extends ViewBoxAnnotatableComponent() { + @observable modalStatus = false; + @observable currentFile = { url: '' }; + @observable history: AssistantMessage[] = []; + @observable.deep current_message: AssistantMessage | undefined = undefined; + + @observable isLoading: boolean = false; + @observable isInitializing: boolean = true; + @observable expandedLogIndex: number | null = null; + @observable linked_docs_to_add: Doc[] = []; + + private openai: OpenAI; + private interim_history: string = ''; + private assistantID: string = ''; + private threadID: string = ''; + private _oldWheel: any; + private vectorStoreID: string = ''; + private mathJaxConfig: any; + private linkedCsvIDs: string[] = []; + + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(ChatBox, fieldKey); + } + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + this.openai = this.initializeOpenAI(); + this.history = []; + this.threadID = StrCast(this.dataDoc.thread_id); + this.assistantID = StrCast(this.dataDoc.assistant_id); + this.vectorStoreID = StrCast(this.dataDoc.vector_store_id); + this.openai = this.initializeOpenAI(); + if (this.assistantID === '' || this.threadID === '' || this.vectorStoreID === '') { + this.createAssistant(); + } else { + this.retrieveCsvUrls(); + this.isInitializing = false; + } + this.mathJaxConfig = { + loader: { load: ['input/asciimath'] }, + tex: { + inlineMath: [ + ['$', '$'], + ['\\(', '\\)'], + ], + displayMath: [ + ['$$', '$$'], + ['[', ']'], + ], + }, + }; + reaction( + () => this.history.map((msg: AssistantMessage) => ({ role: msg.role, text: msg.text, image: msg.image, tool_logs: msg.tool_logs, links: msg.links })), + serializableHistory => { + this.dataDoc.data = JSON.stringify(serializableHistory); + } + ); + } + + toggleToolLogs = (index: number) => { + this.expandedLogIndex = this.expandedLogIndex === index ? null : index; + }; + + retrieveCsvUrls() { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + + linkedDocs.forEach(doc => { + const aiFieldId = StrCast(doc[this.Document[Id] + '_ai_field_id']); + if (CsvCast(doc.data)) { + this.linkedCsvIDs.push(StrCast(aiFieldId)); + console.log(this.linkedCsvIDs); + } + }); + } + + initializeOpenAI() { + const configuration: ClientOptions = { + apiKey: process.env.OPENAI_KEY, + dangerouslyAllowBrowser: true, + }; + return new OpenAI(configuration); + } + + onPassiveWheel = (e: WheelEvent) => { + if (this._props.isContentActive()) { + e.stopPropagation(); + } + }; + + createLink = (linkInfo: string, startIndex: number, endIndex: number, linkType: ANNOTATION_LINK_TYPE, annotationIndex: number = 0) => { + const text = this.interim_history; + const subString = this.current_message?.text.substring(startIndex, endIndex) ?? ''; + if (!text) return; + const textToDisplay = `${annotationIndex}`; + let fileInfo = linkInfo; + const fileName = subString.split('/')[subString.split('/').length - 1]; + if (linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE) { + fileInfo = linkInfo + '!!!' + fileName; + } + + const formattedLink = `[${textToDisplay}](${fileInfo}~~~${linkType})`; + console.log(formattedLink); + const newText = text.replace(subString, formattedLink); + runInAction(() => { + this.interim_history = newText; + console.log(newText); + this.current_message?.links?.push({ + start: startIndex, + end: endIndex, + url: linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE ? fileName : linkInfo, + id: linkType === ANNOTATION_LINK_TYPE.DOWNLOAD_FILE ? linkInfo : undefined, + link_type: linkType, + }); + }); + }; + + @action + createAssistant = async () => { + this.isInitializing = true; + try { + const vectorStore = await this.openai.beta.vectorStores.create({ + name: 'Vector Store for Assistant', + }); + const assistant = await this.openai.beta.assistants.create({ + name: 'Document Analyser Assistant', + instructions: ` + You will analyse documents with which you are provided. You will answer questions and provide insights based on the information in the documents. + For writing math formulas: + You have a MathJax render environment. + - Write all in-line equations within a single dollar sign, $, to render them as TeX (this means any time you want to use a dollar sign to represent a dollar sign itself, you must escape it with a backslash: "$"); + - Use a double dollar sign, $$, to render equations on a new line; + Example: $$x^2 + 3x$$ is output for "x² + 3x" to appear as TeX.`, + model: 'gpt-4-turbo', + tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], + tool_resources: { + file_search: { + vector_store_ids: [vectorStore.id], + }, + code_interpreter: { + file_ids: this.linkedCsvIDs, + }, + }, + }); + const thread = await this.openai.beta.threads.create(); + + runInAction(() => { + this.dataDoc.assistant_id = assistant.id; + this.dataDoc.thread_id = thread.id; + this.dataDoc.vector_store_id = vectorStore.id; + this.assistantID = assistant.id; + this.threadID = thread.id; + this.vectorStoreID = vectorStore.id; + this.isInitializing = false; + }); + } catch (error) { + console.error('Initialization failed:', error); + this.isInitializing = false; + } + }; + + @action + runAssistant = async (inputText: string) => { + // Ensure an assistant and thread are created + if (!this.assistantID || !this.threadID || !this.vectorStoreID) { + await this.createAssistant(); + console.log('Assistant and thread created:', this.assistantID, this.threadID); + } + let currentText: string = ''; + let currentToolCallMessage: string = ''; + + // Send the user's input to the assistant + await this.openai.beta.threads.messages.create(this.threadID, { + role: 'user', + content: inputText, + }); + + // Listen to the streaming responses + const stream = this.openai.beta.threads.runs + .stream(this.threadID, { + assistant_id: this.assistantID, + }) + .on('runStepCreated', (runStep: RunStep) => { + currentText = ''; + runInAction(() => { + this.current_message = { role: ASSISTANT_ROLE.ASSISTANT, text: currentText, tool_logs: '', links: [] }; + }); + this.isLoading = true; + }) + .on('toolCallDelta', (toolCallDelta, snapshot) => { + this.isLoading = false; + if (toolCallDelta.type === 'code_interpreter') { + if (toolCallDelta.code_interpreter?.input) { + currentToolCallMessage += toolCallDelta.code_interpreter.input; + runInAction(() => { + if (this.current_message) { + this.current_message.tool_logs = currentToolCallMessage; + } + }); + } + if (toolCallDelta.code_interpreter?.outputs) { + currentToolCallMessage += '\n Code interpreter output:'; + toolCallDelta.code_interpreter.outputs.forEach(output => { + if (output.type === 'logs') { + runInAction(() => { + if (this.current_message) { + this.current_message.tool_logs += '\n|' + output.logs; + } + }); + } + }); + } + } + }) + .on('textDelta', (textDelta, snapshot) => { + this.isLoading = false; + currentText += textDelta.value; + runInAction(() => { + if (this.current_message) { + // this.current_message = {...this.current_message, text: current_text}; + this.current_message.text = currentText; + } + }); + }) + .on('messageDone', async event => { + console.log(event); + const textItem = event.content.find(item => item.type === 'text'); + if (textItem && textItem.type === 'text') { + const { text } = textItem; + console.log(text.value); + try { + runInAction(() => { + this.interim_history = text.value; + }); + } catch (e) { + console.error('Error parsing JSON response:', e); + } + + const { annotations } = text; + console.log('Annotations: ' + annotations); + let index = 0; + annotations.forEach(async annotation => { + console.log(' ' + annotation); + console.log(' ' + annotation.text); + if (annotation.type === 'file_path') { + const { file_path: filePath } = annotation; + const fileToDownload = filePath.file_id; + console.log(fileToDownload); + if (filePath) { + console.log(filePath); + console.log(fileToDownload); + this.createLink(fileToDownload, annotation.start_index, annotation.end_index, ANNOTATION_LINK_TYPE.DOWNLOAD_FILE); + } + } else { + const { file_citation: fileCitation } = annotation; + if (fileCitation) { + const citedFile = await this.openai.files.retrieve(fileCitation.file_id); + const citationUrl = citedFile.filename; + this.createLink(citationUrl, annotation.start_index, annotation.end_index, ANNOTATION_LINK_TYPE.DASH_DOC, index); + index++; + } + } + }); + runInAction(() => { + if (this.current_message) { + console.log('current message: ' + this.current_message.text); + this.current_message.text = this.interim_history; + this.history.push({ ...this.current_message }); + this.current_message = undefined; + } + }); + } + }) + .on('toolCallDone', toolCall => { + runInAction(() => { + if (this.current_message && currentToolCallMessage) { + this.current_message.tool_logs = currentToolCallMessage; + } + }); + }) + .on('imageFileDone', (content: ImageFile, snapshot: Message) => { + console.log('Image file done:', content); + }) + .on('end', () => { + console.log('Streaming done'); + }); + }; + + @action + goToLinkedDoc = async (link: string) => { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + + const linkedDoc = linkedDocs.find(doc => { + const docUrl = CsvCast(doc.data, PDFCast(doc.data)).url.pathname.replace('/files/pdfs/', '').replace('/files/csvs/', ''); + console.log('URL: ' + docUrl + ' Citation URL: ' + link); + return link === docUrl; + }); + + if (linkedDoc) { + await DocumentManager.Instance.showDocument(DocCast(linkedDoc), { willZoomCentered: true }, () => {}); + } + }; + + @action + askGPT = async (event: React.FormEvent): Promise => { + event.preventDefault(); + + const textInput = event.currentTarget.elements.namedItem('messageInput') as HTMLInputElement; + const trimmedText = textInput.value.trim(); + + if (!this.assistantID || !this.threadID) { + try { + await this.createAssistant(); + } catch (err) { + console.error('Error:', err); + } + } + + if (trimmedText) { + try { + textInput.value = ''; + runInAction(() => { + this.history.push({ role: ASSISTANT_ROLE.USER, text: trimmedText }); + }); + await this.runAssistant(trimmedText); + this.dataDoc.data = this.history.toString(); + } catch (err) { + console.error('Error:', err); + } + } + }; + + @action + uploadLinks = async (linkedDocs: Doc[]) => { + if (this.isInitializing) { + console.log('Initialization in progress, upload aborted.'); + return; + } + const urls = linkedDocs.map(doc => CsvCast(doc.data, PDFCast(doc.data)).url.pathname); + const csvUrls = urls.filter(url => url.endsWith('.csv')); + console.log(this.assistantID, this.threadID, urls); + + const { openai_file_ids: openaiFileIds } = await Networking.PostToServer('/uploadPDFToVectorStore', { urls, threadID: this.threadID, assistantID: this.assistantID, vector_store_id: this.vectorStoreID }); + + linkedDocs.forEach((doc, i) => { + doc[this.Document[Id] + '_ai_field_id'] = openaiFileIds[i]; + console.log('AI Field ID: ' + openaiFileIds[i]); + }); + + if (csvUrls.length > 0) { + for (let i = 0; i < csvUrls.length; i++) { + this.linkedCsvIDs.push(openaiFileIds[urls.indexOf(csvUrls[i])]); + } + console.log('linked csvs:' + this.linkedCsvIDs); + await this.openai.beta.assistants.update(this.assistantID, { + tools: [{ type: 'file_search' }, { type: 'code_interpreter' }], + tool_resources: { + file_search: { + vector_store_ids: [this.vectorStoreID], + }, + code_interpreter: { + file_ids: this.linkedCsvIDs, + }, + }, + }); + } + }; + + downloadToComputer = (url: string, fileName: string) => { + fetch(url, { method: 'get', mode: 'no-cors', referrerPolicy: 'no-referrer' }) + .then(res => res.blob()) + .then(res => { + const aElement = document.createElement('a'); + aElement.setAttribute('download', fileName); + const href = URL.createObjectURL(res); + aElement.href = href; + aElement.setAttribute('target', '_blank'); + aElement.click(); + URL.revokeObjectURL(href); + }); + }; + + createDocumentInDash = async (url: string) => { + const fileSuffix = url.substring(url.lastIndexOf('.') + 1); + console.log(fileSuffix); + let doc: Doc | null = null; + switch (fileSuffix) { + case 'pdf': + doc = DocCast(await DocUtils.DocumentFromType('pdf', url, {})); + break; + case 'csv': + doc = DocCast(await DocUtils.DocumentFromType('csv', url, {})); + break; + case 'png': + case 'jpg': + case 'jpeg': + doc = DocCast(await DocUtils.DocumentFromType('image', url, {})); + break; + default: + console.error('Unsupported file type:', fileSuffix); + break; + } + if (doc) { + doc && this._props.addDocument?.(doc); + await DocumentManager.Instance.showDocument(doc, { willZoomCentered: true }, () => {}); + } + }; + + downloadFile = async (fileInfo: string, downloadType: DOWNLOAD_TYPE) => { + try { + console.log(fileInfo); + const [fileId, fileName] = fileInfo.split(/!!!/); + const { file_path: filePath } = await Networking.PostToServer('/downloadFileFromOpenAI', { file_id: fileId, file_name: fileName }); + const fileLink = CsvCast(new CsvField(filePath)).url.href; + if (downloadType === DOWNLOAD_TYPE.DASH) { + this.createDocumentInDash(fileLink); + } else { + this.downloadToComputer(fileLink, fileName); + } + } catch (error) { + console.error('Error downloading file:', error); + } + }; + + handleDownloadToDevice = () => { + this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DEVICE); + this.modalStatus = false; // Close the modal after the action + this.currentFile = { url: '' }; // Reset the current file + }; + + handleAddToDash = () => { + // Assuming `downloadFile` is a method that handles adding to Dash + this.downloadFile(this.currentFile.url, DOWNLOAD_TYPE.DASH); + this.modalStatus = false; // Close the modal after the action + this.currentFile = { url: '' }; // Reset the current file + }; + + renderModal = () => { + if (!this.modalStatus) return null; + + return ( +
    +
    +

    File Actions

    +

    Choose an action for the file:

    + + + +
    +
    + ); + }; + @action + showModal = () => { + this.modalStatus = true; + }; + + @action + setCurrentFile = (file: { url: string }) => { + this.currentFile = file; + }; + + componentDidMount() { + this._props.setContentViewBox?.(this); + if (this.dataDoc.data) { + try { + const storedHistory = JSON.parse(StrCast(this.dataDoc.data)); + runInAction(() => { + this.history = storedHistory.map((msg: AssistantMessage) => ({ + role: msg.role, + text: msg.text, + quote: msg.quote, + tool_logs: msg.tool_logs, + image: msg.image, + })); + }); + } catch (e) { + console.error('Failed to parse history from dataDoc:', e); + } + } + reaction( + () => { + const linkedDocs = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => DocCast(LinkManager.getOppositeAnchor(d, this.Document))) + .map(d => DocCast(d?.annotationOn, d)) + .filter(d => d); + return linkedDocs; + }, + + linked => this.linked_docs_to_add.push(...linked.filter(linkedDoc => !this.linked_docs_to_add.includes(linkedDoc))) + ); + + observe( + // right now this skips during initialization which is necessary because it would be blank + // However, it will upload the same link twice when it is + this.linked_docs_to_add, + change => { + // observe pushes/splices on a user link DB 'data' field (should only happen for local changes) + switch (change.type as any) { + case 'splice': + if ((change as any).addedCount > 0) { + // maybe check here if its already in the urls datadoc array so doesn't add twice + console.log((change as any).added as Doc[]); + this.uploadLinks((change as any).added as Doc[]); + } + // (change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link))); + break; + case 'update': // let oldValue = change.oldValue; + default: + } + }, + true + ); + } + + render() { + return ( + +
    + {this.isInitializing &&
    Initializing...
    } + {this.renderModal()} +
    { + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = r; + r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }}> +
    + {this.history.map((message, index) => ( + + ))} + {!this.current_message ? null : ( + + )} +
    +
    +
    + + +
    +
    +
    + ); + } +} + +Docs.Prototypes.TemplateMap.set(DocumentType.CHAT, { + layout: { view: ChatBox, dataField: 'data' }, + options: { acl: '', chat: '', chat_history: '', chat_thread_id: '', chat_assistant_id: '', chat_vector_store_id: '' }, +}); diff --git a/src/client/views/nodes/ChatBox/MessageComponent.tsx b/src/client/views/nodes/ChatBox/MessageComponent.tsx new file mode 100644 index 000000000..fced0b4d5 --- /dev/null +++ b/src/client/views/nodes/ChatBox/MessageComponent.tsx @@ -0,0 +1,116 @@ +/* eslint-disable react/require-default-props */ +import React from 'react'; +import { observer } from 'mobx-react'; +import { MathJax, MathJaxContext } from 'better-react-mathjax'; +import ReactMarkdown from 'react-markdown'; +import { TbCircle0Filled, TbCircle1Filled, TbCircle2Filled, TbCircle3Filled, TbCircle4Filled, TbCircle5Filled, TbCircle6Filled, TbCircle7Filled, TbCircle8Filled, TbCircle9Filled } from 'react-icons/tb'; +import { AssistantMessage } from './types'; + +interface MessageComponentProps { + message: AssistantMessage; + toggleToolLogs: (index: number) => void; + expandedLogIndex: number | null; + index: number; + showModal: () => void; + goToLinkedDoc: (url: string) => void; + setCurrentFile: (file: { url: string }) => void; + isCurrent?: boolean; +} + +const MessageComponent: React.FC = function ({ message, toggleToolLogs, expandedLogIndex, goToLinkedDoc, index, showModal, setCurrentFile, isCurrent = false }) { + // const messageClass = `${message.role} ${isCurrent ? 'current-message' : ''}`; + + const LinkRenderer = ({ href, children }: { href: string; children: React.ReactNode }) => { + // console.log(href + " " + children) + const regex = /([a-zA-Z0-9_.!-]+)~~~(citation|file_path)/; + const matches = href.match(regex); + // console.log(href) + // console.log(matches) + const url = matches ? matches[1] : href; + const linkType = matches ? matches[2] : null; + if (linkType === 'citation') { + switch (children) { + case '0': + children = ; + break; + case '1': + children = ; + break; + case '2': + children = ; + break; + case '3': + children = ; + break; + case '4': + children = ; + break; + case '5': + children = ; + break; + case '6': + children = ; + break; + case '7': + children = ; + break; + case '8': + children = ; + break; + case '9': + children = ; + break; + default: + break; + } + } + // console.log(linkType) + const style = { + color: 'lightblue', + verticalAlign: linkType === 'citation' ? 'super' : 'baseline', + fontSize: linkType === 'citation' ? 'smaller' : 'inherit', + }; + + return ( + { + e.preventDefault(); + if (linkType === 'citation') { + goToLinkedDoc(url); + } else if (linkType === 'file_path') { + showModal(); + setCurrentFile({ url }); + } + }} + style={style}> + {children} + + ); + }; + + return ( +
    + + + {message.text ? message.text : ''} + + + {message.image && } +
    + {message.tool_logs && ( + + )} + {expandedLogIndex === index && ( +
    +
    {message.tool_logs}
    +
    + )} +
    +
    + ); +}; + +export default observer(MessageComponent); diff --git a/src/client/views/nodes/ChatBox/types.ts b/src/client/views/nodes/ChatBox/types.ts new file mode 100644 index 000000000..8212a7050 --- /dev/null +++ b/src/client/views/nodes/ChatBox/types.ts @@ -0,0 +1,23 @@ +export enum ASSISTANT_ROLE { + USER = 'User', + ASSISTANT = 'Assistant', +} + +export enum ANNOTATION_LINK_TYPE { + DASH_DOC = 'citation', + DOWNLOAD_FILE = 'file_path', +} + +export enum DOWNLOAD_TYPE { + DASH = 'dash', + DEVICE = 'device', +} + +export interface AssistantMessage { + role: ASSISTANT_ROLE; + text: string; + quote?: string; + image?: string; + tool_logs?: string; + links?: { start: number; end: number; url: string; id?: string; link_type: ANNOTATION_LINK_TYPE }[]; +} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 18529a429..192c7875e 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -79,7 +79,7 @@ export class DocumentContentsView extends ObservableReactComponent = T extends string ? 'string' : T extends number ? 'number' : T extends boolean ? 'boolean' : T extends List ? ListSpec : new (...args: any[]) => T; @@ -122,6 +122,9 @@ export function CsvCast(field: FieldResult, defaultVal: CsvField | null = null) export function WebCast(field: FieldResult, defaultVal: WebField | null = null) { return Cast(field, WebField, defaultVal); } +export function PDFCast(field: FieldResult, defaultVal: PdfField | null = null) { + return Cast(field, PdfField, defaultVal); +} export function ImageCast(field: FieldResult, defaultVal: ImageField | null = null) { return Cast(field, ImageField, defaultVal); } diff --git a/src/server/ApiManagers/AssistantManager.ts b/src/server/ApiManagers/AssistantManager.ts new file mode 100644 index 000000000..82e48167a --- /dev/null +++ b/src/server/ApiManagers/AssistantManager.ts @@ -0,0 +1,131 @@ +import * as fs from 'fs'; +import { createReadStream, writeFile } from 'fs'; +import OpenAI from 'openai'; +import * as path from 'path'; +import { promisify } from 'util'; +import * as uuid from 'uuid'; +import { filesDirectory, publicDirectory } from '..'; +import { Method } from '../RouteManager'; +import ApiManager, { Registration } from './ApiManager'; + +export enum Directory { + parsed_files = 'parsed_files', + images = 'images', + videos = 'videos', + pdfs = 'pdfs', + text = 'text', + pdf_thumbnails = 'pdf_thumbnails', + audio = 'audio', + csv = 'csv', +} + +export function serverPathToFile(directory: Directory, filename: string) { + return path.normalize(`${filesDirectory}/${directory}/${filename}`); +} + +export function pathToDirectory(directory: Directory) { + return path.normalize(`${filesDirectory}/${directory}`); +} + +export function clientPathToFile(directory: Directory, filename: string) { + return `/files/${directory}/${filename}`; +} + +const writeFileAsync = promisify(writeFile); +const readFileAsync = promisify(fs.readFile); + +export default class AssistantManager extends ApiManager { + protected initialize(register: Registration): void { + const openai = new OpenAI({ apiKey: process.env.OPENAI_KEY, dangerouslyAllowBrowser: true }); + + register({ + method: Method.POST, + subscription: '/uploadPDFToVectorStore', + secureHandler: async ({ req, res }) => { + const { urls, threadID, assistantID, vector_store_id } = req.body; + + const csvFilesIds: string[] = []; + const otherFileIds: string[] = []; + const allFileIds: string[] = []; + + const fileProcesses = urls.map(async (source: string) => { + const fullPath = path.join(publicDirectory, source); + const fileData = await openai.files.create({ file: createReadStream(fullPath), purpose: 'assistants' }); + allFileIds.push(fileData.id); + if (source.endsWith('.csv')) { + console.log(source); + csvFilesIds.push(fileData.id); + } else { + openai.beta.vectorStores.files.create(vector_store_id, { file_id: fileData.id }); + otherFileIds.push(fileData.id); + } + }); + try { + await Promise.all(fileProcesses).then(() => { + res.send({ vector_store_id: vector_store_id, openai_file_ids: allFileIds }); + }); + } catch (error) { + res.status(500).send({ error: 'Failed to process files' + error }); + } + }, + }); + + register({ + method: Method.POST, + subscription: '/downloadFileFromOpenAI', + secureHandler: async ({ req, res }) => { + const { file_id, file_name } = req.body; + //let files_directory: string; + let files_directory = '/files/openAIFiles/'; + switch (file_name.split('.').pop()) { + case 'pdf': + files_directory = '/files/pdfs/'; + break; + case 'csv': + files_directory = '/files/csv/'; + break; + case 'png': + case 'jpg': + case 'jpeg': + files_directory = '/files/images/'; + break; + default: + break; + } + + const directory = path.join(publicDirectory, files_directory); + + if (!fs.existsSync(directory)) { + fs.mkdirSync(directory); + } + const file = await openai.files.content(file_id); + const new_file_name = `${uuid.v4()}-${file_name}`; + const file_path = path.join(directory, new_file_name); + const file_array_buffer = await file.arrayBuffer(); + const bufferView = new Uint8Array(file_array_buffer); + try { + const written_file = await writeFileAsync(file_path, bufferView); + console.log(written_file); + console.log(file_path); + console.log(file_array_buffer); + console.log(bufferView); + const file_object = new File([bufferView], file_name); + //DashUploadUtils.upload(file_object, 'openAIFiles'); + res.send({ file_path: path.join(files_directory, new_file_name) }); + /* res.send( { + source: "file", + result: { + accessPaths: { + agnostic: {client: path.join('/files/openAIFiles/', `${uuid.v4()}-${file_name}`)} + }, + rawText: "", + duration: 0, + }, + } ); */ + } catch (error) { + res.status(500).send({ error: 'Failed to write file' + error }); + } + }, + }); + } +} diff --git a/src/server/index.ts b/src/server/index.ts index 1bbf8a105..3151c2975 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -3,7 +3,7 @@ import * as dotenv from 'dotenv'; import * as mobileDetect from 'mobile-detect'; import * as path from 'path'; import { logExecution } from './ActionUtilities'; -import { AdminPrivileges, resolvedPorts } from './SocketData'; +import AssistantManager from './ApiManagers/AssistantManager'; import DataVizManager from './ApiManagers/DataVizManager'; import DeleteManager from './ApiManagers/DeleteManager'; import DownloadManager from './ApiManagers/DownloadManager'; @@ -13,16 +13,17 @@ import SessionManager from './ApiManagers/SessionManager'; import UploadManager from './ApiManagers/UploadManager'; import UserManager from './ApiManagers/UserManager'; import UtilManager from './ApiManagers/UtilManager'; -import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; -import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; import { DashSessionAgent } from './DashSession/DashSessionAgent'; import { AppliedSessionAgent } from './DashSession/Session/agents/applied_session_agent'; import { DashStats } from './DashStats'; import { DashUploadUtils } from './DashUploadUtils'; -import { Database } from './database'; import { Logger } from './ProcessFactory'; import RouteManager, { Method, PublicHandler } from './RouteManager'; import RouteSubscriber from './RouteSubscriber'; +import { AdminPrivileges, resolvedPorts } from './SocketData'; +import { GoogleCredentialsLoader, SSL } from './apis/google/CredentialsLoader'; +import { GoogleApiServerUtils } from './apis/google/GoogleApiServerUtils'; +import { Database } from './database'; import initializeServer from './server_Initialization'; // import GooglePhotosManager from './ApiManagers/GooglePhotosManager'; @@ -72,6 +73,7 @@ function routeSetter({ addSupervisedRoute, logRegistrationOutcome }: RouteManage new UtilManager(), new GeneralGoogleManager(), /* new GooglePhotosManager(), */ new DataVizManager(), + new AssistantManager(), ]; // initialize API Managers -- cgit v1.2.3-70-g09d2