aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/DocumentView.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/DocumentView.tsx')
-rw-r--r--src/client/views/nodes/DocumentView.tsx334
1 files changed, 161 insertions, 173 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index cac276535..05706fe6b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -9,7 +9,7 @@ import { Fade, JackInTheBox } from 'react-awesome-reveal';
import { ClientUtils, DivWidth, isTargetChildOf as isParentOf, lightOrDark, returnFalse, returnVal, simMouseEvent, simulateMouseClick } from '../../../ClientUtils';
import { Utils, emptyFunction } from '../../../Utils';
import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
-import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols';
+import { AclAdmin, AclEdit, AclPrivate, Animation, AudioPlay, DocViews } from '../../../fields/DocSymbols';
import { Id } from '../../../fields/FieldSymbols';
import { InkTool } from '../../../fields/InkField';
import { List } from '../../../fields/List';
@@ -40,53 +40,22 @@ import { FieldsDropdown } from '../FieldsDropdown';
import { ObserverJsxParser } from '../ObservableReactComponent';
import { PinProps } from '../PinFuncs';
import { StyleProp } from '../StyleProp';
+import { TagsView } from '../TagsView';
import { ViewBoxInterface } from '../ViewBoxInterface';
import { GroupActive } from './CollectionFreeFormDocumentView';
-import { DocumentContentsView } from './DocumentContentsView';
+import { DocumentContentsView, DocumentViewProps } from './DocumentContentsView';
import { DocumentLinksButton } from './DocumentLinksButton';
import './DocumentView.scss';
-import { FieldViewProps, FieldViewSharedProps } from './FieldView';
+import { FieldViewProps } from './FieldView';
import { FocusViewOptions } from './FocusViewOptions';
import { OpenWhere, OpenWhereMod } from './OpenWhere';
import { FormattedTextBox } from './formattedText/FormattedTextBox';
import { PresEffect, PresEffectDirection } from './trails/PresEnums';
import SpringAnimation from './trails/SlideEffect';
import { SpringType, springMappings } from './trails/SpringUtils';
-import { TagsView } from '../TagsView';
-export interface DocumentViewProps extends FieldViewSharedProps {
- hideDecorations?: boolean; // whether to suppress all DocumentDecorations when doc is selected
- hideResizeHandles?: boolean; // whether to suppress resized handles on doc decorations when this document is selected
- hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings
- hideDocumentButtonBar?: boolean;
- hideOpenButton?: boolean;
- hideDeleteButton?: boolean;
- hideLinkAnchors?: boolean;
- hideLinkButton?: boolean;
- hideCaptions?: boolean;
- contentPointerEvents?: Property.PointerEvents | undefined; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents
- dontCenter?: 'x' | 'y' | 'xy';
- showTags?: boolean;
- hideFilterStatus?: boolean;
- childHideDecorationTitle?: boolean;
- childHideResizeHandles?: boolean;
- childDragAction?: dropActionType; // allows child documents to be dragged out of collection without holding the embedKey or dragging the doc decorations title bar.
- dragWhenActive?: boolean;
- dontHideOnDrag?: boolean;
- onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected
- DataTransition?: () => string | undefined;
- NativeWidth?: () => number;
- NativeHeight?: () => number;
- contextMenuItems?: () => { script?: ScriptField; method?: () => void; filter?: ScriptField; label: string; icon: string }[];
- dragConfig?: (data: DragManager.DocumentDragData) => void;
- dragStarting?: () => void;
- dragEnding?: () => void;
-
- reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView)
-}
@observer
-export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps & { showAIEditor: boolean }>() {
+export class DocumentViewInternal extends DocComponent<DocumentViewProps & FieldViewProps>() {
// this makes mobx trace() statements more descriptive
public get displayName() { return 'DocumentViewInternal(' + this.Document.title + ')'; } // prettier-ignore
public static SelectAfterContextMenu = true; // whether a document should be selected after it's contextmenu is triggered.
@@ -134,14 +103,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
@computed get border() { return this.style(this.layoutDoc, StyleProp.Border) as string || ""; } // prettier-ignore
@computed get borderRounding() { return this.style(this.layoutDoc, StyleProp.BorderRounding) as string; } // prettier-ignore
@computed get widgetDecorations() { return this.style(this.layoutDoc, StyleProp.Decorations) as JSX.Element; } // prettier-ignore
- @computed get backgroundBoxColor(){ return this.style(this.Document, StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
@computed get showTitle() { return this.style(this.layoutDoc, StyleProp.ShowTitle) as Opt<string>; } // prettier-ignore
@computed get showCaption() { return this.style(this.layoutDoc, StyleProp.ShowCaption) as string ?? ""; } // prettier-ignore
@computed get headerMargin() { return this.style(this.layoutDoc, StyleProp.HeaderMargin) as number ?? 0; } // prettier-ignore
@computed get titleHeight() { return this.style(this.layoutDoc, StyleProp.TitleHeight) as number ?? 0; } // prettier-ignore
- @computed get docContents() { return this.style(this.Document, StyleProp.DocContents) as JSX.Element; } // prettier-ignore
- @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
- @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
+ @computed get backgroundBoxColor(){ return this.style(this.Document, StyleProp.BackgroundColor + ':docView') as string; } // prettier-ignore
+ @computed get docContents() { return this.style(this.Document, StyleProp.DocContents) as JSX.Element; } // prettier-ignore
+ @computed get highlighting() { return this.style(this.Document, StyleProp.Highlighting); } // prettier-ignore
+ @computed get borderPath() { return this.style(this.Document, StyleProp.BorderPath); } // prettier-ignore
@computed get onClickHdlr() { return this._props.onClickScript?.() ?? ScriptCast(this.layoutDoc.onClick ?? this.Document.onClick); } // prettier-ignore
@computed get onDoubleClickHdlr() { return this._props.onDoubleClickScript?.() ?? ScriptCast(this.layoutDoc.onDoubleClick ?? this.Document.onDoubleClick); } // prettier-ignore
@@ -200,9 +169,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
componentDidMount() {
- runInAction(() => {
- this._mounted = true;
- });
+ runInAction(() => (this._mounted = true));
this.setupHandlers();
this._disposers.contentActive = reaction(
() =>
@@ -214,16 +181,12 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
: 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 }
);
}
@@ -336,7 +299,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
};
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;
}
@@ -426,7 +389,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
noOnClick = undoable(() => {
this.Document.ignoreClick = false;
- this.Document.onClick = this.Document[DocData].onClick = undefined;
+ this.Document.onClick = this.Document.$onClick = undefined;
}, 'default on click');
deleteClicked = undoable(() => this._props.removeDocument?.(this.Document), 'delete doc');
@@ -533,7 +496,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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' })
);
@@ -554,7 +517,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => DocumentView.SetLightboxDoc(this.Document), icon: 'external-link-alt' });
}
appearanceItems.push({ description: 'Pin', event: () => this._props.pinToPres(this.Document, {}), icon: 'map-pin' });
- appearanceItems.push({ description: 'AI view', event: () => this._docView?.toggleAIEditor(), icon: 'map-pin' });
+ this._componentView?.componentAIView?.() && appearanceItems.push({ description: 'AI view', event: () => this._docView?.toggleAIEditor(), icon: 'map-pin' });
!Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this._props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' });
!appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' });
@@ -567,9 +530,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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',
@@ -687,14 +648,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
rootSelected = () => this._rootSelected;
panelHeight = () => this._props.PanelHeight() - this.headerMargin - 2 * NumCast(this.Document.borderWidth);
- screenToLocalContent = () =>
- this._props
- .ScreenToLocalTransform()
- .translate(-NumCast(this.Document.borderWidth), -this.headerMargin - NumCast(this.Document.borderWidth))
- .scale(this._props.showAIEditor ? (this._props.PanelHeight() || 1) / this.aiContentsHeight() : 1);
- onClickFunc = this.disableClickScriptFunc ? undefined : () => this.onClickHdlr;
+ aiShift = () => (!this.viewingAiEditor() ? 0 : (this._props.PanelWidth() - this.aiContentsWidth()) / 2);
+ aiScale = () => (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)];
@@ -717,14 +675,14 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
return this._props.styleProvider?.(doc, props, property);
};
- @observable _aiWinHeight = 88;
+ @observable _aiWinHeight = 32;
- private _tagsBtnHeight = 22;
+ TagsBtnHeight = 22;
@computed get currentScale() {
const viewXfScale = this._props.DocumentView!().screenToLocalScale();
- const x = NumCast(this.Document.height) / viewXfScale / 80;
+ const x = NumCast(this.Document._height) / viewXfScale / 80;
const xscale = x >= 1 ? 0 : 1 / (1 + x * (viewXfScale - 1));
- const y = NumCast(this.Document.width) / viewXfScale / 200;
+ const y = NumCast(this.Document._width) / viewXfScale / 200;
const yscale = y >= 1 ? 0 : 1 / (1 + y * viewXfScale - 1);
return Math.max(xscale, yscale, 1 / viewXfScale);
}
@@ -733,16 +691,56 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
*/
@computed get viewScaling() { return 1 / this.currentScale; } // prettier-ignore
/**
- * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its normal 35 pixel size.
+ * The maximum size a UI widget can be scaled so that it won't be bigger in screen pixels than its nominal pixel size.
*/
- @computed get maxWidgetSize() { return Math.min(this._tagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document.width), NumCast(this.Document.height))); } // prettier-ignore
+ @computed get maxWidgetSize() { return Math.min(this.TagsBtnHeight * this.viewScaling, 0.25 * Math.min(NumCast(this.Document._width), NumCast(this.Document._height))); } // prettier-ignore
/**
* How much to reactively scale a UI element so that it is as big as it can be (up to its normal 35pixel size) without being too big for the Doc content
*/
- @computed get uiBtnScaling() { return Math.max(this.maxWidgetSize / this._tagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
+ @computed get uiBtnScaling() { return Math.max(this.maxWidgetSize / this.TagsBtnHeight, 1) * Math.min(1, this.viewScaling); } // prettier-ignore
aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
- aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
+ aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - (this._aiWinHeight + (this.tagsOverlayFunc() ? 22 : 0)) * this.uiBtnScaling);
+ @computed get aiEditor() {
+ return (
+ <div
+ className="documentView-editorView"
+ style={{
+ background: SnappingManager.userVariantColor,
+ width: `${100 / this.uiBtnScaling}%`, //
+ transform: `scale(${this.uiBtnScaling})`,
+ }}
+ ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
+ <div className="documentView-editorView-resizer" />
+ {this._componentView?.componentAIView?.() ?? null}
+ {this._props.DocumentView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+ </div>
+ );
+ }
+ @computed get tagsOverlay() {
+ return (
+ <div
+ className="documentView-noAiWidgets"
+ style={{
+ width: `${100 / this.uiBtnScaling}%`, //
+ transform: `scale(${this.uiBtnScaling})`,
+ height: Number.isNaN(this.maxWidgetSize) ? undefined : this.TagsBtnHeight * this.uiBtnScaling,
+ }}>
+ {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView background={this.backgroundBoxColor} Views={[this._props.DocumentView?.()]} /> : null}
+ </div>
+ );
+ }
+ tagsOverlayFunc = () => (this._props.DocumentView?.().showTags ? this.tagsOverlay : null);
+ @computed get widgetOverlay() {
+ return (
+ <div className="documentView-widgetDecorations" style={{ transform: `scale(${this.uiBtnScaling})` }}>
+ {this.widgetDecorations}
+ </div>
+ );
+ }
+ widgetOverlayFunc = () => (this.widgetDecorations ? this.widgetOverlay : null);
+ viewingAiEditor = () => (this._props.showAIEditor && this._componentView?.componentAIView?.() !== undefined ? this.aiEditor : null);
+ @observable _contentsRef: DocumentContentsView | undefined = undefined;
@computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
@@ -753,63 +751,28 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
className="documentView-contentsView"
style={{
pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
- width: this._props.showAIEditor ? this.aiContentsWidth() : undefined,
- height: this._props.showAIEditor ? this.aiContentsHeight() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
+ width: this.viewingAiEditor() ? this.aiContentsWidth() : undefined,
+ height: this.viewingAiEditor() ? this.aiContentsHeight() : this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
}}>
<DocumentContentsView
{...this._props}
+ ref={action((r: DocumentContentsView) => (this._contentsRef = r))}
layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
pointerEvents={this.contentPointerEvents}
setContentViewBox={this.setContentView}
childFilters={this.childFilters}
- PanelWidth={this._props.showAIEditor ? this.aiContentsWidth : this._props.PanelWidth}
- PanelHeight={this._props.showAIEditor ? this.aiContentsHeight : this.panelHeight}
+ PanelWidth={this.viewingAiEditor() ? this.aiContentsWidth : this._props.PanelWidth}
+ PanelHeight={this.viewingAiEditor() ? this.aiContentsHeight : this.panelHeight}
setHeight={this.setHeight}
isContentActive={this.isContentActive}
- ScreenToLocalTransform={this.screenToLocalContent}
rootSelected={this.rootSelected}
onClickScript={this.onClickFunc}
setTitleFocus={this.setTitleFocus}
hideClickBehaviors={BoolCast(this.Document.hideClickBehaviors)}
/>
</div>
- {!this._props.showAIEditor ? (
- <div
- className="documentView-noAiWidgets"
- style={{
- width: `${100 / this.uiBtnScaling}%`, //
- transform: `scale(${this.uiBtnScaling})`,
- bottom: Number.isNaN(this.maxWidgetSize) ? undefined : this.maxWidgetSize,
- }}>
- {this._props.DocumentView?.() && !this._props.docViewPath().slice(-2)[0].ComponentView?.isUnstyledView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
- </div>
- ) : (
- <>
- <div
- className="documentView-editorView-history"
- ref={r => this.historyRef(this._oldAiWheel, (this._oldAiWheel = r))}
- style={{
- transform: `scale(${this.uiBtnScaling})`,
- height: this.aiContentsHeight() / this.uiBtnScaling,
- width: ((this._props.PanelWidth() - this.aiContentsWidth()) * 0.95) / this.uiBtnScaling,
- }}>
- {this._componentView?.componentAIViewHistory?.() ?? null}
- </div>
- <div
- className="documentView-editorView"
- style={{
- background: SnappingManager.userVariantColor,
- width: `${100 / this.uiBtnScaling}%`, //
- transform: `scale(${this.uiBtnScaling})`,
- }}
- ref={r => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))}>
- <div className="documentView-editorView-resizer" />
- {this._componentView?.componentAIView?.() ?? null}
- {this._props.DocumentView?.() ? <TagsView Views={[this._props.DocumentView?.()]} /> : null}
- </div>
- </>
- )}
- {this.widgetDecorations ?? null}
+ {this.viewingAiEditor() ?? this.tagsOverlayFunc()}
+ {this.widgetOverlayFunc()}
</>
);
}
@@ -827,11 +790,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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
- Document={this.Document}
+ Doc={this.Document}
placeholder={placeholder}
selectFunc={action((field: string | number) => {
if (this.layoutDoc.layout_showTitle) {
@@ -841,7 +804,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
this._changingTitleField = false;
})}
- menuClose={action(() => { this._changingTitleField = false; })} // prettier-ignore
+ menuClose={action(() => (this._changingTitleField = false))}
/>
</div>
);
@@ -857,7 +820,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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;
@@ -934,8 +897,8 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}}>
<FormattedTextBox
{...this._props}
- yPadding={10}
- xPadding={10}
+ yMargin={10}
+ xMargin={10}
fieldKey={this.showCaption}
styleProvider={this.captionStyleProvider}
dontRegisterView
@@ -1043,7 +1006,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
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,
@@ -1158,7 +1121,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
@@ -1176,7 +1139,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public static UniquifyId(inLightbox: boolean | undefined, id: string) {
return (inLightbox ? 'lightbox-' : '') + id;
}
- public ViewGuid = DocumentView.UniquifyId(DocumentView.LightboxContains(this), Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
+ public ViewGuid = DocumentView.UniquifyId(DocumentView.LightboxContains(this), 'D' + Utils.GenerateGuid()); // a unique id associated with the main <div>. used by LinkBox's Xanchor to find the arrowhead locations.
public DocUniqueId = DocumentView.UniquifyId(DocumentView.LightboxContains(this), this.Document[Id]);
constructor(props: DocumentViewProps) {
@@ -1194,6 +1157,25 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
@observable private _selected = false;
@observable public static CurrentlyPlaying: DocumentView[] = []; // audio or video media views that are currently playing
@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;
+ 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;
@@ -1210,7 +1192,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
@computed private get nativeScaling() {
if (this.shouldNotScale) return 1;
- const minTextScale = this.Document.type === DocumentType.RTF ? 0.1 : 0;
+ const minTextScale = [DocumentType.RTF, DocumentType.JOURNAL].includes(this.Document.type as DocumentType) ? 0.1 : 0;
const ai = this._showAIEditor && this.nativeWidth === this.layoutDoc.width ? 95 : 0;
const effNW = Math.max(this.effectiveNativeWidth - ai, 1);
const effNH = Math.max(this.effectiveNativeHeight - ai, 1);
@@ -1264,16 +1246,19 @@ 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
public get ContentDiv() { return this._docViewInternal?._contentDiv; } // prettier-ignore
public get ComponentView() { return this._docViewInternal?._componentView; } // prettier-ignore
public get allLinks() { return this._docViewInternal?._allLinks ?? []; } // prettier-ignore
+ public get TagBtnHeight() { return this._docViewInternal?.TagsBtnHeight; } // prettier-ignore
+ public get UIBtnScaling() { return this._docViewInternal?.uiBtnScaling; } // prettier-ignore
+ 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() {
@@ -1290,7 +1275,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
if (this.ComponentView?.screenBounds?.()) {
return this.ComponentView.screenBounds();
}
- const xf = this.screenToContentsTransform().scale(this.nativeScaling).inverse();
+ const xf = this.screenToContentBoundsTransform().inverse();
const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)];
// transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox
@@ -1327,10 +1312,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);
@@ -1340,15 +1325,15 @@ 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.dataDoc.audioAnnoState ?? AudioAnnoState.stopped;
+ const audioAnnoState = this.Document._audioAnnoState ?? AudioAnnoState.stopped;
const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
const anno = audioAnnos?.lastElement();
if (anno instanceof AudioField) {
@@ -1360,13 +1345,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
autoplay: true,
loop: false,
volume: 0.5,
- onend: action(() => { this.dataDoc.audioAnnoState = AudioAnnoState.stopped; }), // prettier-ignore
+ onend: action(() => (this.Document._audioAnnoState = AudioAnnoState.stopped)),
});
- this.dataDoc.audioAnnoState = AudioAnnoState.playing;
+ this.Document._audioAnnoState = AudioAnnoState.playing;
break;
case AudioAnnoState.playing:
(this.dataDoc[AudioPlay] as Howl)?.stop();
- this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
+ this.Document._audioAnnoState = AudioAnnoState.stopped;
break;
default:
}
@@ -1404,32 +1389,36 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined);
}, 'set custom view');
- public static setDefaultTemplate(checkResult?: boolean) {
- if (checkResult) {
- return Doc.UserDoc().defaultTextLayout;
+ private static getTemplate(view: DocumentView | undefined) {
+ if (view) {
+ if (!view.layoutDoc.isTemplateDoc) {
+ MakeTemplate(view.Document);
+ Doc.AddDocToList(Doc.UserDoc(), 'template_user', view.Document);
+ Doc.AddDocToList(DocListCast(Doc.MyTools?.data)[1], 'data', makeUserTemplateButtonOrImage(view.Document));
+ DocCast(Doc.UserDoc().template_user) && view.Document && Doc.AddDocToList(DocCast(Doc.UserDoc().template_user)!, 'data', view.Document);
+ return view.Document;
+ }
+ return DocCast(Doc.LayoutField(view.Document)) ?? view.Document;
}
+ }
+ public static setDefaultTemplate(checkResult?: boolean) {
+ if (checkResult) return Doc.UserDoc().defaultTextLayout;
const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
undoable(() => {
- let tempDoc: Opt<Doc>;
- if (view) {
- if (!view.layoutDoc.isTemplateDoc) {
- 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);
- } else {
- tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]);
- if (!tempDoc) {
- tempDoc = view.Document;
- while (tempDoc && !Doc.isTemplateDoc(tempDoc)) tempDoc = DocCast(tempDoc.proto);
- }
- }
- }
+ const tempDoc = DocumentView.getTemplate(view);
Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
}, 'set default template')();
return undefined;
}
+ public static setDefaultImageTemplate(checkResult?: boolean) {
+ if (checkResult) return Doc.UserDoc().defaultImageLayout;
+ const view = DocumentView.Selected()[0]?._props.renderDepth > 0 ? DocumentView.Selected()[0] : undefined;
+ undoable(() => {
+ const tempDoc = DocumentView.getTemplate(view);
+ Doc.UserDoc().defaultImageLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined;
+ }, 'set default image template')();
+ return undefined;
+ }
/**
* This switches between the current view of a Doc and a specified alternate layout view.
@@ -1445,10 +1434,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]) {
@@ -1468,7 +1457,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.
*/
@@ -1501,17 +1490,22 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
isHovering = () => this._isHovering;
selfView = () => this;
/**
- * @returns Transform to the document view (in the coordinate system of whatever contains the DocumentView)
+ * @returns Transform to the document view's available panel space (in the coordinate system of whatever contains the DocumentView)
*/
screenToViewTransform = () => this._props.ScreenToLocalTransform();
/**
+ * @returns Transform to the document view after centering in available panel space(in the coordinate system of whatever contains the DocumentView)
+ */
+ private screenToContentBoundsTransform = () => this.screenToViewTransform().translate(-this.centeringX, -this.centeringY);
+ /**
* @returns Transform to the coordinate system of the contents of the document view (includes native dimension scaling and centering)
*/
screenToContentsTransform = () =>
this._props
.ScreenToLocalTransform()
.translate(-this.centeringX, -this.centeringY)
- .scale(1 / this.nativeScaling);
+ .translate(-(this._docViewInternal?.aiShift() ?? 0), 0)
+ .scale((this._docViewInternal?.aiScale() ?? 1) / this.nativeScaling);
htmlOverlay = () => {
const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect));
@@ -1521,7 +1515,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 }}>
@@ -1548,12 +1542,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"
@@ -1581,9 +1571,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?.()}
@@ -1627,7 +1615,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(() => {
@@ -1706,7 +1694,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff
// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) {
- const collectedLinks = DocListCast(linkCollection[DocData].data);
+ const collectedLinks = DocListCast(linkCollection.$data);
let wid = NumCast(linkSource._width);
let embedding: Doc | undefined;
const links = Doc.Links(linkSource);
@@ -1729,7 +1717,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour
ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
const tag = StrCast(collection.title).split('-->')[1];
const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys());
- const collectionDocs = DocListCast(collection[DocData].data).concat(collection);
+ const collectionDocs = DocListCast(collection.$data).concat(collection);
let wid = 100;
let created = false;
const matchedDocs = matchedTags
@@ -1749,6 +1737,6 @@ ScriptingGlobals.add(function updateTagsCollection(collection: Doc) {
return aset;
}, new Set<Doc>());
- created && (collection[DocData].data = new List<Doc>(Array.from(matchedDocs)));
+ created && (collection.$data = new List<Doc>(Array.from(matchedDocs)));
return true;
});