From d818ef151ca65008e5c6bb5e92b709decb3026d8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 14 Apr 2025 18:35:49 -0400 Subject: fixed how templates are expanded to avoid template sub-component conflicts by changing how field keys are named. fixed various Cast functions to be more typesafe by including undefined as part of return type. overhaul of Doc.MakeClone, MakeCopy, FindRefernces - makeClone is no longer async. fixed inlined docs in text docs. --- src/client/views/DocComponent.tsx | 8 +- src/client/views/DocumentButtonBar.tsx | 8 +- src/client/views/FilterPanel.tsx | 4 +- src/client/views/GlobalKeyHandler.ts | 6 +- src/client/views/InkStrokeProperties.ts | 2 +- src/client/views/InkTranscription.tsx | 6 +- src/client/views/LightboxView.tsx | 4 +- src/client/views/Main.tsx | 4 +- src/client/views/MainView.tsx | 42 +- src/client/views/PinFuncs.ts | 8 +- src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/PropertiesView.tsx | 8 +- src/client/views/StyleProvider.tsx | 12 +- src/client/views/StyleProviderQuiz.tsx | 2 +- src/client/views/TagsView.tsx | 6 +- src/client/views/animationtimeline/Timeline.tsx | 2 +- .../views/collections/CollectionCarouselView.tsx | 3 +- .../views/collections/CollectionDockingView.tsx | 16 +- .../views/collections/CollectionNoteTakingView.tsx | 11 +- .../views/collections/CollectionStackingView.tsx | 12 +- .../CollectionStackingViewFieldColumn.tsx | 4 +- src/client/views/collections/CollectionSubView.tsx | 37 +- src/client/views/collections/TabDocView.tsx | 14 +- src/client/views/collections/TreeView.tsx | 20 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 31 +- .../collectionFreeForm/FaceCollectionBox.tsx | 2 +- .../collectionFreeForm/ImageLabelBox.tsx | 4 +- .../collections/collectionFreeForm/MarqueeView.tsx | 6 +- .../collectionLinear/CollectionLinearView.tsx | 4 +- .../collectionSchema/CollectionSchemaView.tsx | 4 +- .../collectionSchema/SchemaTableCell.tsx | 2 +- src/client/views/global/globalScripts.ts | 37 +- src/client/views/newlightbox/NewLightboxView.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 2 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 100 ++-- src/client/views/nodes/FieldView.tsx | 2 +- src/client/views/nodes/IconTagBox.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 24 +- src/client/views/nodes/KeyValueBox.tsx | 8 +- src/client/views/nodes/KeyValuePair.tsx | 4 +- src/client/views/nodes/PDFBox.tsx | 2 +- .../views/nodes/RecordingBox/RecordingBox.tsx | 2 +- src/client/views/nodes/ScreenshotBox.tsx | 2 +- src/client/views/nodes/ScriptingBox.tsx | 13 +- src/client/views/nodes/WebBox.tsx | 18 +- .../nodes/chatbot/chatboxcomponents/ChatBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 55 +- .../views/nodes/formattedText/RichTextMenu.tsx | 6 +- .../views/nodes/formattedText/RichTextRules.ts | 19 +- src/client/views/nodes/trails/PresBox.tsx | 187 +++--- src/client/views/nodes/trails/PresElementBox.scss | 308 ---------- src/client/views/nodes/trails/PresElementBox.tsx | 628 --------------------- src/client/views/nodes/trails/PresSlideBox.scss | 308 ++++++++++ src/client/views/nodes/trails/PresSlideBox.tsx | 628 +++++++++++++++++++++ src/client/views/nodes/trails/index.ts | 2 +- src/client/views/pdf/GPTPopup/GPTPopup.tsx | 2 +- src/client/views/search/FaceRecognitionHandler.tsx | 4 +- src/client/views/search/SearchBox.tsx | 2 +- src/client/views/smartdraw/StickerPalette.tsx | 13 +- 62 files changed, 1350 insertions(+), 1336 deletions(-) delete mode 100644 src/client/views/nodes/trails/PresElementBox.scss delete mode 100644 src/client/views/nodes/trails/PresElementBox.tsx create mode 100644 src/client/views/nodes/trails/PresSlideBox.scss create mode 100644 src/client/views/nodes/trails/PresSlideBox.tsx (limited to 'src/client/views') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 45c80c6f7..1ecd82dbc 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -48,7 +48,7 @@ export function DocComponent

