diff options
Diffstat (limited to 'src')
61 files changed, 664 insertions, 720 deletions
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<{}> { <div className="save" onClick={() => { - 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<DocumentView>(); @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<Doc>, 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<any> | 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<any>) => { 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,67 +2,17 @@ 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<P> extends ObservableReactComponent<React.PropsWithChildren<P>> { - 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<number>; // 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<Opt<DocumentView>>; // 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<number>; - viewTransition?: () => Opt<string>; // 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<RefField | undefined>) => boolean; - componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; - dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => 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) * Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal) @@ -238,11 +188,7 @@ export function ViewBoxAnnotatableComponent<P extends FieldViewProps>() { 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<DocumentDecora @computed get ClippedBounds() { const bounds = { ...this.Bounds }; const leftBounds = this._props.boundsLeft; - const topBounds = LightboxView.LightboxDoc ? 0 : this._props.boundsTop; + const topBounds = DocumentView.LightboxDoc() ? 0 : this._props.boundsTop; bounds.x = Math.max(leftBounds, bounds.x - this._resizeBorderWidth / 2) + this._resizeBorderWidth / 2; bounds.y = Math.max(topBounds, bounds.y - this._resizeBorderWidth / 2 - this._titleHeight) + this._resizeBorderWidth / 2 + this._titleHeight; const borderRadiusDraggerWidth = 15; @@ -277,7 +276,7 @@ export class DocumentDecorations extends ObservableReactComponent<DocumentDecora openDoc = Doc.GetEmbeddings(openDoc).find(embedding => !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<React.PropsWithChil detail: { points, gesture, - bounds: GestureOverlay.getBounds(points), + bounds: InkField.getBounds(points), text, }, }) @@ -370,19 +370,8 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil ); }; - 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 }; - }; - @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<LightboxViewProps> { * @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<LightboxViewProps> { 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<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) { + public SetLightboxDoc = (doc: Opt<Doc>, target?: Doc, future?: Doc[], layoutTemplate?: Doc | string) => { const lightDoc = this._doc; lightDoc && lightDoc !== doc && @@ -110,7 +122,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { 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<LightboxViewProps> { 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<LightboxViewProps> { /> </div> ); - return !this._doc ? null : ( + return !this._doc ? ( + <OverlayView /> + ) : ( <div className="lightboxView-frame" style={{ background: SnappingManager.userBackgroundColor }} @@ -324,3 +338,8 @@ export class LightboxTourBtn extends React.Component<LightboxTourBtnProps> { 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(<FieldLoader />); 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(<MainView />); }, 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 ( - <GestureOverlay isActive={!LightboxView.LightboxDoc}> + <GestureOverlay isActive={!DocumentView.LightboxDoc()}> <div key="docking" className={`mainView-dockingContent${this._leftMenuFlyoutWidth ? '-flyout' : ''}`} @@ -697,7 +699,7 @@ export class MainView extends ObservableReactComponent<{}> { 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} </div> @@ -791,7 +793,7 @@ export class MainView extends ObservableReactComponent<{}> { @computed get leftMenuPanel() { return ( - <div key="menu" className="mainView-leftMenuPanel" style={{ background: SnappingManager.userBackgroundColor, display: LightboxView.LightboxDoc ? 'none' : undefined }}> + <div key="menu" className="mainView-leftMenuPanel" style={{ background: SnappingManager.userBackgroundColor, display: DocumentView.LightboxDoc() ? 'none' : undefined }}> <DocumentView Document={Doc.MyLeftSidebarMenu} addDocument={undefined} @@ -957,7 +959,7 @@ export class MainView extends ObservableReactComponent<{}> { } @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 : ( <div className="mainView-snapLines"> <svg style={{ width: '100%', height: '100%' }}> @@ -1066,7 +1068,7 @@ export class MainView extends ObservableReactComponent<{}> { case 'home': return <DashboardView />; case 'dashboard': default: return (<> - <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || LightboxView.LightboxDoc ? 'none' : undefined, zIndex: 2001 }}> + <div key="dashdiv" style={{ position: 'relative', display: this._hideUI || DocumentView.LightboxDoc() ? 'none' : undefined, zIndex: 2001 }}> <CollectionMenu panelWidth={this.topMenuWidth} panelHeight={this.topMenuHeight} togglePropertiesFlyout={this.togglePropertiesFlyout} toggleTopBar={this.toggleTopBar} topBarHeight={this.headerBarHeightFunc}/> </div> {this.mainDashboardArea} @@ -1083,14 +1085,11 @@ export class MainView extends ObservableReactComponent<{}> { <MarqueeOptionsMenu /> <TimelineMenu /> <RichTextMenu /> - {/* <InkTranscription /> */} {this.snapLines} - <LightboxView key="lightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} /> - {LightboxView.LightboxDoc ? null : <OverlayView />} + <LightboxView key="lightbox" PanelWidth={this._windowWidth} addSplit={CollectionDockingView.AddSplit} PanelHeight={this._windowHeight} maxBorder={this.lightboxMaxBorder} /> <GPTPopup key="gptpopup" /> <SchemaCSVPopUp key="schemacsvpopup" /> <GenerativeFill imageEditorOpen={ImageEditor.Open} imageEditorSource={ImageEditor.Source} imageRootDoc={ImageEditor.RootDoc} addDoc={ImageEditor.AddDoc} /> - {/* <NewLightboxView key="newLightbox" PanelWidth={this._windowWidth} PanelHeight={this._windowHeight} maxBorder={[200, 50]} /> */} </div> ); } 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<T> extends React.Component<T, {}> })); // 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<Prope getOnClick = (clickCol: Doc) => { 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<Doc>, props: Opt<FieldViewProps & case StyleProp.Highlighting: if (doc && (Doc.IsSystem(doc) || doc.type === DocumentType.FONTICON)) return undefined; if (doc && !doc.layout_disableBrushing && !disableBrushing) { - const selected = Array.from(doc?.[DocViews]??[]).filter(dv => 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<Doc>, props: Opt<FieldViewProps> 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<P> extends ObservableReactComponent<React.PropsWithChildren<P>> { + 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<number>; // 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<Opt<DocumentView>>; // 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<number>; + viewTransition?: () => Opt<string>; // 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<RefField | undefined>) => boolean; + componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; + dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => 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<CSV menuCallback = (x: number, y: number) => { 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<CSV x, y, true, - this._props.pivotField, + pivotField, // when created, the new doc's pivotField will be set to pivotValue pivotValue ); - Array.from(Object.keys(Doc.GetProto(dataDoc))) - .filter(fieldKey => 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<StackedTimelineAnch // const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null); // const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => 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<Partial<collection layoutAutoHeight => 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<Partial<collectionTree onContextMenu = (): void => { // 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<CollectionViewProps>() implements ViewBoxInterface { +export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewProps>() { 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<TabDocViewProps> { static _allTabs = new ObservableSet<TabDocView>(); + 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<TabDocViewProps> { @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<CollectionFreeFormPannableContentsProps> { 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<Partial<collection } const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; - const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); + const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !DocumentView.LightboxDoc()); const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? options?.zoomScale ?? 0.75 : undefined); // focus on the document in the collection @@ -620,7 +617,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return false; }; forceStrokeGesture = (e: PointerEvent, gesture: Gestures, points: InkData, text?: any) => { - 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<Partial<collection @action setPan(panXIn: number, panYIn: number, panTime: number = 0, allowScroll = false) { let [panX, panY] = [panXIn, panYIn]; - // this is the easiest way to do this -> 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<Partial<collection panX = clamp(panX, xrangeMin - widScaling / 2, xrangeMax + widScaling / 2); panY = clamp(panY, yrangeMin - hgtScaling / 2, yrangeMax + hgtScaling / 2); } - if (!this.layoutDoc._lockedTransform || LightboxView.LightboxDoc) { + if (!this.layoutDoc._lockedTransform || DocumentView.LightboxDoc()) { this.setPanZoomTransition(panTime); const minScale = NumCast(this.dataDoc._freeform_scale_min, 1); const scale = 1 - minScale / this.zoomScaling(); @@ -882,6 +877,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection nudge = (x: number, y: number, nudgeTime: number = 500) => { 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<Partial<collection } return undefined; }; + + removeDocument = (docs: Doc | Doc[], annotationKey?: string | undefined) => { + 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<Partial<collection focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus} addDocTab={this.addDocTab} addDocument={this._props.addDocument} - removeDocument={this._props.removeDocument} + removeDocument={this.removeDocument} moveDocument={this._props.moveDocument} pinToPres={this._props.pinToPres} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} @@ -1525,7 +1530,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection incrementalRendering = () => 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<SchemaTableCellProps> { constructor(props: SchemaTableCellProps) { @@ -67,7 +75,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro } static addFieldDoc = (docs: Doc | Doc[] /* , where: OpenWhere */) => { - 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<SchemaTableCellPro return { color, textDecoration, fieldProps, cursor, pointerEvents }; } - @computed get selected() { - const selectedDocs: Doc[] | undefined = this._props.selectedCells(); - return this._props.isRowActive() && selectedDocs?.filter(doc => 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<SchemaTableCellPro pointerEvents, }}> <EditableView - ref={r => 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<SchemaTableCellPro get getCellType() { const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType; const cellValue = this._props.Document[this._props.fieldKey]; + if (cellValue instanceof ImageField) return ColumnType.Image; if (cellValue instanceof DateField) return ColumnType.Date; if (cellValue instanceof RichTextField) return ColumnType.RTF; if (typeof cellValue === 'number') return ColumnType.Any; if (typeof cellValue === 'string' && columnTypeStr !== FInfoFieldType.enumeration) return ColumnType.Any; if (typeof cellValue === 'boolean') return ColumnType.Boolean; - - if (columnTypeStr && columnTypeStr in FInfotoColType) { - return FInfotoColType[columnTypeStr]; - } + if (columnTypeStr && columnTypeStr in FInfotoColType) return FInfotoColType[columnTypeStr]; return ColumnType.Any; } get content() { - const cellType: ColumnType = this.getCellType; // prettier-ignore - switch (cellType) { - case ColumnType.Image: return <SchemaImageCell {...this._props} />; - case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />; - case ColumnType.RTF: return <SchemaRTFCell {...this._props} />; + switch (this.getCellType) { + case ColumnType.Image: return <SchemaImageCell {...this._props} />; + case ColumnType.Boolean: return <SchemaBoolCell {...this._props} />; + case ColumnType.RTF: return <SchemaRTFCell {...this._props} />; case ColumnType.Enumeration: return <SchemaEnumerationCell {...this._props} options={this._props.getFinfo(this._props.fieldKey)?.values?.map(val => Field.toString(val))} />; - case ColumnType.Date: return <SchemaDateCell {...this._props} />; - default: return this.defaultCellContent; + case ColumnType.Date: return <SchemaDateCell {...this._props} />; + default: return this.defaultCellContent; } } @@ -193,13 +193,13 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro const shift: boolean = e.shiftKey; const ctrl: boolean = e.ctrlKey; if (this._props.isRowActive?.() !== false) { - if (this.selected && ctrl) { + if (selectedCell(this._props) && ctrl) { this._props.selectCell(this._props.Document, this._props.col, shift, ctrl); e.stopPropagation(); - } else !this.selected && this._props.selectCell(this._props.Document, this._props.col, shift, ctrl); + } else !selectedCell(this._props) && this._props.selectCell(this._props.Document, this._props.col, shift, ctrl); } })} - style={{ padding: this._props.padding, maxWidth: this._props.maxWidth?.(), width: this._props.columnWidth() || undefined, border: this.selected ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined }}> + 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} </div> ); @@ -329,20 +329,14 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps makeObservable(this); } - @computed get selected() { - const selected = this._props.selectedCells(); - return this._props.isRowActive() && selected && selected?.filter(doc => 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 ( - <div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}> - {this.selected ? <FormattedTextBox {...fieldProps} autoFocus onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + <div className="schemaRTFCell" style={{ fontStyle: selectedCell(this._props) ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}> + {selectedCell(this._props) ? <FormattedTextBox {...fieldProps} autoFocus onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} </div> ); } @@ -354,10 +348,6 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp makeObservable(this); } - @computed get selected() { - const selected = this._props.selectedCells(); - return this._props.isRowActive() && selected && selected?.filter(doc => 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<SchemaTableCellProp <EditableView contents={undefined} fieldContents={fieldProps} - editing={this.selected ? undefined : false} + editing={selectedCell(this._props) ? undefined : false} GetValue={() => 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<SchemaTableC makeObservable(this); } - @computed get selected() { - const selected = this._props.selectedCells(); - return this._props.isRowActive() && selected && selected?.filter(doc => 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 ( - <div className={`newLightboxButtonMenu-container`}> + <div className="newLightboxButtonMenu-container"> <div className="newLightboxView-navBtn" title="toggle fit width" onClick={e => { e.stopPropagation(); NewLightboxView.LightboxDoc!._fitWidth = !NewLightboxView.LightboxDoc!._fitWidth; - }}></div> + }} + /> <div className="newLightboxView-tabBtn" title="open in tab" @@ -27,7 +30,8 @@ export const ButtonMenu = (props: IButtonMenu) => { CollectionDockingView.AddSplit(NewLightboxView.LightboxDoc || NewLightboxView.LightboxDoc!, OpenWhereMod.none); DocumentView.DeselectAll(); NewLightboxView.SetNewLightboxDoc(undefined); - }}></div> + }} + /> <div className="newLightboxView-penBtn" title="toggle pen annotation" @@ -35,7 +39,8 @@ export const ButtonMenu = (props: IButtonMenu) => { onClick={e => { e.stopPropagation(); Doc.ActiveTool = Doc.ActiveTool === InkTool.Pen ? InkTool.None : InkTool.Pen; - }}></div> + }} + /> <div className="newLightboxView-exploreBtn" title="toggle explore mode to navigate among documents only" @@ -43,7 +48,8 @@ export const ButtonMenu = (props: IButtonMenu) => { onClick={action(e => { e.stopPropagation(); SnappingManager.SetExploreMode(!SnappingManager.ExploreMode); - })}></div> + })} + /> </div> ); -}; +} 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 ( - <div className={`exploreView-container`}> + <div className="exploreView-container"> {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 ( - <div className={`exploreView-doc`} onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}> + <div className="exploreView-doc" onClick={() => {}} style={{ top: `calc(50% + ${y}%)`, left: `calc(50% + ${x}%)` }}> {rec.title} </div> ); - } else return null; + } + return null; })} - <div className={`exploreView-doc`} style={{ top: `calc(50% + ${0}%)`, left: `calc(50% + ${0}%)`, background: '#073763', color: 'white' }}> + <div className="exploreView-doc" style={{ top: `calc(50% + ${0}%)`, left: `calc(50% + ${0}%)`, background: '#073763', color: 'white' }}> {StrCast(NewLightboxView.LightboxDoc?.title)} </div> </div> ); -}; +} 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<Doc | undefined>(LightboxView.LightboxDoc); + const [doc, setDoc] = React.useState<Doc | undefined>(DocumentView.LightboxDoc()); const [editing, setEditing] = React.useState<boolean>(false); const [title, setTitle] = React.useState<JSX.Element | null>(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<boolean>(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<LightboxViewProps> { 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<LightboxViewProps> { 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<LightboxViewProps> { 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<LightboxViewProps> { @computed get documentView() { - if (!LightboxView.LightboxDoc) return null; + const lightboxDoc = DocumentView.LightboxDoc(); + if (!lightboxDoc) return null; return ( <GestureOverlay isActive> <DocumentView ref={action((r: DocumentView | null) => { 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<LightboxViewProps> { newLightboxWidth = () => this.props.PanelWidth - 420; newLightboxHeight = () => this.props.PanelHeight - 140; newLightboxScreenToLocal = () => new Transform(-this.leftBorder, -this.topBorder, 1); - navBtn = (left: Opt<string | number>, bottom: Opt<number>, top: number, icon: string, display: () => string, click: (e: React.MouseEvent) => void, color?: string) => ( - <div - className="newLightboxView-navBtn-frame" - style={{ - display: display(), - left, - width: bottom !== undefined ? undefined : Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), - bottom, - }}> - <div className="newLightboxView-navBtn" title={color} style={{ top, color: color ? 'red' : 'white', background: color ? 'white' : undefined }} onClick={click}> - <div style={{ height: 10 }}>{color}</div> - <FontAwesomeIcon icon={icon as any} size="3x" /> - </div> - </div> - ); 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 : ( <div className="newLightboxView-frame" onPointerDown={e => { @@ -376,7 +340,7 @@ export class NewLightboxTourBtn extends React.Component<NewLightboxTourBtnProps> 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<boolean>(true); const [showMore, setShowMore] = React.useState<boolean>(false); const [keywordsLoc, setKeywordsLoc] = React.useState<string[]>([]); @@ -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<string>(kw); + lightboxDoc.keywords = new List<string>(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 ( <div - className={`recommendationlist-container`} + className="recommendationlist-container" onPointerDown={e => { e.stopPropagation(); }}> - <div className={`header`}> - <div className={`title`}>Recommendations</div> + <div className="header"> + <div className="title">Recommendations</div> {NewLightboxView.LightboxDoc && ( <div style={{ fontSize: 10 }}> 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. </div> )} - <div className={`lb-label`}>Keywords</div> + <div className="lb-label">Keywords</div> {loadingKeywords ? ( - <div className={`keywords`}> + <div className="keywords"> <div className={`keyword ${loadingKeywords && 'loading'}`} /> <div className={`keyword ${loadingKeywords && 'loading'}`} /> <div className={`keyword ${loadingKeywords && 'loading'}`} /> <div className={`keyword ${loadingKeywords && 'loading'}`} /> </div> ) : ( - <div className={`keywords`}> + <div className="keywords"> {keywordsLoc && - keywordsLoc.map((word, ind) => { - return ( - <div className={`keyword`}> - {word} - <IconButton - type={Type.PRIM} - size={Size.XSMALL} - color={Colors.DARK_GRAY} - icon={<GrClose />} - onClick={() => { - let kw = keywordsLoc; - kw.splice(ind); - NewLightboxView.SetKeywords(kw); - }} - /> - </div> - ); - })} + keywordsLoc.map((word, ind) => ( + <div className="keyword"> + {word} + <IconButton + type={Type.PRIM} + size={Size.XSMALL} + color={Colors.DARK_GRAY} + icon={<GrClose />} + onClick={() => { + const kw = keywordsLoc; + kw.splice(ind); + NewLightboxView.SetKeywords(kw); + }} + /> + </div> + ))} </div> )} {!showMore ? ( <div - className={`lb-caret`} + className="lb-caret" onClick={() => { setShowMore(true); }}> More <FaCaretDown /> </div> ) : ( - <div className={`more`}> + <div className="more"> <div - className={`lb-caret`} + className="lb-caret" onClick={() => { setShowMore(false); }}> Less <FaCaretUp /> </div> - <div className={`lb-label`}>Type</div> - <div className={`lb-label`}>Sources</div> + <div className="lb-label">Type</div> + <div className="lb-label">Sources</div> </div> )} </div> - <div className={`recommendations`}> - {recs && - recs.map((rec: IRecommendation) => { - return <Recommendation {...rec} />; - })} - </div> + <div className="recommendations">{recs && recs.map((rec: IRecommendation) => <Recommendation {...rec} />)}</div> </div> ); -}; +} 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<CollectionFreeF <div style={{ position: 'absolute', width: this.PanelWidth(), height: this.PanelHeight(), background: 'lightGreen' }} /> ) : ( <DocumentView - parent={this} // eslint-disable-next-line react/jsx-props-no-spreading {...OmitKeys(this._props,this.WrapperKeys.map(val => 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<FieldViewProps>() implements ViewBoxInterface { +export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _marqueeref = React.createRef<MarqueeAnnotator>(); private _annotationLayer: React.RefObject<HTMLDivElement> = 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<FieldViewProps, 'fieldKey'>; 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<DocumentContentsViewProps> { + 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 ObservableReactComponent<DocumentConte TraceMobx(); if (this._props.LayoutTemplateString) return this._props.LayoutTemplateString; if (!this.layoutDoc) return '<p>awaiting layout</p>'; - 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 ? "<FieldView {...props} fieldKey='data' />" : KeyValueBox.LayoutString(); + if (layout === undefined) return this._props.Document.data ? "<FieldView {...props} fieldKey='data' />" : DocumentContentsView.DefaultLayoutString; if (typeof layout === 'string') return layout; return '<p>Loading layout</p>'; } @@ -223,42 +190,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte key={42} blacklistedAttrs={emptyPath} renderInWrapper={false} - components={{ - 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, - }} + components={DocumentContentsView.Components} bindings={bindings} jsx={layoutFrame} showWarnings diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index dc597e5ff..8df28a770 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -8,7 +8,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal'; import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnFalse, returnVal, simulateMouseClick } from '../../../ClientUtils'; -import { Utils, emptyFunction, emptyPath } from '../../../Utils'; +import { Utils, emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc'; import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -35,20 +35,21 @@ import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch, undoable } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { DocComponent, ViewBoxInterface } from '../DocComponent'; +import { DocComponent } from '../DocComponent'; import { EditableView } from '../EditableView'; import { FieldsDropdown } from '../FieldsDropdown'; -import { LightboxView } from '../LightboxView'; +import { ObserverJsxParser } from '../ObservableReactComponent'; import { PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; -import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; +import { ViewBoxInterface } from '../ViewBoxInterface'; +import { DocumentContentsView } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; import { FieldViewProps, FieldViewSharedProps } from './FieldView'; import { FocusViewOptions } from './FocusViewOptions'; -import { OpenWhere } from './OpenWhere'; +import { OpenWhere, OpenWhereMod } from './OpenWhere'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; -import { PresEffect, PresEffectDirection } from './trails'; +import { PresEffect, PresEffectDirection } from './trails/PresEnums'; export interface DocumentViewProps extends FieldViewSharedProps { hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected @@ -77,7 +78,7 @@ export interface DocumentViewProps extends FieldViewSharedProps { dragStarting?: () => void; dragEnding?: () => void; - parent?: any; + parent?: any; // parent React component view (see CollectionFreeFormDocumentView) } @observer export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps>() { @@ -320,7 +321,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document if (this.onDoubleClickHandler?.script) { UndoManager.RunInBatch(() => 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<FieldViewProps & Document const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : []; if (this._props.renderDepth === 0) { - appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => 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<FieldViewProps & Document @observer export class DocumentView extends DocComponent<DocumentViewProps>() { 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<Doc>, 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<Doc>, 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<DocumentViewProps>() { public static addView: (dv: DocumentView) => void | undefined; public static removeView: (dv: DocumentView) => void | undefined; public static addViewRenderedCb: (doc: Opt<Doc>, 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<DocumentView>; public static getContextPath: (doc: Opt<Doc>, includeExistingViews?: boolean) => Doc[]; @@ -1032,8 +1057,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { 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 <div>. 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 <div>. 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<DocumentViewProps>() { }}> <DocumentViewInternal {...this._props} + parent={undefined} fieldKey={this.LayoutFieldKey} DataTransition={this.DataTransition} DocumentView={this.selfView} @@ -1451,58 +1477,52 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() implements ViewBoxInterface { +export class MapBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { } 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<FieldViewProps>() { 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<FieldViewProps>() { 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<FormattedTextB public static LayoutString(fieldStr: string) { return FieldView.LayoutString(FormattedTextBox, fieldStr); } - public static blankState = () => 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<string>(['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<FormattedTextB constructor(props: FormattedTextBoxProps) { super(props); makeObservable(this); - FormattedTextBox.Instance = this; this._recordingStart = Date.now(); } @@ -1435,14 +1433,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB return true; }, dispatchTransaction: this.dispatchTransaction, - nodeViews: { - 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, self); }, // prettier-ignore - dashField(node: any, view: any, getPos: any) { return new DashFieldView(node, view, getPos, self); }, // prettier-ignore - equation(node: any, view: any, getPos: any) { return new EquationView(node, view, getPos, self); }, // 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 - }, + nodeViews: FormattedTextBox.nodeViews(this), clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); @@ -1463,7 +1454,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB (this._editorView as any).TextView = this; } - const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, Doc.SelectOnLoad) && (!LightboxView.LightboxDoc || LightboxView.Contains(this.DocumentView?.())); + const selectOnLoad = Doc.AreProtosEqual(this._props.TemplateDataDocument ?? this.Document, Doc.SelectOnLoad) && (!DocumentView.LightboxDoc() || DocumentView.LightboxContains(this.DocumentView?.())); const selLoadChar = FormattedTextBox.SelectOnLoadChar; if (selectOnLoad) { Doc.SetSelectOnLoad(undefined); diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 6b4f5e073..75492d2f9 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -24,14 +24,12 @@ import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SerializationHelper } from '../../../util/SerializationHelper'; import { SnappingManager } from '../../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; -import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; import { CollectionFreeFormPannableContents } from '../../collections/collectionFreeForm/CollectionFreeFormPannableContents'; import { CollectionView } from '../../collections/CollectionView'; import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; -import { LightboxView } from '../../LightboxView'; import { pinDataTypes as dataTypes } from '../../PinFuncs'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; @@ -46,6 +44,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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<FieldViewProps>() { 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<FieldViewProps>() { default: } }); - LightboxView.Instance.SetLightboxDoc(undefined); + DocumentView.SetLightboxDoc(undefined); Doc.RemFromMyOverlay(this.Document); return PresStatus.Edit; }; @@ -892,7 +894,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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<FieldViewProps>() { 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<SearchBoxProps>() { @observable _searchString = ''; @observable _docTypeString = 'all'; @observable _results: Map<Doc, string[]> = new Map<Doc, string[]>(); - @observable _recommendations: IRecommendation[] = []; + // @observable _recommendations: IRecommendation[] = []; @observable _pageRanks: Map<Doc, number> = new Map<Doc, number>(); @observable _linkedDocsOut: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>(); @observable _linkedDocsIn: Map<Doc, Set<Doc>> = new Map<Doc, Set<Doc>>(); @@ -375,29 +375,29 @@ export class SearchBox extends ViewBoxBaseComponent<SearchBoxProps>() { 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<SearchBoxProps>() { }); // eslint-disable-next-line react/jsx-props-no-spreading - const recommendationsJSX: JSX.Element[] = this._recommendations.map(props => <Recommendation {...props} />); + const recommendationsJSX: JSX.Element[] = []; // this._recommendations.map(props => <Recommendation {...props} />); return ( <div className="searchBox-container" style={{ pointerEvents: 'all', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}> 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> = 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<T extends RefField> 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<FieldType> | 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 ); |