aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DocumentView.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2025-04-14 18:35:49 -0400
committerbobzel <zzzman@gmail.com>2025-04-14 18:35:49 -0400
commitd818ef151ca65008e5c6bb5e92b709decb3026d8 (patch)
treeae1d821c717cfb4b38c36b519d03b45ed90e9831 /src/client/views/nodes/DocumentView.tsx
parent1525fe600142d955fa24e939322f45cbca9d1cba (diff)
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.
Diffstat (limited to 'src/client/views/nodes/DocumentView.tsx')
-rw-r--r--src/client/views/nodes/DocumentView.tsx100
1 files changed, 51 insertions, 49 deletions
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<DocumentViewProps & Field
}
componentDidMount() {
- runInAction(() => {
- this._mounted = true;
- });
+ runInAction(() => (this._mounted = true));
this.setupHandlers();
this._disposers.contentActive = reaction(
() =>
@@ -183,16 +181,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
: Doc.ActiveTool !== InkTool.None || SnappingManager.CanEmbed || this.rootSelected() || this.Document.forceActive || this._componentView?.isAnyChildContentActive?.() || this._props.isContentActive()
? true
: undefined,
- active => {
- 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<DocumentViewProps & Field
};
const clickFunc = this.onClickFunc?.()?.script ? () => (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<DocumentViewProps & Field
const items = this._props.styleProvider?.(this.Document, this._props, StyleProp.ContextMenuItems) as ContextMenuProps[];
items?.forEach(item => 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<DocumentViewProps & Field
zorderItems.push({
description: !this.layoutDoc._keepZDragged ? 'Keep ZIndex when dragged' : 'Allow ZIndex to change when dragged',
event: undoable(
- action(() => {
- 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<DocumentViewProps & Field
.scale(this.viewingAiEditor() ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
onClickFunc = () => (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<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
+ setContentView = action((view: ViewBoxInterface<FieldViewProps>) => (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<DocumentViewProps & Field
}
widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null);
+ _contentsRef = React.createRef<DocumentContentsView>();
@computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -778,6 +771,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
}}>
<DocumentContentsView
{...this._props}
+ ref={this._contentsRef}
layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
pointerEvents={this.contentPointerEvents}
setContentViewBox={this.setContentView}
@@ -812,8 +806,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
fieldsDropdown = (placeholder: string) => (
<div
- ref={r => { 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 */})` }}>
<FieldsDropdown
Doc={this.Document}
@@ -826,7 +820,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
}
this._changingTitleField = false;
})}
- menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore
+ menuClose={action(() => (this._changingTitleField = false))}
/>
</div>
);
@@ -842,7 +836,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field
const background = StrCast(
this.layoutDoc.layout_headingColor,
// StrCast(SharingManager.Instance.users.find(u => 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<DocumentViewProps & Field
root: Doc
) {
const effectDirection = (presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) as PresEffectDirection;
- const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null));
+ const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null) ?? null);
const effectProps = {
left: effectDirection === PresEffectDirection.Left,
right: effectDirection === PresEffectDirection.Right,
@@ -1143,7 +1137,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
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<DocumentViewProps>() {
@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<DocumentViewProps>() {
!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<DocumentViewProps>() {
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<DocumentViewProps>() {
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<DocumentViewProps>() {
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<DocumentViewProps>() {
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<DocumentViewProps>() {
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<DocumentViewProps>() {
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<DocumentViewProps>() {
}),
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<DocumentViewProps>() {
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<DocumentViewProps>() {
<div
id={this.ViewGuid}
className="contentFittingDocumentView"
- onPointerEnter={action(() => {
- this._isHovering = true;
- })}
- onPointerLeave={action(() => {
- this._isHovering = false;
- })}>
+ onPointerEnter={action(() => (this._isHovering = true))} //
+ onPointerLeave={action(() => (this._isHovering = false))}>
{!this.Document || !this._props.PanelWidth() ? null : (
<div
className="contentFittingDocumentView-previewDoc"
@@ -1570,9 +1574,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
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<DocumentViewProps>() {
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(() => {