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.tsx164
1 files changed, 125 insertions, 39 deletions
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 30f9e6363..f1b48be4a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -85,7 +85,7 @@ export interface DocumentViewProps extends FieldViewSharedProps {
reactParent?: React.Component; // parent React component view (see CollectionFreeFormDocumentView)
}
@observer
-export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps>() {
+export class DocumentViewInternal extends DocComponent<FieldViewProps & DocumentViewProps & { showAIEditor: boolean }>() {
// 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.
@@ -109,7 +109,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
private _mainCont = React.createRef<HTMLDivElement>();
private _titleRef = React.createRef<EditableView>();
private _dropDisposer?: DragManager.DragDropDisposer;
- constructor(props: FieldViewProps & DocumentViewProps) {
+ constructor(props: FieldViewProps & DocumentViewProps & { showAIEditor: boolean }) {
super(props);
makeObservable(this);
}
@@ -355,7 +355,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
onPointerDown = (e: React.PointerEvent): void => {
if (this._props.isGroupActive?.() === GroupActive.child && !this._props.isDocumentActive?.()) return;
this._longPressSelector = setTimeout(() => SnappingManager.LongPress && this._props.select(false), 1000);
- if (!DocumentView.DownDocView) DocumentView.DownDocView = this._docView;
this._downX = e.clientX;
this._downY = e.clientY;
@@ -459,10 +458,9 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
}
if (annoData || this.Document !== linkdrag.linkSourceDoc.embedContainer) {
const dropDoc = annoData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.Document;
- const linkDoc = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]);
+ const linkDoc = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, { layout_isSvg: true }, undefined, [de.x, de.y - 50]);
if (linkDoc) {
de.complete.linkDocument = linkDoc;
- linkDoc.layout_isSvg = true;
DocumentView.linkCommonAncestor(linkDoc)?.ComponentView?.addDocument?.(linkDoc);
}
}
@@ -553,6 +551,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' });
!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' });
@@ -685,7 +684,11 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
rootSelected = () => this._rootSelected;
panelHeight = () => this._props.PanelHeight() - this.headerMargin;
- screenToLocalContent = () => this._props.ScreenToLocalTransform().translate(0, -this.headerMargin);
+ screenToLocalContent = () =>
+ this._props
+ .ScreenToLocalTransform()
+ .translate(0, -this.headerMargin)
+ .scale(this._props.showAIEditor ? (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)); } // prettier-ignore
setContentView = action((view: ViewBoxInterface<FieldViewProps>) => { this._componentView = view; }); // prettier-ignore
@@ -711,35 +714,110 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
return this._props.styleProvider?.(doc, props, property);
};
+ @observable _aiWinHeight = 88;
+
+ private _tagsBtnHeight = 22;
+ @computed get currentScale() {
+ const viewXfScale = this._props.DocumentView!().screenToLocalScale();
+ 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 yscale = y >= 1 ? 0 : 1 / (1 + y * viewXfScale - 1);
+ return Math.max(xscale, yscale, 1 / viewXfScale);
+ }
+ /**
+ * How much the content of the view is being scaled based on its nesting and its fit-to-width settings
+ */
+ @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.
+ */
+ @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
+
+ aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1);
+ aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - this._aiWinHeight * this.uiBtnScaling);
@computed get viewBoxContents() {
TraceMobx();
const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString;
const noBackground = this.Document.isGroup && !this._componentView?.isUnstyledView?.() && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent');
return (
- <div
- className="documentView-contentsView"
- style={{
- pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'),
- height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined,
- }}>
- <DocumentContentsView
- {...this._props}
- layoutFieldKey={StrCast(this.Document.layout_fieldKey, 'layout')}
- pointerEvents={this.contentPointerEvents}
- setContentViewBox={this.setContentView}
- childFilters={this.childFilters}
- PanelHeight={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>
+ <>
+ <div
+ 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,
+ }}>
+ <DocumentContentsView
+ {...this._props}
+ 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}
+ 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: this.maxWidgetSize,
+ }}>
+ {this.widgetDecorations ?? 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={{
+ 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.widgetDecorations ?? null}
+ </div>
+ </>
+ )}
+ </>
);
}
+ _oldHistoryWheel: HTMLDivElement | null = null;
+ _oldAiWheel: HTMLDivElement | null = null;
+ onPassiveWheel = (e: WheelEvent) => {
+ e.stopPropagation();
+ };
+
+ protected historyRef = (lastEle: HTMLDivElement | null, ele: HTMLDivElement | null) => {
+ lastEle?.removeEventListener('wheel', this.onPassiveWheel);
+ ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false });
+ };
captionStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption');
fieldsDropdown = (placeholder: string) => (
@@ -895,7 +973,6 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
{this.captionView}
</div>
)}
- {this.widgetDecorations ?? null}
</div>
));
};
@@ -1071,15 +1148,11 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
* @param doc Doc to snapshot
* @returns promise of icon ImageField
*/
- public static GetDocImage(doc: Doc) {
+ public static GetDocImage(doc?: Doc) {
return DocumentView.getDocumentView(doc)
?.ComponentView?.updateIcon?.()
- .then(() => ImageCast(DocCast(doc).icon));
+ .then(() => ImageCast(doc!.icon, ImageCast(doc![Doc.LayoutFieldKey(doc!)])));
}
- /**
- * The DocumentView below the cursor at the start of a gesture (that receives the pointerDown event). Used by GestureOverlay to determine the doc a gesture should apply to.
- */
- public static DownDocView: DocumentView | undefined; // the first DocView that receives a pointerdown event. used by GestureOverlay to determine the doc a gesture should apply to.
public get displayName() { return 'DocumentView(' + (this.Document?.title??"") + ')'; } // prettier-ignore
private _htmlOverlayEffect: Opt<Doc>;
@@ -1131,10 +1204,13 @@ 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;
- if (this.layout_fitWidth || this._props.PanelHeight() / (this.effectiveNativeHeight || 1) > this._props.PanelWidth() / (this.effectiveNativeWidth || 1)) {
- return Math.max(minTextScale, this._props.PanelWidth() / (this.effectiveNativeWidth || 1)); // width-limited or layout_fitWidth
+ 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);
+ if (this.layout_fitWidth || (this._props.PanelHeight() - ai) / effNH > (this._props.PanelWidth() - ai) / effNW) {
+ return Math.max(minTextScale, (this._props.PanelWidth() - ai) / effNW); // width-limited or layout_fitWidth
}
- return Math.max(minTextScale, this._props.PanelHeight() / (this.effectiveNativeHeight || 1)); // height-limited or unscaled
+ return Math.max(minTextScale, (this._props.PanelHeight() - ai) / effNH); // height-limited or unscaled
}
@computed private get panelWidth() {
return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this._props.PanelWidth();
@@ -1290,6 +1366,13 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}
};
+ @observable public _showAIEditor: boolean = false;
+
+ @action
+ public toggleAIEditor = () => {
+ this._showAIEditor = !this._showAIEditor;
+ };
+
public setTextHtmlOverlay = action((text: string | undefined, effect?: Doc) => {
this._htmlOverlayText = text;
this._htmlOverlayEffect = effect;
@@ -1385,7 +1468,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
public docViewPath = () => (this.containerViewPath ? [...this.containerViewPath(), this] : [this]);
layout_fitWidthFunc = (/* doc: Doc */) => BoolCast(this.layout_fitWidth);
- screenToLocalScale = () => this._props.ScreenToLocalTransform().Scale;
+ screenToLocalScale = () => this.screenToViewTransform().Scale;
isSelected = () => this.IsSelected;
select = (extendSelection: boolean, focusSelection?: boolean) => {
if (!this._props.dontSelect?.()) DocumentView.SelectView(this, extendSelection);
@@ -1404,6 +1487,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
NativeHeight = () => this.effectiveNativeHeight;
PanelWidth = () => this.panelWidth;
PanelHeight = () => this.panelHeight;
+ ReducedPanelWidth = () => this.panelWidth / 2;
+ ReducedPanelHeight = () => this.panelWidth / 2;
NativeDimScaling = () => this.nativeScaling;
hideLinkCount = () => !!this.hideLinkButton;
isHovering = () => this._isHovering;
@@ -1472,6 +1557,7 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
}}>
<DocumentViewInternal
{...this._props}
+ showAIEditor={this._showAIEditor}
reactParent={undefined}
isHovering={this.isHovering}
fieldKey={this.LayoutFieldKey}