() { * This Doc inherits from the dataDoc, and may or may not inherit (or be) the layoutDoc. */ get rootDoc() { - return DocCast(this.Document.rootDocument, this.Document); + return DocCast(this.Document.rootDocument, this.Document)!; } /** @@ -64,7 +64,7 @@ export function DocComponent

() { * This may or may not inherit from the data doc. */ @computed get layoutDoc() { - return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.()); + return this._props.LayoutTemplateString ? this.Document : Doc.LayoutDoc(this.Document, this._props.LayoutTemplate?.()); } /** @@ -115,7 +115,7 @@ export function ViewBoxBaseComponent

() { * consider: Document, layoutDoc, dataDoc */ get rootDoc() { - return DocCast(this.Document.rootDocument, this.Document); + return DocCast(this.Document.rootDocument, this.Document)!; } /** * This is the document being rendered by the React component. In the @@ -189,7 +189,7 @@ export function ViewBoxAnnotatableComponent

() { * other Doc options (Document, layoutDoc, dataDoc) */ @computed get rootDoc() { - return DocCast(this.Document.rootDocument, this.Document); + return DocCast(this.Document.rootDocument, this.Document)!; } /** * This is the document being rendered. It may be a template so it may or may no inherit from the data doc. diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 6f2f051cd..8a850467a 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -303,7 +303,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( DocumentView.Selected().forEach(dv => { dv.layoutDoc._layout_showTags = !showing; if (e.shiftKey) - DocListCast(dv.Document[Doc.LayoutFieldKey(dv.Document) + '_annotations']).forEach(doc => { + DocListCast(dv.Document[Doc.LayoutDataKey(dv.Document) + '_annotations']).forEach(doc => { if (doc.face) doc.hidden = showing; }); }); @@ -479,13 +479,13 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( const doc = rootView?.Document; if (doc) { const anchor = rootView.ComponentView?.getAnchor?.(true) ?? doc; - const trail = DocCast(anchor.presentationTrail) ?? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail), true); + const trail = DocCast(anchor.presentationTrail) ?? (DocCast(Doc.UserDoc().emptyTrail) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail)!, true) : undefined); if (trail !== anchor.presentationTrail) { - DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' }); + trail && DocUtils.MakeLink(anchor, trail, { link_relationship: 'link trail' }); anchor.presentationTrail = trail; } Doc.ActivePresentation = trail; - this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight); + trail && this._props.views().lastElement()?._props.addDocTab(trail, OpenWhere.replaceRight); } e.stopPropagation(); }; diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index c3b3f9d0c..848a77004 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -182,7 +182,7 @@ export class FilterPanel extends ObservableReactComponent { return targetView?.ComponentView?.annotationKey || (targetView?.ComponentView?.fieldKey ?? 'data'); } @computed get targetDocChildren() { - return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_sidebar'])]; + return [...DocListCast(this.Document?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.Document[Doc.LayoutDataKey(this.Document) + '_sidebar'])]; } @computed get rangeFilters() { @@ -236,7 +236,7 @@ export class FilterPanel extends ObservableReactComponent { if (facetVal instanceof RichTextField || typeof facetVal === 'string') rtFields++; facetVal !== undefined && valueSet.add(Field.toString(facetVal as FieldType)); (facetVal === true || facetVal === false) && valueSet.add(Field.toString(!facetVal)); - const fieldKey = Doc.LayoutFieldKey(t); + const fieldKey = Doc.LayoutDataKey(t); const annos = !Field.toString(Doc.LayoutField(t) as FieldType).includes('CollectionView'); DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 94c023330..948beec74 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -239,7 +239,7 @@ export class KeyManager { break; case 'i': { - const importBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImports); + const importBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyImports); if (importBtn) { MainView.Instance.selectLeftSidebarButton(importBtn); } @@ -247,7 +247,7 @@ export class KeyManager { break; case 's': { - const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyTrails); + const trailsBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyTrails); if (trailsBtn) { MainView.Instance.selectLeftSidebarButton(trailsBtn); } @@ -257,7 +257,7 @@ export class KeyManager { if (DocumentView.Selected().length === 1 && DocumentView.Selected()[0].ComponentView?.search) { DocumentView.Selected()[0].ComponentView?.search?.('', false, false); } else { - const searchBtn = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MySearcher); + const searchBtn = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MySearcher); if (searchBtn) { MainView.Instance.selectLeftSidebarButton(searchBtn); } diff --git a/src/client/views/InkStrokeProperties.ts b/src/client/views/InkStrokeProperties.ts index 6854476e2..425f5b6bb 100644 --- a/src/client/views/InkStrokeProperties.ts +++ b/src/client/views/InkStrokeProperties.ts @@ -341,7 +341,7 @@ export class InkStrokeProperties { */ snapControl = (inkView: DocumentView, controlIndex: number) => { const inkDoc = inkView.Document; - const ink = Cast(inkDoc[Doc.LayoutFieldKey(inkDoc)], InkField)?.inkData; + const ink = Cast(inkDoc[Doc.LayoutDataKey(inkDoc)], InkField)?.inkData; if (ink) { const screenDragPt = inkView.ComponentView?.ptToScreen?.(ink[controlIndex]); diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 1293aa9d0..2e6b477e9 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -118,9 +118,9 @@ export class InkTranscription extends React.Component { const times: number[] = []; validInks - .filter(i => Cast(i[Doc.LayoutFieldKey(i)], InkField)) + .filter(i => Cast(i[Doc.LayoutDataKey(i)], InkField)) .forEach(i => { - const d = Cast(i[Doc.LayoutFieldKey(i)], InkField, null); + const d = Cast(i[Doc.LayoutDataKey(i)], InkField, null); const inkStroke = DocumentView.getDocumentView(i)?.ComponentView as InkingStroke; strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y }))); times.push(DateCast(i.author_date).getDate().getTime()); @@ -382,7 +382,7 @@ export class InkTranscription extends React.Component { render() { return ( -

+
diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index de2c7cd09..0eb21b943 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -136,7 +136,7 @@ export class LightboxView extends ObservableReactComponent { return this.SetLightboxDoc( doc, undefined, - [...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)), + [...DocListCast(doc[Doc.LayoutDataKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...this._future].sort((a, b) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow)), layoutTemplate ); }; @@ -187,7 +187,7 @@ export class LightboxView extends ObservableReactComponent { saved: this._savedState, }); if (this._docTarget) { - const fieldKey = Doc.LayoutFieldKey(this._docTarget); + const fieldKey = Doc.LayoutDataKey(this._docTarget); const contents = [...DocListCast(this._docTarget[fieldKey]), ...DocListCast(this._docTarget[fieldKey + '_annotations'])]; const links = Doc.Links(this._docTarget) .map(link => Doc.getOppositeAnchor(link, this._docTarget!)!) diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 22725a2b9..e4bbb1c0f 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -61,7 +61,7 @@ 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 { PresBox, PresSlideBox } from './nodes/trails'; import { FaceRecognitionHandler } from './search/FaceRecognitionHandler'; import { SearchBox } from './search/SearchBox'; import { StickerPalette } from './smartdraw/StickerPalette'; @@ -135,7 +135,7 @@ FieldLoader.ServerLoadStatus = { requested: 0, retrieved: 0, message: 'cache' }; AudioBox, RecordingBox, PresBox, - PresElementBox, + PresSlideBox, SearchBox, ImageLabelBox, FaceCollectionBox, diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index be6e2fecb..ad6bb09c7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -93,7 +93,7 @@ export class MainView extends ObservableReactComponent { @observable private _dashUIWidth: number = 0; // width of entire main dashboard region including left menu buttons and properties panel (but not including the dashboard selector button row) @observable private _dashUIHeight: number = 0; // height of entire main dashboard region including top menu buttons @observable private _panelContent: string = 'none'; - @observable private _sidebarContent: Doc = Doc.MyLeftSidebarPanel; + @observable private _sidebarContent?: Doc = Doc.MyLeftSidebarPanel; @observable private _leftMenuFlyoutWidth: number = 0; @computed get _hideUI() { return SnappingManager.HideUI || (this.mainDoc && this.mainDoc._type_collection !== CollectionViewType.Docking); @@ -173,7 +173,7 @@ export class MainView extends ObservableReactComponent { views => views.length > 1 && document.activeElement instanceof HTMLElement && document.activeElement?.blur() ); reaction( - () => Doc.MyDockedBtns.linearView_IsOpen, + () => Doc.MyDockedBtns?.linearView_IsOpen, open => SnappingManager.SetPrintToConsole(!!open) ); const scriptTag = document.createElement('script'); @@ -199,7 +199,7 @@ export class MainView extends ObservableReactComponent { ele.outerHTML = ''; }, 1000); } - this._sidebarContent.proto = undefined; + this._sidebarContent && (this._sidebarContent.proto = undefined); if (!MainView.Live) { DocServer.setLivePlaygroundFields([ 'dataTransition', @@ -636,8 +636,10 @@ export class MainView extends ObservableReactComponent { openPresentation = (pres: Doc) => { if (pres.type === DocumentType.PRES) { CollectionDockingView.AddSplit(pres, OpenWhereMod.right, undefined, PresBox.PanelName); - Doc.MyTrails && (Doc.ActivePresentation = pres); - Doc.AddDocToList(Doc.MyTrails, 'data', pres); + if (Doc.MyTrails) { + Doc.ActivePresentation = pres; + Doc.AddDocToList(Doc.MyTrails, 'data', pres); + } this.closeFlyout(); } }; @@ -645,16 +647,16 @@ export class MainView extends ObservableReactComponent { @action createNewFolder = async () => { const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }); - Doc.AddDocToList(Doc.MyFilesystem, 'data', folder); + Doc.MyFilesystem && Doc.AddDocToList(Doc.MyFilesystem, 'data', folder); }; waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined); headerBarScreenXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.headerBarDocHeight(), 1); mainScreenToLocalXf = () => new Transform(-this.leftScreenOffsetOfMainDocView - this.leftMenuFlyoutWidth(), -this.topOfMainDocContent, 1); - addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.AddDocToList(this.headerBarDoc, 'data', doc), true); - removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true); + addHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => !!this.headerBarDoc && Doc.AddDocToList(this.headerBarDoc, 'data', doc), true); + removeHeaderDoc = (docs: Doc | Doc[]) => toList(docs).reduce((done, doc) => !!this.headerBarDoc && Doc.RemoveDocFromList(this.headerBarDoc, 'data', doc), true); @computed get headerBarDocView() { - return ( + return !this.headerBarDoc ? null : (
{
{this.docButtons}
- ) : ( + ) : !this._sidebarContent ? null : (
{ } @computed get leftMenuPanel() { - return ( + return !Doc.MyLeftSidebarMenu ? null : (
{ break; default: this._leftMenuFlyoutWidth = this._leftMenuFlyoutWidth || 250; - this._sidebarContent.proto = DocCast(button.target); + this._sidebarContent && (this._sidebarContent.proto = DocCast(button.target)); SnappingManager.SetLastPressedBtn(button[Id]); } } @@ -875,8 +877,10 @@ export class MainView extends ObservableReactComponent { */ addHotKey = (hotKey: string) => { const filterIcons = DocCast(DocCast(Doc.UserDoc().myContextMenuBtns)?.Filter); - const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons); - Doc.AddToFilterHotKeys(menuDoc); + if (filterIcons) { + const menuDoc = CurrentUserUtils.setupContextMenuBtn(CurrentUserUtils.filterBtnDesc(ToTagName(hotKey), 'question'), filterIcons); + Doc.AddToFilterHotKeys(menuDoc); + } }; @computed get mainInnerContent() { @@ -941,13 +945,13 @@ export class MainView extends ObservableReactComponent { closeFlyout = action(() => { SnappingManager.SetLastPressedBtn(''); this._panelContent = 'none'; - this._sidebarContent.proto = undefined; + this._sidebarContent && (this._sidebarContent.proto = undefined); this._leftMenuFlyoutWidth = 0; }); - remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && Doc.RemoveDocFromList(Doc.MyDockedBtns, 'data', doc), true); + remButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !doc.dragOnlyWithinContainer && !!Doc.MyDockedBtns && Doc.RemoveDocFromList(Doc.MyDockedBtns!, 'data', doc), true); moveButtonDoc = (docs: Doc | Doc[], targetCol: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => this.remButtonDoc(docs) && addDocument(docs); - addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && Doc.AddDocToList(Doc.MyDockedBtns, 'data', doc), true); + addButtonDoc = (docs: Doc | Doc[]) => toList(docs).reduce((flg: boolean, doc) => flg && !!Doc.MyDockedBtns && Doc.AddDocToList(Doc.MyDockedBtns!, 'data', doc), true); buttonBarXf = () => { if (!this._docBtnRef.current) return Transform.Identity(); diff --git a/src/client/views/PinFuncs.ts b/src/client/views/PinFuncs.ts index d756830da..805dd9a49 100644 --- a/src/client/views/PinFuncs.ts +++ b/src/client/views/PinFuncs.ts @@ -70,13 +70,13 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { pinProps.pinData.dataview || pinProps.pinData.poslayoutview || pinProps?.activeFrame !== undefined; - const fkey = Doc.LayoutFieldKey(targetDoc); + const fkey = Doc.LayoutDataKey(targetDoc); if (pinProps.pinData.dataview) { pinDoc.config_usePath = targetDoc[fkey + '_usePath']; pinDoc.config_data = Field.Copy(targetDoc[fkey]); } if (pinProps.pinData.dataannos) { - const fieldKey = '$' + Doc.LayoutFieldKey(targetDoc) + +'_annotations'; + const fieldKey = '$' + Doc.LayoutDataKey(targetDoc) + +'_annotations'; pinDoc.config_annotations = new List(DocListCast(targetDoc[fieldKey]).filter(doc => !doc.layout_unrendered)); } if (pinProps.pinData.inkable) { @@ -87,7 +87,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { } if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop; if (pinProps.pinData.clippable) { - const fieldKey = Doc.LayoutFieldKey(targetDoc); + const fieldKey = Doc.LayoutDataKey(targetDoc); pinDoc.config_clipWidth = targetDoc[fieldKey + '_clipWidth']; } if (pinProps.pinData.datarange) { @@ -127,7 +127,7 @@ export function PinDocView(pinDocIn: Doc, pinProps: PinProps, targetDoc: Doc) { } if (pinProps.pinData.temporal) { pinDoc.config_clipStart = targetDoc._layout_currentTimecode; - const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1); + const duration = NumCast(pinDoc[`${Doc.LayoutDataKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1); pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration); } } diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 6778a6691..66936c64c 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -447,7 +447,7 @@ export class PropertiesButtons extends React.Component { }; render() { - const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)]; + const layoutField = this.selectedDoc?.[Doc.LayoutDataKey(this.selectedDoc)]; const isText = DocumentView.Selected().lastElement()?.ComponentView instanceof FormattedTextBox; const isInk = this.selectedDoc?.layout_isSvg; const isImage = layoutField instanceof ImageField; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 7fcb15afe..e7186c0e3 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -525,7 +525,7 @@ export class PropertiesView extends ObservableReactComponent 0; const type = [DocumentType.AUDIO, DocumentType.VID].includes(DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as DocumentType) ? (DocCast(PresBox.Instance.activeItem?.annotationOn)?.type as DocumentType) - : PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type; + : PresBox.Instance.activeItem + ? PresBox.targetRenderedDoc(PresBox.Instance.activeItem)?.type + : undefined; return (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index e8876f96f..9110e198f 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -129,7 +129,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt, props: Opt = layoutDoc && StrCast(alternate ? layoutDoc['backgroundColor' + alternate]:undefined, - doc.rootDocument - ? StrCast(layoutDoc.backgroundColor, StrCast(DocCast(doc.rootDocument).backgroundColor)) // for nested templates: use template's color, then root doc's color + DocCast(doc.rootDocument) + ? StrCast(layoutDoc.backgroundColor, StrCast(DocCast(doc.rootDocument)!.backgroundColor)) // for nested templates: use template's color, then root doc's color : layoutDoc === doc ? StrCast(doc.backgroundColor) : StrCast(StrCast(Doc.GetT(layoutDoc, 'backgroundColor', 'string', true), StrCast(doc.backgroundColor, StrCast(layoutDoc.backgroundColor)) // otherwise, use expanded template coloor, then root doc's color, then template's inherited color @@ -262,7 +262,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt { const newEmbeddings = TagItem.allDocsWithTag(this._props.tag).map(doc => Doc.MakeEmbedding(doc)); // Create a new collection and set up configurations. + const emptyCol = DocCast(Doc.UserDoc().emptyCollection); const newCollection = ((doc: Doc) => { doc.$data = new List(newEmbeddings); doc.$title = this._props.tag; @@ -182,7 +184,7 @@ export class TagItem extends ObservableReactComponent { doc.layout_fitWidth = true; doc._layout_showTags = true; return doc; - })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); + })(emptyCol ? Doc.MakeCopy(emptyCol, true) : Docs.Create.FreeformDocument([], {})); newEmbeddings.forEach(embed => Doc.SetContainer(embed, newCollection)); // Add the collection to the tag document's list of associated smart collections. @@ -342,7 +344,7 @@ export class TagsView extends ObservableReactComponent { const tagsList = new Set(StrListCast(this.View.dataDoc.tags)); const chatTagsList = new Set(StrListCast(this.View.dataDoc.tags_chat)); const facesList = new Set( - DocListCast(this.View.dataDoc[Doc.LayoutFieldKey(this.View.Document) + '_annotations']) + DocListCast(this.View.dataDoc[Doc.LayoutDataKey(this.View.Document) + '_annotations']) .concat(this.View.Document) .filter(d => d.face) .map(doc => StrCast(DocCast(doc.face)?.title)) diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index cd2c7df1b..814e9a7a0 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -92,7 +92,7 @@ export class Timeline extends ObservableReactComponent => { const docs = DocListCast(this.Document[this.fieldKey]); if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { - const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)); + const annoOn = DocCast(anchor.annotationOn, anchor); + const newIndex = NumCast(anchor.config_carousel_index, (annoOn && docs.getIndex(annoOn)) ?? 0); options.didMove = newIndex !== this.layoutDoc._carousel_index; options.didMove && (this.layoutDoc._carousel_index = newIndex); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 094e61db9..ea6259a32 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -473,11 +473,11 @@ export class CollectionDockingView extends CollectionSubView() { } return undefined; } - public static async TakeSnapshot(doc: Doc | undefined, clone = false) { + public static TakeSnapshot(doc: Doc | undefined, clone = false) { if (!doc) return undefined; let json = StrCast(doc.dockingConfig); if (clone) { - const cloned = await Doc.MakeClone(doc); + const cloned = Doc.MakeClone(doc); Array.from(cloned.map.entries()).forEach(entry => { json = json.replace(entry[0], entry[1][Id]); }); @@ -524,11 +524,11 @@ export class CollectionDockingView extends CollectionSubView() { this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); const dashDoc = tab.DashDoc; if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) { - Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true); + Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true); // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined; if (!dashDoc.embedContainer) { - Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true); + Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true); Doc.RemoveEmbedding(dashDoc, dashDoc); } } @@ -562,7 +562,7 @@ export class CollectionDockingView extends CollectionSubView() { _layout_fitWidth: true, title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`, }); - Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); + Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); inheritParentAcls(this.Document, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } @@ -579,7 +579,7 @@ export class CollectionDockingView extends CollectionSubView() { _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`, }); - Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); + Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); inheritParentAcls(this.dataDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } @@ -668,7 +668,5 @@ ScriptingGlobals.add( ); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(async function snapshotDashboard() { - const batch = UndoManager.StartBatch('snapshot'); - await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard); - batch.end(); + undoable(() => CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard), 'snapshot dashboard'); }, 'creates a snapshot copy of a dashboard'); diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 173147f64..7f639a11e 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -321,11 +321,10 @@ export class CollectionNoteTakingView extends CollectionSubView() { return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth); }; - // how to get the height of a document. Nothing special here. getDocHeight(d?: Doc) { if (!d || d.hidden) return 0; - const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); - const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument; + const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.()); + const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined; const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0); @@ -566,7 +565,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { @undoBatch remColumn = (value: SchemaHeaderField) => { - const colHdrData = Array.from(Cast(this.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); + const colHdrData = Array.from(Cast(this.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []); if (value) { const index = colHdrData.indexOf(value); index !== -1 && colHdrData.splice(index, 1); @@ -585,7 +584,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } return undefined; }); - const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); + const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []); const newColWidth = 1 / (this.numGroupColumns + 1); columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)); value && this.resizeColumns(columnHeaders); @@ -642,7 +641,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth); rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth); const headers = Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null); - headers.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]()); + headers?.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]()); }; // renderedSections returns a list of all of the JSX elements used (columns and dividers). If the view diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 112510265..9155227dd 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -315,8 +315,8 @@ export class CollectionStackingView extends CollectionSubView([]) : undefined; + const dataField = textBox.Document[Doc.LayoutDataKey(newDoc)]; + newDoc['$' + Doc.LayoutDataKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List([]) : undefined; if (layoutFieldKey !== 'layout' && textBox.Document[layoutFieldKey] instanceof Doc) { newDoc[layoutFieldKey] = textBox.Document[layoutFieldKey]; } @@ -340,7 +340,7 @@ export class CollectionStackingView extends CollectionSubView (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined); @observable docRefs = new ObservableMap(); - childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null)); + childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null) ?? null); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, trans: () => string, count: number) { @@ -408,7 +408,7 @@ export class CollectionStackingView extends CollectionSubView () => { if (!d) return 0; - const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); + const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.()); const maxWidth = this.columnWidth / this.numGroupColumns; if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) { return Math.min(NumCast(d._width), maxWidth); @@ -418,8 +418,8 @@ export class CollectionStackingView extends CollectionSubView () => StrCast(d?.dataTransition)); getDocHeight = computedFn((d?: Doc) => () => { if (!d || d.hidden) return 0; - const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); - const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument; + const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.()); + const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined; const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 66839ba7f..994669734 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -274,7 +274,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< const container = DocCast(this._props.Doc.rootDocument)?.[DocData] ? Doc.GetProto(this._props.Doc) : this._props.Doc; if (container.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, container); - return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); + return Doc.AddDocToList(container, Doc.LayoutDataKey(container), created); } return this._props.addDocument?.(created) || false; } @@ -316,7 +316,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< ref={this._headerRef} style={{ marginTop: this._props.yMargin, - width: this._props.columnWidth + width: this._props.columnWidth, }}> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 375c0fe53..bc7d6f897 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import * as rp from 'request-promise'; import { ClientUtils, DashColor, returnFalse } from '../../../ClientUtils'; import CursorField from '../../../fields/CursorField'; -import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, expandedFieldName, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc'; import { AclPrivate, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -132,16 +132,23 @@ export function CollectionSubView() { hasChildDocs = () => this.childLayoutPairs.map(pair => pair.layout); @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] { - const { TemplateDataDocument } = this._props; - const validPairs = this.childDocs - .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc)) - .filter( - pair => - // filter out any documents that have a proto that we don't have permissions to - !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)) - ) - .filter(pair => !this._filterFunc?.(pair.layout!)); - return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types + const lastEle = this.DocumentView?.(); + if (!lastEle?.IsInvalid(this.Document)) { + const rootTemplate = lastEle && Doc.LayoutDoc(lastEle.rootDoc).isTemplateDoc && Doc.LayoutDoc(lastEle.rootDoc); + const templateFieldKey = rootTemplate && + [expandedFieldName(rootTemplate), + ...this._props.docViewPath() + .filter(dv => dv.Document.isTemplateForField) + .map(dv => dv.Document.title), + ].join('_'); // prettier-ignore + return this.childDocs + .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? this._props.TemplateDataDocument : undefined, doc, templateFieldKey || "")) + .filter(pair => // filter out any documents that have a proto that we don't have permissions to + !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate))) + .filter(pair => !this._filterFunc?.(pair.layout!)) + .map(({ data, layout }) => ({ data: data!, layout: layout! })); // prettier-ignore + } + return []; } /** * This is the raw, stored list of children on a collection. If you modify this list, the database will be updated @@ -161,7 +168,7 @@ export function CollectionSubView() { }; collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters); - collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []); + collectionRangeDocFilters = () => this._focusRangeFilters ?? StrListCast(this.Document._childFiltersByRanges); // child filters apply to the descendants of the documents in this collection childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; // unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack @@ -178,7 +185,7 @@ export function CollectionSubView() { rawdocs = [this.dataField]; } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. - rawdocs = Cast(this.dataField, listSpec(Doc), null); + rawdocs = DocListCast(this.dataField); } else if (this.dataField) { // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc. // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection. @@ -206,7 +213,7 @@ export function CollectionSubView() { let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0; if (notFiltered) { notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0; - const fieldKey = Doc.LayoutFieldKey(d); + const fieldKey = Doc.LayoutDataKey(d); const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List)?.some(ele => !(ele instanceof Doc)); const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey]; const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar']; @@ -219,7 +226,7 @@ export function CollectionSubView() { newarray = []; // eslint-disable-next-line no-loop-func subDocs.forEach(t => { - const docFieldKey = Doc.LayoutFieldKey(t); + const docFieldKey = Doc.LayoutDataKey(t); const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List)?.some(ele => !(ele instanceof Doc)); notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length)); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 568a08792..4348bc7dc 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -15,7 +15,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast, StrCast, toList } from '../../../fields/Types'; +import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types'; import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; @@ -145,7 +145,7 @@ export class TabMinimapView extends ObservableReactComponent } color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} />
@@ -207,10 +207,10 @@ export class TabDocView extends ObservableReactComponent { const docs = toList(docIn); const batch = UndoManager.StartBatch('Pin doc to pres trail'); - const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); + const curPres = Doc.ActivePresentation ?? (DocCast(Doc.UserDoc().emptyTrail) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail)!, true) : Docs.Create.PresDocument({})); if (!Doc.ActivePresentation) { - Doc.AddDocToList(Doc.MyTrails, 'data', curPres); + Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', curPres); Doc.ActivePresentation = curPres; } @@ -236,7 +236,7 @@ export class TabDocView extends ObservableReactComponent { pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header - const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null); + const duration = NumCast(doc[`${Doc.LayoutDataKey(pinDoc)}_duration`], null); if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc); if (!pinProps?.audioRange && duration !== undefined) { @@ -566,7 +566,7 @@ export class TabDocView extends ObservableReactComponent { return false; }; - getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); + getCurrentFrame = () => NumCast(DocCast(PresBox.Instance.activeItem?.presentation_targetDoc)?._currentFrame); focusFunc = () => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 4dc937864..4beb75074 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -159,7 +159,7 @@ export class TreeView extends ObservableReactComponent { return this.Document[DocLayout]; } @computed get fieldKey() { - return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document)); + return StrCast(this.Document._treeView_FieldKey, Doc.LayoutDataKey(this.Document)); } @computed get childDocs() { return this.childDocList(this.fieldKey); @@ -186,7 +186,7 @@ export class TreeView extends ObservableReactComponent { moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { if (this.Document !== target && addDoc !== returnFalse) { - const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); + const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.DisableCompute(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse if (canAdd1 && this._props.removeDoc?.(doc) === true) { @@ -431,9 +431,9 @@ export class TreeView extends ObservableReactComponent { localAdd = (docs: Doc | Doc[]): boolean => { const innerAdd = (doc: Doc): boolean => { - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; + const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); + dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)!); return added; }; return toList(docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -511,9 +511,9 @@ export class TreeView extends ObservableReactComponent { const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc); const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => { const innerAdd = (iDoc: Doc) => { - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; + const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true); - dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)); + dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)!); return added; }; return toList(docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean); @@ -633,7 +633,7 @@ export class TreeView extends ObservableReactComponent { d.zIndex = i; }); } - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; + const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false); !dataIsComputed && added && Doc.SetContainer(doc, this.Document); @@ -883,8 +883,8 @@ export class TreeView extends ObservableReactComponent { ]; }; childContextMenuItems = () => { - const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []); - const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []); + const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), [])!; + const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), [])!; const icons = StrListCast(this.Document.childContextMenuIcons); return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; @@ -1296,7 +1296,7 @@ export class TreeView extends ObservableReactComponent { const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => { if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return; - const fieldKey = Doc.LayoutFieldKey(newParent); + const fieldKey = Doc.LayoutDataKey(newParent); if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { remove(child); DocumentView.SetSelectOnLoad(child); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 626538976..842293358 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -275,7 +275,7 @@ export class CollectionFreeFormView extends CollectionSubView this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined); - panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))); + panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? '')); fitContentOnce = () => { const { cx, cy, scale } = this.contentBounds(); // prettier-ignore this.layoutDoc._freeform_panX = cx; @@ -388,7 +388,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).includes(anchor); if (!anchorInCollection && !anchorInChildViews) { return undefined; @@ -1308,7 +1308,7 @@ export class CollectionFreeFormView extends CollectionSubView this._props.PanelHeight() / this.nativeDimScaling + 1e-4; switch ( !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?// @@ -1489,12 +1489,15 @@ export class CollectionFreeFormView extends CollectionSubView { const textDoc = DocCast(textBox.Document); - const newDoc = Doc.MakeCopy(textDoc, true); - newDoc['$' + Doc.LayoutFieldKey(newDoc)] = undefined; // the copy should not copy the text contents of it source, just the render style - newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10); - newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0); - DocumentView.SetSelectOnLoad(newDoc); - return this.addDocument?.(newDoc); + if (textDoc) { + const newDoc = Doc.MakeCopy(textDoc, true); + newDoc['$' + Doc.LayoutDataKey(newDoc)] = undefined; // the copy should not copy the text contents of it source, just the render style + newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10); + newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0); + DocumentView.SetSelectOnLoad(newDoc); + return this.addDocument?.(newDoc); + } + return false; }, 'copied text note'); onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => { @@ -1741,7 +1744,7 @@ export class CollectionFreeFormView extends CollectionSubView([anchor]); } @@ -1985,7 +1988,7 @@ export class CollectionFreeFormView extends CollectionSubView { - Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor); + DocCast(Doc.UserDoc().emptyCollection) && (DocCast(Doc.UserDoc().emptyCollection)!.backgroundColor = StrCast(this.layoutDoc.backgroundColor)); }, icon: 'palette', }); @@ -2085,7 +2088,7 @@ export class CollectionFreeFormView extends CollectionSubView Math.max(0, this._props.PanelHeight() - 30); lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15); onPassiveWheel = (e: WheelEvent) => { - const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight); + const docHeight = NumCast(this.Document[Doc.LayoutDataKey(this.Document) + '_nativeHeight'], this.nativeHeight); const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling; this._props.isSelected() && !scrollable && e.preventDefault(); }; @@ -2108,7 +2111,7 @@ export class CollectionFreeFormView extends CollectionSubView ); } - transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))); + transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? '')); get pannableContents() { this.incrementalRender(); // needs to happen synchronously or freshly typed text documents will flash and miss their first characters return ( @@ -2385,7 +2388,7 @@ ScriptingGlobals.add(function datavizFromSchema() { const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text'); if (!keys) return; - const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]); + const children = DocListCast(view.Document[Doc.LayoutDataKey(view.Document)]); const csvRows = []; csvRows.push(keys.join(',')); for (let i = 0; i < children.length; i++) { diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index c7aa410c6..72485aa86 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -187,7 +187,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent() { ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); })}> {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => { - const [name, type] = ImageCastToNameType(doc[Doc.LayoutFieldKey(doc)]) ?? ['-missing-', '.png']; + const [name, type] = ImageCastToNameType(doc[Doc.LayoutDataKey(doc)]) ?? ['-missing-', '.png']; return (
() { const imageInfos = this._selectedImages.map(async doc => { if (!doc.$tags_chat) { - const url = ImageCastWithSuffix(doc[Doc.LayoutFieldKey(doc)], '_o') ?? ''; + const url = ImageCastWithSuffix(doc[Doc.LayoutDataKey(doc)], '_o') ?? ''; return imageUrlToBase64(url).then(hrefBase64 => !hrefBase64 ? undefined : gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels => @@ -310,7 +310,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent() { {this._displayImageInformation ? (
{this._selectedImages.map(doc => { - const [name, type] = ImageCastToNameType(doc[Doc.LayoutFieldKey(doc)]); + const [name, type] = ImageCastToNameType(doc[Doc.LayoutDataKey(doc)]); return (
{ - const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper); + const groupButton = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyImageGrouper); if (groupButton) { this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG); ImageLabelBoxData.Instance.setData(this._selectedDocs); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 80116dd2f..8d3947653 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -102,7 +102,7 @@ export class CollectionLinearView extends CollectionSubView() { getLinkUI = () => !DocumentLinksButton.StartLink ? null : ( - e.stopPropagation()}> + e.stopPropagation()}> Creating link from:{' '} @@ -126,7 +126,7 @@ export class CollectionLinearView extends CollectionSubView() { ); getCurrentlyPlayingUI = () => !DocumentView.CurrentlyPlaying?.length ? null : ( - + Currently playing: {DocumentView.CurrentlyPlaying.map((clip, i) => ( diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 5803acca0..82ca96839 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1193,7 +1193,7 @@ export class CollectionSchemaView extends CollectionSubView() { let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0; if (notFiltered) { notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0; - const fieldKey = Doc.LayoutFieldKey(d); + const fieldKey = Doc.LayoutDataKey(d); const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List)?.some(ele => !(ele instanceof Doc)); const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey]; const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar']; @@ -1206,7 +1206,7 @@ export class CollectionSchemaView extends CollectionSubView() { newarray = []; // eslint-disable-next-line no-loop-func subDocs.forEach(t => { - const docFieldKey = Doc.LayoutFieldKey(t); + const docFieldKey = Doc.LayoutDataKey(t); const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List)?.some(ele => !(ele instanceof Doc)); notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length)); DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc)); diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 173984dc7..8e1edc1ee 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -326,7 +326,7 @@ export class SchemaImageCell extends ObservableReactComponent Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) + .map(doc => Cast(doc[Doc.LayoutDataKey(doc)], ImageField, null)?.url) .filter(url => url) .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 32afb3d3d..f4beb1004 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -76,7 +76,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole const selView = selectedViews.lastElement(); const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] || defaultBorder(); + return (contentFrameNumber !== undefined && CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey]) || defaultBorder(); } setDefaultBorder(color ?? 'transparent'); selectedViews.forEach(dv => { @@ -87,7 +87,7 @@ ScriptingGlobals.add(function setBorderColor(color?: string, checkResult?: boole obj[fieldKey] = color; CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj); } else { - const dataKey = Doc.LayoutFieldKey(dv.Document); + const dataKey = Doc.LayoutDataKey(dv.Document); const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', ''); dv.layoutDoc[fieldKey + alternate] = undefined; dv.dataDoc[fieldKey + alternate] = color; @@ -133,7 +133,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj); } else { const colorDoc = dv.isTemplateForField ? dv.layoutDoc : dv.dataDoc; // assigning to a template's compoment field should not assign to the data doc - const dataKey = Doc.LayoutFieldKey(colorDoc); + const dataKey = Doc.LayoutDataKey(colorDoc); const alternate = (dv.layoutDoc[dataKey + '_usePath'] ? '_' + dv.layoutDoc[dataKey + '_usePath'] : '').replace(':hover', ''); colorDoc[fieldKey + alternate] = color; } @@ -158,7 +158,7 @@ ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) { // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { if (checkResult) { - return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc().headingColor; + return DocumentView.Selected().length ? StrCast(DocumentView.SelectedDocs().lastElement().layout_headingColor) : Doc.SharingDoc()?.headingColor; } if (DocumentView.Selected().length) { DocumentView.SelectedDocs().forEach(doc => { @@ -166,8 +166,11 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title'); }); } else { - Doc.SharingDoc().headingColor = undefined; - Doc.GetProto(Doc.SharingDoc()).headingColor = color === 'transparent' ? undefined : color; + const sharing = Doc.SharingDoc(); + if (sharing) { + sharing.headingColor = undefined; + Doc.GetProto(sharing).headingColor = color === 'transparent' ? undefined : color; + } Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'title'); } return undefined; @@ -234,30 +237,30 @@ ScriptingGlobals.add(function showFreeform( setDoc: (doc: Doc) => { doc._freeform_useClusters = !doc._freeform_useClusters; }, }], ['time', { - checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "time", - setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "time", + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "time" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Time}, // prettier-ignore }], ['docType', { - checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "type", - setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "type", + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "type" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Type}, // prettier-ignore }], ['color', { - checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "color", - setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutFieldKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "color", + setDoc: (doc: Doc, dv: DocumentView) => { doc?.[Doc.LayoutDataKey(doc)+"_sort"] === "color" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Color}, // prettier-ignore }], ['tag', { - checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort"]) === "tag", - setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutFieldKey(doc)+"_sort"] = '' : doc[Doc.LayoutFieldKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore + checkResult: (doc: Doc) => StrCast(doc?.[Doc.LayoutDataKey(doc)+"_sort"]) === "tag", + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort"] === "tag" ? doc[Doc.LayoutDataKey(doc)+"_sort"] = '' : doc[Doc.LayoutDataKey(doc)+"_sort"] = docSortings.Tag}, // prettier-ignore }], ['reverse', { - checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutFieldKey(doc)+"_sort_reverse"]), - setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutFieldKey(doc)+"_sort_reverse"] = !doc[Doc.LayoutFieldKey(doc)+"_sort_reverse"]; }, + checkResult: (doc: Doc) => BoolCast(doc?.[Doc.LayoutDataKey(doc)+"_sort_reverse"]), + setDoc: (doc: Doc, dv: DocumentView) => { doc[Doc.LayoutDataKey(doc)+"_sort_reverse"] = !doc[Doc.LayoutDataKey(doc)+"_sort_reverse"]; }, }], ['toggle-chat', { checkResult: (doc: Doc) => SnappingManager.ChatVisible, setDoc: (doc: Doc, dv: DocumentView) => { if (SnappingManager.ChatVisible){ - doc[Doc.LayoutFieldKey(doc)+"_sort"] = ''; + doc[Doc.LayoutDataKey(doc)+"_sort"] = ''; SnappingManager.SetChatVisible(false); } else { SnappingManager.SetChatVisible(true); diff --git a/src/client/views/newlightbox/NewLightboxView.tsx b/src/client/views/newlightbox/NewLightboxView.tsx index b060fc0b6..87cef01d2 100644 --- a/src/client/views/newlightbox/NewLightboxView.tsx +++ b/src/client/views/newlightbox/NewLightboxView.tsx @@ -77,7 +77,7 @@ export class NewLightboxView extends React.Component { NewLightboxView.SetNewLightboxDoc( doc, undefined, - [...DocListCast(doc[Doc.LayoutFieldKey(doc)]), ...DocListCast(doc[Doc.LayoutFieldKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort( + [...DocListCast(doc[Doc.LayoutDataKey(doc)]), ...DocListCast(doc[Doc.LayoutDataKey(doc) + '_annotations']).filter(anno => anno.annotationOn !== doc), ...(NewLightboxView._future ?? [])].sort( (a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow) ), layoutTemplate diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index e0d59cc9d..d4c512342 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -396,7 +396,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent() { returnFalse, action(() => { const newDoc = DocUtils.GetNewTextDoc('', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); - const textField = Doc.LayoutFieldKey(newDoc); + const textField = Doc.LayoutDataKey(newDoc); const newDocData = newDoc[DocData]; newDocData[`${textField}_recordingSource`] = this.dataDoc; newDocData[`${textField}_recordingStart`] = ComputedField.MakeFunction(`this.${textField}_recordingSource.${this.fieldKey}_recordingStart`); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 53a3d3631..940c4cb99 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -71,7 +71,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames + public static animDataFields = (doc: Doc) => (Doc.LayoutDataKey(doc) ? [Doc.LayoutDataKey(doc)] : []); // fields that are configured to be animatable using animation frames public static from(dv?: DocumentView): CollectionFreeFormDocumentView | undefined { return dv?._props.reactParent instanceof CollectionFreeFormDocumentView ? dv._props.reactParent : undefined; } @@ -187,7 +187,7 @@ export class CollectionFreeFormDocumentView extends DocComponent() { const getFrom = DocCast(this.layoutDoc.dataViz_asSchema); if (!getFrom?.schema_columnKeys) return undefined; const keys = StrListCast(getFrom?.schema_columnKeys).filter(key => key !== 'text'); - const children = DocListCast(getFrom?.[Doc.LayoutFieldKey(getFrom)]); + const children = DocListCast(getFrom?.[Doc.LayoutDataKey(getFrom)]); const current: { [key: string]: string }[] = []; children .filter(child => child) diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 6f004bed3..504c1491e 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -188,8 +188,8 @@ export class DocumentContentsView extends ObservableReactComponent, onInput: Opt): JsxBindings { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d88d8c44d..521934152 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -169,9 +169,7 @@ export class DocumentViewInternal extends DocComponent { - this._mounted = true; - }); + runInAction(() => (this._mounted = true)); this.setupHandlers(); this._disposers.contentActive = reaction( () => @@ -183,16 +181,12 @@ export class DocumentViewInternal extends DocComponent { - this._isContentActive = active; - }, + active => (this._isContentActive = active), { fireImmediately: true } ); this._disposers.pointerevents = reaction( () => this.style(this.Document, StyleProp.PointerEvents) as Property.PointerEvents | undefined, - pointerevents => { - this._pointerEvents = pointerevents; - }, + pointerevents => (this._pointerEvents = pointerevents), { fireImmediately: true } ); } @@ -305,7 +299,7 @@ export class DocumentViewInternal extends DocComponent (this.onClickFunc?.()?.script.run(scriptProps, console.log).result as Opt<{ select: boolean }>)?.select && this._props.select(false) : undefined; if (!clickFunc) { - // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part + // onDragStart implies a button doc that we don't want to select when clicking. if (this.layoutDoc.onDragStart && !(e.ctrlKey || e.button > 0)) stopPropagate = false; preventDefault = false; } @@ -502,7 +496,7 @@ export class DocumentViewInternal extends DocComponent ContextMenu.Instance.addItem(item)); - const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), []); + const customScripts = Cast(this.Document.contextMenuScripts, listSpec(ScriptField), [])!; StrListCast(this.Document.contextMenuLabels).forEach((label, i) => cm.addItem({ description: label, event: () => customScripts[i]?.script.run({ documentView: this, this: this.Document, scriptContext: this._props.scriptContext }), icon: 'sticky-note' }) ); @@ -536,9 +530,7 @@ export class DocumentViewInternal extends DocComponent { - this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged; - }), + action(() => (this.layoutDoc._keepZWhenDragged = !this.layoutDoc._keepZWhenDragged)), 'set zIndex drag' ), icon: 'hand-point-up', @@ -663,7 +655,7 @@ export class DocumentViewInternal extends DocComponent (this.disableClickScriptFunc ? undefined : this.onClickHdlr); setHeight = (height: number) => { !this._props.suppressSetHeight && (this.layoutDoc._height = Math.min(NumCast(this.layoutDoc._maxHeight, Number.MAX_SAFE_INTEGER), height + 2 * NumCast(this.Document.borderWidth))); } // prettier-ignore - setContentView = action((view: ViewBoxInterface) => { this._componentView = view; }); // prettier-ignore + setContentView = action((view: ViewBoxInterface) => (this._componentView = view)); isContentActive = (): boolean | undefined => this._isContentActive; childFilters = () => [...this._props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; @@ -763,6 +755,7 @@ export class DocumentViewInternal extends DocComponent (this.widgetDecorations ? this.widgetOverlay : null); viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null); + _contentsRef = React.createRef(); @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; @@ -778,6 +771,7 @@ export class DocumentViewInternal extends DocComponent , props: Opt, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); fieldsDropdown = (placeholder: string) => (
{ r && runInAction(() => (this._titleDropDownInnerWidth = DivWidth(r)));}} // prettier-ignore - onPointerDown={action(() => { this._changingTitleField = true; })} // prettier-ignore + ref={action((r:HTMLDivElement|null) => r && (this._titleDropDownInnerWidth = DivWidth(r)))} // prettier-ignore + onPointerDown={action(() => (this._changingTitleField = true))} style={{ width: 'max-content', background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> { this._changingTitleField = false; })} // prettier-ignore + menuClose={action(() => (this._changingTitleField = false))} />
); @@ -842,7 +836,7 @@ export class DocumentViewInternal extends DocComponent u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, - StrCast(Doc.SharingDoc().headingColor, SnappingManager.userBackgroundColor) + StrCast(Doc.SharingDoc()?.headingColor, SnappingManager.userBackgroundColor) // ) ); const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._titleDropDownInnerWidth * this.titleHeight) / 30) : 0; @@ -1028,7 +1022,7 @@ export class DocumentViewInternal extends DocComponent() { public static GetDocImage(doc?: Doc) { return DocumentView.getDocumentView(doc) ?.ComponentView?.updateIcon?.() - .then(() => ImageCast(doc!.icon, ImageCast(doc![Doc.LayoutFieldKey(doc!)]))); + .then(() => ImageCast(doc!.icon, ImageCast(doc![Doc.LayoutDataKey(doc!)]))); } public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore @@ -1181,6 +1175,24 @@ export class DocumentView extends DocComponent() { @observable public TagPanelHeight = 0; @observable public TagPanelEditing = false; + /** + * Tests whether the component Doc being rendered matches the Doc that this view thinks its rendering. + * When switching the layout_fieldKey, component views may react before the internal DocumentView has re-rendered. + * If this happens, the component view may try to write invalid data, such as when expanding a template (which + * depends on the DocumentView being in synch with the subcomponents). + * @param renderDoc sub-component Doc being rendered + * @returns boolean whether sub-component Doc is in synch with the layoutDoc that this view thinks its rendering + */ + IsInvalid = (renderDoc?: Doc): boolean => { + const docContents = this._docViewInternal?._contentsRef.current; + return !( + (!renderDoc || + (docContents?.layoutDoc === renderDoc && // + !this.docViewPath().some(dv => dv.IsInvalid()))) && + docContents?._props.layoutFieldKey === this.Document.layout_fieldKey + ); + }; + @computed get showTags() { return this.Document._layout_showTags || this._props.showTags; } @@ -1250,7 +1262,7 @@ export class DocumentView extends DocComponent() { !BoolCast(this.Document.dontRegisterView, this._props.dontRegisterView) && DocumentView.removeView(this); } - public set IsSelected(val) { runInAction(() => { this._selected = val; }); } // prettier-ignore + public set IsSelected(val) { runInAction(() => (this._selected = val)) } // prettier-ignore public get IsSelected() { return this._selected; } // prettier-ignore public get IsContentActive(){ return this._docViewInternal?.isContentActive(); } // prettier-ignore public get topMost() { return this._props.renderDepth === 0; } // prettier-ignore @@ -1262,7 +1274,7 @@ export class DocumentView extends DocComponent() { public get HasAIEditor() { return !!this._docViewInternal?._componentView?.componentAIView?.(); } // prettier-ignore get LayoutFieldKey() { - return Doc.LayoutFieldKey(this.Document, this._props.LayoutTemplateString); + return Doc.LayoutDataKey(this.Document, this._props.LayoutTemplateString); } @computed get layout_fitWidth() { @@ -1316,10 +1328,10 @@ export class DocumentView extends DocComponent() { public toggleNativeDimensions = () => this._docViewInternal && this.Document.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.NativeDimScaling() ?? 1, this._props.PanelWidth(), this._props.PanelHeight()); - public iconify(finished?: () => void, animateTime?: number) { + public iconify = action((finished?: () => void, animateTime?: number) => { this.ComponentView?.updateIcon?.(); const animTime = this._docViewInternal?.animateScaleTime(); - runInAction(() => { this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); }); // prettier-ignore + this._docViewInternal && animateTime !== undefined && (this._docViewInternal._animateScaleTime = animateTime); const finalFinished = action(() => { finished?.(); this._docViewInternal && (this._docViewInternal._animateScaleTime = animTime); @@ -1329,12 +1341,12 @@ export class DocumentView extends DocComponent() { this.switchViews(true, 'icon', finalFinished); if (layoutFieldKey && layoutFieldKey !== 'layout' && layoutFieldKey !== 'layout_icon') this.Document.deiconifyLayout = layoutFieldKey.replace('layout_', ''); } else { - const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null); + const deiconifyLayout = StrCast(this.Document.deiconifyLayout); this.switchViews(!!deiconifyLayout, deiconifyLayout, finalFinished, true); this.Document.deiconifyLayout = undefined; this._props.bringToFront?.(this.Document); } - } + }); public playAnnotation = () => { const audioAnnoState = this.Document._audioAnnoState ?? AudioAnnoState.stopped; @@ -1349,7 +1361,7 @@ export class DocumentView extends DocComponent() { autoplay: true, loop: false, volume: 0.5, - onend: action(() => { this.Document._audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore + onend: action(() => (this.Document._audioAnnoState = AudioAnnoState.stopped)), }); this.Document._audioAnnoState = AudioAnnoState.playing; break; @@ -1405,14 +1417,10 @@ export class DocumentView extends DocComponent() { tempDoc = view.Document; MakeTemplate(tempDoc); Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc); - Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc)); - tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc); + Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(tempDoc)); + DocCast(Doc.UserDoc().template_user) && tempDoc && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', tempDoc); } else { - tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]); - if (!tempDoc) { - tempDoc = view.Document; - while (tempDoc && !Doc.isTemplateDoc(tempDoc)) tempDoc = DocCast(tempDoc.proto); - } + tempDoc = DocCast(Doc.LayoutField(view.Document)); } } Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined; @@ -1434,10 +1442,10 @@ export class DocumentView extends DocComponent() { if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(!!defaultLayout, defaultLayout, undefined, true); else this.switchViews(true, detailLayoutKeySuffix, undefined, true); }; - public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { + public switchViews = action((custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { const batch = UndoManager.StartBatch('switchView:' + view); // shrink doc first.. - runInAction(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); }); // prettier-ignore + this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1); setTimeout( action(() => { if (useExistingLayout && custom && this.Document['layout_' + view]) { @@ -1457,7 +1465,7 @@ export class DocumentView extends DocComponent() { }), Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10) ); - }; + }); /** * @returns a hierarchy path through the nested DocumentViews that display this view. The last element of the path is this view. */ @@ -1510,7 +1518,7 @@ export class DocumentView extends DocComponent() { ref={r => { const val = r?.style.display !== 'none'; // if the outer overlay has been displayed, trigger the innner div to start it's opacity fade in transition if (r && val !== this._enableHtmlOverlayTransitions) { - setTimeout(action(() => { this._enableHtmlOverlayTransitions = val; })); // prettier-ignore + setTimeout(action(() => (this._enableHtmlOverlayTransitions = val))); } }} style={{ display: !this._htmlOverlayText ? 'none' : undefined }}> @@ -1537,12 +1545,8 @@ export class DocumentView extends DocComponent() {
{ - this._isHovering = true; - })} - onPointerLeave={action(() => { - this._isHovering = false; - })}> + onPointerEnter={action(() => (this._isHovering = true))} // + onPointerLeave={action(() => (this._isHovering = false))}> {!this.Document || !this._props.PanelWidth() ? null : (
() { fitWidth={this.layout_fitWidthFunc} ScreenToLocalTransform={this.screenToContentsTransform} focus={this._props.focus || emptyFunction} - ref={action((r: DocumentViewInternal | null) => { - r && (this._docViewInternal = r); - })} + ref={action((r: DocumentViewInternal | null) => r && (this._docViewInternal = r))} /> {this.htmlOverlay()} {this.ComponentView?.infoUI?.()} @@ -1616,7 +1618,7 @@ export class DocumentView extends DocComponent() { 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 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(() => { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index ad6d93d43..de49f502f 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -80,7 +80,7 @@ export interface FieldViewSharedProps { setTitleFocus?: () => void; focus: FocusFuncType; onClickScript?: () => ScriptField | undefined; - onDoubleClickScript?: () => ScriptField; + onDoubleClickScript?: () => ScriptField | undefined; onPointerDownScript?: () => ScriptField; onPointerUpScript?: () => ScriptField; onKey?: (e: KeyboardEvent, textBox: FormattedTextBox) => boolean | undefined; diff --git a/src/client/views/nodes/IconTagBox.tsx b/src/client/views/nodes/IconTagBox.tsx index e3924eca7..0bbd6a0d3 100644 --- a/src/client/views/nodes/IconTagBox.tsx +++ b/src/client/views/nodes/IconTagBox.tsx @@ -108,7 +108,7 @@ export class IconTagBox extends ObservableReactComponent { )); // prettier-ignore - const audioannos = StrListCast(this.View.Document[Doc.LayoutFieldKey(this.View.Document) + '_audioAnnotations_text']); + const audioannos = StrListCast(this.View.Document[Doc.LayoutDataKey(this.View.Document) + '_audioAnnotations_text']); return !buttons.length && !audioannos.length ? null : (
{audioannos.length ? this.renderAudioButtons(this.View, audioannos.lastElement()) : null} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 2b9a78596..0c475b7bb 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -216,7 +216,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { } else if (hitDropTarget(e.target as HTMLElement, this._regenerateIconRef.current)) { this._regenerateLoading = true; const drag = de.complete.docDragData.draggedDocuments.lastElement(); - const dragField = drag[Doc.LayoutFieldKey(drag)]; + const dragField = drag[Doc.LayoutDataKey(drag)]; const descText = RTFCast(dragField)?.Text || StrCast(dragField) || RTFCast(drag.text)?.Text || StrCast(drag.text) || StrCast(this.Document.title); const oldPrompt = StrCast(this.Document.ai_firefly_prompt, StrCast(this.Document.title)); const newPrompt = (text: string) => (oldPrompt ? `${oldPrompt} ~~~ ${text}` : text); @@ -224,7 +224,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { added = false; } else if (de.altKey || !this.dataDoc[this.fieldKey]) { const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; - const targetField = Doc.LayoutFieldKey(layoutDoc); + const targetField = Doc.LayoutDataKey(layoutDoc); const targetDoc = layoutDoc[DocData]; if (targetDoc[targetField] instanceof ImageField) { added = true; @@ -264,7 +264,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { DocListCast(this.dataDoc[this.annotationKey]).forEach(doc => { doc.x = (NumCast(doc.x) / oldnativeWidth) * newnativeWidth; doc.y = (NumCast(doc.y) / oldnativeWidth) * newnativeWidth; - if (!RTFCast(doc[Doc.LayoutFieldKey(doc)])) { + if (!RTFCast(doc[Doc.LayoutDataKey(doc)])) { doc.width = (NumCast(doc.width) / oldnativeWidth) * newnativeWidth; doc.height = (NumCast(doc.height) / oldnativeWidth) * newnativeWidth; } @@ -351,9 +351,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { event: () => { Networking.PostToServer('/queryFireflyImageText', { file: (file => { - const ext = extname(file); - return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); - })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href), + const ext = file ? extname(file) : ''; + return file?.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); + })(ImageCast(this.Document[Doc.LayoutDataKey(this.Document)])?.url.href), }).then(text => alert(text)); }, icon: 'expand-arrows-alt', @@ -364,9 +364,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { Networking.PostToServer('/expandImage', { prompt: 'sunny skies', file: (file => { - const ext = extname(file); - return file.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); - })(ImageCast(this.Document[Doc.LayoutFieldKey(this.Document)])?.url.href), + const ext = file ? extname(file) : ''; + return file?.replace(ext, (this._error ? '_o' : this._curSuffix) + ext); + })(ImageCast(this.Document[Doc.LayoutDataKey(this.Document)])?.url.href), }).then(res => { const info = res as Upload.ImageInformation; const img = Docs.Create.ImageDocument(info.accessPaths.agnostic.client, { title: 'expand:' + this.Document.title }); @@ -510,7 +510,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() {
DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs), { openLocation: OpenWhere.addRight })} + onClick={() => DocCast(this.Document.ai_firefly_generatedDocs) && DocumentView.showDocument(DocCast(this.Document.ai_firefly_generatedDocs)!, { openLocation: OpenWhere.addRight })} style={{ display: (this._props.isContentActive() && (SnappingManager.CanEmbed || this.Document.ai_firefly_generatedDocs)) || this._regenerateLoading ? 'block' : 'none', transform: `scale(${this.uiBtnScaling})`, @@ -532,7 +532,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const defaultUrl = new URL(ClientUtils.prepend(DefaultPath)); const altpaths = alts - ?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url ?? defaultUrl) : defaultUrl)) + ?.map(doc => (doc instanceof Doc ? (ImageCast(doc[Doc.LayoutDataKey(doc)])?.url ?? defaultUrl) : defaultUrl)) .filter(url => url) .map(url => this.choosePath(url)) ?? []; // acc ess the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; @@ -664,7 +664,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent() { const url = firstImg.pathname; const imgField = new ImageField(url); this._prevImgs.length === 0 && - this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field.url.pathname }); + this._prevImgs.push({ prompt: StrCast(this.dataDoc.ai_firefly_prompt), seed: this.dataDoc.ai_firefly_seed as number, href: this.paths.lastElement(), pathname: field?.url.pathname ?? '' }); this._prevImgs.unshift({ prompt: firstImg.prompt, seed: firstImg.seed, pathname: url }); this.dataDoc.ai_firefly_history = JSON.stringify(this._prevImgs); this.dataDoc.ai_firefly_prompt = firstImg.prompt; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index be897b3f3..706607fe1 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -96,7 +96,7 @@ export class KeyValueBox extends ViewBoxBaseComponent() { const { script, type, onDelegate } = kvpScript; const chooseDelegate = forceOnDelegate || onDelegate || keyIn.startsWith('_'); const key = chooseDelegate && keyIn.startsWith('$') ? keyIn.slice(1) : keyIn; - const target = chooseDelegate ? doc : DocCast(doc.proto, doc); + const target = chooseDelegate ? doc : DocCast(doc.proto, doc)!; let field: FieldType | undefined; switch (type) { case 'computed': field = new ComputedField(script); break; // prettier-ignore @@ -261,15 +261,15 @@ export class KeyValueBox extends ViewBoxBaseComponent() { getFieldView = () => { const rows = this.rows.filter(row => row.isChecked); if (rows.length > 1) { - const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.Document).title}`, _chromeHidden: true }); + const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${this.Document.title}`, _chromeHidden: true }); rows.forEach(row => { - const field = this.createFieldView(DocCast(this.Document), row); + const field = this.createFieldView(this.Document, row); field && Doc.AddDocToList(parent, 'data', field); row.uncheck(); }); return parent; } - return rows.length ? this.createFieldView(DocCast(this.Document), rows.lastElement()) : undefined; + return rows.length ? this.createFieldView(this.Document, rows.lastElement()) : undefined; }; createFieldView = (templateDoc: Doc, row: KeyValuePair) => { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index c9e0aea5a..d8f968a40 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -61,7 +61,7 @@ export class KeyValuePair extends ObservableReactComponent { }; render() { - let doc = this._props.keyName.startsWith('_') ? this._props.doc[DocLayout] : this._props.doc; + let doc: Doc | undefined = this._props.keyName.startsWith('_') ? this._props.doc[DocLayout] : this._props.doc; const layoutField = doc !== this._props.doc; const key = layoutField ? this._props.keyName.replace(/^_/, '') : this._props.keyName; let protoCount = 0; @@ -85,7 +85,7 @@ export class KeyValuePair extends ObservableReactComponent { style={hover} className="keyValuePair-td-key-delete" onClick={undoable(() => { - delete (Object.keys(doc).indexOf(key) !== -1 ? doc : DocCast(this._props.doc.proto))[key]; + doc && delete (Object.keys(doc).indexOf(key) !== -1 ? doc : DocCast(this._props.doc.proto)!)[key]; }, 'set key value')}> X diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 36d260fb9..83c44c80f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -210,7 +210,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent() { this._disposers.scroll = reaction( () => this.layoutDoc.layout_scrollTop, () => { - if (!(ComputedField.WithoutComputed(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) { + if (!(ComputedField.DisableCompute(() => FieldValue(this.Document[this.SidebarKey + '_panY'])) instanceof ComputedField)) { this.Document[this.SidebarKey + '_panY'] = ComputedField.MakeFunction('this.layout_scrollTop'); } this.layoutDoc[this.SidebarKey + '_freeform_scale'] = 1; diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 53783e8a3..25c708da8 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -98,7 +98,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { }); screengrabber.overlayX = 70; // was -400 screengrabber.overlayY = 590; // was 0 - screengrabber['$' + Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true; + screengrabber['$' + Doc.LayoutDataKey(screengrabber) + '_trackScreen'] = true; Doc.AddToMyOverlay(screengrabber); // just adds doc to overlay DocumentView.addViewRenderedCb(screengrabber, docView => { RecordingBox.screengrabber = docView.ComponentView as RecordingBox; diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 999f9c1cd..4f02d68d6 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -284,7 +284,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent() setupDictation = () => { if (this.dataDoc[this.fieldKey + '_dictation']) return; const dictationText = DocUtils.GetNewTextDoc('dictation', NumCast(this.Document.x), NumCast(this.Document.y) + NumCast(this.layoutDoc._height) + 10, NumCast(this.layoutDoc._width), 2 * NumCast(this.layoutDoc._height)); - const textField = Doc.LayoutFieldKey(dictationText); + const textField = Doc.LayoutDataKey(dictationText); dictationText._layout_autoHeight = false; const dictationTextProto = dictationText[DocData]; dictationTextProto[`${textField}_recordingSource`] = this.dataDoc; diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 8da422039..38a43e8d4 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -1,14 +1,12 @@ -/* eslint-disable react/button-has-type */ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ResizeObserver from 'resize-observer-polyfill'; import { returnAlways, returnEmptyString } from '../../../ClientUtils'; -import { Doc } from '../../../fields/Doc'; +import { Doc, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; -import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; @@ -26,10 +24,8 @@ import './ScriptingBox.scss'; import * as ts from 'typescript'; import { FieldType } from '../../../fields/ObjectField'; -// eslint-disable-next-line @typescript-eslint/no-var-requires const getCaretCoordinates = require('textarea-caret'); -// eslint-disable-next-line @typescript-eslint/no-var-requires const ReactTextareaAutocomplete = require('@webscopeio/react-textarea-autocomplete').default; @observer @@ -105,7 +101,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent() this.dataDoc[this.fieldKey + '-functionDescription'] = value; } @computed({ keepAlive: true }) get compileParams() { - return Cast(this.dataDoc[this.fieldKey + '-params'], listSpec('string'), []); + return StrListCast(this.dataDoc[this.fieldKey + '-params']); } set compileParams(value) { this.dataDoc[this.fieldKey + '-params'] = new List(value); @@ -426,7 +422,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent() onChange={e => this.viewChanged(e, parameter)} value={typeof this.dataDoc[parameter] === 'string' ? 'S' + StrCast(this.dataDoc[parameter]) : typeof this.dataDoc[parameter] === 'number' ? 'N' + NumCast(this.dataDoc[parameter]) : 'B' + BoolCast(this.dataDoc[parameter])}> {types.map((type, i) => ( - // eslint-disable-next-line react/no-array-index-key
}> -
- e.stopPropagation()} /> -
- - ); - items.push( - Customize Slide
}> -
{ - this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false); - PresBox.Instance.navigateToActiveItem(); - PresBox.Instance.openProperties(); - PresBox.Instance.slideToModify = this.Document; - }}> - e.stopPropagation()} /> -
- - ); - return items; - } - - @computed get mainItem() { - const { presBox, slideDoc: activeItem } = this; - const isSelected: boolean = !!this.selectedArray?.has(activeItem); - const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres; - const miniView: boolean = this.toolbarWidth <= 110; - const presBoxColor: string = StrCast(presBox?._backgroundColor); - const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; - - return ( -
{ - this.toggleProperties(); - this.presBoxView?.regularSelect(activeItem, this._itemRef.current!, this._dragRef.current!, false); - })} - onPointerOver={this.onPointerOver} - onPointerLeave={this.onPointerLeave} - onPointerDown={this.headerDown}> - {miniView ? ( -
- {`${this.indexInPres + 1}.`} -
- ) : ( -
-
-
{ - e.stopPropagation(); - if (this._itemRef.current && this._dragRef.current) { - this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, true, false, false); - } - }} - onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}. `}
- StrCast(activeItem.title)} SetValue={this.onSetValue} /> -
- {/*
{"Movement speed"}
}>
{this.transition}
*/} - {/*
{"Duration"}
}>
{this.duration}
*/} -
- {...this.presButtons} -
- {this.renderEmbeddedInline} -
- )} -
- ); - } - - render() { - return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem; - } -} - -Docs.Prototypes.TemplateMap.set(DocumentType.PRESELEMENT, { - layout: { view: PresElementBox, dataField: 'data' }, - options: { acl: '', title: 'pres element template', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true, isTemplateForField: 'data' }, -}); diff --git a/src/client/views/nodes/trails/PresSlideBox.scss b/src/client/views/nodes/trails/PresSlideBox.scss new file mode 100644 index 000000000..9ac2b5a94 --- /dev/null +++ b/src/client/views/nodes/trails/PresSlideBox.scss @@ -0,0 +1,308 @@ +$light-blue: #aeddf8; +$dark-blue: #5b9fdd; +$light-background: #ececec; +$slide-background: #d5dce2; +$slide-active: #5b9fdd; + +.testingv2 { + background-color: red; +} + +.presItem-container { + cursor: grab; + display: flex; + grid-template-columns: 20px auto; + font-family: Roboto; + letter-spacing: normal; + position: relative; + pointer-events: all; + width: 100%; + height: 100%; + font-weight: 400; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + align-items: center; + + // .presItem-number { + // margin-top: 3.5px; + // font-size: 12px; + // font-weight: 700; + // text-align: center; + // justify-self: center; + // align-self: flex-start; + // position: relative; + // display: inline-block; + // overflow: hidden; + // } +} + +.presItem-slide { + position: relative; + height: 100%; + width: 100%; + border-bottom: 0.5px solid grey; + display: flex; + align-items: center; + + .presItem-number { + cursor: pointer; + &:hover { + background-color: $light-blue; + } + } + .presItem-name { + display: flex; + min-width: 20px; + z-index: 300; + top: 2px; + align-self: center; + font-size: 11px; + font-family: Roboto; + font-weight: 500; + position: relative; + padding-left: 10px; + padding-right: 10px; + letter-spacing: normal; + width: max-content; + text-overflow: ellipsis; + overflow: hidden; + white-space: pre; + } + + .presItem-docName { + min-width: 20px; + z-index: 300; + align-self: center; + font-size: 9px; + font-family: Roboto; + font-weight: 300; + position: relative; + padding-left: 10px; + padding-right: 10px; + letter-spacing: normal; + width: max-content; + text-overflow: ellipsis; + overflow: hidden; + white-space: pre; + grid-row: 2; + grid-column: 1/6; + } + + .presItem-time { + align-self: center; + position: relative; + padding-right: 10px; + top: 1px; + font-size: 10; + font-weight: 300; + font-family: Roboto; + z-index: 300; + letter-spacing: normal; + } + + .presItem-embedded { + overflow: hidden; + grid-row: 3; + grid-column: 1/8; + position: relative; + display: inline-block; + } + + .presItem-embeddedMask { + width: 100%; + height: 100%; + position: absolute; + border-radius: 3px; + top: 0; + left: 0; + z-index: 1; + overflow: hidden; + } + + .presItem-slideButtons { + display: flex; + position: absolute; + width: max-content; + justify-self: right; + justify-content: flex-end; + + .slideButton { + cursor: pointer; + position: relative; + border-radius: 100px; + z-index: 300; + width: 18px; + height: 18px; + display: flex; + font-size: 12px; + justify-self: center; + align-self: center; + background-color: rgba(0, 0, 0, 0.5); + color: white; + justify-content: center; + align-items: center; + transition: 0.2s; + margin-right: 3px; + } + + .slideButton:hover { + background-color: rgba(0, 0, 0, 1); + transform: scale(1.2); + } + } +} + +// .presItem-slide:hover { +// .presItem-slideButtons { +// display: flex; +// grid-column: 7; +// grid-row: 1/3; +// width: max-content; +// justify-self: right; +// justify-content: flex-end; + +// .slideButton { +// cursor: pointer; +// position: relative; +// border-radius: 100px; +// z-index: 300; +// width: 18px; +// height: 18px; +// display: flex; +// font-size: 12px; +// justify-self: center; +// align-self: center; +// background-color: rgba(0, 0, 0, 0.5); +// color: white; +// justify-content: center; +// align-items: center; +// transition: 0.2s; +// margin-right: 3px; +// } + +// .slideButton:hover { +// background-color: rgba(0, 0, 0, 1); +// transform: scale(1.2); +// } +// } +// } + +.presItem-slide.active { + //box-shadow: 0 0 0px 2.5px $dark-blue; + border: $dark-blue solid 2.5px; +} + +.presItem-slide.group { + border-radius: 5px; +} + +.presItem-slide.activeGroup { + border-radius: 5px; + box-shadow: 0 0 0px 2.5px $dark-blue; +} + +.presItem-groupSlideContainer { + position: absolute; + /* grid-row: 3; */ + /* grid-column: 1/8; */ + top: 28; + display: block; + background: #92adb9; + width: 100%; + height: 10px; + border-radius: 0px 0px 5px 5px; + height: calc(100% - 28px); + display: grid; + grid-template-rows: auto auto auto; + grid-template-columns: 100%; + justify-items: left; + align-items: center; +} + +.presItem-groupSlide { + position: relative; + background-color: #d5dce2; + border-radius: 5px; + height: calc(100% - 7px); + width: calc(100% - 20px); + left: 15px; + /* height: 20px; */ + /* width: calc(100% - 19px); */ + display: flex; + grid-template-rows: 16px 10px auto; + grid-template-columns: max-content max-content max-content max-content auto; +} + +.presItem-multiDrag { + font-family: Roboto; + font-weight: 600; + color: white; + text-align: center; + justify-content: center; + align-content: center; + width: 100px; + height: 30px; + position: absolute; + background-color: $dark-blue; + z-index: 4000; + border-radius: 10px; + box-shadow: black 0.4vw 0.4vw 0.8vw; + line-height: 30px; +} + +.presItem-miniSlide { + font-weight: 700; + font-size: 12; + grid-column: 1/8; + align-self: center; + justify-self: center; + background-color: #d5dce2; + width: 26px; + text-align: center; + height: 26px; + line-height: 28px; + border-radius: 100%; +} + +.presItem-miniSlide.active { + box-shadow: 0 0 0px 1.5px $dark-blue; +} + +.expandButton { + cursor: pointer; + position: absolute; + border-radius: 100px; + bottom: 0; + left: -18; + z-index: 300; + width: 15px; + height: 15px; + display: flex; + font-size: 12px; + justify-self: center; + align-self: center; + background-color: #92adb9; + color: white; + justify-content: center; + align-items: center; + transition: 0.2s; + margin-right: 3px; +} + +.expandButton:hover { + background-color: rgba(0, 0, 0, 1); + transform: scale(1.2); +} + +.presItem-groupNum { + color: #d5dce2; + position: absolute; + left: -15px; + top: 1; + font-weight: 600; + font-size: 12; +} diff --git a/src/client/views/nodes/trails/PresSlideBox.tsx b/src/client/views/nodes/trails/PresSlideBox.tsx new file mode 100644 index 000000000..3dbb3da88 --- /dev/null +++ b/src/client/views/nodes/trails/PresSlideBox.tsx @@ -0,0 +1,628 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Tooltip } from '@mui/material'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { returnFalse, returnTrue, setupMoveUpEvents } from '../../../../ClientUtils'; +import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; +import { Id } from '../../../../fields/FieldSymbols'; +import { List } from '../../../../fields/List'; +import { BoolCast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; +import { emptyFunction } from '../../../../Utils'; +import { Docs } from '../../../documents/Documents'; +import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { DragManager } from '../../../util/DragManager'; +import { SnappingManager } from '../../../util/SnappingManager'; +import { Transform } from '../../../util/Transform'; +import { undoable, undoBatch } from '../../../util/UndoManager'; +import { TreeView } from '../../collections/TreeView'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { EditableView } from '../../EditableView'; +import { Colors } from '../../global/globalEnums'; +import { PinDocView } from '../../PinFuncs'; +import { StyleProp } from '../../StyleProp'; +import { returnEmptyDocViewList } from '../../StyleProvider'; +import { DocumentView } from '../DocumentView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { PresBox } from './PresBox'; +import './PresSlideBox.scss'; +import { PresMovement } from './PresEnums'; +/** + * This class models the view a document added to presentation will have in the presentation. + * It involves some functionality for its buttons and options. + */ +@observer +export class PresSlideBox extends ViewBoxBaseComponent() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(PresSlideBox, fieldKey); + } + private _itemRef: React.RefObject = React.createRef(); + private _dragRef: React.RefObject = React.createRef(); + private _titleRef: React.RefObject = React.createRef(); + private _heightDisposer: IReactionDisposer | undefined; + readonly expandViewHeight = 100; + readonly collapsedHeight = 35; + + constructor(props: FieldViewProps) { + super(props); + makeObservable(this); + } + + @observable _dragging = false; + + // the presentation view that renders this slide + @computed get presBoxView() { + return this.DocumentView?.() + .containerViewPath?.() + .slice() + .reverse() + .find(dv => dv?.ComponentView instanceof PresBox)?.ComponentView as Opt; + } + + // the presentation view document that renders this slide + @computed get presBox() { + return this.presBoxView?.Document; + } + + // Since this node is being rendered with a template, this method retrieves + // the actual slide being rendered from the auto-generated rendering template + @computed get slideDoc() { + return this.rootDoc; + } + + // this is the document in the workspaces that is targeted by the slide + @computed get targetDoc() { + return DocCast(this.slideDoc.presentation_targetDoc, this.slideDoc)!; + } + + // computes index of this presentation slide in the presBox list + @computed get indexInPres() { + return this.presBoxView?.SlideIndex(this.slideDoc) ?? 0; + } + + @computed get selectedArray() { + return this.presBoxView?.selectedArray; + } + + @computed get videoRecordingIsInOverlay() { + return Doc.MyOverlayDocs.some(doc => doc.slides === this.slideDoc); + } + + componentDidMount() { + this.layoutDoc.layout_hideLinkButton = true; + this._heightDisposer = reaction( + () => ({ expand: this.slideDoc.presentation_expandInlineButton, height: this.collapsedHeight }), + ({ expand, height }) => { + this.layoutDoc._height = height + (expand ? this.expandViewHeight : 0); + }, + { fireImmediately: true } + ); + } + componentWillUnmount() { + this._heightDisposer?.(); + } + + presExpandDocumentClick = () => { + this.slideDoc.presentation_expandInlineButton = !this.slideDoc.presentation_expandInlineButton; + }; + embedHeight = () => this.collapsedHeight + this.expandViewHeight; + embedWidth = () => this._props.PanelWidth() / 2; + // prettier-ignore + styleProvider = ( doc: Doc | undefined, props: Opt, property: string ) => + (property === StyleProp.Opacity ? 1 : this._props.styleProvider?.(doc, props, property)); + /** + * The function that is responsible for rendering a preview or not for this + * presentation element. + */ + @computed get renderEmbeddedInline() { + return !this.slideDoc.presentation_expandInlineButton || !this.targetDoc ? null : ( +
+ +
+ ); + } + + @computed get renderGroupSlides() { + const childDocs = DocListCast(this.targetDoc.data); + const groupSlides = childDocs.map((doc: Doc, ind: number) => ( +
{ + e.stopPropagation(); + e.preventDefault(); + this.presBoxView?.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, e.shiftKey || e.ctrlKey || e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); + this.presExpandDocumentClick(); + }}> +
{`${ind + 1}.`}
+
+ StrCast(doc.title)} + SetValue={(value: string) => { + doc.title = !value.trim().length ? '-untitled-' : value; + return true; + }} + /> +
+
+ )); + return groupSlides; + } + + @computed get transition() { + let transitionInS: number; + if (this.slideDoc.presentation_transition) transitionInS = NumCast(this.slideDoc.presentation_transition) / 1000; + else transitionInS = 0.5; + return this.slideDoc.presentation_movement === PresMovement.Jump || this.slideDoc.presentation_movement === PresMovement.None ? null : 'M: ' + transitionInS + 's'; + } + + @action + headerDown = (e: React.PointerEvent) => { + const element = e.target as HTMLDivElement; + e.stopPropagation(); + e.preventDefault(); + if (element && !(e.ctrlKey || e.metaKey || e.button === 2)) { + this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false); + setupMoveUpEvents(this, e, this.startDrag, emptyFunction, clickEv => { + clickEv.stopPropagation(); + clickEv.preventDefault(); + this.presBoxView?.modifierSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, clickEv.shiftKey || clickEv.ctrlKey || clickEv.metaKey, clickEv.ctrlKey || clickEv.metaKey, clickEv.shiftKey); + this.presBoxView?.activeItem && this.showRecording(this.presBoxView?.activeItem); + }); + } + }; + + /** + * Function to drag and drop the pres element to a diferent location + */ + startDrag = (e: PointerEvent) => { + this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false); + const miniView: boolean = this.toolbarWidth <= 100; + const activeItem = this.slideDoc; + const dragArray = this.presBoxView?._dragArray ?? []; + const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []); + if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.slideDoc); + dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.DocumentView?.()?._props.treeViewDoc; + dragData.moveDocument = this._props.moveDocument; + const dragItem: HTMLElement[] = []; + const classesToRestore = new Map(); + if (dragArray.length === 1) { + const doc = this._itemRef.current || dragArray[0]; + if (doc) { + classesToRestore.set(doc, doc.className); + doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide'; + dragItem.push(doc); + } + } else if (dragArray.length >= 1) { + const doc = document.createElement('div'); + doc.className = 'presItem-multiDrag'; + doc.innerText = 'Move ' + (this.selectedArray?.size ?? 0) + ' slides'; + doc.style.position = 'absolute'; + doc.style.top = e.clientY + 'px'; + doc.style.left = e.clientX - 50 + 'px'; + dragItem.push(doc); + } + + if (activeItem) { + runInAction(() => { + this._dragging = true; + }); + DragManager.StartDocumentDrag( + dragItem.map(ele => ele), + dragData, + e.clientX, + e.clientY, + undefined, + action(() => { + Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1])); + this._dragging = false; + }) + ); + return true; + } + return false; + }; + + onPointerOver = () => { + document.removeEventListener('pointermove', this.onPointerMove); + document.addEventListener('pointermove', this.onPointerMove); + }; + + onPointerMove = (e: PointerEvent) => { + const slide = this._itemRef.current; + const dragIsPresItem = DragManager.docsBeingDragged.some(d => d.presentation_targetDoc); + if (slide && dragIsPresItem) { + const rect = slide.getBoundingClientRect(); + const y = e.clientY - rect.top; // y position within the element. + const height = slide.clientHeight; + const halfLine = height / 2; + if (y <= halfLine) { + slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`; + slide.style.borderBottom = '0px'; + } else if (y > halfLine) { + slide.style.borderTop = '0px'; + slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`; + } + } + document.removeEventListener('pointermove', this.onPointerMove); + }; + + onPointerLeave = () => { + const slide = this._itemRef.current; + if (slide) { + slide.style.borderTop = '0px'; + slide.style.borderBottom = '0px'; + } + document.removeEventListener('pointermove', this.onPointerMove); + }; + + @action + toggleProperties = () => { + if (SnappingManager.PropertiesWidth < 5) { + SnappingManager.SetPropertiesWidth(250); + } + }; + + removePresentationItem = undoable( + action((e: React.MouseEvent) => { + e.stopPropagation(); + if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) { + this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1; + } + this._props.removeDocument?.(this.slideDoc); + this.presBoxView?.removeFromSelectedArray(this.slideDoc); + this.removeAllRecordingInOverlay(); + }), + 'Remove doc from pres trail' + ); + + // set title of the individual pres slide + onSetValue = undoable( + action((value: string) => { + this.slideDoc.title = !value.trim().length ? '-untitled-' : value; + return true; + }), + 'set title of pres element' + ); + + /** + * Method called for updating the view of the currently selected document + * + * @param targetDoc + * @param activeItem + */ + @undoBatch + updateCapturedContainerLayout = (presTargetDoc: Doc, activeItem: Doc) => { + const targetDoc = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc; + activeItem.config_x = NumCast(targetDoc.x); + activeItem.config_y = NumCast(targetDoc.y); + activeItem.config_rotation = NumCast(targetDoc.rotation); + activeItem.config_width = NumCast(targetDoc.width); + activeItem.config_height = NumCast(targetDoc.height); + activeItem.config_pinLayout = !activeItem.config_pinLayout; + // activeItem.config_pinLayout = true; + }; + + /** + * Method called for updating the view of the currently selected document + * + * @param presTargetDoc + * @param activeItem + */ + updateCapturedViewContents = undoable( + action((presTargetDoc: Doc, activeItem: Doc) => { + const target = DocCast(presTargetDoc.annotationOn) ?? presTargetDoc; + PinDocView(activeItem, { pinData: PresBox.pinDataTypes(target) }, target); + }), + 'updated captured view contents' + ); + + // a previously recorded video will have timecode defined + static videoIsRecorded = (activeItem: Opt) => 'layout_currentTimecode' in (DocCast(activeItem?.recording) ?? {}); + + removeAllRecordingInOverlay = () => Doc.MyOverlayDocs.filter(doc => doc.slides === this.slideDoc).forEach(Doc.RemFromMyOverlay); + + /// remove all videos that have been recorded from overlay (leave videso that are being recorded to avoid losing data) + static removeEveryExistingRecordingInOverlay = () => { + Doc.MyOverlayDocs.filter(doc => doc.slides !== null && PresSlideBox.videoIsRecorded(DocCast(doc.slides))) // + .forEach(Doc.RemFromMyOverlay); + }; + + hideRecording = undoable( + action((e: React.MouseEvent) => { + e.stopPropagation(); + this.removeAllRecordingInOverlay(); + }), + 'hide video recording' + ); + + showRecording = undoable( + action((activeItem: Doc, iconClick: boolean = false) => { + // remove the overlays on switch *IF* not opened from the specific icon + if (!iconClick) PresSlideBox.removeEveryExistingRecordingInOverlay(); + + DocCast(activeItem.recording) && Doc.AddToMyOverlay(DocCast(activeItem.recording)!); + }), + 'show video recording' + ); + + startRecording = undoable( + action((e: React.MouseEvent, activeItem: Doc) => { + e.stopPropagation(); + if (PresSlideBox.videoIsRecorded(activeItem)) { + // if we already have an existing recording + this.showRecording(activeItem, true); + // // if we already have an existing recording + // Doc.AddToMyOverlay(Cast(activeItem.recording, Doc, null)); + } else { + // we dont have any recording + // Remove every recording that already exists in overlay view + // this is a design decision to clear to focus in on the recoding mode + PresSlideBox.removeEveryExistingRecordingInOverlay(); + + // create and add a recording to the slide + // make recording box appear in the bottom right corner of the screen + Doc.AddToMyOverlay( + (activeItem.recording = Docs.Create.WebCamDocument('', { + _width: 384, + _height: 216, + overlayX: window.innerWidth - 384 - 20, + overlayY: window.innerHeight - 216 - 20, + layout_hideDocumentButtonBar: true, + layout_hideDecorationTitle: true, + layout_hideOpenButton: true, + cloneFieldFilter: new List(['isSystem']), + slides: activeItem, // attach the slide to the recording + })) + ); + } + }), + 'start video recording' + ); + + @undoBatch + lfg = (e: React.MouseEvent) => { + e.stopPropagation(); + // TODO: fix this bug + // const { toggleChildrenRun } = this.slideDoc; + TreeView.ToggleChildrenRun.get(this.slideDoc)?.(); + + // call this.slideDoc.recurChildren() to get all the children + // if (iconClick) PresSlideBox.showVideo = false; + }; + + @computed + get toolbarWidth(): number { + const presBoxDocView = DocumentView.getDocumentView(this.presBox); + const width = NumCast(this.presBox?._width); + return presBoxDocView ? presBoxDocView._props.PanelWidth() : width || 300; + } + + @computed get presButtons() { + const { presBox, targetDoc, slideDoc: activeItem } = this; + const presBoxColor = StrCast(presBox?._backgroundColor); + const presColorBool = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; + const hasChildren = BoolCast(this.slideDoc?.hasChildren); + + const items: JSX.Element[] = []; + + items.push( + Update captured doc layout
}> +
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedContainerLayout(targetDoc, activeItem), true)} + style={{ opacity: activeItem.config_pinLayout ? 1 : 0.5, fontWeight: 700, display: 'flex' }}> + L +
+ + ); + items.push( + Update captured doc content
}> +
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.updateCapturedViewContents(targetDoc, activeItem))} + style={{ opacity: activeItem.config_pinData || activeItem.config_pinView ? 1 : 0.5, fontWeight: 700, display: 'flex' }}> + C +
+ + ); + items.push( + {this.videoRecordingIsInOverlay ? 'Hide Recording' : `${PresSlideBox.videoIsRecorded(activeItem) ? 'Show' : 'Start'} recording`}
}> +
(this.videoRecordingIsInOverlay ? this.hideRecording(e) : this.startRecording(e, activeItem))} style={{ fontWeight: 700 }}> + e.stopPropagation()} /> +
+ + ); + if (this.indexInPres !== 0) { + items.push( + + {!activeItem.presentation_groupWithUp + ? 'Not grouped with previous slide (click to group)' + : activeItem.presentation_groupWithUp === 1 + ? 'Run simultaneously with previous slide (click again to run after)' + : 'Run after previous slide (click to ungroup from previous)'} +
+ }> +
{ + activeItem.presentation_groupWithUp = (NumCast(activeItem.presentation_groupWithUp) + 1) % 3; + }} + style={{ + zIndex: 1000 - this.indexInPres, + fontWeight: 700, + backgroundColor: activeItem.presentation_groupWithUp ? (presColorBool ? presBoxColor : Colors.MEDIUM_BLUE) : undefined, + outline: NumCast(activeItem.presentation_groupWithUp) > 1 ? 'solid black 1px' : undefined, + height: activeItem.presentation_groupWithUp ? 53 : 18, + transform: activeItem.presentation_groupWithUp ? 'translate(0, -17px)' : undefined, + }}> +
+ e.stopPropagation()} /> +
+
+ + ); + } + items.push( + {this.slideDoc.presentation_expandInlineButton ? 'Minimize' : 'Expand'}
}> +
{ + e.stopPropagation(); + this.presExpandDocumentClick(); + }}> + e.stopPropagation()} /> +
+ + ); + if (!Doc.noviceMode && hasChildren) { + // TODO: replace with if treeveiw, has childrenDocs + items.push( + Run child processes (tree only)
}> +
{ + e.stopPropagation(); + this.lfg(e); + }} + style={{ fontWeight: 700 }}> + e.stopPropagation()} /> +
+ + ); + } + items.push( + Remove from presentation}> +
+ e.stopPropagation()} /> +
+
+ ); + items.push( + Customize Slide}> +
{ + this.presBoxView?.regularSelect(this.slideDoc, this._itemRef.current!, this._dragRef.current!, true, false); + PresBox.Instance.navigateToActiveItem(); + PresBox.Instance.openProperties(); + PresBox.Instance.slideToModify = this.Document; + }}> + e.stopPropagation()} /> +
+
+ ); + return items; + } + + @computed get mainItem() { + const { presBox, slideDoc: activeItem } = this; + const isSelected: boolean = !!this.selectedArray?.has(activeItem); + const isCurrent: boolean = this.presBox?._itemIndex === this.indexInPres; + const miniView: boolean = this.toolbarWidth <= 110; + const presBoxColor: string = StrCast(presBox?._backgroundColor); + const presColorBool: boolean = presBoxColor ? presBoxColor !== Colors.WHITE && presBoxColor !== 'transparent' : false; + + return ( +
{ + this.toggleProperties(); + this.presBoxView?.regularSelect(activeItem, this._itemRef.current!, this._dragRef.current!, false); + })} + onPointerOver={this.onPointerOver} + onPointerLeave={this.onPointerLeave} + onPointerDown={this.headerDown}> + {miniView ? ( +
+ {`${this.indexInPres + 1}.`} +
+ ) : ( +
+
+
{ + e.stopPropagation(); + if (this._itemRef.current && this._dragRef.current) { + this.presBoxView?.modifierSelect(activeItem, this._itemRef.current, this._dragRef.current, true, false, false); + } + }} + onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}. `}
+ StrCast(activeItem.title)} SetValue={this.onSetValue} /> +
+ {/*
{"Movement speed"}
}>
{this.transition}
*/} + {/*
{"Duration"}
}>
{this.duration}
*/} +
+ {...this.presButtons} +
+ {this.renderEmbeddedInline} +
+ )} +
+ ); + } + + render() { + return !(this.slideDoc instanceof Doc) || this.targetDoc instanceof Promise ? null : this.mainItem; + } +} + +Docs.Prototypes.TemplateMap.set(DocumentType.PRESSLIDE, { + layout: { view: PresSlideBox, dataField: 'data' }, + options: { acl: '', title: 'presSlide', _layout_fitWidth: true, _xMargin: 0, isTemplateDoc: true }, +}); diff --git a/src/client/views/nodes/trails/index.ts b/src/client/views/nodes/trails/index.ts index 7b18974df..a5bc55221 100644 --- a/src/client/views/nodes/trails/index.ts +++ b/src/client/views/nodes/trails/index.ts @@ -1,3 +1,3 @@ export * from './PresBox'; -export * from './PresElementBox'; +export * from './PresSlideBox'; export * from './PresEnums'; diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index e3ee51424..c45d8e052 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -210,7 +210,7 @@ export class GPTPopup extends ObservableReactComponent { generateFireflyImage = (imgDesc: string) => { const selView = DocumentView.Selected().lastElement(); const selDoc = selView?.Document; - if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutFieldKey(selDoc)] instanceof ImageField)) { + if (selDoc && (selView._props.renderDepth > 1 || selDoc[Doc.LayoutDataKey(selDoc)] instanceof ImageField)) { const oldPrompt = StrCast(selDoc.ai_firefly_prompt, StrCast(selDoc.title)); const newPrompt = oldPrompt ? `${oldPrompt} ~~~ ${imgDesc}` : imgDesc; return DrawingFillHandler.drawingToImage(selDoc, 100, newPrompt, selDoc) diff --git a/src/client/views/search/FaceRecognitionHandler.tsx b/src/client/views/search/FaceRecognitionHandler.tsx index 86e28a768..f60eb486e 100644 --- a/src/client/views/search/FaceRecognitionHandler.tsx +++ b/src/client/views/search/FaceRecognitionHandler.tsx @@ -60,7 +60,7 @@ export class FaceRecognitionHandler { * @param imgDoc image with faces * @returns faceDoc array */ - public static ImageDocFaceAnnos = (imgDoc: Doc) => DocListCast(imgDoc[`${Doc.LayoutFieldKey(imgDoc)}_annotations`]).filter(doc => doc.face); + public static ImageDocFaceAnnos = (imgDoc: Doc) => DocListCast(imgDoc[`${Doc.LayoutDataKey(imgDoc)}_annotations`]).filter(doc => doc.face); /** * returns a list of all face collection Docs on the current dashboard @@ -207,7 +207,7 @@ export class FaceRecognitionHandler { } else if (imgDoc.type === DocumentType.LOADING && !imgDoc.loadingError) { setTimeout(() => this.classifyFacesInImage(imgDoc), 1000); } else { - const imgUrl = ImageCast(imgDoc[Doc.LayoutFieldKey(imgDoc)]); + const imgUrl = ImageCast(imgDoc[Doc.LayoutDataKey(imgDoc)]); if (imgUrl && !DocListCast(Doc.MyFaceCollection.examinedFaceDocs).includes(imgDoc[DocData])) { // only examine Docs that have an image and that haven't already been examined. Doc.AddDocToList(Doc.MyFaceCollection, 'examinedFaceDocs', imgDoc[DocData]); diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 1f6e80bd1..ad0f56cb9 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -218,7 +218,7 @@ export class SearchBox extends ViewBoxBaseComponent() { .filter(d => d) // eslint-disable-next-line no-loop-func .map(async d => { - const fieldKey = Doc.LayoutFieldKey(d); + const fieldKey = Doc.LayoutDataKey(d); const annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView'); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; const dataDocs = await DocListCastAsync(data); diff --git a/src/client/views/smartdraw/StickerPalette.tsx b/src/client/views/smartdraw/StickerPalette.tsx index 2260d1f73..0e234e966 100644 --- a/src/client/views/smartdraw/StickerPalette.tsx +++ b/src/client/views/smartdraw/StickerPalette.tsx @@ -56,10 +56,10 @@ export class StickerPalette extends ObservableReactComponent ( <> - {this.renderDoc(Doc.MyStickers, (r: DocumentView) => { - this._docView = r; - })} + {Doc.MyStickers && + this.renderDoc(Doc.MyStickers, (r: DocumentView) => { + this._docView = r; + })}