diff options
44 files changed, 358 insertions, 345 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 724725c23..e96b8e231 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -202,6 +202,9 @@ export function aggregateBounds(boundsList: { x: number; y: number; width?: numb export function intersectRect(r1: { left: number; top: number; width: number; height: number }, r2: { left: number; top: number; width: number; height: number }) { return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); } +export function rectContains(r1: { left: number; top: number; width: number; height: number }, r2: { left: number; top: number; width: number; height: number }) { + if (r1.left < r2.left && r1.left + r1.width > r2.left + r2.width && r1.top < r2.top && r1.top + r1.height > r2.top + r2.height) return true; +} export function stringHash(s?: string) { return !s ? undefined : Math.abs(s.split('').reduce((a, b) => (n => n & n)((a << 5) - a + b.charCodeAt(0)), 0)); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 45feb0b2f..9fbc82bef 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -796,6 +796,7 @@ pie title Minerals in my tap water { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: InkInkTool.Pen, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, { title: "Highlight",toolTip: "Highlight (Ctrl+H)", btnType: ButtonType.ToggleButton, icon: "highlighter", toolType: InkInkTool.Highlight, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }}, { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: InkInkTool.Write, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, + { title: "Math", toolTip: "Math", btnType: ButtonType.ToggleButton, icon: "calculator", toolType: InkInkTool.Math, ignoreClick: true, scripts: {onClick:'{ return setActiveTool(this.toolType, true, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, ]}, { title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: InkProperty.StrokeWidth,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}, numBtnMin: 1, linearView_btnWidth:40}, { title: "Color", toolTip: "Stroke color", btnType: ButtonType.ColorButton, icon: "pen", toolType: InkProperty.StrokeColor,ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"!activeInkTool()"}}, @@ -1032,6 +1033,9 @@ pie title Minerals in my tap water doc[`active${InkInkTool.Write}Color`] ?? (doc[`active${InkInkTool.Write}Color`] = "rgb(255, 0, 0)"); doc[`active${InkInkTool.Write}Width`] ?? (doc[`active${InkInkTool.Write}Width`] = 1); doc[`active${InkInkTool.Write}Bezier`] ?? (doc[`active${InkInkTool.Write}Bezier`] = "0"); + doc[`active${InkInkTool.Math}Color`] ?? (doc[`active${InkInkTool.Math}Color`] = "rgb(0, 0, 255)"); + doc[`active${InkInkTool.Math}Width`] ?? (doc[`active${InkInkTool.Math}Width`] = 1); + doc[`active${InkInkTool.Math}Bezier`] ?? (doc[`active${InkInkTool.Math}Bezier`] = "0"); doc[`active${InkInkTool.Highlight}Color`] ?? (doc[`active${InkInkTool.Highlight}Color`] = 'transparent'); doc[`active${InkInkTool.Highlight}Width`] ?? (doc[`active${InkInkTool.Highlight}Width`] = 20); doc[`active${InkInkTool.Highlight}Bezier`] ?? (doc[`active${InkInkTool.Highlight}Bezier`] = "0"); diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index d9447b7ec..deeabaa28 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -236,6 +236,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { return this._editing; }; + setInputRef = (r: HTMLInputElement | HTMLTextAreaElement | null) => (this._inputref = r); renderEditor() { return this._props.autosuggestProps ? ( <Autosuggest @@ -255,7 +256,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { ) : this._props.oneLine !== false && this._props.GetValue()?.toString().indexOf('\n') === -1 ? ( <input className="editableView-input" - ref={r => { this._inputref = r; }} // prettier-ignore + ref={this.setInputRef} style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minWidth: 20, background: this._props.background }} placeholder={this._props.placeholder} onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} @@ -270,7 +271,7 @@ export class EditableView extends ObservableReactComponent<EditableProps> { ) : ( <textarea className="editableView-input" - ref={r => { this._inputref = r; }} // prettier-ignore + ref={this.setInputRef} style={{ display: this._props.display, overflow: 'auto', fontSize: this._props.fontSize, minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20, background: this._props.background }} placeholder={this._props.placeholder} onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index 777a34ebc..8488c5293 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -3,7 +3,7 @@ import { action, computed, makeObservable, observable, runInAction } from 'mobx' import { observer } from 'mobx-react'; import * as React from 'react'; import { setupMoveUpEvents } from '../../ClientUtils'; -import { emptyFunction, intersectRect } from '../../Utils'; +import { emptyFunction, intersectRect, rectContains } from '../../Utils'; import { Doc } from '../../fields/Doc'; import { InkData, InkField, InkTool } from '../../fields/InkField'; import { NumCast } from '../../fields/Types'; @@ -166,7 +166,7 @@ export class GestureOverlay extends ObservableReactComponent<React.PropsWithChil */ docsInBoundingBox = (boundingBox: { topLeft: number[]; bottomRight: number[] }, childDocs: Doc[]): Doc[] => { const rect = { left: boundingBox.topLeft[0], top: boundingBox.topLeft[1], width: boundingBox.bottomRight[0] - boundingBox.topLeft[0], height: boundingBox.bottomRight[1] - boundingBox.topLeft[1] }; - return childDocs.filter(doc => intersectRect(rect, { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) })); + return childDocs.filter(doc => rectContains(rect, { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) })); }; /** * Determines if what the array of cusp/intersection data corresponds to a scribble. diff --git a/src/client/views/InkTranscription.tsx b/src/client/views/InkTranscription.tsx index 6220033d4..32ca5f56b 100644 --- a/src/client/views/InkTranscription.tsx +++ b/src/client/views/InkTranscription.tsx @@ -32,7 +32,7 @@ export class InkTranscription extends React.Component { // eslint-disable-next-line @typescript-eslint/no-explicit-any @observable _iinkEditor: any = undefined; // eslint-disable-next-line @typescript-eslint/no-explicit-any - private lastJiix: any; + @observable _iinkMathEditor: any = undefined; private currGroup?: Doc; private collectionFreeForm?: CollectionFreeFormView; @@ -44,7 +44,7 @@ export class InkTranscription extends React.Component { @action // eslint-disable-next-line @typescript-eslint/no-explicit-any setMathRef = async (r: any) => { - if (!this._textRegister && r) { + if (!this._mathRegister && r) { const options = { configuration: { server: { @@ -55,19 +55,17 @@ export class InkTranscription extends React.Component { protocol: 'WEBSOCKET', }, recognition: { - type: 'TEXT', - lang: 'en_US', - text: { - mimeTypes: ['application/vnd.myscript.jiix'] as 'application/vnd.myscript.jiix'[], + type: 'MATH', + math: { + mimeTypes: ['application/x-latex'] as unknown as 'application/vnd.myscript.jiix'[], }, }, }, }; - await iink.Editor.load(r, 'INKV2', options); - - this._textRegister = r; + this._iinkMathEditor = await iink.Editor.load(r, 'INKV2', options); + this._mathRegister = r; // eslint-disable-next-line @typescript-eslint/no-explicit-any - r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + r?.addEventListener('exported', (e: any) => this.exportInk(e)); return (this._textRef = r); } @@ -97,12 +95,14 @@ export class InkTranscription extends React.Component { this._iinkEditor = await iink.Editor.load(r, 'INKV2', options); this._textRegister = r; // eslint-disable-next-line @typescript-eslint/no-explicit-any - r?.addEventListener('exported', (e: any) => this.exportInk(e, this._textRef)); + r?.addEventListener('exported', (e: any) => this.exportInk(e)); return (this._textRef = r); } }; + _ffview: CollectionFreeFormView | undefined; + /** * Handles processing Dash Doc data for ink transcription. * @@ -110,7 +110,7 @@ export class InkTranscription extends React.Component { * @param inkDocs the ink docs contained within the selected group * @param math boolean whether to do math transcription or not */ - transcribeInk = (groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => { + transcribeInk = (ffview: CollectionFreeFormView, groupDoc: Doc | undefined, inkDocs: Doc[], math: boolean) => { if (!groupDoc) return; const validInks = inkDocs.filter(s => s.type === DocumentType.INK); @@ -126,13 +126,10 @@ export class InkTranscription extends React.Component { strokes.push(d.inkData.map(pd => inkStroke.ptToScreen({ X: pd.X, Y: pd.Y }))); times.push(authorTime); }); + this._ffview = ffview; this.currGroup = groupDoc; const pointerData = strokes.map((stroke, i) => this.inkJSON(stroke, times[i])); - if (math) { - this._iinkEditor.importPointEvents(pointerData); - } else { - this._iinkEditor.importPointEvents(pointerData); - } + (math ? this._iinkMathEditor : this._iinkEditor).importPointEvents(pointerData); }; convertPointsToString(points: InkData[]): string { return points[0].map(point => `new Point(${point.X}, ${point.Y})`).join(','); @@ -204,83 +201,92 @@ export class InkTranscription extends React.Component { * @param ref the ref to the editor */ // eslint-disable-next-line @typescript-eslint/no-explicit-any - exportInk = async (e: any, ref: any) => { - const exports = e.detail['application/vnd.myscript.jiix']; - if (exports) { - if (exports['type'] == 'Math') { - const latex = exports['application/x-latex']; - if (this.currGroup) { - this.currGroup.text = latex; - this.currGroup.title = latex; - } + exportInk = (e: { detail: { [key: string]: string } }) => { + const exports = e.detail; + if (exports['application/x-latex']) { + const latex = exports['application/x-latex']; + if (this.currGroup) { + this.currGroup.text = latex; + this.currGroup.title = latex; + } - ref.editor.clear(); - } else if (exports['type'] == 'Text') { - if (exports['application/vnd.myscript.jiix']) { - this.lastJiix = JSON.parse(exports['application/vnd.myscript.jiix']); - // map timestamp to strokes - const timestampWord = new Map<number, string>(); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - this.lastJiix.words.map((word: any) => { - if (word.items) { - word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => { - const ms = Date.parse(i.timestamp); - timestampWord.set(ms, word.label); - }); - } + this._ffview?.addDocument( + Docs.Create.EquationDocument(latex, { + title: '', + x: this.currGroup?.x as number, + y: (this.currGroup?.y as number) + (this.currGroup?.height as number), + nativeHeight: 40, + _height: 50, + nativeWidth: 40, + _width: 50, + }) + ); + // this.showRecogBox(latex as string); + this._iinkMathEditor.clear(); + } else if (exports['application/vnd.myscript.jiix']) { + const lastJiix = exports['application/vnd.myscript.jiix'] as unknown as { words: string[]; label: string }; + // map timestamp to strokes + const timestampWord = new Map<number, string>(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + lastJiix.words.map((word: any) => { + if (word.items) { + word.items.forEach((i: { id: string; timestamp: string; X: Array<number>; Y: Array<number>; F: Array<number> }) => { + const ms = Date.parse(i.timestamp); + timestampWord.set(ms, word.label); }); - - const wordInkDocMap = new Map<string, Doc[]>(); - if (this.currGroup) { - const docList = DocListCast(this.currGroup.data); - docList.forEach((inkDoc: Doc) => { - // just having the times match up and be a unique value (actual timestamp doesn't matter) - const ms = (DateCast(inkDoc.author_date)?.getDate().getTime() ?? 0) + 14400000; - const word = timestampWord.get(ms); - if (!word) { - return; - } - const entry = wordInkDocMap.get(word); - if (entry) { - entry.push(inkDoc); - wordInkDocMap.set(word, entry); - } else { - const newEntry = [inkDoc]; - wordInkDocMap.set(word, newEntry); - } - }); - if (this.lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap); - } } - const text = exports['label']; + }); - if (this.currGroup && text) { - DocumentView.getDocumentView(this.currGroup)?.ComponentView?.updateIcon?.(); - const image = await this.getIcon(); - const { href } = (image as URLField).url; - const hrefParts = href.split('.'); - const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; - let response; - try { - const hrefBase64 = await imageUrlToBase64(hrefComplete); - response = await gptHandwriting(hrefBase64); - } catch { - console.error('Error getting image'); - } - const textBoxText = 'iink: ' + text + '\n' + '\n' + 'ChatGPT: ' + response; - this.currGroup.transcription = response; - this.currGroup.title = response; - if (!this.currGroup.hasTextBox) { - const newDoc = Docs.Create.TextDocument(textBoxText, { title: '', x: this.currGroup.x as number, y: (this.currGroup.y as number) + (this.currGroup.height as number) }); - newDoc.height = 200; - this.collectionFreeForm?.addDocument(newDoc); - this.currGroup.hasTextBox = true; + const wordInkDocMap = new Map<string, Doc[]>(); + if (this.currGroup) { + DocListCast(this.currGroup.data).forEach((inkDoc: Doc) => { + // just having the times match up and be a unique value (actual timestamp doesn't matter) + const ms = (DateCast(inkDoc.author_date)?.getDate().getTime() ?? 0) + 14400000; + const word = timestampWord.get(ms); + if (word) { + const entry = wordInkDocMap.get(word); + if (entry) { + entry.push(inkDoc); + wordInkDocMap.set(word, entry); + } else { + const newEntry = [inkDoc]; + wordInkDocMap.set(word, newEntry); + } } - ref.editor.clear(); - } + }); + if (lastJiix.words.length > 1) this.subgroupsTranscriptions(wordInkDocMap); } + this.showRecogBox(lastJiix.label); + this._iinkEditor.clear(); } }; + private showRecogBox(text: string) { + if (this.currGroup) { + let response; + // DocumentView.getDocumentView(this.currGroup)?.ComponentView?.updateIcon?.(); + // const image = await this.getIcon(); + // const { href } = (image as URLField).url; + // const hrefParts = href.split('.'); + // const hrefComplete = `${hrefParts[0]}_o.${hrefParts[1]}`; + // try { + // const hrefBase64 = await imageUrlToBase64(hrefComplete); + // response = await gptHandwriting(hrefBase64); + // } catch { + // console.error('Error getting image'); + // } + const textBoxText = 'iink: ' + text + '\n' + '\n' + 'ChatGPT: ' + response; + + this.currGroup.transcription = response; + this.currGroup.title = response; + if (!this.currGroup.hasTextBox) { + const newDoc = Docs.Create.TextDocument(textBoxText, { title: '', x: this.currGroup.x as number, y: (this.currGroup.y as number) + (this.currGroup.height as number) }); + newDoc.height = 200; + this.collectionFreeForm?.addDocument(newDoc); + this.currGroup.hasTextBox = true; + } + } + } + /** * gets the icon of the collection that was just made * @returns the image of the collection @@ -299,7 +305,7 @@ export class InkTranscription extends React.Component { */ createInkGroup() { // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (Doc.ActiveTool === InkTool.Ink && Doc.ActiveInk === InkInkTool.Write) { + if (Doc.ActiveTool === InkTool.Ink && [InkInkTool.Write, InkInkTool.Math].includes(Doc.ActiveInk)) { CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those const selected = ffView.unprocessedDocs; @@ -309,7 +315,7 @@ export class InkTranscription extends React.Component { ); ffView.unprocessedDocs = []; - InkTranscription.Instance.transcribeInk(newCollection, selected, false); + InkTranscription.Instance.transcribeInk(ffView, newCollection, selected, Doc.ActiveInk === InkInkTool.Math); }); } CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 253db08de..4b651af7d 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -124,7 +124,7 @@ export class InkingStroke extends ViewBoxAnnotatableComponent<FieldViewProps>() ); ffView.unprocessedDocs = []; - InkTranscription.Instance.transcribeInk(newCollection, selected, false); + InkTranscription.Instance.transcribeInk(ffView, newCollection, selected, false); } }; diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 0eb21b943..62b1180ec 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -244,6 +244,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { </div> </div> ); + setStickerRef = (r: StickerPalette | null) => (this._annoPaletteView = r); render() { let downx = 0; let downy = 0; @@ -317,7 +318,7 @@ export class LightboxView extends ObservableReactComponent<LightboxViewProps> { </GestureOverlay> </div> - {this._showPalette && <StickerPalette ref={r => (this._annoPaletteView = r)} Doc={DocCast(Doc.UserDoc().myLightboxDrawings)} />} + {this._showPalette && DocCast(Doc.UserDoc().myLightboxDrawings) && <StickerPalette ref={this.setStickerRef} Doc={DocCast(Doc.UserDoc().myLightboxDrawings)!} />} {this.renderNavBtn(0, undefined, this._props.PanelHeight / 2 - 12.5, 'chevron-left', this._doc && this._history.length ? true : false, this.previous)} {this.renderNavBtn( this._props.PanelWidth - Math.min(this._props.PanelWidth / 4, this._props.maxBorder[0]), diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 13b14617c..867a5a304 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -921,19 +921,19 @@ export class MainView extends ObservableReactComponent<object> { ); } + setMainDashRef = (r: HTMLDivElement | null) => + r && + new ResizeObserver( + action(() => { + this._dashUIWidth = r.getBoundingClientRect().width; + this._dashUIHeight = r.getBoundingClientRect().height; + }) + ).observe(r); @computed get mainDashboardArea() { return !this.userDoc ? null : ( <div className="mainView-dashboardArea" - ref={r => { - r && - new ResizeObserver( - action(() => { - this._dashUIWidth = r.getBoundingClientRect().width; - this._dashUIHeight = r.getBoundingClientRect().height; - }) - ).observe(r); - }} + ref={this.setMainDashRef} style={{ color: 'black', height: `calc(100% - ${this.topOfDashUI + this.topMenuHeight()}px)`, @@ -1072,6 +1072,14 @@ export class MainView extends ObservableReactComponent<object> { }; lightboxMaxBorder = [200, 50]; + setMainViewRef = (r: HTMLDivElement | null) => + r && + new ResizeObserver( + action(() => { + this._windowWidth = r.getBoundingClientRect().width; + this._windowHeight = r.getBoundingClientRect().height; + }) + ).observe(r); render() { return ( <div @@ -1085,15 +1093,7 @@ export class MainView extends ObservableReactComponent<object> { ele.scrollTop = ele.scrollLeft = 0; })(document.getElementById('root')!) } - ref={r => { - r && - new ResizeObserver( - action(() => { - this._windowWidth = r.getBoundingClientRect().width; - this._windowHeight = r.getBoundingClientRect().height; - }) - ).observe(r); - }}> + ref={this.setMainViewRef}> {this.inkResources} <DictationOverlay /> <SharingManager /> diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx index 067020a62..3755b1ec1 100644 --- a/src/client/views/UndoStack.tsx +++ b/src/client/views/UndoStack.tsx @@ -9,6 +9,7 @@ import './UndoStack.scss'; @observer export class UndoStack extends React.Component<object> { + setRef = (r: HTMLDivElement | null) => r?.scroll({ behavior: 'auto', top: (r?.scrollHeight ?? 0) + 20 }); render() { const background = UndoManager.batchCounter.get() ? 'yellow' : SettingsManager.userVariantColor; const color = UndoManager.batchCounter.get() ? 'black' : SettingsManager.userColor; @@ -25,7 +26,7 @@ export class UndoStack extends React.Component<object> { popup={ <div className="undoStack-commandsContainer" - ref={r => r?.scroll({ behavior: 'auto', top: (r?.scrollHeight ?? 0) + 20 })} + ref={this.setRef} style={{ background, color, diff --git a/src/client/views/animationtimeline/Timeline.tsx b/src/client/views/animationtimeline/Timeline.tsx index 814e9a7a0..c2bd01334 100644 --- a/src/client/views/animationtimeline/Timeline.tsx +++ b/src/client/views/animationtimeline/Timeline.tsx @@ -533,6 +533,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps & { Doc: D this._totalLength = RegionHelpers.convertPixelTime(this._time, 'mili', 'pixel', this._tickSpacing, this._tickIncrement); }; + setTrackRef = (r: Track) => this.mapOfTracks.push(r); /** * if you have any question here, just shoot me an email or text. * basically the only thing you need to edit besides render methods in track (individual track lines) and keyframe (green region) @@ -554,7 +555,7 @@ export class Timeline extends ObservableReactComponent<FieldViewProps & { Doc: D {[...this.children, this._props.Doc].map(doc => ( <Track key={doc[Id]} - ref={ref => this.mapOfTracks.push(ref)} + ref={this.setTrackRef} timeline={this} animatedDoc={doc} currentBarX={this._currentBarX} diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 7f639a11e..7a4408931 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -256,9 +256,10 @@ export class CollectionNoteTakingView extends CollectionSubView() { const height = () => this.getDocHeight(doc); let dref: Opt<DocumentView>; const noteTakingDocTransform = () => this.getDocTransform(doc, dref); + const setRef = (r: DocumentView | null) => (dref = r || undefined); return ( <DocumentView - ref={r => (dref = r || undefined)} + ref={setRef} Document={doc} TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined} pointerEvents={this.blockPointerEventsWhenDragging} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index bdeb7d944..fbdd23315 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import * as CSS from 'csstype'; -import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, ObservableMap, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, DivHeight, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils'; @@ -391,10 +391,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }); // This following three functions must be from the view Mehek showed - columnDividerDown = (e: React.PointerEvent) => { - runInAction(() => { - this._cursor = 'grabbing'; - }); + columnDividerDown = action((e: React.PointerEvent) => { + this._cursor = 'grabbing'; const batch = UndoManager.StartBatch('stacking width'); setupMoveUpEvents( this, @@ -406,7 +404,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection }), emptyFunction ); - }; + }); @action onDividerMove = (e: PointerEvent) => { this.Document._layout_columnWidth = Math.max(10, (this._props.DocumentView?.().screenToViewTransform().transformPoint(e.clientX, 0)[0] ?? 0) - this.xMargin); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 8c535534a..b5efa7a72 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -304,6 +304,12 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< ContextMenu.Instance.displayMenu(pt[0], pt[1], undefined, true); }; + setRef = (r: HTMLDivElement | null) => { + if (this._headerRef && this._props.colHeaderRefs.includes(this._headerRef)) this._props.colHeaderRefs.splice(this._props.colStackRefs.indexOf(this._headerRef), 1); + r && this._props.colHeaderRefs.push(r); + this._headerRef = r; + }; + @computed get innards() { TraceMobx(); const key = this._props.pivotField; @@ -315,11 +321,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< <div key={heading} className="collectionStackingView-sectionHeader" - ref={r => { - if (this._headerRef && this._props.colHeaderRefs.includes(this._headerRef)) this._props.colHeaderRefs.splice(this._props.colStackRefs.indexOf(this._headerRef), 1); - r && this._props.colHeaderRefs.push(r); - this._headerRef = r; - }} + ref={this.setRef} style={{ marginTop: 0, marginBottom: this._props.gridGap, diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index bee5d016d..4625965b4 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -407,6 +407,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree remAnnotationDocument = (doc: Doc | Doc[]) => this.removeDocument(doc, `${this._props.fieldKey}_annotations`) || false; moveAnnotationDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[], annotationKey?: string) => boolean) => this.moveDocument(doc, targetCollection, addDocument) || false; + setRef = (r: HTMLDivElement | null) => !this.Document.treeView_HasOverlay && r && this.createTreeDropTarget(r); @observable _headerHeight = 0; @computed get content() { const background = () => this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string; @@ -428,7 +429,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree <div className="collectionTreeView-contents" key="tree" - ref={r => !this.Document.treeView_HasOverlay && r && this.createTreeDropTarget(r)} + ref={this.setRef} style={{ ...(!titleBar ? { marginLeft: this.marginX(), paddingTop: this.marginTop() } : {}), color: color(), diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 5b2f1ff81..f84c7d3c0 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1294,6 +1294,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return null; } + const setRef = (r: TreeView | null) => treeViewRefs.set(child, r || undefined); 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.LayoutDataKey(newParent); @@ -1317,7 +1318,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return ( <TreeView key={child[Id]} - ref={r => treeViewRefs.set(child, r || undefined)} + ref={setRef} Document={pair.layout} dataDoc={pair.data} treeViewParent={treeViewParent} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 32ace463d..6e9e503f4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -562,7 +562,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); const inkDoc = this.createInkDoc(points, B); if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc.$backgroundColor = 'transparent'; - if (Doc.ActiveInk === InkInkTool.Write) { + if ([InkInkTool.Write, InkInkTool.Math].includes(Doc.ActiveInk)) { this.unprocessedDocs.push(inkDoc); CollectionFreeFormView.collectionsWithUnprocessedInk.add(this); } @@ -2220,18 +2220,41 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection ); }; + @computed get inkEraser() { + return ( + Doc.ActiveTool === InkTool.Eraser && + Doc.ActiveEraser === InkEraserTool.Radius && + this._showEraserCircle && ( + <div + onPointerMove={this.onCursorMove} + style={{ + position: 'fixed', + left: this._eraserX, + top: this._eraserY, + width: (ActiveEraserWidth() + 5) * 2, + height: (ActiveEraserWidth() + 5) * 2, + borderRadius: '50%', + border: '1px solid gray', + transform: 'translate(-50%, -50%)', + }} + /> + ) + ); + } + + setRef = (r: HTMLDivElement | null) => { + this.createDashEventsTarget(r); + this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel); + r?.addEventListener('mouseleave', this.onMouseLeave); + r?.addEventListener('mouseenter', this.onMouseEnter); + }; render() { TraceMobx(); return ( <div className="collectionfreeformview-container" id={this._paintedId} - ref={r => { - this.createDashEventsTarget(r); - this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel); - r?.addEventListener('mouseleave', this.onMouseLeave); - r?.addEventListener('mouseenter', this.onMouseEnter); - }} + ref={this.setRef} onWheel={this.onPointerWheel} onClick={this.onClick} onPointerDown={this.onPointerDown} @@ -2246,21 +2269,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection width: `${100 / this.nativeDimScaling}%`, height: this._props.getScrollHeight?.() ?? `${100 / this.nativeDimScaling}%`, }}> - {Doc.ActiveTool === InkTool.Eraser && Doc.ActiveEraser === InkEraserTool.Radius && this._showEraserCircle && ( - <div - onPointerMove={this.onCursorMove} - style={{ - position: 'fixed', - left: this._eraserX, - top: this._eraserY, - width: (ActiveEraserWidth() + 5) * 2, - height: (ActiveEraserWidth() + 5) * 2, - borderRadius: '50%', - border: '1px solid gray', - transform: 'translate(-50%, -50%)', - }} - /> - )} + {this.inkEraser} {this.paintFunc ? ( <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads ) : this._lightboxDoc ? ( diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index 142085e14..c31558dff 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -150,6 +150,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, this.Document); }, 'remove doc from face'); + setRef = (r: HTMLDivElement | null) => this.fixWheelEvents(r, this._props.isContentActive); render() { return ( <div className="face-document-item" ref={ele => this.createDropTarget(ele!)}> @@ -176,7 +177,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none', }} - ref={r => this.fixWheelEvents(r, this._props.isContentActive)}> + ref={this.setRef}> {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => { const [name, type] = ImageCastToNameType(doc?.[Doc.LayoutDataKey(doc)]) ?? ['-missing-', '.png']; return ( diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ff78b332a..4191aaca8 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -762,29 +762,26 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps } e.stopPropagation(); }; + setRef = (r: HTMLDivElement | null) => { + r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); + this.MarqueeRef = r; + }; render() { return ( - <> - <div - className="marqueeView" - ref={r => { - r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); - this.MarqueeRef = r; - }} - style={{ - overflow: StrCast(this._props.Document._overflow), - cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', - }} - onDragOver={e => e.preventDefault()} - onScroll={e => { - e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; - }} - onClick={this.onClick} - onPointerDown={this.onPointerDown}> - {this._visible ? this.marqueeDiv : null} - {this.props.children} - </div> - </> + <div + className="marqueeView" + ref={this.setRef} + style={{ + overflow: StrCast(this._props.Document._overflow), + cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', + }} + onDragOver={e => e.preventDefault()} + onScroll={e => (e.currentTarget.scrollTop = (e.currentTarget.scrollLeft = 0))} // prettier-ignore + onClick={this.onClick} + onPointerDown={this.onPointerDown}> + {this._visible ? this.marqueeDiv : null} + {this.props.children} + </div> ); } } diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index d0a1e6f0d..435f618d9 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -158,14 +158,13 @@ export class CollectionLinearView extends CollectionSubView() { let dref: Opt<HTMLDivElement>; const docXf = () => this.getTransform(dref); + const setRef = (r: HTMLDivElement | null) => (dref = r || undefined); // const scalable = pair.layout.onClick || pair.layout.onDragStart; return hidden ? null : ( <div className={preview ? 'preview' : `collectionLinearView-docBtn`} key={doc[Id]} - ref={r => { - dref = r || undefined; - }} + ref={setRef} style={{ pointerEvents: 'all', width: NumCast(doc._width), diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 6442385c0..2576bdf9b 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1043,17 +1043,16 @@ export class CollectionSchemaView extends CollectionSubView() { if (!this._oldKeysWheel?.scrollTop && e.deltaY <= 0) e.preventDefault(); e.stopPropagation(); }; + setRef = (r: HTMLDivElement | null) => { + this._oldKeysWheel?.removeEventListener('wheel', this.onKeysPassiveWheel); + this._oldKeysWheel = r; + r?.addEventListener('wheel', this.onKeysPassiveWheel, { passive: false }); + }; _oldKeysWheel: HTMLDivElement | null = null; @computed get keysDropdown() { return ( <div className="schema-key-search"> - <div - className="schema-key-list" - ref={r => { - this._oldKeysWheel?.removeEventListener('wheel', this.onKeysPassiveWheel); - this._oldKeysWheel = r; - r?.addEventListener('wheel', this.onKeysPassiveWheel, { passive: false }); - }}> + <div className="schema-key-list" ref={this.setRef}> {this._menuKeys.map(key => ( <div key={key} @@ -1294,6 +1293,9 @@ export class CollectionSchemaView extends CollectionSubView() { screenToLocal = () => this.ScreenToLocalBoxXf().translate(-this.tableWidth, 0); previewWidthFunc = () => this.previewWidth; displayedDocsFunc = () => this.docsWithDrag.docs; + setColHdrRef = (r: SchemaColumnHeader | null) => r && this._headerRefs.push(r); + setPreviewRef = (r: HTMLDivElement | null) => (this._previewRef = r); + render() { return ( <div @@ -1331,7 +1333,7 @@ export class CollectionSchemaView extends CollectionSubView() { {this.columnKeys.map((key, index) => ( <SchemaColumnHeader //cleanupField={this.cleanupComputedField} - ref={r => r && this._headerRefs.push(r)} + ref={this.setColHdrRef} keysDropdown={this.keysDropdown} schemaView={this} columnWidth={() => CollectionSchemaView._minColWidth} //TODO: update @@ -1379,11 +1381,7 @@ export class CollectionSchemaView extends CollectionSubView() { </div> {this.previewWidth > 0 && <div className="schema-preview-divider" style={{ width: CollectionSchemaView._previewDividerWidth }} onPointerDown={this.onDividerDown} />} {this.previewWidth > 0 && ( - <div - style={{ width: `${this.previewWidth}px` }} - ref={ref => { - this._previewRef = ref; - }}> + <div style={{ width: `${this.previewWidth}px` }} ref={this.setPreviewRef}> {Array.from(this._selectedDocs).lastElement() && ( <DocumentView Document={Array.from(this._selectedDocs).lastElement()} diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx index 9ad94cb31..412daa105 100644 --- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx +++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx @@ -341,12 +341,14 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro return <span className="editableView-static">{this._props.fieldContents ? <FieldView {...this._props.fieldContents} /> : ''}</span>; }; + setRef = (r: HTMLDivElement | null) => (this._inputref = r); + renderEditor = () => { return ( <div contentEditable className="schemaField-editing" - ref={r => (this._inputref = r)} + ref={this.setRef} style={{ minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }} onBlur={() => (this._props.refSelectModeInfo.enabled ? setTimeout(() => this.setIsFocused(true), 1000) : this.finalizeEdit(false, true, false))} onInput={this.onChange} diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 134f2ed31..64bfab856 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -118,6 +118,10 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea return { color, fieldProps, cursor }; }; + setRef = (r: EditableView | null) => { + this._inputRef = r; + this._props.autoFocus && r?.setIsFocused(true); + }; @computed get editableView() { const { color, fieldProps } = this.renderProps(this._props); @@ -133,10 +137,7 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea width: '100%', }}> <EditableView - ref={r => { - this._inputRef = r; - this._props.autoFocus && r?.setIsFocused(true); - }} + ref={this.setRef} oneLine={true} allowCRs={false} contents={''} diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 8b34b4139..02e0d8100 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -185,6 +185,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return eqSymbol + modField; }; + setRef = (r: SchemaCellField | null) => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true); @computed get defaultCellContent() { const { color, textDecoration, fieldProps, pointerEvents } = SchemaTableCell.renderProps(this._props); @@ -204,7 +205,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro Doc={this._props.Doc} highlightCells={(text: string) => this._props.highlightCells(this.adjustSelfReference(text))} getCells={(text: string) => this._props.eqHighlightFunc(this.adjustSelfReference(text))} - ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)} + ref={this.setRef} oneLine={this._props.oneLine} contents={undefined} fieldContents={fieldProps} diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index cb3adae10..e098d50d8 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -408,7 +408,7 @@ function setActiveTool(tool: InkTool | InkEraserTool | InkInkTool | Gestures, ke } runInAction(() => { const eraserTool = tool === InkTool.Eraser ? Doc.ActiveEraser : [InkEraserTool.Stroke, InkEraserTool.Radius, InkEraserTool.Segment].includes(tool as InkEraserTool) ? (tool as InkEraserTool) : undefined; - const inkTool = tool === InkTool.Ink ? Doc.ActiveInk : [InkInkTool.Pen, InkInkTool.Write, InkInkTool.Highlight].includes(tool as InkInkTool) ? (tool as InkInkTool) : undefined; + const inkTool = tool === InkTool.Ink ? Doc.ActiveInk : [InkInkTool.Pen, InkInkTool.Write, InkInkTool.Math, InkInkTool.Highlight].includes(tool as InkInkTool) ? (tool as InkInkTool) : undefined; if (GestureOverlay.Instance) { SnappingManager.SetKeepGestureMode(keepPrim); } diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 9369ff98a..0b7033e57 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -420,6 +420,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { } }; + setRef = (r: LineChart | Histogram | PieChart | null) => (this._vizRenderer = r ?? undefined); // toggles for user to decide which chart type to view the data in @computed get renderVizView() { const scale = this._props.NativeDimScaling?.() || 1; @@ -437,9 +438,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { if (!this.records.length) return 'no data/visualization'; switch (this.dataVizView) { case DataVizView.TABLE: return <TableBox {...sharedProps} Doc={this.Document} specHighlightedRow={this._specialHighlightedRow} docView={this.DocumentView} selectAxes={this.selectAxes} selectTitleCol={this.selectTitleCol}/>; - case DataVizView.LINECHART: return <LineChart {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} vizBox={this} />; - case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} />; - case DataVizView.PIECHART: return <PieChart {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => {this._vizRenderer = r ?? undefined;}} + case DataVizView.LINECHART: return <LineChart {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={this.setRef} vizBox={this} />; + case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={this.setRef} />; + case DataVizView.PIECHART: return <PieChart {...sharedProps} Doc={this.Document} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={this.setRef} margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />; default: } // prettier-ignore diff --git a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx index fb083ea75..8f6ecab57 100644 --- a/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx +++ b/src/client/views/nodes/DataVizBox/DocCreatorMenu/DocCreatorMenu.tsx @@ -540,6 +540,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> ]; //prettier-ignore } + setRef = (r: HTMLDivElement) => (this._ref = r); render() { const topButton = (icon: string, opt: string, func: () => void, tag: string) => ( <div className={`top-button-container ${tag} ${opt === this._menuContent ? 'selected' : ''}`}> @@ -558,7 +559,7 @@ export class DocCreatorMenu extends ObservableReactComponent<DocCreateMenuProps> {!this._shouldDisplay ? undefined : ( <div className="docCreatorMenu-cont" - ref={r => (this._ref = r)} + ref={this.setRef} style={{ display: '', left: this._pageX, diff --git a/src/client/views/nodes/DataVizBox/components/Histogram.tsx b/src/client/views/nodes/DataVizBox/components/Histogram.tsx index a7c4a00b0..f51683991 100644 --- a/src/client/views/nodes/DataVizBox/components/Histogram.tsx +++ b/src/client/views/nodes/DataVizBox/components/Histogram.tsx @@ -413,6 +413,10 @@ export class Histogram extends ObservableReactComponent<HistogramProps> { }); }; + setChartRef = (r: HTMLDivElement | null) => { + this._histogramRef = r; + r && this.drawChart(this._histogramData, this.width, this.height); + }; render() { if (!this.selectedBins) this.layoutDoc.dataViz_histogram_selectedBins = new List<string>(); @@ -446,12 +450,7 @@ export class Histogram extends ObservableReactComponent<HistogramProps> { size={Size.XSMALL} /> </div> - <div - ref={r => { - this._histogramRef = r; - r && this.drawChart(this._histogramData, this.width, this.height); - }} - /> + <div ref={this.setChartRef} /> {selected !== 'none' ? ( <div className="selected-data"> Selected: {selected} diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 80fadf178..732681e05 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -347,6 +347,10 @@ export class LineChart extends ObservableReactComponent<LineChartProps> { .style('pointer-events', 'none') .style('transform', `translate(${xScale(d0.x) - this.width}px,${yScale(d0.y)}px)`); } + setLineRef = (r: HTMLDivElement | null) => { + this._lineChartRef = r; + this.drawChart([this._lineChartData], this.rangeVals, this.width, this.height); + }; render() { const selectedPt = this._currSelected ? `{ ${this._props.axes[0]}: ${this._currSelected.x} ${this._props.axes[1]}: ${this._currSelected.y} }` : 'none'; @@ -378,12 +382,7 @@ export class LineChart extends ObservableReactComponent<LineChartProps> { fillWidth /> </div> - <div - ref={r => { - this._lineChartRef = r; - this.drawChart([this._lineChartData], this.rangeVals, this.width, this.height); - }} - /> + <div ref={this.setLineRef} /> {selectedPt !== 'none' ? ( <div className="selected-data"> {`Selected: ${selectedPt}`} diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index 0ae70786f..cf476b8d0 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -373,10 +373,10 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { const sliceName = StrCast(sliceTitle) ? StrCast(sliceTitle).replace(/\$/g, '').replace(/%/g, '').replace(/#/g, '').replace(/</g, '') : sliceTitle; const sliceColors = Cast(this._props.layoutDoc.dataViz_pie_sliceColors, listSpec('string'), null); - sliceColors.forEach(each => { + sliceColors?.forEach(each => { if (each.split('::')[0] === sliceName) sliceColors.splice(sliceColors.indexOf(each), 1); }); - sliceColors.push(StrCast(sliceName + '::' + color)); + sliceColors?.push(StrCast(sliceName + '::' + color)); }; @action changeHistogramCheckBox = () => { @@ -384,6 +384,10 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { this.drawChart(this._pieChartData, this.width, this.height); }; + setChartRef = (r: HTMLDivElement | null) => { + this._piechartRef = r; + this.drawChart(this._pieChartData, this.width, this.height); + }; render() { let titleAccessor = 'dataViz_pie_title'; if (this._props.axes.length === 2) titleAccessor = titleAccessor + this._props.axes[0] + '-' + this._props.axes[1]; @@ -443,12 +447,7 @@ export class PieChart extends ObservableReactComponent<PieChartProps> { Organize data as histogram </div> ) : null} - <div - ref={r => { - this._piechartRef = r; - this.drawChart(this._pieChartData, this.width, this.height); - }} - /> + <div ref={this.setChartRef} /> {selected !== 'none' ? ( <div className="selected-data"> Selected: {selected} diff --git a/src/client/views/nodes/DiagramBox.tsx b/src/client/views/nodes/DiagramBox.tsx index 7cfccf0dc..6a31f64ce 100644 --- a/src/client/views/nodes/DiagramBox.tsx +++ b/src/client/views/nodes/DiagramBox.tsx @@ -185,6 +185,8 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { return '( )'; }; + setRef = (r: HTMLDivElement | null) => this.fixWheelEvents(r, this._props.isContentActive); + setDiagramBoxRef = (r: HTMLDivElement | null) => r && this.renderMermaidAsync.call(this, this.removeWords(this.mermaidcode), r); render() { return ( <div @@ -192,7 +194,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { style={{ pointerEvents: this._props.isContentActive() ? undefined : 'none', }} - ref={r => this.fixWheelEvents(r, this._props.isContentActive)}> + ref={this.setRef}> <div className="DIYNodeBox-searchbar"> <input type="text" value={this._inputValue} onKeyDown={action(e => e.key === 'Enter' && this.generateMermaidCode())} onChange={action(e => (this._inputValue = e.target.value))} /> <button type="button" onClick={this.generateMermaidCode}> @@ -208,7 +210,7 @@ export class DiagramBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { ) : this._generating ? ( <div className="loading-circle" /> ) : ( - <div className="diagramBox" ref={r => r && this.renderMermaidAsync.call(this, this.removeWords(this.mermaidcode), r)}> + <div className="diagramBox" ref={this.setDiagramBoxRef}> {this._errorMessage || 'Type a prompt to generate a diagram'} </div> )} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 504c1491e..32741a0fe 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -261,9 +261,7 @@ export class DocumentContentsView extends ObservableReactComponent<DocumentConte jsx={layoutFrame} showWarnings // eslint-disable-next-line @typescript-eslint/no-explicit-any - onError={(test: any) => { - console.log('DocumentContentsView:' + test, bindings, layoutFrame); - }} + onError={(test: any) => console.log('DocumentContentsView:' + test, bindings, layoutFrame)} /> ); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index fe95f15af..bd71115db 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -701,16 +701,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewProps & Field aiContentsWidth = () => (this.aiContentsHeight() * (this._props.NativeWidth?.() || 1)) / (this._props.NativeHeight?.() || 1); aiContentsHeight = () => Math.max(10, this._props.PanelHeight() - (this._aiWinHeight + (this.tagsOverlayFunc() ? 22 : 0)) * this.uiBtnScaling); + setAiRef = action((r: HTMLDivElement | null) => this.historyRef(this._oldHistoryWheel, (this._oldHistoryWheel = r))); + @computed get aiEditor() { return ( <div className="documentView-editorView" + ref={this.setAiRef} 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} @@ -1507,18 +1509,16 @@ export class DocumentView extends DocComponent<DocumentViewProps>() { .translate(-(this._docViewInternal?.aiShift() ?? 0), 0) .scale((this._docViewInternal?.aiScale() ?? 1) / this.nativeScaling); + setHtmlOverlayRef = (r: HTMLDivElement | null) => { + 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))); + } + }; htmlOverlay = () => { const effect = StrCast(this._htmlOverlayEffect?.presentation_effect, StrCast(this._htmlOverlayEffect?.followLinkAnimEffect)); return ( - <div - className="documentView-htmlOverlay" - 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))); - } - }} - style={{ display: !this._htmlOverlayText ? 'none' : undefined }}> + <div className="documentView-htmlOverlay" ref={this.setHtmlOverlayRef} style={{ display: !this._htmlOverlayText ? 'none' : undefined }}> <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this._enableHtmlOverlayTransitions ? 0.9 : 0 }}> {DocumentViewInternal.AnimationEffect( <div className="webBox-textHighlight"> diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index e0ab04692..2ce24b688 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -116,12 +116,13 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { this.layoutDoc._nativeHeight = mathHeight; } }; + setRef = (r: HTMLDivElement) => r && this._ref.current?.element.current && this.updateSize(this._ref.current?.element.current); render() { TraceMobx(); const scale = this._props.NativeDimScaling?.() || 1; return ( <div - ref={r => r && this._ref.current?.element.current && this.updateSize(this._ref.current?.element.current)} + ref={this.setRef} className="equationBox-cont" onKeyDown={e => e.stopPropagation()} onPointerDown={e => !e.ctrlKey && e.stopPropagation()} diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 8e4b64851..e4d37e006 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -121,11 +121,12 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps> } // if (this.layout_autoHeight) this.tryUpdateScrollHeight(); }; + setRef = (r: HTMLDivElement | null) => r && this.createGraph(r); @computed get theGraph() { return ( <div id={`${this._plotId}`} - ref={r => r && this.createGraph(r)} + ref={this.setRef} style={{ position: 'absolute', width: '100%', height: '100%' }} onPointerDown={e => { e.stopPropagation(); diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index e1ecc2018..c5948cbbd 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -169,6 +169,25 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { } }; + setRef = (r: HTMLDivElement | null) => { + this._divRef?.removeEventListener('beforeinput', this.beforeInput); + this._divRef = r; + if (this._divRef) { + this._divRef.addEventListener('beforeinput', this.beforeInput); + + if (DocumentView.SelectOnLoad === this.Document) { + DocumentView.SetSelectOnLoad(undefined); + this._liveTextUndo = FormattedTextBox.LiveTextUndo; + FormattedTextBox.LiveTextUndo = undefined; + this._divRef.focus(); + } + this.fitTextToBox(this._divRef); + if (this.Title) { + this.resetCursor(); + } + } else this._timeout && clearTimeout(this._timeout); + }; + render() { TraceMobx(); const boxParams = this.fitTextToBox(undefined); // this causes mobx to trigger re-render when data changes @@ -228,24 +247,7 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps>() { __html: `<span class="textFitted textFitAlignVert" style="display: inline-block; text-align: center; font-size: 100px; height: 0px;">${this.Title?.startsWith('#') ? '' : (this.Title ?? '')}</span>`, }} contentEditable={this._props.onClickScript?.() ? undefined : true} - ref={r => { - this._divRef?.removeEventListener('beforeinput', this.beforeInput); - this._divRef = r; - if (this._divRef) { - this._divRef.addEventListener('beforeinput', this.beforeInput); - - if (DocumentView.SelectOnLoad === this.Document) { - DocumentView.SetSelectOnLoad(undefined); - this._liveTextUndo = FormattedTextBox.LiveTextUndo; - FormattedTextBox.LiveTextUndo = undefined; - this._divRef.focus(); - } - this.fitTextToBox(this._divRef); - if (this.Title) { - this.resetCursor(); - } - } else this._timeout && clearTimeout(this._timeout); - }} + ref={this.setRef} /> </div> </div> diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 78c8a686c..d31fadf77 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -99,6 +99,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { } }; + setRef = (r: HTMLDivElement | null) => (this._divRef = r); render() { TraceMobx(); @@ -149,28 +150,18 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { const aid = targetAhyperlinks?.find(alink => container?.contains(alink))?.id ?? targetAhyperlinks?.lastElement()?.id; const bid = targetBhyperlinks?.find(blink => container?.contains(blink))?.id ?? targetBhyperlinks?.lastElement()?.id; if (!aid || !bid) { - setTimeout( - action(() => { - this._forceAnimate += 0.01; - }) - ); + setTimeout(action(() => (this._forceAnimate += 0.01))); return null; } if (foundParent) { setTimeout( - action(() => { - this._forceAnimate += 0.01; - }), + action(() => (this._forceAnimate += 0.01)), 1 ); } - - if (at || bt) - setTimeout( - action(() => { - this._forceAnimate += 0.01; - }) - ); // this forces an update during a transition animation + if (at || bt) { + setTimeout(action(() => (this._forceAnimate += 0.01))); // this forces an update during a transition animation + } const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting) as { highlightStyle: string; highlightColor: string; highlightIndex: number; highlightStroke: boolean }; const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined; const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string; @@ -217,7 +208,7 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { id={this.DocumentView?.().DocUniqueId} className="linkBox-label" tabIndex={-1} - ref={r => (this._divRef = r)} + ref={this.setRef} onPointerDown={e => e.stopPropagation()} onFocus={() => { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined, this.dataDoc); diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 5b07b303a..1d6f41c65 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -250,6 +250,10 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps ); } + setDocViewRef = (r: DocumentView | null) => { + const targetanchor = this._linkDoc && this._linkSrc && Doc.getOppositeAnchor(this._linkDoc, this._linkSrc); + targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {}); + }; @computed get docPreview() { return (!this._linkDoc || !this._targetDoc || !this._linkSrc) && !this._toolTipText ? null : ( <div className="linkDocPreview-inner"> @@ -279,10 +283,7 @@ export class LinkDocPreview extends ObservableReactComponent<LinkDocPreviewProps this._toolTipText ) : ( <DocumentView - ref={r => { - const targetanchor = this._linkDoc && this._linkSrc && Doc.getOppositeAnchor(this._linkDoc, this._linkSrc); - targetanchor && this._targetDoc !== targetanchor && r?._props.focus?.(targetanchor, {}); - }} + ref={this.setDocViewRef} Document={this._targetDoc!} moveDocument={returnFalse} styleProvider={this._props.styleProvider} diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 603dcad5c..4677e0e61 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -171,19 +171,21 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps>() ContextMenu.Instance.addItem({ description: 'Options...', subitems, icon: 'video' }); }; + setRef = (r: HTMLVideoElement | null) => { + this._videoRef = r; + setTimeout(() => { + if (this.layoutDoc.mediaState === mediaState.PendingRecording && this._videoRef) { + this.toggleRecording(); + } + }, 100); + }; + @computed get content() { return ( <video className="videoBox-content" key="video" - ref={r => { - this._videoRef = r; - setTimeout(() => { - if (this.layoutDoc.mediaState === mediaState.PendingRecording && this._videoRef) { - this.toggleRecording(); - } - }, 100); - }} + ref={this.setRef} autoPlay={this._screenCapture} style={{ width: this._screenCapture ? '100%' : undefined, height: this._screenCapture ? '100%' : undefined }} onCanPlay={this.videoLoad} diff --git a/src/client/views/nodes/calendarBox/CalendarBox.tsx b/src/client/views/nodes/calendarBox/CalendarBox.tsx index a4183a11a..a2fa83b5a 100644 --- a/src/client/views/nodes/calendarBox/CalendarBox.tsx +++ b/src/client/views/nodes/calendarBox/CalendarBox.tsx @@ -298,10 +298,9 @@ export class CalendarBox extends CollectionSubView() { ev.preventDefault(); }); }} - // for dragging and dropping (mirror) - eventDragStart={(arg) => { + eventDragStart={arg => { const mirror = arg.el.cloneNode(true) as HTMLElement; const rect = arg.el.getBoundingClientRect(); @@ -312,25 +311,24 @@ export class CalendarBox extends CollectionSubView() { mirror.classList.add('custom-drag-mirror'); mirror.style.width = `${rect.width}px`; mirror.style.height = `${rect.height}px`; - + document.body.appendChild(mirror); - + const moveListener = (ev: MouseEvent) => { - mirror.style.left = `${ev.clientX}px`; - mirror.style.top = `${ev.clientY}px`; + mirror.style.left = `${ev.clientX}px`; + mirror.style.top = `${ev.clientY}px`; }; - + window.addEventListener('mousemove', moveListener); // hide the actual box arg.el.style.visibility = 'hidden'; arg.el.style.opacity = '0'; - + (arg.el as any)._mirrorElement = mirror; (arg.el as any)._moveListener = moveListener; }} - - eventDragStop={(arg) => { + eventDragStop={arg => { const el = arg.el as any; const mirror = el._mirrorElement; const moveListener = el._moveListener; @@ -338,15 +336,18 @@ export class CalendarBox extends CollectionSubView() { // show the actual box el.style.visibility = 'visible'; el.style.opacity = '1'; - + if (mirror) document.body.removeChild(mirror); if (moveListener) window.removeEventListener('mousemove', moveListener); }} - /> ); } + setRef = (r: HTMLDivElement | null) => { + this.createDashEventsTarget(r); + this.fixWheelEvents(r, this._props.isContentActive); + }; render() { const scale = this._props.ScreenToLocalTransform().Scale; const scaledWidth = this._props.PanelWidth(); @@ -361,11 +362,8 @@ export class CalendarBox extends CollectionSubView() { height: scaledHeight, overflow: 'hidden', position: 'relative', - }} - ref={r => { - this.createDashEventsTarget(r); - this.fixWheelEvents(r, this._props.isContentActive); }} + ref={this.setRef} onPointerDown={e => { setTimeout( action(() => { @@ -383,9 +381,8 @@ export class CalendarBox extends CollectionSubView() { transformOrigin: 'top left', width: scaledWidth / scale, height: scaledHeight / scale, - }} - > - {this.renderCalendar} + }}> + {this.renderCalendar} </div> </div> ); diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx index 6c3da8977..8043111b9 100644 --- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx +++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx @@ -974,6 +974,8 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; _dictation: DictationButton | null = null; + setInputRef = (r: HTMLInputElement) => (this._textInputRef = r); + setDictationRef = (r: DictationButton) => (this._dictation = r); /** * Renders the chat interface, including the message list, input field, and other UI elements. */ @@ -1002,9 +1004,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { <form onSubmit={this.askGPT} className="chat-input"> <input - ref={r => { - this._textInputRef = r; - }} + ref={this.setInputRef} type="text" name="messageInput" autoComplete="off" @@ -1023,13 +1023,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { </svg> )} </button> - <DictationButton - ref={r => { - this._dictation = r; - }} - setInput={this.setChatInput} - inputRef={this._textInputRef} - /> + <DictationButton ref={this.setDictationRef} setInput={this.setChatInput} inputRef={this._textInputRef} /> </form> {/* Popup for citation */} {this._citationPopup.visible && ( diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 07cb795f1..255ee1afe 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2129,6 +2129,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } }; + setRef = (r: HTMLDivElement | null) => this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel); + setScrollRef = (r: HTMLDivElement | null) => (this._scrollRef = r); render() { TraceMobx(); const scale = this.nativeScaling(); @@ -2143,7 +2145,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB ) : styleFromLayout?.height === '0px' ? null : ( <div className="formattedTextBox" - ref={r => this.fixWheelEvents(r, this._props.isContentActive, this.onPassiveWheel)} + ref={this.setRef} style={{ ...(this._props.dontScale ? {} @@ -2181,9 +2183,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB onDoubleClick={this.onDoubleClick}> <div className="formattedTextBox-outer" - ref={r => { - this._scrollRef = r; - }} + ref={this.setScrollRef} style={{ width: this.noSidebar ? '100%' : `calc(100% - ${this.sidebarWidthPercent})`, overflow: this.layoutDoc._createDocOnCR || this.layoutDoc._layout_hideScroll ? 'hidden' : this.layout_autoHeight ? 'visible' : undefined, diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index cb2a1f13f..04b312ca5 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1743,6 +1743,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return <div />; } + setAiEffectsRef = (r: HTMLTextAreaElement | null) => + setTimeout(() => { + if (r && !r.textContent) { + r.style.height = ''; + r.style.height = r.scrollHeight + 'px'; + } + }); + + setAnimDictationRef = (r: DictationButton | null) => (this._animationDictation = r); /** * This chatbox is for getting slide effect transition suggestions from gpt and visualizing them */ @@ -1755,14 +1764,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <ReactTextareaAutosize placeholder="Use AI to suggest effects. Leave blank for random results." className="pres-chatbox" - ref={r => { - setTimeout(() => { - if (r && !r.textContent) { - r.style.height = ''; - r.style.height = r.scrollHeight + 'px'; - } - }); - }} + ref={this.setAiEffectsRef} value={this._animationChat} onChange={e => { e.currentTarget.style.height = ''; @@ -1784,12 +1786,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { color={SnappingManager.userVariantColor} onClick={this.customizeAnimations} /> - <DictationButton - ref={r => { - this._animationDictation = r; - }} - setInput={this.setAnimationChat} - /> + <DictationButton ref={this.setAnimDictationRef} setInput={this.setAnimationChat} /> </div> <div style={{ alignItems: 'center' }}> Click a box to use the effect. @@ -1821,6 +1818,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); } + setPropertiesRef = (r: HTMLTextAreaElement | null) => + setTimeout(() => { + if (r && !r.textContent) { + r.style.height = ''; + r.style.height = r.scrollHeight + 'px'; + } + }); + + setSlideDictationRef = (r: DictationButton | null) => (this._slideDictation = r); + @computed get transitionDropdown() { const { activeItem } = this; // Retrieving spring timing properties @@ -1855,14 +1862,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <ReactTextareaAutosize placeholder="Describe how to modify the slide properties." className="pres-chatbox" - ref={r => { - setTimeout(() => { - if (r && !r.textContent) { - r.style.height = ''; - r.style.height = r.scrollHeight + 'px'; - } - }); - }} + ref={this.setPropertiesRef} value={this._chatInput} onChange={e => { e.currentTarget.style.height = ''; @@ -1874,12 +1874,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { e.stopPropagation(); }} /> - <DictationButton - ref={r => { - this._slideDictation = r; - }} - setInput={this.setChatInput} - /> + <DictationButton ref={this.setSlideDictationRef} setInput={this.setChatInput} /> </div> <Button style={{ alignSelf: 'flex-end' }} diff --git a/src/client/views/pdf/GPTPopup/GPTPopup.tsx b/src/client/views/pdf/GPTPopup/GPTPopup.tsx index 568e48edf..660284397 100644 --- a/src/client/views/pdf/GPTPopup/GPTPopup.tsx +++ b/src/client/views/pdf/GPTPopup/GPTPopup.tsx @@ -515,6 +515,7 @@ export class GPTPopup extends ObservableReactComponent<object> { </div> ); + setDictationRef = (r: DictationButton | null) => (this._askDictation = r); promptBox = (heading: string, value: string, onChange: (e: string) => string, placeholder: string) => ( <> <div className="gptPopup-sortBox"> @@ -541,7 +542,7 @@ export class GPTPopup extends ObservableReactComponent<object> { background={SettingsManager.userVariantColor} onClick={() => this.callGpt(this._mode)} /> - <DictationButton ref={r => (this._askDictation = r)} setInput={onChange} /> + <DictationButton ref={this.setDictationRef} setInput={onChange} /> </div> </> ); diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index d1dda106a..f2c09b6b7 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -18,6 +18,7 @@ export enum InkInkTool { Pen = 'Pen', Highlight = 'Highlight', Write = 'Write', + Math = 'Math', } export enum InkEraserTool { |