diff options
Diffstat (limited to 'src/client/views/nodes')
38 files changed, 693 insertions, 666 deletions
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss index f99011b8f..7f0a39550 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss @@ -1,9 +1,9 @@ .collectionFreeFormDocumentView-container { - transform-origin: left top; + transform-origin: 50% 50%; position: absolute; background-color: transparent; touch-action: manipulation; top: 0; left: 0; - //pointer-events: none; + pointer-events: none; } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 085f9f023..421d431b3 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, trace } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; import { List } from '../../../fields/List'; @@ -6,10 +6,9 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { numberRange } from '../../../Utils'; +import { numberRange, OmitKeys } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { SelectionManager } from '../../util/SelectionManager'; -import { Transform } from '../../util/Transform'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { DocComponent } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; @@ -17,26 +16,113 @@ import './CollectionFreeFormDocumentView.scss'; import { DocumentView, DocumentViewProps, OpenWhere } from './DocumentView'; import React = require('react'); -export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { - dataProvider?: (doc: Doc, replica: string) => { x: number; y: number; zIndex?: number; rotation?: number; color?: string; backgroundColor?: string; opacity?: number; highlight?: boolean; z: number; transition?: string } | undefined; - sizeProvider?: (doc: Doc, replica: string) => { width: number; height: number } | undefined; - renderCutoffProvider: (doc: Doc) => boolean; +export interface CollectionFreeFormDocumentViewWrapperProps extends DocumentViewProps { + x: number; + y: number; + z: number; + width: number; + height: number; zIndex?: number; + rotation?: number; + color?: string; + backgroundColor?: string; + opacity?: number; + highlight?: boolean; + transition?: string; dataTransition?: string; - replica: string; + RenderCutoffProvider: (doc: Doc) => boolean; + CollectionFreeFormView: CollectionFreeFormView; +} +@observer +export class CollectionFreeFormDocumentViewWrapper extends DocComponent<CollectionFreeFormDocumentViewWrapperProps>() implements CollectionFreeFormDocumentViewProps { + @observable X = this.props.x; + @observable Y = this.props.y; + @observable Z = this.props.z; + @observable ZIndex = this.props.zIndex; + @observable Rotation = this.props.rotation; + @observable Opacity = this.props.opacity; + @observable BackgroundColor = this.props.backgroundColor; + @observable Color = this.props.color; + @observable Highlight = this.props.highlight; + @observable Width = this.props.width; + @observable Height = this.props.height; + @observable Transition = this.props.transition; + @observable DataTransition = this.props.dataTransition; + CollectionFreeFormView = this.props.CollectionFreeFormView; // needed for type checking + RenderCutoffProvider = this.props.RenderCutoffProvider; // needed for type checking + + @computed get WrapperKeys() { + return Object.keys(this).filter(key => key.startsWith('w_')).map(key => key.replace('w_', '')) + .map(key => ({upper:key, lower:key[0].toLowerCase() + key.substring(1)})); // prettier-ignore + } + + // wrapper functions around prop fields that have been converted to observables to keep 'props' from ever changing. + // this way, downstream code only invalidates when it uses a specific prop, not when any prop changes + w_X = () => this.X; // prettier-ignore + w_Y = () => this.Y; // prettier-ignore + w_Z = () => this.Z; // prettier-ignore + w_ZIndex = () => this.ZIndex ?? NumCast(this.props.Document.zIndex); // prettier-ignore + w_Rotation = () => this.Rotation ?? NumCast(this.props.Document._rotation); // prettier-ignore + w_Opacity = () => this.Opacity; // prettier-ignore + w_BackgroundColor = () => this.BackgroundColor ?? Cast(this.props.Document._backgroundColor, 'string', null); // prettier-ignore + w_Color = () => this.Color ?? Cast(this.props.Document._color, 'string', null); // prettier-ignore + w_Highlight = () => this.Highlight; // prettier-ignore + w_Width = () => this.Width; // prettier-ignore + w_Height = () => this.Height; // prettier-ignore + w_Transition = () => this.Transition; // prettier-ignore + w_DataTransition = () => this.DataTransition; // prettier-ignore + + PanelWidth = () => this.Width || this.props.PanelWidth?.(); // prettier-ignore + PanelHeight = () => this.Height || this.props.PanelHeight?.(); // prettier-ignore + @action + componentDidUpdate() { + this.WrapperKeys.forEach(keys => ((this as any)[keys.upper] = (this.props as any)[keys.lower])); + } + render() { + const layoutProps = this.WrapperKeys.reduce((val, keys) => [(val['w_' + keys.upper] = (this as any)['w_' + keys.upper]), val][1], {} as { [key: string]: Function }); + return ( + <CollectionFreeFormDocumentView + {...OmitKeys(this.props, this.WrapperKeys.map(keys => keys.lower) ).omit} // prettier-ignore + {...layoutProps} + PanelWidth={this.PanelWidth} + PanelHeight={this.PanelHeight} + /> + ); + } +} +export interface CollectionFreeFormDocumentViewProps { + w_X: () => number; + w_Y: () => number; + w_Z: () => number; + w_ZIndex?: () => number; + w_Rotation?: () => number; + w_Color: () => string; + w_BackgroundColor: () => string; + w_Opacity: () => number | undefined; + w_Highlight: () => boolean | undefined; + w_Transition: () => string | undefined; + w_Width: () => number; + w_Height: () => number; + w_DataTransition: () => string | undefined; + PanelWidth: () => number; + PanelHeight: () => number; + RenderCutoffProvider: (doc: Doc) => boolean; CollectionFreeFormView: CollectionFreeFormView; } @observer -export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps>() { +export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps & DocumentViewProps>() { + get displayName() { // this makes mobx trace() statements more descriptive + return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')'; + } // prettier-ignore public static animFields: { key: string; val?: number }[] = [ - { key: '_height' }, - { key: '_width' }, { key: 'x' }, { key: 'y' }, + { key: 'opacity', val: 1 }, + { key: '_height' }, + { key: '_width' }, { key: '_rotation', val: 0 }, { key: '_layout_scrollTop' }, - { key: 'opacity', val: 1 }, { key: '_currentFrame' }, { key: 'freeform_scale', val: 1 }, { key: 'freeform_panX' }, @@ -44,52 +130,18 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF ]; // fields that are configured to be animatable using animation frames public static animStringFields = ['backgroundColor', 'color', 'fillColor']; // fields that are configured to be animatable using animation frames public static animDataFields = (doc: Doc) => (Doc.LayoutFieldKey(doc) ? [Doc.LayoutFieldKey(doc)] : []); // fields that are configured to be animatable using animation frames - @observable _animPos: number[] | undefined = undefined; - @observable _contentView: DocumentView | undefined | null; - get displayName() { - // this makes mobx trace() statements more descriptive - return 'CollectionFreeFormDocumentView(' + this.rootDoc.title + ')'; - } - get transform() { - return `translate(${this.X}px, ${this.Y}px) rotate(${NumCast(this.Rot, this.Rot)}deg)`; - } - get X() { - return this.dataProvider?.x ?? NumCast(this.Document.x); - } - get Y() { - return this.dataProvider?.y ?? NumCast(this.Document.y); - } - get ZInd() { - return this.dataProvider?.zIndex ?? NumCast(this.Document.zIndex); - } - get Rot() { - return this.dataProvider?.rotation ?? NumCast(this.Document._rotation); - } - get Opacity() { - return this.dataProvider?.opacity; - } - get BackgroundColor() { - return this.dataProvider?.backgroundColor ?? Cast(this.Document._backgroundColor, 'string', null); - } - get Color() { - return this.dataProvider?.color ?? Cast(this.Document._color, 'string', null); - } - @computed get dataProvider() { - return this.props.dataProvider?.(this.props.Document, this.props.replica); - } - @computed get sizeProvider() { - return this.props.sizeProvider?.(this.props.Document, this.props.replica); + get CollectionFreeFormView() { + return this.props.CollectionFreeFormView; } styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { if (doc === this.layoutDoc) { - // prettier-ignore switch (property) { - case StyleProp.Opacity: return this.Opacity; // only change the opacity for this specific document, not its children - case StyleProp.BackgroundColor: return this.BackgroundColor; - case StyleProp.Color: return this.Color; - } + case StyleProp.Opacity: return this.props.w_Opacity(); // only change the opacity for this specific document, not its children + case StyleProp.BackgroundColor: return this.props.w_BackgroundColor(); + case StyleProp.Color: return this.props.w_Color(); + } // prettier-ignore } return this.props.styleProvider?.(doc, props, property); }; @@ -126,21 +178,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF }); } - public static setupZoom(doc: Doc, targDoc: Doc) { - const width = new List<number>(); - const height = new List<number>(); - const top = new List<number>(); - const left = new List<number>(); - width.push(NumCast(targDoc._width)); - height.push(NumCast(targDoc._height)); - top.push(NumCast(targDoc._height) / -2); - left.push(NumCast(targDoc._width) / -2); - doc['viewfinder-width-indexed'] = width; - doc['viewfinder-height-indexed'] = height; - doc['viewfinder-top-indexed'] = top; - doc['viewfinder-left-indexed'] = left; - } - public static setupKeyframes(docs: Doc[], currTimecode: number, makeAppear: boolean = false) { docs.forEach(doc => { if (doc.appearFrame === undefined) doc.appearFrame = currTimecode; @@ -182,16 +219,16 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF } }; - dragEnding = () => this.props.CollectionFreeFormView?.dragEnding(); - dragStarting = () => this.props.CollectionFreeFormView?.dragStarting(false, true); - nudge = (x: number, y: number) => { - this.props.Document.x = NumCast(this.props.Document.x) + x; - this.props.Document.y = NumCast(this.props.Document.y) + y; + const [locX, locY] = this.props.ScreenToLocalTransform().transformDirection(x, y); + this.props.Document.x = this.props.w_X() + locX; + this.props.Document.y = this.props.w_Y() + locY; }; - panelWidth = () => this.sizeProvider?.width || this.props.PanelWidth?.(); - panelHeight = () => this.sizeProvider?.height || this.props.PanelHeight?.(); - screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); + screenToLocalTransform = () => + this.props + .ScreenToLocalTransform() + .translate(-this.props.w_X(), -this.props.w_Y()) + .rotateDeg(-(this.props.w_Rotation?.() || 0)); returnThis = () => this; /// this indicates whether the doc view is activated because of its relationshop to a group @@ -200,38 +237,29 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF // 'inactive' - this is a group child but it is not active // undefined - this is not activated by a group isGroupActive = () => { - if (this.props.CollectionFreeFormView.isAnyChildContentActive()) return undefined; + if (this.CollectionFreeFormView.isAnyChildContentActive()) return undefined; const isGroup = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent'); return isGroup ? (this.props.isDocumentActive?.() ? 'group' : this.props.isGroupActive?.() ? 'child' : 'inactive') : this.props.isGroupActive?.() ? 'child' : undefined; }; + public static CollectionFreeFormDocViewClassName = 'collectionFreeFormDocumentView-container'; render() { TraceMobx(); - const divProps: DocumentViewProps = { - ...this.props, - CollectionFreeFormDocumentView: this.returnThis, - styleProvider: this.styleProvider, - ScreenToLocalTransform: this.screenToLocalTransform, - PanelWidth: this.panelWidth, - PanelHeight: this.panelHeight, - isGroupActive: this.isGroupActive, - }; + const passOnProps = OmitKeys(this.props, Object.keys(this.props).filter(key => key.startsWith('w_'))).omit; // prettier-ignore return ( <div - className="collectionFreeFormDocumentView-container" + className={CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName} style={{ - width: this.panelWidth(), - height: this.panelHeight(), - transform: this.transform, - transformOrigin: '50% 50%', - transition: this.dataProvider?.transition ?? (this.props.dataTransition ? this.props.dataTransition : this.dataProvider ? this.dataProvider.transition : StrCast(this.layoutDoc.dataTransition)), - zIndex: this.ZInd, - display: this.sizeProvider?.width ? undefined : 'none', - pointerEvents: 'none', + width: this.props.PanelWidth(), + height: this.props.PanelHeight(), + transform: `translate(${this.props.w_X()}px, ${this.props.w_Y()}px) rotate(${NumCast(this.props.w_Rotation?.())}deg)`, + transition: this.props.w_Transition?.() ?? (this.props.w_DataTransition?.() || this.props.w_Transition?.()), + zIndex: this.props.w_ZIndex?.(), + display: this.props.w_Width?.() ? undefined : 'none', }}> - {this.props.renderCutoffProvider(this.props.Document) ? ( - <div style={{ position: 'absolute', width: this.panelWidth(), height: this.panelHeight(), background: 'lightGreen' }} /> + {this.props.RenderCutoffProvider(this.props.Document) ? ( + <div style={{ position: 'absolute', width: this.props.PanelWidth(), height: this.props.PanelHeight(), background: 'lightGreen' }} /> ) : ( - <DocumentView {...divProps} ref={action((r: DocumentView | null) => (this._contentView = r))} /> + <DocumentView {...passOnProps} CollectionFreeFormDocumentView={this.returnThis} styleProvider={this.styleProvider} ScreenToLocalTransform={this.screenToLocalTransform} isGroupActive={this.isGroupActive} /> )} </div> ); diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 1b6fe5748..23ffd1080 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -3,10 +3,12 @@ import { action } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { Doc } from '../../../fields/Doc'; -import { Height, Width } from '../../../fields/DocSymbols'; +import { Height } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; -import { StrCast } from '../../../fields/Types'; +import { NumCast, StrCast } from '../../../fields/Types'; +import { DashColor } from '../../../Utils'; import { DocumentType } from '../../documents/DocumentTypes'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; @@ -14,8 +16,6 @@ import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } import './ColorBox.scss'; import { FieldView, FieldViewProps } from './FieldView'; import { RichTextMenu } from './formattedText/RichTextMenu'; -import { ScriptingGlobals } from '../../util/ScriptingGlobals'; -import { DashColor } from '../../../Utils'; @observer export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() { @@ -51,7 +51,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() { } render() { - const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[Height](), this.props.PanelWidth() / this.rootDoc[Width]()); + const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / NumCast(this.rootDoc._height), this.props.PanelWidth() / NumCast(this.rootDoc._width)); return ( <div className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`} @@ -84,9 +84,6 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() { } } - -ScriptingGlobals.add( - function interpColors(c1:string, c2:string, weight=0.5) { - return DashColor(c1).mix(DashColor(c2),weight) - } -)
\ No newline at end of file +ScriptingGlobals.add(function interpColors(c1: string, c2: string, weight = 0.5) { + return DashColor(c1).mix(DashColor(c2), weight); +}); diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index b09fcd882..ff394e5f5 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -20,7 +20,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); } - protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined; private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined]; @observable _animating = ''; diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index f3670622a..e712a25a0 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -41,6 +41,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _ffref = React.createRef<CollectionFreeFormView>(); private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); + anchorMenuClick?: () => undefined | ((anchor: Doc) => void); + crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined; @observable _marqueeing: number[] | undefined; @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @computed get annotationLayer() { @@ -255,7 +257,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; // toggles for user to decide which chart type to view the data in - renderVizView = () => { + @computed get renderVizView() { + const scale = this.props.NativeDimScaling?.() || 1; const sharedProps = { rootDoc: this.rootDoc, layoutDoc: this.layoutDoc, @@ -267,16 +270,13 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; if (!this.records.length) return 'no data/visualization'; switch (this.dataVizView) { - case DataVizView.TABLE: - return <TableBox {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />; - case DataVizView.LINECHART: - return <LineChart vizBox={this} {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; - case DataVizView.HISTOGRAM: - return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; - case DataVizView.PIECHART: - return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; - } - }; + case DataVizView.TABLE: return <TableBox {...sharedProps} docView={this.props.DocumentView} selectAxes={this.selectAxes} />; + case DataVizView.LINECHART: return <LineChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; + case DataVizView.HISTOGRAM: return <Histogram {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} />; + case DataVizView.PIECHART: return <PieChart {...sharedProps} dataDoc={this.dataDoc} fieldKey={this.fieldKey} ref={r => (this._vizRenderer = r ?? undefined)} + margin={{ top: 10, right: 15, bottom: 15, left: 15 }} />; + } // prettier-ignore + } @action onPointerDown = (e: React.PointerEvent): void => { @@ -319,6 +319,8 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; render() { + const scale = this.props.NativeDimScaling?.() || 1; + return !this.records.length ? ( // displays how to get data into the DataVizBox if its empty <div className="start-message">To create a DataViz box, either import / drag a CSV file into your canvas or copy a data table and use the command 'ctrl + p' to bring the data table to your canvas.</div> @@ -328,15 +330,19 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { onPointerDown={this.marqueeDown} style={{ pointerEvents: this.props.isContentActive() === true ? 'all' : 'none', + width: `${100 / scale}%`, + height: `${100 / scale}%`, + transform: `scale(${scale})`, + position: 'absolute', }} onWheel={e => e.stopPropagation()} ref={this._mainCont} > <div className={'datatype-button'}> - <Toggle text={'TABLE'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz == DataVizView.TABLE} /> - <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.LINECHART} /> - <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz == DataVizView.HISTOGRAM} /> - <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == DataVizView.PIECHART} /> + <Toggle text={' TABLE '} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.TABLE)} toggleStatus={this.layoutDoc._dataViz === DataVizView.TABLE} /> + <Toggle text={'LINECHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.LINECHART)} toggleStatus={this.layoutDoc._dataViz === DataVizView.LINECHART} /> + <Toggle text={'HISTOGRAM'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.HISTOGRAM)} toggleStatus={this.layoutDoc._dataViz === DataVizView.HISTOGRAM} /> + <Toggle text={'PIE CHART'} toggleType={ToggleType.BUTTON} type={Type.SEC} color={'black'} onClick={e => (this.layoutDoc._dataViz = DataVizView.PIECHART)} toggleStatus={this.layoutDoc._dataViz == -DataVizView.PIECHART} /> </div> <CollectionFreeFormView @@ -358,7 +364,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { addDocument={this.addDocument}> </CollectionFreeFormView> - {this.renderVizView()} + {this.renderVizView} <div className="dataviz-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }} @@ -383,20 +389,36 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { {this.sidebarHandle} {this.annotationLayer} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( + // <MarqueeAnnotator + // rootDoc={this.rootDoc} + // scrollTop={0} + // down={this._marqueeing} + // scaling={this.props.NativeDimScaling} + // docView={this.props.DocumentView} + // addDocument={this.sidebarAddDocument} + // finishMarquee={this.finishMarquee} + // savedAnnotations={this.savedAnnotations} + // selectionText={returnEmptyString} + // annotationLayer={this._annotationLayer.current} + // mainCont={this._mainCont.current} + // highlightDragSrcColor={''} + // /> + <MarqueeAnnotator - rootDoc={this.rootDoc} - scrollTop={0} - down={this._marqueeing} - scaling={this.props.NativeDimScaling} - docView={this.props.docViewPath().slice(-1)[0]} - addDocument={this.sidebarAddDocument} - finishMarquee={this.finishMarquee} - savedAnnotations={this.savedAnnotations} - selectionText={returnEmptyString} - annotationLayer={this._annotationLayer.current} - mainCont={this._mainCont.current} - highlightDragSrcColor={''} - /> + rootDoc={this.rootDoc} + getPageFromScroll={undefined} + anchorMenuClick={this.anchorMenuClick} + scrollTop={0} + annotationLayerScrollTop={NumCast(this.props.Document._layout_scrollTop)} + addDocument={this.sidebarAddDocument} + docView={this.props.DocumentView!} + finishMarquee={this.finishMarquee} + savedAnnotations={this.savedAnnotations} + selectionText={returnEmptyString} + annotationLayer={this._annotationLayer.current} + mainCont={this._mainCont.current} + anchorMenuCrop={this.crop} + /> )} </div> diff --git a/src/client/views/nodes/DataVizBox/components/PieChart.tsx b/src/client/views/nodes/DataVizBox/components/PieChart.tsx index df65810b5..4622b470a 100644 --- a/src/client/views/nodes/DataVizBox/components/PieChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/PieChart.tsx @@ -88,11 +88,7 @@ export class PieChart extends React.Component<PieChartProps> { componentDidMount = () => { this._disposers.chartData = reaction( () => ({ dataSet: this._pieChartData, w: this.width, h: this.height }), - ({ dataSet, w, h }) => { - if (dataSet!.length > 0) { - this.drawChart(dataSet, w, h); - } - }, + ({ dataSet, w, h }) => dataSet!.length > 0 && this.drawChart(dataSet, w, h), { fireImmediately: true } ); }; diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 3685a198e..012021dda 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -220,7 +220,7 @@ export class TableBox extends React.Component<TableBoxProps> { </thead> <tbody> {this._tableDataIds - .filter(rowId => this.startID <= rowId && rowId <= this.endID) + .filter((rowId, i) => this.startID <= i && i <= this.endID) ?.map(rowId => ( <tr key={rowId} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index e1de2fa76..8c8c987fe 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -98,7 +98,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> { }; render() { const style: { [key: string]: any } = {}; - const divKeys = OmitKeys(this.props, ['children', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; + const divKeys = OmitKeys(this.props, ['children', 'dragStarting', 'dragEnding', 'htmltag', 'RootDoc', 'scaling', 'Document', 'key', 'onInput', 'onClick', '__proto__']).omit; const replacer = (match: any, expr: string, offset: any, string: any) => { // bcz: this executes a script to convert a propery expression string: { script } into a value return (ScriptField.MakeFunction(expr, { self: Doc.name, this: Doc.name, scale: 'number' })?.script.run({ self: this.props.RootDoc, this: this.props.Document, scale: this.props.scaling }).result as string) || ''; @@ -119,7 +119,7 @@ export class HTMLtag extends React.Component<HTMLtagProps> { @observer export class DocumentContentsView extends React.Component< DocumentViewProps & { - isSelected: (outsideReaction: boolean) => boolean; + isSelected: () => boolean; select: (ctrl: boolean) => void; NativeDimScaling?: () => number; setHeight?: (height: number) => void; diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 931594568..406a1b8fb 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -61,7 +61,6 @@ .documentView-htmlOverlayInner { box-shadow: black 0.2vw 0.2vw 0.8vw; background: rgb(255, 255, 255); - overflow: auto; position: relative; margin: auto; padding: 20px; @@ -120,6 +119,10 @@ display: flex; justify-content: center; align-items: center; + position: relative; // allows contents to be positioned relative/below title + > .formattedTextBox { + position: absolute; // position a child text box + } .sharingIndicator { height: 30px; @@ -183,12 +186,15 @@ top: 0; width: 100%; height: 14; - background: rgba(0, 0, 0, 0.4); + opacity: 0.5; text-align: center; text-overflow: ellipsis; white-space: pre; position: absolute; display: flex; // this allows title field dropdown to be inline with editable title + &:hover { + opacity: 1; + } } .documentView-titleWrapper-hover { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 98b13f90f..0d6b88392 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,11 +1,11 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { Dropdown, DropdownType, Type } from 'browndash-components'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; -import { AclPrivate, Animation, AudioPlay, DocData, Width } from '../../../fields/DocSymbols'; +import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -24,7 +24,6 @@ import { Networking } from '../../Network'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; -import { InteractionUtils } from '../../util/InteractionUtils'; import { FollowLinkScript } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; @@ -64,6 +63,14 @@ declare class MediaRecorder { constructor(e: any); } +export enum OpenWhereMod { + none = '', + left = 'left', + right = 'right', + top = 'top', + bottom = 'bottom', + keyvalue = 'keyValue', +} export enum OpenWhere { lightbox = 'lightbox', add = 'add', @@ -79,14 +86,7 @@ export enum OpenWhere { inParent = 'inParent', inParentFromScreen = 'inParentFromScreen', overlay = 'overlay', -} -export enum OpenWhereMod { - none = '', - left = 'left', - right = 'right', - top = 'top', - bottom = 'bottom', - rightKeyValue = 'rightKeyValue', + addRightKeyvalue = 'add:right:keyValue', } export interface DocFocusOptions { @@ -115,7 +115,7 @@ export interface DocComponentView { getAnchor?: (addAsAnnotation: boolean, pinData?: PinProps) => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) restoreView?: (viewSpec: Doc) => boolean; scrollPreview?: (docView: DocumentView, doc: Doc, focusSpeed: number, options: DocFocusOptions) => Opt<number>; // returns the duration of the focus - brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number) => void; // highlight a region of a view (used by freeforms) + brushView?: (view: { width: number; height: number; panX: number; panY: number }, transTime: number, holdTime: number) => void; // highlight a region of a view (used by freeforms) getView?: (doc: Doc) => Promise<Opt<DocumentView>>; // returns a nested DocumentView for the specified doc or undefined addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) @@ -134,6 +134,7 @@ export interface DocComponentView { setFocus?: () => void; // sets input focus to the componentView setData?: (data: Field | Promise<RefField | undefined>) => boolean; componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; + dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set<Doc>) => void; incrementalRendering?: () => void; layout_fitWidth?: () => boolean; // whether the component always fits width (eg, KeyValueBox) overridePointerEvents?: () => 'all' | 'none' | undefined; // if the conmponent overrides the pointer events for the document (e.g, KeyValueBox always allows pointer events) @@ -141,7 +142,7 @@ export interface DocComponentView { annotationKey?: string; getTitle?: () => string; getCenter?: (xf: Transform) => { X: number; Y: number }; - screenBounds?: () => { left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }; + screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>; ptToScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; ptFromScreen?: (pt: { X: number; Y: number }) => { X: number; Y: number }; snapPt?: (pt: { X: number; Y: number }, excludeSegs?: number[]) => { nearestPt: { X: number; Y: number }; distance: number }; @@ -166,7 +167,6 @@ export interface DocumentViewSharedProps { 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. - dataTransition?: string; // specifies animation transition - used by collectionPile and potentially other layout engines when changing the size of documents so that the change won't be abrupt styleProvider: Opt<StyleProviderFunc>; setTitleFocus?: () => void; focus: DocFocusFunc; @@ -176,7 +176,7 @@ export interface DocumentViewSharedProps { searchFilterDocs: () => Doc[]; layout_showTitle?: () => string; whenChildContentsActiveChanged: (isActive: boolean) => void; - rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected + rootSelected: () => boolean; // whether the root of a template has been selected addDocTab: (doc: Doc, where: OpenWhere) => boolean; filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; @@ -191,6 +191,7 @@ export interface DocumentViewSharedProps { yPadding?: number; dropAction?: dropActionType; dontRegisterView?: boolean; + dragWhenActive?: boolean; hideLinkButton?: boolean; hideCaptions?: boolean; ignoreAutoHeight?: boolean; @@ -234,13 +235,15 @@ export interface DocumentViewProps extends DocumentViewSharedProps { onPointerUp?: () => ScriptField; onBrowseClick?: () => ScriptField | undefined; onKey?: (e: React.KeyboardEvent, fieldProps: FieldViewProps) => boolean | undefined; + dragStarting?: () => void; + dragEnding?: () => void; } // these props are only available in DocumentViewIntenral export interface DocumentViewInternalProps extends DocumentViewProps { NativeWidth: () => number; NativeHeight: () => number; - isSelected: (outsideReaction?: boolean) => boolean; + isSelected: () => boolean; select: (ctrlPressed: boolean, shiftPress?: boolean) => void; DocumentView: () => DocumentView; viewPath: () => DocumentView[]; @@ -261,8 +264,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps private _mainCont = React.createRef<HTMLDivElement>(); private _titleRef = React.createRef<EditableView>(); private _dropDisposer?: DragManager.DragDropDisposer; - private _holdDisposer?: InteractionUtils.MultiTouchEventDisposer; - protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; @observable _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class @observable _animateScaleTime: Opt<number>; // milliseconds for animating between views. defaults to 300 if not uset @@ -320,7 +321,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } @observable _pointerEvents: 'none' | 'all' | 'visiblePainted' | undefined; @computed get pointerEvents(): 'none' | 'all' | 'visiblePainted' | undefined { - TraceMobx(); return this._pointerEvents; } @computed get finalLayoutKey() { @@ -333,7 +333,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps return this.props.NativeHeight(); } @computed get disableClickScriptFunc() { - TraceMobx(); const onScriptDisable = this.props.onClickScriptDisable ?? this._componentView?.onClickScriptDisable?.() ?? this.layoutDoc.onClickScriptDisable; // prettier-ignore return ( @@ -358,7 +357,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps componentWillUnmount() { this.cleanupHandlers(true); } + @observable _mounted = false; // turn off all pointer events if component isn't yet mounted (enables nested Docs in alternate UI textboxes that appear on hover which otherwise would grab focus from the text box, reverting to the original UI ) + @action componentDidMount() { + this._mounted = true; this.setupHandlers(); this._disposers.contentActive = reaction( () => { @@ -391,15 +393,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this.cleanupHandlers(false); if (this._mainCont.current) { this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document, this.preDropFunc); - this._multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(this._mainCont.current, this.onTouchStart.bind(this)); - this._holdDisposer = InteractionUtils.MakeHoldTouchTarget(this._mainCont.current, this.handle1PointerHoldStart.bind(this)); } } @action cleanupHandlers(unbrush: boolean) { this._dropDisposer?.(); - this._multiTouchDisposer?.(); - this._holdDisposer?.(); unbrush && Doc.UnBrushDoc(this.props.Document); Object.values(this._disposers).forEach(disposer => disposer?.()); } @@ -468,7 +466,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps }, console.log ); UndoManager.RunInBatch(() => (func().result?.select === true ? this.props.select(false) : ''), 'on double click'); } else if (!Doc.IsSystem(this.rootDoc) && (defaultDblclick === undefined || defaultDblclick === 'default')) { - UndoManager.RunInBatch(() => LightboxView.AddDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap'); + UndoManager.RunInBatch(() => LightboxView.Instance.AddDocTab(this.rootDoc, OpenWhere.lightbox), 'double tap'); SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } else { @@ -568,7 +566,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._type_collection !== CollectionViewType.Docking) e.preventDefault(); // listen to move events if document content isn't active or document is draggable - if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive))) { + if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive, this.props.dragWhenActive))) { document.addEventListener('pointermove', this.onPointerMove); } } @@ -588,7 +586,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps }; cleanupPointerEvents = () => { - this.cleanUpInteractions(); document.removeEventListener('pointermove', this.onPointerMove); document.removeEventListener('pointerup', this.onPointerUp); }; @@ -665,7 +662,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]); if (de.complete.linkDocument) { de.complete.linkDocument.layout_isSvg = true; - this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView.addDocument(de.complete.linkDocument); + this.props.DocumentView().CollectionFreeFormView?.addDocument(de.complete.linkDocument); } } e.stopPropagation(); @@ -682,7 +679,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (!portalLink) { DocUtils.MakeLink( this.props.Document, - Docs.Create.FreeformDocument([], { _width: NumCast(this.layoutDoc._width) + 10, _height: NumCast(this.layoutDoc._height), _isLightbox: true, _layout_fitWidth: true, title: StrCast(this.props.Document.title) + ' [Portal]' }), + Docs.Create.FreeformDocument([], { + _width: NumCast(this.layoutDoc._width) + 10, + _height: Math.max(NumCast(this.layoutDoc._height), NumCast(this.layoutDoc._width) + 10), + _isLightbox: true, + _layout_fitWidth: true, + title: StrCast(this.props.Document.title) + ' [Portal]', + }), { link_relationship: 'portal to:portal from' } ); } @@ -735,7 +738,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (e && !(e.nativeEvent as any).dash) { const onDisplay = () => { - if (this.rootDoc.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear. + if (this.rootDoc.type !== DocumentType.MAP) DocumentViewInternal.SelectAfterContextMenu && !this.props.isSelected() && SelectionManager.SelectView(this.props.DocumentView(), false); // on a mac, the context menu is triggered on mouse down, but a YouTube video becaomes interactive when selected which means that the context menu won't show up. by delaying the selection until hopefully after the pointer up, the context menu will appear. setTimeout(() => simulateMouseClick(document.elementFromPoint(e.clientX, e.clientY), e.clientX, e.clientY, e.screenX, e.screenY)); }; if (navigator.userAgent.includes('Macintosh')) { @@ -756,14 +759,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (!this.props.Document.isFolder) { const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layout_fieldKey)], Doc, null); - const appearance = cm.findByDescription('UI Controls...'); + const appearance = cm.findByDescription('Appearance...'); const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : []; if (this.props.renderDepth === 0) { - appearanceItems.push({ description: 'Open in Lightbox', event: () => LightboxView.SetLightboxDoc(this.rootDoc), icon: 'hand-point-right' }); + appearanceItems.splice(0, 0, { description: 'Open in Lightbox', event: () => LightboxView.Instance.SetLightboxDoc(this.rootDoc), icon: 'external-link-alt' }); } + this.rootDoc.type === DocumentType.PRES && appearanceItems.push({ description: 'Pin', event: () => this.props.pinToPres(this.rootDoc, {}), icon: 'eye' }); !Doc.noviceMode && templateDoc && appearanceItems.push({ description: 'Open Template ', event: () => this.props.addDocTab(templateDoc, OpenWhere.addRight), icon: 'eye' }); - !appearance && appearanceItems.length && cm.addItem({ description: 'UI Controls...', subitems: appearanceItems, icon: 'compass' }); + !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'compass' }); if (!Doc.IsSystem(this.rootDoc) && this.rootDoc.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.rootDoc._type_collection as any)) { const existingOnClick = cm.findByDescription('OnClick...'); @@ -837,7 +841,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); } } - constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'table-columns' }); + constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, OpenWhere.addRightKeyvalue), icon: 'table-columns' }); cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' }); const help = cm.findByDescription('Help...'); @@ -894,13 +898,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps else cm.moveAfter(help); e?.stopPropagation(); // DocumentViews should stop propagation of this event - cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); + cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15, undefined, undefined, undefined); }; @computed get _rootSelected() { - return this.props.isSelected(false) || (this.props.Document.rootDocument && this.props.rootSelected?.(false)) || false; + return this.props.isSelected() || (this.props.Document.rootDocument && this.props.rootSelected?.()) || false; } - rootSelected = (outsideReaction?: boolean) => this._rootSelected; + rootSelected = () => this._rootSelected; panelHeight = () => this.props.PanelHeight() - this.headerMargin; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler); @@ -927,12 +931,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get contents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this.props.LayoutTemplateString; - const noBackground = this.rootDoc._isGroup && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent'); + const noBackground = this.rootDoc._isGroup && !this.props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.rootDoc.backgroundColor || this.rootDoc.backgroundColor === 'transparent'); return ( <div className="documentView-contentsView" style={{ - pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? 'all', + pointerEvents: (isInk || noBackground ? 'none' : this.contentPointerEvents()) ?? (this._mounted ? 'all' : 'none'), height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined, }}> <DocumentContentsView @@ -1016,7 +1020,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps removeDocument={this.hideLink(link)} styleProvider={this.anchorStyleProvider} LayoutTemplate={undefined} - LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${Doc.LinkEndpoint(link, this.rootDoc)}`)} + LayoutTemplateString={LinkAnchorBox.LayoutString(`link_anchor_${LinkManager.anchorIndex(link, this.rootDoc)}`)} /> </div> )); @@ -1160,7 +1164,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc; const background = StrCast( this.layoutDoc.layout_headingColor, - StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(this.layoutDoc.layout_headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor))) + StrCast(SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, StrCast(Doc.SharingDoc().headingColor, SettingsManager.userBackgroundColor)) ); const dropdownWidth = this._titleRef.current?._editing || this._changingTitleField ? Math.max(10, (this._dropDownInnerWidth * this.titleHeight) / 30) : 0; const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); @@ -1172,6 +1176,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`} key="title" style={{ + zIndex: 1, position: this.headerMargin ? 'relative' : 'absolute', height: this.titleHeight, width: 100 - sidebarWidthPercent + '%', @@ -1232,10 +1237,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this.contents ) : ( <div className="documentView-styleWrapper"> - {' '} - {!this.headerMargin ? this.contents : titleView} - {!this.headerMargin ? titleView : this.contents} - {' ' /* */} + {titleView} + {this.contents} {captionView} </div> ); @@ -1345,6 +1348,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @observer export class DocumentView extends React.Component<DocumentViewProps> { public static ROOT_DIV = 'documentView-effectsWrapper'; + @observable _selected = false; + public get SELECTED() { + return this._selected; + } + public set SELECTED(val) { + this._selected = val; + } @observable public static Interacting = false; @observable public static LongPress = false; @observable public static ExploreMode = false; @@ -1354,9 +1364,10 @@ export class DocumentView extends React.Component<DocumentViewProps> { } @observable public docView: DocumentViewInternal | undefined | null; @observable public textHtmlOverlay: Opt<string>; + @observable public textHtmlOverlayTime: Opt<number>; @observable private _isHovering = false; - public htmlOverlayEffect = ''; + public htmlOverlayEffect: Opt<Doc>; public get displayName() { return 'DocumentView(' + this.props.Document?.title + ')'; } // this makes mobx trace() statements more descriptive @@ -1408,7 +1419,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; // prettier-ignore DocServer.GetRefField(docId).then(docx => - LightboxView.SetLightboxDoc( + LightboxView.Instance.SetLightboxDoc( (docx as Doc) ?? // reuse existing pivot view of documents, or else create a new collection Docs.Create.StackingDocument([], { title: linkAnchor.title + '-pivot', _width: 500, _height: 500, target: linkAnchor, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(self, self.target)') }, docId) ) @@ -1485,7 +1496,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); } @computed get panelHeight() { - if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.nativeHeightUnfrozen)) { + if (this.effectiveNativeHeight && (!this.layout_fitWidth || !this.layoutDoc.layout_reflowVertical)) { return Math.min(this.props.PanelHeight(), this.effectiveNativeHeight * this.nativeScaling); } return this.props.PanelHeight(); @@ -1497,7 +1508,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && - (!this.layoutDoc.nativeHeightUnfrozen || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight())) + (!this.layoutDoc.layout_reflowVertical || (!this.layout_fitWidth && this.effectiveNativeHeight * this.nativeScaling <= this.props.PanelHeight())) ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2) : 0; } @@ -1508,18 +1519,22 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.props.dontCenter?.includes('y') ? 0 : this.Yshift; } + @computed get CollectionFreeFormView() { + return this.CollectionFreeFormDocumentView?.CollectionFreeFormView; + } + @computed get CollectionFreeFormDocumentView() { + return this.props.CollectionFreeFormDocumentView?.(); + } + public toggleNativeDimensions = () => this.docView && this.rootDoc.type !== DocumentType.INK && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.NativeDimScaling, this.props.PanelWidth(), this.props.PanelHeight()); public getBounds = () => { if (!this.docView?.ContentDiv || this.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; } - if (this.docView._componentView?.screenBounds) { + if (this.docView._componentView?.screenBounds?.()) { return this.docView._componentView.screenBounds(); } - const xf = this.docView.props - .ScreenToLocalTransform() - .scale(this.trueNativeWidth() ? this.nativeScaling : 1) - .inverse(); + const xf = this.docView.props.ScreenToLocalTransform().scale(this.nativeScaling).inverse(); const [[left, top], [right, bottom]] = [xf.transformPoint(0, 0), xf.transformPoint(this.panelWidth, this.panelHeight)]; if (this.docView.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { @@ -1579,15 +1594,18 @@ export class DocumentView extends React.Component<DocumentViewProps> { scaleToScreenSpace = () => (1 / (this.props.NativeDimScaling?.() || 1)) * this.screenToLocalTransform().Scale; docViewPathFunc = () => this.docViewPath; - isSelected = (outsideReaction?: boolean) => SelectionManager.IsSelected(this, outsideReaction); + isSelected = () => this.SELECTED; select = (extendSelection: boolean, focusSelection?: boolean) => { - SelectionManager.SelectView(this, extendSelection); - if (focusSelection) { - DocumentManager.Instance.showDocument(this.rootDoc, { - willZoomCentered: true, - zoomScale: 0.9, - zoomTime: 500, - }); + if (this.isSelected() && SelectionManager.Views().length > 1) SelectionManager.DeselectView(this); + else { + SelectionManager.SelectView(this, extendSelection); + if (focusSelection) { + DocumentManager.Instance.showDocument(this.rootDoc, { + willZoomCentered: true, + zoomScale: 0.9, + zoomTime: 500, + }); + } } }; NativeWidth = () => this.effectiveNativeWidth; @@ -1596,17 +1614,16 @@ export class DocumentView extends React.Component<DocumentViewProps> { PanelHeight = () => this.panelHeight; NativeDimScaling = () => this.nativeScaling; selfView = () => this; - trueNativeWidth = () => returnVal(this.props.NativeWidth?.(), Doc.NativeWidth(this.layoutDoc, this.props.DataDoc, false)); screenToLocalTransform = () => this.props .ScreenToLocalTransform() .translate(-this.centeringX, -this.centeringY) - .scale(this.trueNativeWidth() ? 1 / this.nativeScaling : 1); + .scale(1 / this.nativeScaling); + + @action componentDidMount() { - this._disposers.updateContentsScript = reaction( - () => ScriptCast(this.rootDoc.updateContentsScript)?.script?.run({ this: this.props.Document, self: Cast(this.rootDoc, Doc, null) || this.props.Document }).result, - output => output - ); + this.rootDoc[DocViews].add(this); + this._disposers.updateContentsScript = reaction(() => ScriptCast(this.rootDoc.updateContentsScript)?.script?.run({ this: this.rootDoc, self: this.rootDoc }).result, emptyFunction); this._disposers.height = reaction( // increase max auto height if document has been resized to be greater than current max () => NumCast(this.layoutDoc._height), @@ -1617,23 +1634,36 @@ export class DocumentView extends React.Component<DocumentViewProps> { ); !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this); } + @action componentWillUnmount() { + this.rootDoc[DocViews].delete(this); Object.values(this._disposers).forEach(disposer => disposer?.()); !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.RemoveView(this); } + // want the htmloverlay to be able to fade in but we also want it to be display 'none' until it is needed. + // unfortunately, CSS can't transition animate any properties for something that is display 'none'. + // so we need to first activate the div, then, after a render timeout, start the opacity transition. + @observable enableHtmlOverlayTransitions: boolean = false; @computed get htmlOverlay() { - return !this.textHtmlOverlay ? null : ( - <div className="documentView-htmlOverlay"> - <div className="documentView-htmlOverlayInner"> - <Fade delay={0} duration={500}> - {DocumentViewInternal.AnimationEffect( - <div className="webBox-textHighlight"> - <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} /> - </div>, - { presentation_effect: this.htmlOverlayEffect ?? 'Zoom' } as any as Doc, - this.rootDoc - )}{' '} - </Fade> + 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.textHtmlOverlay ? 'none' : undefined }}> + <div className="documentView-htmlOverlayInner" style={{ transition: `all 500ms`, opacity: this.enableHtmlOverlayTransitions ? 0.9 : 0 }}> + {DocumentViewInternal.AnimationEffect( + <div className="webBox-textHighlight"> + <ObserverJsxParser autoCloseVoidElements={true} key={42} onError={(e: any) => console.log('PARSE error', e)} renderInWrapper={false} jsx={StrCast(this.textHtmlOverlay)} /> + </div>, + { ...(this.htmlOverlayEffect ?? {}), presentation_effect: effect ?? PresEffect.Zoom } as any as Doc, + this.rootDoc + )} </div> </div> ); @@ -1651,7 +1681,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { className="contentFittingDocumentView-previewDoc" ref={this.ContentRef} style={{ - transition: this.props.dataTransition, + transition: 'inherit', // this.props.dataTransition, transform: `translate(${this.centeringX}px, ${this.centeringY}px)`, width: xshift ?? `${(100 * (this.props.PanelWidth() - this.Xshift * 2)) / this.props.PanelWidth()}%`, height: this.props.forceAutoHeight @@ -1689,8 +1719,7 @@ ScriptingGlobals.add(function deiconifyView(documentView: DocumentView) { }); ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView) { - //documentView.iconify(() => - LightboxView.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0); + LightboxView.Instance.AddDocTab(documentView.rootDoc, OpenWhere.lightbox, 'layout'); //, 0); }); ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { @@ -1700,7 +1729,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) { const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data); - let wid = linkSource[Width](); + let wid = NumCast(linkSource._width); let embedding: Doc | undefined; const links = LinkManager.Links(linkSource); links.forEach(link => { @@ -1711,7 +1740,7 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour embedding.x = wid; embedding.y = 0; embedding._lockedPosition = false; - wid += otherdoc[Width](); + wid += NumCast(otherdoc._width); Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding); } }); diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index a77e4bdd1..d347c285b 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -2,7 +2,6 @@ import EquationEditor from 'equation-editor-react'; import { action, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; @@ -68,7 +67,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { } if (e.key === 'Tab') { const graph = Docs.Create.FunctionPlotDocument([this.rootDoc], { - x: NumCast(this.layoutDoc.x) + this.layoutDoc[Width](), + x: NumCast(this.layoutDoc.x) + NumCast(this.layoutDoc._width), y: NumCast(this.layoutDoc.y), _width: 400, _height: 300, @@ -85,8 +84,18 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { updateSize = () => { const style = this._ref.current && getComputedStyle(this._ref.current.element.current); if (style?.width.endsWith('px') && style?.height.endsWith('px')) { - this.layoutDoc._width = Math.max(35, Number(style.width.replace('px', ''))); - this.layoutDoc._height = Math.max(25, Number(style.height.replace('px', ''))); + if (this.layoutDoc._nativeWidth) { + // if equation has been scaled then editing the expression must also edit the native dimensions to keep the aspect ratio + const prevNwidth = NumCast(this.layoutDoc._nativeWidth); + const prevNheight = NumCast(this.layoutDoc._nativeHeight); + this.layoutDoc._nativeWidth = Math.max(35, Number(style.width.replace('px', ''))); + this.layoutDoc._nativeHeight = Math.max(25, Number(style.height.replace('px', ''))); + this.layoutDoc._width = (NumCast(this.layoutDoc._width) * NumCast(this.layoutDoc._nativeWidth)) / prevNwidth; + this.layoutDoc._height = (NumCast(this.layoutDoc._height) * NumCast(this.layoutDoc._nativeHeight)) / prevNheight; + } else { + this.layoutDoc._width = Math.max(35, Number(style.width.replace('px', ''))); + this.layoutDoc._height = Math.max(25, Number(style.height.replace('px', ''))); + } } }; render() { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index f014f842e..f7f94c546 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -21,7 +21,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { select: (isCtrlPressed: boolean) => void; isContentActive: (outsideReaction?: boolean) => boolean | undefined; isDocumentActive?: () => boolean | undefined; - isSelected: (outsideReaction?: boolean) => boolean; + isSelected: () => boolean; setHeight?: (height: number) => void; NativeDimScaling?: () => number; // scaling the DocumentView does to transform its contents into its panel & needed by ScreenToLocal NOTE: Must also be added to DocumentViewInternalsProps onBrowseClick?: () => ScriptField | undefined; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index d2e1293da..de5ad1631 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -46,6 +46,15 @@ export class FontIconBox extends DocComponent<ButtonProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FontIconBox, fieldKey); } + // + // This controls whether fontIconButtons will display labels under their icons or not + // + public static get ShowIconLabels() { + return BoolCast(Doc.UserDoc()._showLabel); + } + public static set ShowIconLabels(show: boolean) { + Doc.UserDoc()._showLabel = show; + } @observable noTooltip = false; showTemplate = (): void => { const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); @@ -161,7 +170,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() { Doc.UnBrushAllDocs(); })}> {this.Icon(color)} - {!this.label || !Doc.GetShowIconLabels() ? null : ( + {!this.label || !FontIconBox.ShowIconLabels ? null : ( <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> {' '} {this.label}{' '} diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 29943e156..3ffda5a35 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -128,6 +128,7 @@ right: 0; bottom: 0; z-index: 2; + cursor: default; } .imageBox-fader img { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 2f4f788d4..103843046 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from 'mobx-react'; import { extname } from 'path'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { DocData, Width } from '../../../fields/DocSymbols'; +import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -50,7 +50,6 @@ const uploadIcons = { @observer export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { - protected _multiTouchDisposer?: import('../../util/InteractionUtils').InteractionUtils.MultiTouchEventDisposer | undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } @@ -71,6 +70,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp private _disposers: { [name: string]: IReactionDisposer } = {}; private _getAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = () => undefined; private _overlayIconRef = React.createRef<HTMLDivElement>(); + private _marqueeref = React.createRef<MarqueeAnnotator>(); @observable _curSuffix = ''; @observable _uploadIcon = uploadIcons.idle; @@ -85,7 +85,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor + const visibleAnchor = this._getAnchor?.(this._savedAnnotations, true); // use marquee anchor, otherwise, save zoom/pan as anchor const anchor = visibleAnchor ?? Docs.Create.ConfigDocument({ @@ -116,7 +116,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp ); const layoutDoc = this.layoutDoc; this._disposers.path = reaction( - () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[Width]() }), + () => ({ nativeSize: this.nativeSize, width: NumCast(this.layoutDoc._width) }), ({ nativeSize, width }) => { if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) { this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; @@ -240,7 +240,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp croppingProto.freeform_panY_max = anchh / viewScale; if (addCrop) { DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' }); - cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width](); + cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width); cropping.y = NumCast(this.rootDoc.y); this.props.addDocTab(cropping, OpenWhere.inParent); } @@ -474,7 +474,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); - @observable _marqueeing: number[] | undefined; @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @computed get annotationLayer() { TraceMobx(); @@ -488,7 +487,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; + this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]); return true; }), returnFalse, @@ -500,7 +499,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @action finishMarquee = () => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; - this._marqueeing = undefined; + this._marqueeref.current?.onTerminateSelection(); this.props.select(false); }; focus = (anchor: Doc, options: DocFocusOptions) => (anchor.type === DocumentType.CONFIG ? undefined : this._ffref.current?.focus(anchor, options)); @@ -558,13 +557,14 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp {this.content} </CollectionFreeFormView> {this.annotationLayer} - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( + {!this._mainCont.current || !this._annotationLayer.current ? null : ( <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={0} - down={this._marqueeing} - scaling={this.props.NativeDimScaling} - docView={this.props.docViewPath().slice(-1)[0]} + annotationLayerScrollTop={0} + scaling={returnOne} + annotationLayerScaling={this.props.NativeDimScaling} + docView={this.props.DocumentView!} addDocument={this.addDocument} finishMarquee={this.finishMarquee} savedAnnotations={this.savedAnnotations} diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index f22cb195f..6f99b7c28 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -7,7 +7,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { EditableView } from '../EditableView'; import { DefaultStyleProvider } from '../StyleProvider'; -import { OpenWhere } from './DocumentView'; +import { OpenWhere, OpenWhereMod } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; @@ -50,7 +50,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { if (value instanceof Doc) { e.stopPropagation(); e.preventDefault(); - ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, ((OpenWhere.addRight as string) + 'KeyValue') as OpenWhere), icon: 'layer-group' }); + ContextMenu.Instance.addItem({ description: 'Open Fields', event: () => this.props.addDocTab(value, OpenWhere.addRightKeyvalue), icon: 'layer-group' }); ContextMenu.Instance.displayMenu(e.clientX, e.clientY); } }; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index e66fed84b..416cb11cc 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -2,7 +2,6 @@ import React = require('react'); import { Bezier } from 'bezier-js'; import { computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; -import { Height, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast } from '../../../fields/Types'; import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils'; @@ -33,11 +32,11 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { return DocumentManager.Instance.getDocumentView(anchor_2, this.props.docViewPath()[this.props.docViewPath().length - 2]); // this.props.docViewPath().lastElement()); } screenBounds = () => { - if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { + if (this.layoutDoc._layout_isSvg && this.anchor1 && this.anchor2 && this.anchor1.CollectionFreeFormView) { const a_invXf = this.anchor1.props.ScreenToLocalTransform().inverse(); const b_invXf = this.anchor2.props.ScreenToLocalTransform().inverse(); - const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(this.anchor1.rootDoc[Width](), this.anchor1.rootDoc[Height]()) }; - const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(this.anchor2.rootDoc[Width](), this.anchor2.rootDoc[Height]()) }; + const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(this.anchor1.rootDoc._width), NumCast(this.anchor1.rootDoc._height)) }; + const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(this.anchor2.rootDoc._width), NumCast(this.anchor2.rootDoc._height)) }; const pts = [] as number[][]; pts.push([(a_scrBds.tl[0] + a_scrBds.br[0]) / 2, (a_scrBds.tl[1] + a_scrBds.br[1]) / 2]); @@ -51,23 +50,23 @@ export class LinkBox extends ViewBoxBaseComponent<FieldViewProps>() { ); return { left: agg.x, top: agg.y, right: agg.r, bottom: agg.b, center: undefined }; } - return { left: 0, top: 0, right: 0, bottom: 0, center: undefined }; + return undefined; }; disposer: IReactionDisposer | undefined; componentDidMount() { this.props.setContentView?.(this); this.disposer = reaction( () => { - if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView) { + if (this.layoutDoc._layout_isSvg && (this.anchor1 || this.anchor2)?.CollectionFreeFormView) { const a = (this.anchor1 ?? this.anchor2)!; const b = (this.anchor2 ?? this.anchor1)!; const parxf = this.props.docViewPath()[this.props.docViewPath().length - 2].ComponentView as CollectionFreeFormView; - const this_xf = parxf?.getTransform() ?? Transform.Identity; //this.props.ScreenToLocalTransform(); + const this_xf = parxf?.screenToLocalXf ?? Transform.Identity; //this.props.ScreenToLocalTransform(); const a_invXf = a.props.ScreenToLocalTransform().inverse(); const b_invXf = b.props.ScreenToLocalTransform().inverse(); - const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(a.rootDoc[Width](), a.rootDoc[Height]()) }; - const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(b.rootDoc[Width](), b.rootDoc[Height]()) }; + const a_scrBds = { tl: a_invXf.transformPoint(0, 0), br: a_invXf.transformPoint(NumCast(a.rootDoc._width), NumCast(a.rootDoc._height)) }; + const b_scrBds = { tl: b_invXf.transformPoint(0, 0), br: b_invXf.transformPoint(NumCast(b.rootDoc._width), NumCast(b.rootDoc._height)) }; const a_bds = { tl: this_xf.transformPoint(a_scrBds.tl[0], a_scrBds.tl[1]), br: this_xf.transformPoint(a_scrBds.br[0], a_scrBds.br[1]) }; const b_bds = { tl: this_xf.transformPoint(b_scrBds.tl[0], b_scrBds.tl[1]), br: this_xf.transformPoint(b_scrBds.br[0], b_scrBds.br[1]) }; diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 198cbe851..7e4f1da8e 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -3,13 +3,13 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import wiki from 'wikijs'; -import { Doc, DocCastAsync, Opt } from '../../../fields/Doc'; -import { Height, Width } from '../../../fields/DocSymbols'; +import { Doc, Opt } from '../../../fields/Doc'; import { Cast, DocCast, NumCast, PromiseValue, StrCast } from '../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnNone, setupMoveUpEvents } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; +import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; @@ -19,7 +19,6 @@ import { Transform } from '../../util/Transform'; import { DocumentView, DocumentViewSharedProps, OpenWhere } from './DocumentView'; import './LinkDocPreview.scss'; import React = require('react'); -import { DocumentManager } from '../../util/DocumentManager'; interface LinkDocPreviewProps { linkDoc?: Doc; @@ -56,21 +55,20 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { LinkDocPreview._instance = this; } - @action init() { + @action + init() { var linkTarget = this.props.linkDoc; this._linkSrc = this.props.linkSrc; this._linkDoc = this.props.linkDoc; - const link_anchor_1 = this._linkDoc?.link_anchor_1 as Doc; - const link_anchor_2 = this._linkDoc?.link_anchor_2 as Doc; + const link_anchor_1 = DocCast(this._linkDoc?.link_anchor_1); + const link_anchor_2 = DocCast(this._linkDoc?.link_anchor_2); if (link_anchor_1 && link_anchor_2) { linkTarget = Doc.AreProtosEqual(link_anchor_1, this._linkSrc) || Doc.AreProtosEqual(link_anchor_1?.annotationOn as Doc, this._linkSrc) ? link_anchor_2 : link_anchor_1; } if (linkTarget?.annotationOn && linkTarget?.type !== DocumentType.RTF) { - // want to show annotation embedContainer document if annotation is not text - linkTarget && DocCastAsync(linkTarget.annotationOn).then(action(anno => (this._markerTargetDoc = this._targetDoc = anno))); - } else { - this._markerTargetDoc = this._targetDoc = linkTarget; + linkTarget = DocCast(linkTarget.annotationOn); // want to show annotation embedContainer document if annotation is not text } + this._markerTargetDoc = this._targetDoc = linkTarget; this._toolTipText = ''; this.updateHref(); } @@ -190,17 +188,17 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { width = () => { if (!this._targetDoc) return 225; - if (this._targetDoc[Width]() < this._targetDoc?.[Height]()) { - return (Math.min(225, this._targetDoc[Height]()) * this._targetDoc[Width]()) / this._targetDoc[Height](); + if (NumCast(this._targetDoc._width) < NumCast(this._targetDoc._height)) { + return (Math.min(225, NumCast(this._targetDoc._height)) * NumCast(this._targetDoc._width)) / NumCast(this._targetDoc._height); } - return Math.min(225, NumCast(this._targetDoc?.[Width](), 225)); + return Math.min(225, NumCast(this._targetDoc._width, 225)); }; height = () => { if (!this._targetDoc) return 225; - if (this._targetDoc[Width]() > this._targetDoc?.[Height]()) { - return (Math.min(225, this._targetDoc[Width]()) * this._targetDoc[Height]()) / this._targetDoc[Width](); + if (NumCast(this._targetDoc._width) > NumCast(this._targetDoc._height)) { + return (Math.min(225, NumCast(this._targetDoc._width)) * NumCast(this._targetDoc._height)) / NumCast(this._targetDoc._width); } - return Math.min(225, NumCast(this._targetDoc?.[Height](), 225)); + return Math.min(225, NumCast(this._targetDoc._height, 225)); }; @computed get previewHeader() { return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : ( diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index bdc074e0c..4bb0f14d2 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -1,4 +1,4 @@ -import { observable, runInAction } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import ReactLoading from 'react-loading'; @@ -36,11 +36,28 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LoadingBox, fieldKey); } + @observable public static CurrentlyLoading: Doc[] = []; // this assignment doesn't work. the actual assignment happens in DocumentManager's constructor + // removes from currently loading display + @action + public static removeCurrentlyLoading(doc: Doc) { + if (LoadingBox.CurrentlyLoading) { + const index = LoadingBox.CurrentlyLoading.indexOf(doc); + index !== -1 && LoadingBox.CurrentlyLoading.splice(index, 1); + } + } + + // adds doc to currently loading display + @action + public static addCurrentlyLoading(doc: Doc) { + if (LoadingBox.CurrentlyLoading.indexOf(doc) === -1) { + LoadingBox.CurrentlyLoading.push(doc); + } + } _timer: any; @observable progress = ''; componentDidMount() { - if (!Doc.CurrentlyLoading?.includes(this.rootDoc)) { + if (!LoadingBox.CurrentlyLoading?.includes(this.rootDoc)) { this.rootDoc.loadingError = 'Upload interrupted, please try again'; } else { const updateFunc = async () => { diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx index 7af4d9b59..f6680aac0 100644 --- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx +++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx @@ -48,7 +48,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> { componentDidMount() { this._disposer = reaction( () => SelectionManager.Views().slice(), - selected => MapAnchorMenu.Instance.fadeOut(true) + sel => MapAnchorMenu.Instance.fadeOut(true) ); } // audioDown = (e: React.PointerEvent) => { diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 50b070e7f..9b75ca7e3 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -5,11 +5,10 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; -import { DocCss, Highlight, Width } from '../../../../fields/DocSymbols'; +import { DocCss, Highlight } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; -import { InkTool } from '../../../../fields/InkField'; import { DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; import { Docs, DocUtils } from '../../../documents/Documents'; import { DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; @@ -21,7 +20,6 @@ import { undoable, UndoManager } from '../../../util/UndoManager'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; -import { MarqueeAnnotator } from '../../MarqueeAnnotator'; import { SidebarAnnos } from '../../SidebarAnnos'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; @@ -67,14 +65,11 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return FieldView.LayoutString(MapBox, fieldKey); } private _dragRef = React.createRef<HTMLDivElement>(); - private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); - private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _sidebarRef = React.createRef<SidebarAnnos>(); private _ref: React.RefObject<HTMLDivElement> = React.createRef(); private _disposers: { [key: string]: IReactionDisposer } = {}; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void); - @observable private _marqueeing: number[] | undefined; @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); @@ -170,7 +165,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps .ScreenToLocalTransform() .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); - const fullWidth = this.layoutDoc[Width](); + const fullWidth = NumCast(this.layoutDoc._width); const mapWidth = fullWidth - this.sidebarWidth(); if (this.sidebarWidth() + localDelta[0] > 0) { this.layoutDoc._layout_showSidebar = true; @@ -278,28 +273,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func); - @action - onMarqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { - setupMoveUpEvents( - this, - e, - action(e => { - MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; - return true; - }), - returnFalse, - () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), - false - ); - } - }; - @action finishMarquee = (x?: number, y?: number) => { - this._marqueeing = undefined; - x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.rootDoc); - }; - addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey); pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none'); @@ -307,8 +280,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); - transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()]; + transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter]; infoWidth = () => this.props.PanelWidth() / 5; infoHeight = () => this.props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; @@ -832,23 +805,6 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps isAnyChildContentActive={this.isAnyChildContentActive} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} /> */} - - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( - <MarqueeAnnotator - rootDoc={this.rootDoc} - anchorMenuClick={this.anchorMenuClick} - scrollTop={0} - down={this._marqueeing} - scaling={returnOne} - addDocument={this.addDocumentWrapper} - docView={this.props.docViewPath().lastElement()} - finishMarquee={this.finishMarquee} - savedAnnotations={this.savedAnnotations} - annotationLayer={this._annotationLayer.current} - selectionText={returnEmptyString} - mainCont={this._mainCont.current} - /> - )} </div> {/* </LoadScript > */} <div className="mapBox-sidebar" style={{ width: `${this.sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx index 407a91dd0..6bad7d724 100644 --- a/src/client/views/nodes/MapBox/MapBox2.tsx +++ b/src/client/views/nodes/MapBox/MapBox2.tsx @@ -4,11 +4,9 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, runInAc import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; -import { Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; -import { InkTool } from '../../../../fields/InkField'; import { NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; @@ -16,7 +14,6 @@ import { UndoManager } from '../../../util/UndoManager'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; -import { MarqueeAnnotator } from '../../MarqueeAnnotator'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { Annotation } from '../../pdf/Annotation'; import { SidebarAnnos } from '../../SidebarAnnos'; @@ -107,8 +104,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private selectedPlace: Doc | undefined; @observable private markerMap: { [id: string]: google.maps.Marker } = {}; @observable private center = navigator.geolocation ? navigator.geolocation.getCurrentPosition : defaultCenter; - @observable private _marqueeing: number[] | undefined; - @observable private _isAnnotating = false; @observable private inputRef = React.createRef<HTMLInputElement>(); @observable private searchMarkers: google.maps.Marker[] = []; @observable private searchBox = new window.google.maps.places.Autocomplete(this.inputRef.current!, options); @@ -120,7 +115,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return DocListCast(this.dataDoc[this.annotationKey]); } @observable private toggleAddMarker = false; - private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); @observable _showSidebar = false; @computed get SidebarShown() { @@ -368,7 +362,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps .ScreenToLocalTransform() .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); - const fullWidth = this.layoutDoc[Width](); + const fullWidth = NumCast(this.layoutDoc._width); const mapWidth = fullWidth - this.sidebarWidth(); if (this.sidebarWidth() + localDelta[0] > 0) { this._showSidebar = true; @@ -482,29 +476,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean) => void) => (this._setPreviewCursor = func); - @action - onMarqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { - setupMoveUpEvents( - this, - e, - action(e => { - MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; - return true; - }), - returnFalse, - () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), - false - ); - } - }; - @action finishMarquee = (x?: number, y?: number) => { - this._marqueeing = undefined; - this._isAnnotating = false; - x !== undefined && y !== undefined && this._setPreviewCursor?.(x, y, false, false, this.props.Document); - }; - addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => { return this.addDocument(doc, annotationKey); }; @@ -547,8 +518,8 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); - transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()]; + transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this.props.childFilters(), Utils.OpaqueBackgroundFilter]; infoWidth = () => this.props.PanelWidth() / 5; infoHeight = () => this.props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; @@ -599,22 +570,6 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps {/* {this.handleMapCenter(this._map)} */} </GoogleMap> </div> - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( - <MarqueeAnnotator - rootDoc={this.rootDoc} - anchorMenuClick={this.anchorMenuClick} - scrollTop={0} - down={this._marqueeing} - scaling={returnOne} - addDocument={this.addDocumentWrapper} - docView={this.props.docViewPath().lastElement()} - finishMarquee={this.finishMarquee} - savedAnnotations={this.savedAnnotations} - annotationLayer={this._annotationLayer.current} - selectionText={returnEmptyString} - mainCont={this._mainCont.current} - /> - )} </div> {/* </LoadScript > */} <div className="MapBox2-sidebar" style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index c068d9dd7..108fa5ce5 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -4,7 +4,6 @@ import { observer } from 'mobx-react'; import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Height, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { ComputedField } from '../../../fields/ScriptField'; @@ -63,7 +62,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps super(props); const nw = Doc.NativeWidth(this.Document, this.dataDoc) || 927; const nh = Doc.NativeHeight(this.Document, this.dataDoc) || 1200; - !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw)); + !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw)); if (this.pdfUrl) { if (PDFBox.pdfcache.get(this.pdfUrl.url.href)) runInAction(() => (this._pdf = PDFBox.pdfcache.get(this.pdfUrl!.url.href))); else if (PDFBox.pdfpromise.get(this.pdfUrl.url.href)) PDFBox.pdfpromise.get(this.pdfUrl.url.href)?.then(action((pdf: any) => (this._pdf = pdf))); @@ -104,15 +103,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const docViewContent = this.props.docViewPath().lastElement().ContentDiv!; const newDiv = docViewContent.cloneNode(true) as HTMLDivElement; - newDiv.style.width = this.layoutDoc[Width]().toString(); - newDiv.style.height = this.layoutDoc[Height]().toString(); + newDiv.style.width = NumCast(this.layoutDoc._width).toString(); + newDiv.style.height = NumCast(this.layoutDoc._height).toString(); this.replaceCanvases(docViewContent, newDiv); const htmlString = this._pdfViewer?._mainCont.current && new XMLSerializer().serializeToString(newDiv); const anchx = NumCast(cropping.x); const anchy = NumCast(cropping.y); - const anchw = cropping[Width]() * (this.props.NativeDimScaling?.() || 1); - const anchh = cropping[Height]() * (this.props.NativeDimScaling?.() || 1); + const anchw = NumCast(cropping._width) * (this.props.NativeDimScaling?.() || 1); + const anchh = NumCast(cropping._height) * (this.props.NativeDimScaling?.() || 1); const viewScale = 1; cropping.title = 'crop: ' + this.rootDoc.title; cropping.x = NumCast(this.rootDoc.x) + NumCast(this.rootDoc._width); @@ -169,8 +168,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps CollectionFreeFormView.UpdateIcon( filename, docViewContent, - this.layoutDoc[Width](), - this.layoutDoc[Height](), + NumCast(this.layoutDoc._width), + NumCast(this.layoutDoc._height), this.props.PanelWidth(), this.props.PanelHeight(), NumCast(this.layoutDoc._layout_scrollTop), @@ -196,7 +195,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps () => this.props.isSelected(), () => { document.removeEventListener('keydown', this.onKeyDown); - this.props.isSelected(true) && document.addEventListener('keydown', this.onKeyDown); + this.props.isSelected() && document.addEventListener('keydown', this.onKeyDown); }, { fireImmediately: true } ); @@ -255,8 +254,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this.dataDoc[this.props.fieldKey + '_numPages'] = np; Doc.SetNativeWidth(this.dataDoc, Math.max(Doc.NativeWidth(this.dataDoc), (nw * 96) / 72)); Doc.SetNativeHeight(this.dataDoc, (nh * 96) / 72); - this.layoutDoc._height = this.layoutDoc[Width]() / (Doc.NativeAspect(this.dataDoc) || 1); - !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw)); + this.layoutDoc._height = NumCast(this.layoutDoc._width) / (Doc.NativeAspect(this.dataDoc) || 1); + !this.Document._layout_fitWidth && (this.Document._height = NumCast(this.Document._width) * (nh / nw)); }; public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { @@ -331,7 +330,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const ratio = (curNativeWidth + ((onButton ? 1 : -1) * localDelta[0]) / (this.props.NativeDimScaling?.() || 1)) / nativeWidth; if (ratio >= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; - onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]); + onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]); this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; @@ -348,15 +347,15 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps toggleSidebar = action((preview: boolean = false) => { const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']); const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; - const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth; + const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? PDFBox.openSidebarWidth + PDFBox.sidebarResizerWidth : 0) + NumCast(this.layoutDoc._width)) / NumCast(this.layoutDoc._width); const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { this._previewNativeWidth = nativeWidth * sideratio; - this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth; + this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth; this._showSidebar = true; } else { this.layoutDoc.nativeWidth = nativeWidth * pdfratio; - this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth; + this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth; this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth; } }); diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index ebb8a3374..b2512a0e5 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -6,7 +6,6 @@ import { observer } from 'mobx-react'; // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three'; import { DateField } from '../../../fields/DateField'; import { Doc } from '../../../fields/Doc'; -import { Height, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, DocCast, NumCast } from '../../../fields/Types'; @@ -18,16 +17,16 @@ import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { CaptureManager } from '../../util/CaptureManager'; import { SettingsManager } from '../../util/SettingsManager'; +import { TrackMovements } from '../../util/TrackMovements'; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline'; import { ContextMenu } from '../ContextMenu'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; +import { media_state } from './AudioBox'; import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import './ScreenshotBox.scss'; import { VideoBox } from './VideoBox'; -import { TrackMovements } from '../../util/TrackMovements'; -import { media_state } from './AudioBox'; declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has @@ -145,7 +144,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl if (!nativeWidth || !nativeHeight) { if (!nativeWidth) Doc.SetNativeWidth(this.dataDoc, 1200); Doc.SetNativeHeight(this.dataDoc, (nativeWidth || 1200) / aspect); - this.layoutDoc._height = (this.layoutDoc[Width]() || 0) / aspect; + this.layoutDoc._height = NumCast(this.layoutDoc._width) / aspect; } }; @@ -295,7 +294,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl dictationTextProto.mediaState = ComputedField.MakeFunction(`self.${textField}_recordingSource.mediaState`); this.dataDoc[this.fieldKey + '_dictation'] = dictationText; }; - videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth(); + videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], NumCast(this.layoutDoc._height)) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], NumCast(this.layoutDoc._width))) * this.props.PanelWidth(); formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); render() { TraceMobx(); diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 7c8a1849e..197c520c7 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -27,7 +27,6 @@ const _global = (window /* browser */ || global) /* node */ as any; @observer export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps>() { private dropDisposer?: DragManager.DragDropDisposer; - protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined; public static LayoutString(fieldStr: string) { return FieldView.LayoutString(ScriptingBox, fieldStr); } diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index f803715ad..f90c19050 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -100,6 +100,7 @@ padding: 0 10px 0 7px; transition: opacity 0.3s; z-index: 10001; + transform-origin: top left; .timecode-controls { display: flex; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index d7f7c9b73..8bf2f4ce5 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,7 +4,6 @@ import { action, computed, IReactionDisposer, observable, ObservableMap, reactio import { observer } from 'mobx-react'; import { basename } from 'path'; import { Doc, StrListCast } from '../../../fields/Doc'; -import { Height, Width } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; @@ -65,6 +64,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp private _youtubeIframeId: number = -1; private _youtubeContentCreated = false; private _audioPlayer: HTMLAudioElement | null = null; + private _marqueeref = React.createRef<MarqueeAnnotator>(); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); // outermost div private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _playRegionTimer: any = null; // timeout for playback @@ -72,7 +72,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @observable _stackedTimeline: any; // CollectionStackedTimeline ref @observable static _nativeControls: boolean; // default html controls - @observable _marqueeing: number[] | undefined; // coords for marquee selection @observable _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @observable _screenCapture = false; @observable _clicking = false; // used for transition between showing/hiding timeline @@ -163,7 +162,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp if ( // need to include range inputs because after dragging time slider it becomes target element !(e.target instanceof HTMLInputElement && !(e.target.type === 'range')) && - this.props.isSelected(true) + this.props.isSelected() ) { switch (e.key) { case 'ArrowLeft': @@ -355,8 +354,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp updateIcon = () => { const makeIcon = (returnedfilename: string) => { this.dataDoc.icon = new ImageField(returnedfilename); - this.dataDoc.icon_nativeWidth = this.layoutDoc[Width](); - this.dataDoc.icon_nativeHeight = this.layoutDoc[Height](); + this.dataDoc.icon_nativeWidth = NumCast(this.layoutDoc._width); + this.dataDoc.icon_nativeHeight = NumCast(this.layoutDoc._height); }; this.Snapshot(undefined, undefined, makeIcon); }; @@ -627,7 +626,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp () => !this._playing && this.Seek(NumCast(this.layoutDoc._layout_currentTimecode)) ); this._disposers.youtubeReactionDisposer = reaction( - () => Doc.ActiveTool === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentView.Interacting, + () => Doc.ActiveTool === InkTool.None && this.props.isSelected() && !SnappingManager.GetIsDragging() && !DocumentView.Interacting, interactive => (iframe.style.pointerEvents = interactive ? 'all' : 'none'), { fireImmediately: true } ); @@ -868,7 +867,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; + this._marqueeref.current?.onInitiateSelection([e.clientX, e.clientY]); return true; }), returnFalse, @@ -882,7 +881,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp // ends marquee selection @action finishMarquee = () => { - this._marqueeing = undefined; + this._marqueeref.current?.onTerminateSelection(); this.props.select(true); }; @@ -913,7 +912,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp .scale(100 / this.heightPercent); }; - marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100; marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0]; timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`]; @@ -938,8 +936,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp <div className="videoBox-ui" style={{ - transformOrigin: 'top left', - transform: `rotate(${NumCast(this.rootDoc.rotation)}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`, + transform: `rotate(${this.props.ScreenToLocalTransform().inverse().RotateDeg}deg) translate(${-(xRight - xPos) + 10}px, ${yBot - yMid - uiHeight - uiMargin}px)`, left: xPos, top: yMid, height: uiHeight, @@ -953,6 +950,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp ); }; + thumbnails = () => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']); // renders CollectionStackedTimeline @computed get renderTimeline() { return ( @@ -964,7 +962,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp fieldKey={this.annotationKey} dictationKey={this.fieldKey + '_dictation'} mediaPath={this.audiopath} - thumbnails={() => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])} + thumbnails={this.thumbnails} renderDepth={this.props.renderDepth + 1} startTag={'_timecodeToShow' /* videoStart */} endTag={'_timecodeToHide' /* videoEnd */} @@ -1096,13 +1094,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp </CollectionFreeFormView> </div> {this.annotationLayer} - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( + {!this._mainCont.current || !this._annotationLayer.current ? null : ( <MarqueeAnnotator + ref={this._marqueeref} rootDoc={this.rootDoc} scrollTop={0} - down={this._marqueeing} - scaling={this.marqueeFitScaling} - docView={this.props.docViewPath().slice(-1)[0]} + annotationLayerScrollTop={0} + scaling={returnOne} + annotationLayerScaling={this.props.NativeDimScaling} + docView={this.props.DocumentView!} containerOffset={this.marqueeOffset} addDocument={this.addDocWithTimecode} finishMarquee={this.finishMarquee} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 2aca314da..e42fb4a03 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,9 +1,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; -import { Height, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { HtmlField } from '../../../fields/HtmlField'; import { InkTool } from '../../../fields/InkField'; @@ -52,6 +51,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _outerRef: React.RefObject<HTMLDivElement> = React.createRef(); + private _marqueeref = React.createRef<MarqueeAnnotator>(); private _disposers: { [name: string]: IReactionDisposer } = {}; private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); private _keyInput = React.createRef<HTMLInputElement>(); @@ -67,10 +67,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable private _searching: boolean = false; @observable private _showSidebar = false; @observable private _webPageHasBeenRendered = false; - @observable private _overlayAnnoInfo: Opt<Doc>; @observable private _marqueeing: number[] | undefined; - @observable private _isAnnotating = false; - @observable private _iframeClick: HTMLIFrameElement | undefined = undefined; + get marqueeing() { + return this._marqueeing; + } + set marqueeing(val) { + val && this._marqueeref.current?.onInitiateSelection(val); + !val && this._marqueeref.current?.onTerminateSelection(); + this._marqueeing = val; + } @observable private _iframe: HTMLIFrameElement | null = null; @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @observable private _scrollHeight = NumCast(this.layoutDoc.scrollHeight); @@ -81,7 +86,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this._url ? WebBox.urlHash(this._url) + '' : ''; } @computed get scrollHeight() { - return Math.max(this.layoutDoc[Height](), this._scrollHeight); + return Math.max(NumCast(this.layoutDoc._height), this._scrollHeight); } @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); @@ -209,7 +214,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (!nativeWidth || !nativeHeight || Math.abs(nativeWidth / nativeHeight - youtubeaspect) > 0.05) { if (!nativeWidth) Doc.SetNativeWidth(this.layoutDoc, 600); Doc.SetNativeHeight(this.layoutDoc, (nativeWidth || 600) / youtubeaspect); - this.layoutDoc._height = this.layoutDoc[Width]() / youtubeaspect; + this.layoutDoc._height = NumCast(this.layoutDoc._width) / youtubeaspect; } } // else it's an HTMLfield } else if (this.webField && !this.dataDoc.text) { @@ -246,6 +251,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action createTextAnnotation = (sel: Selection, selRange: Range | undefined) => { if (this._mainCont.current && selRange) { + if (this.rootDoc[this.props.fieldKey] instanceof HtmlField) this._mainCont.current.style.transform = `rotate(${NumCast(this.props.DocumentView!().screenToLocalTransform().RotateDeg)}deg)`; const clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { const rect = clientRects.item(i); @@ -262,12 +268,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._annotationLayer.current && MarqueeAnnotator.previewNewAnnotation(this._savedAnnotations, this._annotationLayer.current, annoBox, 1); } } + this._mainCont.current.style.transform = ''; } - //this._selectionText = selRange.cloneContents().textContent || ""; this._selectionContent = selRange?.cloneContents(); this._selectionText = this._selectionContent?.textContent || ''; // clear selection + this._textAnnotationCreator = undefined; if (sel.empty) sel.empty(); // Chrome else if (sel.removeAllRanges) sel.removeAllRanges(); // Firefox return this._savedAnnotations; @@ -276,7 +283,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps focus = (anchor: Doc, options: DocFocusOptions) => { if (anchor !== this.rootDoc && this._outerRef.current) { const windowHeight = this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); - const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), anchor[Height](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[Height](), this._scrollHeight)); + const scrollTo = Utils.scrollIntoView(NumCast(anchor.y), NumCast(anchor._height), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + NumCast(anchor._height), this._scrollHeight)); if (scrollTo !== undefined) { if (this._initialScroll === undefined) { const focusTime = options.zoomTime ?? 500; @@ -315,7 +322,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ele.append(contents); } } catch (e) {} - const visibleAnchor = this._getAnchor(this._savedAnnotations, false); + const visibleAnchor = this._getAnchor(this._savedAnnotations, true); const anchor = visibleAnchor ?? Docs.Create.ConfigDocument({ @@ -335,6 +342,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action iframeUp = (e: PointerEvent) => { + this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value this._textAnnotationCreator = undefined; this.props.docViewPath().lastElement()?.docView?.cleanupPointerEvents(); // pointerup events aren't generated on containing document view, so we have to invoke it here. if (this._iframe?.contentWindow && this._iframe.contentDocument && !this._iframe.contentWindow.getSelection()?.isCollapsed) { @@ -354,26 +362,27 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; @action webClipDown = (e: React.PointerEvent) => { - const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); + e.stopPropagation(); + const sel = window.getSelection(); + this._textAnnotationCreator = undefined; + if (sel?.empty) sel.empty(); // Chrome + else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox + // bcz: NEED TO unrotate e.clientX and e.clientY const word = getWordAtPoint(e.target, e.clientX, e.clientY); this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc); MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - e.button !== 2 && (this._marqueeing = [e.clientX, e.clientY]); - if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) { - e.stopPropagation(); - setTimeout( - action(() => (this._marqueeing = undefined)), - 100 - ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. - } else { - this._isAnnotating = true; - this.props.select(false); - e.stopPropagation(); + + if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) { + if (e.button !== 2) this.marqueeing = [e.clientX, e.clientY]; e.preventDefault(); } document.addEventListener('pointerup', this.webClipUp); }; + @action webClipUp = (e: PointerEvent) => { + if (window.getSelection()?.isCollapsed && this._marqueeref.current?.isEmpty) { + this.marqueeing = undefined; + } document.removeEventListener('pointerup', this.webClipUp); this._getAnchor = AnchorMenu.Instance?.GetAnchor; // need to save AnchorMenu's getAnchor since a subsequent selection on another doc will overwrite this value const sel = window.getSelection(); @@ -382,7 +391,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._selectionText = sel.toString(); AnchorMenu.Instance.setSelectedText(sel.toString()); this._textAnnotationCreator = () => this.createTextAnnotation(sel, selRange); - AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); + (!sel.isCollapsed || this.marqueeing) && AnchorMenu.Instance.jumpTo(e.clientX, e.clientY); // Changing which document to add the annotation to (the currently selected WebBox) GPTPopup.Instance.setSidebarId(`${this.props.fieldKey}_${this._urlHash ? this._urlHash + '_' : ''}sidebar`); GPTPopup.Instance.addDoc = this.sidebarAddDocument; @@ -390,36 +399,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; @action iframeDown = (e: PointerEvent) => { - const mainContBounds = Utils.GetScreenTransform(this._mainCont.current!); - const scale = (this.props.NativeDimScaling?.() || 1) * mainContBounds.scale; - const word = getWordAtPoint(e.target, e.clientX, e.clientY); - this._setPreviewCursor?.(e.clientX, e.clientY, false, true, this.rootDoc); + this.props.select(false); + const locpt = { + x: (e.clientX / NumCast(this.rootDoc.nativeWidth)) * this.props.PanelWidth(), + y: ((e.clientY - NumCast(this.rootDoc.layout_scrollTop))/ NumCast(this.rootDoc.nativeHeight)) * this.props.PanelHeight() }; // prettier-ignore + const scrclick = this.props.DocumentView?.().props.ScreenToLocalTransform().inverse().transformPoint(locpt.x, locpt.y)!; + const scrcent = this.props + .DocumentView?.() + .props.ScreenToLocalTransform() + .inverse() + .transformPoint(NumCast(this.rootDoc.width) / 2, NumCast(this.rootDoc.height) / 2)!; + const theclickoff = Utils.rotPt(scrclick[0] - scrcent[0], scrclick[1] - scrcent[1], -this.props.ScreenToLocalTransform().Rotate); + const theclick = [theclickoff.x + scrcent[0], theclickoff.y + scrcent[1]]; MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - e.button !== 2 && (this._marqueeing = [e.clientX * scale + mainContBounds.translateX, e.clientY * scale + mainContBounds.translateY - NumCast(this.layoutDoc._layout_scrollTop) * scale]); - if (word || (e.target as any)?.className?.includes('rangeslider') || (e.target as any)?.onclick || (e.target as any)?.parentNode?.onclick) { - setTimeout( - action(() => (this._marqueeing = undefined)), - 100 - ); // bcz: hack .. anchor menu is setup within MarqueeAnnotator so we need to at least create the marqueeAnnotator even though we aren't using it. - } else { - this._iframeClick = this._iframe ?? undefined; - this._isAnnotating = true; - this.props.select(false); - e.stopPropagation(); - e.preventDefault(); - } - - // bcz: hack - iframe grabs all events which messes up how we handle contextMenus. So this super naively simulates the event stack to get the specific menu items and the doc view menu items. - if (e.button === 2 || (e.button === 0 && e.altKey)) { + const word = getWordAtPoint(e.target, e.clientX, e.clientY); + if (!word && !(e.target as any)?.className?.includes('rangeslider') && !(e.target as any)?.onclick && !(e.target as any)?.parentNode?.onclick) { + this.marqueeing = theclick; e.preventDefault(); - //e.stopPropagation(); - ContextMenu.Instance.closeMenu(); - ContextMenu.Instance.setIgnoreEvents(true); } }; isFirefox = () => 'InstallTrigger' in window; // navigator.userAgent.indexOf("Chrome") !== -1; - iframeClick = () => this._iframeClick; - iframeScaling = () => 1 / this.props.ScreenToLocalTransform().Scale; addWebStyleSheet(document: any, styleType: string = 'text/css') { if (document) { @@ -490,7 +489,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._scrollHeight = Math.max(this._scrollHeight, iframeContent.body.scrollHeight || 0); if (this._scrollHeight) { this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight); - this.layoutDoc.height = Math.min(this.layoutDoc[Height](), (this.layoutDoc[Width]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth)); + this.layoutDoc.height = Math.min(NumCast(this.layoutDoc._height), (NumCast(this.layoutDoc._width) * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth)); } }; const swidth = Math.max(NumCast(this.layoutDoc.nativeWidth), iframeContent.body.scrollWidth || 0); @@ -498,7 +497,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const aspectResize = swidth / NumCast(this.rootDoc.nativeWidth); this.rootDoc.nativeWidth = swidth; this.rootDoc.nativeHeight = NumCast(this.rootDoc.nativeHeight) * aspectResize; - this.layoutDoc.height = this.layoutDoc[Height]() * aspectResize; + this.layoutDoc.height = NumCast(this.layoutDoc._height) * aspectResize; } initHeights(); this._iframetimeout && clearTimeout(this._iframetimeout); @@ -708,10 +707,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps icon: 'snowflake', }); funcs.push({ - description: (!this.layoutDoc.layout_forceReflow ? 'Force' : 'Prevent') + ' Reflow', + description: (!this.layoutDoc.layout_reflowHorizontal ? 'Force' : 'Prevent') + ' Reflow', event: () => { - const nw = !this.layoutDoc.layout_forceReflow ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1); - this.layoutDoc.layout_forceReflow = !nw; + const nw = !this.layoutDoc.layout_reflowHorizontal ? undefined : Doc.NativeWidth(this.layoutDoc) - this.sidebarWidth() / (this.props.NativeDimScaling?.() || 1); + this.layoutDoc.layout_reflowHorizontal = !nw; if (nw) { Doc.SetInPlace(this.layoutDoc, this.fieldKey + '_nativeWidth', nw, true); } @@ -723,38 +722,56 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } }; + /** + * This gets called when some other child of the webbox is selected and a pointer down occurs on the webbox. + * it's also called for html clippings when you click outside the bounds of the clipping + * @param e + */ @action onMarqueeDown = (e: React.PointerEvent) => { + const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection(); + this._textAnnotationCreator = undefined; + if (sel?.empty) sel.empty(); // Chrome + else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox + this.marqueeing = [e.clientX, e.clientY]; if (!e.altKey && e.button === 0 && this.props.isContentActive(true) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { setupMoveUpEvents( this, e, action(e => { MarqueeAnnotator.clearAnnotations(this._savedAnnotations); - this._marqueeing = [e.clientX, e.clientY]; return true; }), returnFalse, - () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), + action(() => { + this.marqueeing = undefined; + MarqueeAnnotator.clearAnnotations(this._savedAnnotations); + }), false ); + } else { + this.marqueeing = undefined; } }; @action finishMarquee = (x?: number, y?: number, e?: PointerEvent) => { this._getAnchor = AnchorMenu.Instance?.GetAnchor; - this._marqueeing = undefined; - this._isAnnotating = false; - this._iframeClick = undefined; + this.marqueeing = undefined; + this._textAnnotationCreator = undefined; const sel = this._url ? this._iframe?.contentDocument?.getSelection() : window.document.getSelection(); if (sel?.empty) sel.empty(); // Chrome else if (sel?.removeAllRanges) sel.removeAllRanges(); // Firefox + this._setPreviewCursor?.(x ?? 0, y ?? 0, false, !this._marqueeref.current?.isEmpty, this.rootDoc); if (x !== undefined && y !== undefined) { - this._setPreviewCursor?.(x, y, false, false, this.rootDoc); ContextMenu.Instance.closeMenu(); ContextMenu.Instance.setIgnoreEvents(false); if (e?.button === 2 || e?.altKey) { - this.specificContextMenu(undefined as any); - this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y); + e?.preventDefault(); + e?.stopPropagation(); + setTimeout(() => { + // if menu comes up right away, the down event can still be active causing a menu item to be selected + this.specificContextMenu(undefined as any); + this.props.docViewPath().lastElement().docView?.onContextMenu(undefined, x, y); + }); } } }; @@ -762,6 +779,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @observable lighttext = false; @computed get urlContent() { + if (this.props.ScreenToLocalTransform().Scale > 25) return <div></div>; setTimeout( action(() => { if (this._initialScroll === undefined && !this._webPageHasBeenRendered) { @@ -796,7 +814,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps key={this._warning} className="webBox-iframe" ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} - style={{ pointerEvents: this._isAnyChildContentActive || DocumentView.Interacting ? 'none' : undefined }} + style={{ pointerEvents: DocumentView.Interacting ? 'none' : undefined }} src={url} onLoad={this.iframeLoaded} scrolling="no" // ugh.. on windows, I get an inner scroll bar for the iframe's body even though the scrollHeight should be set to the full height of the document. @@ -838,7 +856,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (ratio >= 1) { this.layoutDoc.nativeWidth = nativeWidth * ratio; this.layoutDoc.nativeHeight = nativeHeight * (1 + ratio); - onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]); + onButton && (this.layoutDoc._width = NumCast(this.layoutDoc._width) + localDelta[0]); this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; @@ -873,24 +891,25 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps toggleSidebar = action((preview: boolean = false) => { var nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']); if (!nativeWidth) { - const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[Width](); + const defaultNativeWidth = NumCast(this.rootDoc.nativeWidth, this.rootDoc[this.fieldKey] instanceof WebField ? 850 : NumCast(this.Document._width)); Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth); - Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[Height]() / this.Document[Width]()) * defaultNativeWidth); + Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (NumCast(this.Document._height) / NumCast(this.Document._width)) * defaultNativeWidth); nativeWidth = NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']); } const sideratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth : 0) + nativeWidth) / nativeWidth; - const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + nativeWidth) / nativeWidth; + const pdfratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? WebBox.openSidebarWidth + WebBox.sidebarResizerWidth : 0) + NumCast(this.layoutDoc.width)) / NumCast(this.layoutDoc.width); const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { this._previewNativeWidth = nativeWidth * sideratio; - this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth; + this._previewWidth = (NumCast(this.layoutDoc._width) * nativeWidth * sideratio) / curNativeWidth; this._showSidebar = true; } else { this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar; - this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth; + this.layoutDoc._width = (NumCast(this.layoutDoc._width) * nativeWidth * pdfratio) / curNativeWidth; if (!this.layoutDoc._layout_showSidebar && !(this.dataDoc[this.fieldKey] instanceof WebField)) { this.layoutDoc.nativeWidth = this.dataDoc[this.fieldKey + '_nativeWidth'] = undefined; } else { + !this.layoutDoc._layout_showSidebar && (this.dataDoc[this.fieldKey + '_nativeWidth'] = this.dataDoc[this.fieldKey + '_nativeHeight'] = undefined); this.layoutDoc.nativeWidth = nativeWidth * pdfratio; } } @@ -918,7 +937,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps className={'webBox-cont' + (interactive ? '-interactive' : '')} onKeyDown={e => e.stopPropagation()} style={{ - width: !this.layoutDoc.layout_forceReflow ? NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']) || `100%` : '100%', + width: !this.layoutDoc.layout_reflowHorizontal ? NumCast(this.layoutDoc[this.fieldKey + '_nativeWidth']) || `100%` : '100%', transform: `scale(${this.zoomScaling()}) translate(${-NumCast(this.layoutDoc.freeform_panX)}px, ${-NumCast(this.layoutDoc.freeform_panY)}px)`, }}> {this._hackHide ? null : this.urlContent} @@ -940,7 +959,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps {this.inlineTextAnnotations .sort((a, b) => NumCast(a.y) - NumCast(b.y)) .map(anno => ( - <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> + <Annotation {...this.props} fieldKey={this.annotationKey} pointerEvents={this.pointerEvents} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} /> ))} </div> ); @@ -1002,7 +1021,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps onWheel={this.onZoomWheel} onScroll={e => this.setDashScrollTop(this._outerRef.current?.scrollTop || 0)} onPointerDown={this.onMarqueeDown}> - <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight) || '100%', pointerEvents }}> + <div className="webBox-innerContent" style={{ height: (this._webPageHasBeenRendered && this._scrollHeight > this.props.PanelHeight() && this._scrollHeight) || '100%', pointerEvents }}> {this.content} <div style={{ display: SnappingManager.GetCanEmbed() ? 'none' : undefined, mixBlendMode: 'multiply' }}>{this.renderTransparentAnnotations}</div> {this.renderOpaqueAnnotations} @@ -1047,14 +1066,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ); } searchStringChanged = (e: React.ChangeEvent<HTMLInputElement>) => (this._searchString = e.currentTarget.value); - showInfo = action((anno: Opt<Doc>) => (this._overlayAnnoInfo = anno)); setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt<Doc>) => void) => (this._setPreviewCursor = func); panelWidth = () => this.props.PanelWidth() / (this.props.NativeDimScaling?.() || 1) - this.sidebarWidth() + WebBox.sidebarResizerWidth; panelHeight = () => this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1); scrollXf = () => this.props.ScreenToLocalTransform().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; - transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.IsOpaqueFilter()])]; + transparentFilter = () => [...this.props.childFilters(), Utils.TransparentBackgroundFilter]; + opaqueFilter = () => [...this.props.childFilters(), Utils.noDragDocsFilter, ...(SnappingManager.GetCanEmbed() ? [] : [Utils.OpaqueBackgroundFilter])]; childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { if (this.inlineTextAnnotations.includes(doc)) return 'none'; @@ -1065,7 +1083,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown() ? 'all' // : 'none'; - annotationPointerEvents = () => (this.props.isContentActive() && (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none'); + annotationPointerEvents = () => (this.props.isContentActive() && (SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? 'all' : 'none'); render() { const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); @@ -1088,27 +1106,26 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }} onContextMenu={this.specificContextMenu}> {this.webpage} - {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? null : ( - <div style={{ transformOrigin: 'top left', transform: `scale(${1 / scale})` }}> - <MarqueeAnnotator - rootDoc={this.rootDoc} - iframe={this.isFirefox() ? this.iframeClick : undefined} - iframeScaling={this.isFirefox() ? this.iframeScaling : undefined} - anchorMenuClick={this.anchorMenuClick} - scrollTop={0} - down={this._marqueeing} - scaling={returnOne} - addDocument={this.addDocumentWrapper} - docView={this.props.docViewPath().lastElement()} - finishMarquee={this.finishMarquee} - savedAnnotations={this.savedAnnotationsCreator} - selectionText={this.selectionText} - annotationLayer={this._annotationLayer.current} - mainCont={this._mainCont.current} - /> - </div> - )} </div> + {!this._mainCont.current || !this._annotationLayer.current ? null : ( + <div style={{ position: 'absolute', height: '100%', width: '100%', pointerEvents: this.marqueeing ? 'all' : 'none' }}> + <MarqueeAnnotator + ref={this._marqueeref} + rootDoc={this.rootDoc} + anchorMenuClick={this.anchorMenuClick} + scrollTop={NumCast(this.layoutDoc.layout_scrollTop)} + annotationLayerScrollTop={0} + scaling={this.props.NativeDimScaling} + addDocument={this.addDocumentWrapper} + docView={this.props.DocumentView!} + finishMarquee={this.finishMarquee} + savedAnnotations={this.savedAnnotationsCreator} + selectionText={this.selectionText} + annotationLayer={this._annotationLayer.current} + mainCont={this._mainCont.current} + /> + </div> + )} <div className="webBox-sideResizer" style={{ diff --git a/src/client/views/nodes/WebBoxRenderer.js b/src/client/views/nodes/WebBoxRenderer.js index 425ef3e54..914adb404 100644 --- a/src/client/views/nodes/WebBoxRenderer.js +++ b/src/client/views/nodes/WebBoxRenderer.js @@ -2,7 +2,7 @@ * * @param {StyleSheetList} styleSheets */ -var ForeignHtmlRenderer = function (styleSheets) { +const ForeignHtmlRenderer = function (styleSheets) { const self = this; /** @@ -10,7 +10,7 @@ var ForeignHtmlRenderer = function (styleSheets) { * @param {String} binStr */ const binaryStringToBase64 = function (binStr) { - return new Promise(function (resolve) { + return new Promise(resolve => { const reader = new FileReader(); reader.readAsDataURL(binStr); reader.onloadend = function () { @@ -31,17 +31,17 @@ var ForeignHtmlRenderer = function (styleSheets) { * @returns {Promise} */ const getResourceAsBase64 = function (webUrl, inurl) { - return new Promise(function (resolve, reject) { + return new Promise((resolve, reject) => { const xhr = new XMLHttpRequest(); - //const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl; - //const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl; - var url = inurl; + // const url = inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl; + // const url = CorsProxy(inurl.startsWith("/") && !inurl.startsWith("//") ? webUrl + inurl : inurl);// inurl.startsWith("http") ? CorsProxy(inurl) : inurl; + let url = inurl; if (inurl.startsWith('/static')) { url = new URL(webUrl).origin + inurl; } else if (inurl.startsWith('/') && !inurl.startsWith('//')) { url = CorsProxy(new URL(webUrl).origin + inurl); } else if (!inurl.startsWith('http') && !inurl.startsWith('//')) { - url = CorsProxy(webUrl + '/' + inurl); + url = CorsProxy(`${webUrl}/${inurl}`); } else if (inurl.startsWith('https') && !inurl.startsWith(window.location.origin)) { url = CorsProxy(inurl); } @@ -57,7 +57,7 @@ var ForeignHtmlRenderer = function (styleSheets) { resourceBase64: resBase64, }); } else if (xhr.readyState === XMLHttpRequest.DONE) { - console.log("COULDN'T FIND: " + (inurl.startsWith('/') ? webUrl + inurl : inurl)); + console.log(`COULDN'T FIND: ${inurl.startsWith('/') ? webUrl + inurl : inurl}`); resolve({ resourceUrl: '', resourceBase64: inurl, @@ -76,7 +76,7 @@ var ForeignHtmlRenderer = function (styleSheets) { */ const getMultipleResourcesAsBase64 = function (webUrl, urls) { const promises = []; - for (let i = 0; i < urls.length; i++) { + for (let i = 0; i < urls.length; i += 1) { promises.push(getResourceAsBase64(webUrl, urls[i])); } return Promise.all(promises); @@ -98,7 +98,7 @@ var ForeignHtmlRenderer = function (styleSheets) { } let val = ''; - for (let i = idx + prefixToken.length; i < str.length; i++) { + for (let i = idx + prefixToken.length; i < str.length; i += 1) { if (suffixTokens.indexOf(str[i]) !== -1) { break; } @@ -114,6 +114,15 @@ var ForeignHtmlRenderer = function (styleSheets) { /** * + * @param {String} str + * @returns {String} + */ + const removeQuotes = function (str) { + return str.replace(/["']/g, ''); + }; + + /** + * * @param {String} cssRuleStr * @returns {String[]} */ @@ -127,13 +136,12 @@ var ForeignHtmlRenderer = function (styleSheets) { break; } searchStartIndex = url.foundAtIndex + url.value.length + 1; - if (mustEndWithQuote && url.value[url.value.length - 1] !== '"') continue; - const unquoted = removeQuotes(url.value); - if (!unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") )*/ || unquoted === 'http://' || unquoted === 'https://') { - continue; + if (!mustEndWithQuote || url.value[url.value.length - 1] === '"') { + const unquoted = removeQuotes(url.value); + if (unquoted /* || (!unquoted.startsWith('http')&& !unquoted.startsWith("/") ) */ && unquoted !== 'http://' && unquoted !== 'https://') { + if (unquoted) urlsFound.push(unquoted); + } } - - unquoted && urlsFound.push(unquoted); } return urlsFound; @@ -151,15 +159,6 @@ var ForeignHtmlRenderer = function (styleSheets) { return getUrlsFromCssString(html, 'source=', [' ', '>', '\t'], true); }; - /** - * - * @param {String} str - * @returns {String} - */ - const removeQuotes = function (str) { - return str.replace(/["']/g, ''); - }; - const escapeRegExp = function (string) { return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); // $& means the whole matched string }; @@ -172,8 +171,8 @@ var ForeignHtmlRenderer = function (styleSheets) { * * @returns {Promise<String>} */ - const buildSvgDataUri = async function (webUrl, contentHtml, width, height, scroll, xoff) { - return new Promise(async function (resolve, reject) { + const buildSvgDataUri = async function (webUrl, inputContentHtml, width, height, scroll, xoff) { + return new Promise(async (resolve, reject) => { /* !! The problems !! * 1. CORS (not really an issue, expect perhaps for images, as this is a general security consideration to begin with) * 2. Platform won't wait for external assets to load (fonts, images, etc.) @@ -181,17 +180,19 @@ var ForeignHtmlRenderer = function (styleSheets) { // copy styles let cssStyles = ''; - let urlsFoundInCss = []; + const urlsFoundInCss = []; - for (let i = 0; i < styleSheets.length; i++) { + for (let i = 0; i < styleSheets.length; i += 1) { try { const rules = styleSheets[i].cssRules; - for (let j = 0; j < rules.length; j++) { + for (let j = 0; j < rules.length; j += 1) { const cssRuleStr = rules[j].cssText; urlsFoundInCss.push(...getUrlsFromCssString(cssRuleStr)); cssStyles += cssRuleStr; } - } catch (e) {} + } catch (e) { + /* empty */ + } } // const fetchedResourcesFromStylesheets = await getMultipleResourcesAsBase64(webUrl, urlsFoundInCss); @@ -202,15 +203,15 @@ var ForeignHtmlRenderer = function (styleSheets) { // } // } - contentHtml = contentHtml + let contentHtml = inputContentHtml .replace(/<source[^>]*>/g, '') // <picture> tags have a <source> which has a srcset field of image refs. instead of converting each, just use the default <img> of the picture .replace(/noscript/g, 'div') .replace(/<div class="mediaset"><\/div>/g, '') // when scripting isn't available (ie, rendering web pages here), <noscript> tags should become <div>'s. But for Brown CS, there's a layout problem if you leave the empty <mediaset> tag .replace(/<link[^>]*>/g, '') // don't need to keep any linked style sheets because we've already processed all style sheets above .replace(/srcset="([^ "]*)[^"]*"/g, 'src="$1"'); // instead of converting each item in the srcset to a data url, just convert the first one and use that - let urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:')); + const urlsFoundInHtml = getImageUrlsFromFromHtml(contentHtml).filter(url => !url.startsWith('data:')); const fetchedResources = webUrl ? await getMultipleResourcesAsBase64(webUrl, urlsFoundInHtml) : []; - for (let i = 0; i < fetchedResources.length; i++) { + for (let i = 0; i < fetchedResources.length; i += 1) { const r = fetchedResources[i]; if (r.resourceUrl) { contentHtml = contentHtml.replace(new RegExp(escapeRegExp(r.resourceUrl), 'g'), r.resourceBase64); @@ -230,7 +231,7 @@ var ForeignHtmlRenderer = function (styleSheets) { // contentRootElem.style.transform = "scale(0.08)" contentRootElem.innerHTML = styleElemString + contentHtml; contentRootElem.setAttribute('xmlns', 'http://www.w3.org/1999/xhtml'); - //document.body.appendChild(contentRootElem); + // document.body.appendChild(contentRootElem); const contentRootElemString = new XMLSerializer().serializeToString(contentRootElem); @@ -256,13 +257,13 @@ var ForeignHtmlRenderer = function (styleSheets) { * @return {Promise<Image>} */ this.renderToImage = async function (webUrl, html, width, height, scroll, xoff) { - return new Promise(async function (resolve, reject) { + return new Promise(async (resolve, reject) => { const img = new Image(); - console.log('BUILDING SVG for:' + webUrl); + console.log(`BUILDING SVG for: ${webUrl}`); img.src = await buildSvgDataUri(webUrl, html, width, height, scroll, xoff); img.onload = function () { - console.log('IMAGE SVG created:' + webUrl); + console.log(`IMAGE SVG created: ${webUrl}`); resolve(img); }; }); @@ -276,7 +277,7 @@ var ForeignHtmlRenderer = function (styleSheets) { * @return {Promise<Image>} */ this.renderToCanvas = async function (webUrl, html, width, height, scroll, xoff, oversample) { - return new Promise(async function (resolve, reject) { + return new Promise(async (resolve, reject) => { const img = await self.renderToImage(webUrl, html, width, height, scroll, xoff); const canvas = document.createElement('canvas'); @@ -298,7 +299,7 @@ var ForeignHtmlRenderer = function (styleSheets) { * @return {Promise<String>} */ this.renderToBase64Png = async function (webUrl, html, width, height, scroll, xoff, oversample) { - return new Promise(async function (resolve, reject) { + return new Promise(async (resolve, reject) => { const canvas = await self.renderToCanvas(webUrl, html, width, height, scroll, xoff, oversample); resolve(canvas.toDataURL('image/png')); }); @@ -320,8 +321,8 @@ export function CreateImage(webUrl, styleSheets, html, width, height, scroll, xo ); } -var ClipboardUtils = new (function () { - var permissions = { +const ClipboardUtils = new (function () { + const permissions = { 'image/bmp': true, 'image/gif': true, 'image/png': true, @@ -330,8 +331,8 @@ var ClipboardUtils = new (function () { }; function getType(types) { - for (var j = 0; j < types.length; ++j) { - var type = types[j]; + for (let j = 0; j < types.length; j += 1) { + const type = types[j]; if (permissions[type]) { return type; } @@ -339,10 +340,10 @@ var ClipboardUtils = new (function () { return null; } function getItem(items) { - for (var i = 0; i < items.length; ++i) { - var item = items[i]; + for (let i = 0; i < items.length; i += 1) { + const item = items[i]; if (item) { - var type = getType(item.types); + const type = getType(item.types); if (type) { return item.getType(type); } @@ -352,7 +353,7 @@ var ClipboardUtils = new (function () { } function loadFile(file, callback) { if (window.FileReader) { - var reader = new FileReader(); + const reader = new FileReader(); reader.onload = function () { callback(reader.result, null); }; @@ -366,23 +367,23 @@ var ClipboardUtils = new (function () { } this.readImage = function (callback) { if (navigator.clipboard) { - var promise = navigator.clipboard.read(); + const promise = navigator.clipboard.read(); promise - .then(function (items) { - var promise = getItem(items); - if (promise == null) { + .then(items => { + const promise2 = getItem(items); + if (promise2 == null) { callback(null, null); return; } - promise - .then(function (result) { + promise2 + .then(result => { loadFile(result, callback); }) - .catch(function (error) { + .catch(error => { callback(null, 'Reading clipboard error.'); }); }) - .catch(function (error) { + .catch(error => { callback(null, 'Reading clipboard error.'); }); } else { diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index c5167461b..e8b9a98b7 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom/client'; import { Doc } from '../../../../fields/Doc'; import { Height, Width } from '../../../../fields/DocSymbols'; import { NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnFalse, Utils } from '../../../../Utils'; +import { emptyFunction, returnFalse, returnTrue, Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; import { Transform } from '../../../util/Transform'; @@ -177,6 +177,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { }; componentWillUnmount = () => Object.values(this._disposers).forEach(disposer => disposer?.()); + isContentActive = () => this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.(); render() { return !this._dashDoc || !this._finalLayout || this.props.hidden ? null : ( @@ -190,6 +191,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { display: 'inline-block', left: 0, top: 0, + pointerEvents: this.isContentActive() ? undefined : 'none', }} onPointerLeave={this.onPointerLeave} onPointerEnter={this.onPointerEnter} @@ -203,7 +205,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { rootSelected={returnFalse} //{this._textBox.props.isSelected} removeDocument={this.removeDoc} isDocumentActive={returnFalse} - isContentActive={emptyFunction} + isContentActive={this.isContentActive} styleProvider={this._textBox.props.styleProvider} docViewPath={this._textBox.props.docViewPath} ScreenToLocalTransform={this.getDocTransform} @@ -213,7 +215,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { PanelWidth={this._finalLayout[Width]} PanelHeight={this._finalLayout[Height]} focus={this.outerFocus} - whenChildContentsActiveChanged={returnFalse} + whenChildContentsActiveChanged={this.props.tbox.whenChildContentsActiveChanged} bringToFront={emptyFunction} dontRegisterView={false} childFilters={this.props.tbox?.props.childFilters} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index d5ad128fe..0a64c8062 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -177,6 +177,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna style={{ width: this.props.width, height: this.props.height, + pointerEvents: this.props.tbox.props.isSelected() || this.props.tbox.isAnyChildContentActive?.() ? undefined : 'none', }}> {this.props.hideKey ? null : ( <span className="dashFieldView-labelSpan" title="click to see related tags" onPointerDown={this.onPointerDownLabelSpan}> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 818c0cbe7..717efd5a9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -49,6 +49,7 @@ audiotag:hover { transition: opacity 1s; width: 100%; position: relative; + transform-origin: left top; top: 0; left: 0; } @@ -89,6 +90,7 @@ audiotag:hover { bottom: 0; width: 11; height: 11; + cursor: default; } .formattedTextBox-outer { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 41b1c59b0..b65c440d1 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1,6 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; +import { setCORS } from 'google-translate-api-browser'; import { isEqual } from 'lodash'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; @@ -14,7 +15,7 @@ import { EditorView } from 'prosemirror-view'; import { BsMarkdownFill } from 'react-icons/bs'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; -import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, Height, UpdatingFromServer, Width } from '../../../../fields/DocSymbols'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -52,6 +53,7 @@ import { AnchorMenu } from '../../pdf/AnchorMenu'; import { GPTPopup } from '../../pdf/GPTPopup/GPTPopup'; import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; +import { media_state } from '../AudioBox'; import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { LinkDocPreview } from '../LinkDocPreview'; @@ -71,8 +73,6 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); -import { media_state } from '../AudioBox'; -import { setCORS } from 'google-translate-api-browser'; // setting up cors-anywhere server address const translate = setCORS('http://cors-anywhere.herokuapp.com/'); export const GoogleRef = 'googleDocId'; @@ -93,6 +93,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps static _bulletStyleSheet: any = addStyleSheet(); static _userStyleSheet: any = addStyleSheet(); static _hadSelection: boolean = false; + private _selectionHTML: string | undefined; private _sidebarRef = React.createRef<SidebarAnnos>(); private _sidebarTagRef = React.createRef<React.Component>(); private _ref: React.RefObject<HTMLDivElement> = React.createRef(); @@ -315,7 +316,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps DragManager.StartAnchorAnnoDrag([ele], new DragManager.AnchorAnnoDragData(this.props.docViewPath().lastElement(), () => this.getAnchor(true), targetCreator), e.pageX, e.pageY); }); const coordsB = this._editorView!.coordsAtPos(this._editorView!.state.selection.to); - this.props.isSelected(true) && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); + this.props.isSelected() && AnchorMenu.Instance.jumpTo(coordsB.left, coordsB.bottom); + let ele: Opt<HTMLDivElement> = undefined; + try { + const contents = window.getSelection()?.getRangeAt(0).cloneContents(); + if (contents) { + ele = document.createElement('div'); + ele.append(contents); + } + this._selectionHTML = ele?.innerHTML; + } catch (e) {} }; dispatchTransaction = (tx: Transaction) => { @@ -434,10 +444,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps var tr = this._editorView.state.tr as any; const autoAnch = this._editorView.state.schema.marks.autoLinkAnchor; tr = tr.removeMark(0, tr.doc.content.size, autoAnch); - DocListCast(Doc.MyPublishedDocs.data).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); + Doc.MyPublishedDocs.forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); - // this.prepareForTyping(); } oldAutoLinks.filter(oldLink => !newAutoLinks.has(oldLink) && oldLink.link_anchor_2 !== this.rootDoc).forEach(LinkManager.Instance.deleteLink); }; @@ -460,7 +469,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (!(cfield instanceof ComputedField)) { this.dataDoc.title = (prefix + str.substring(0, Math.min(40, str.length)) + (str.length > 40 ? '...' : '')).trim(); if (str.startsWith('@') && str.length > 1) { - Doc.AddDocToList(Doc.MyPublishedDocs, undefined, this.rootDoc); + Doc.AddToMyPublished(this.rootDoc); } } } @@ -487,9 +496,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps ) || DocUtils.MakeLink(this.rootDoc, target, { link_relationship: LinkManager.AutoKeywords })!); newAutoLinks.add(alink); + // DocCast(alink.link_anchor_1).followLinkLocation = 'add:right'; const allAnchors = [{ href: Doc.localServerPath(target), title: 'a link', anchorId: this.props.Document[Id] }]; allAnchors.push(...(node.marks.find((m: Mark) => m.type.name === schema.marks.autoLinkAnchor.name)?.attrs.allAnchors ?? [])); - const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term', location: 'add:right' }); + const link = editorView.state.schema.marks.autoLinkAnchor.create({ allAnchors, title: 'auto term' }); tr = tr.addMark(pos, pos + node.nodeSize, link); } }); @@ -579,8 +589,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps // embed document when drag marked as embed } else if (de.embedKey) { const node = schema.nodes.dashDoc.create({ - width: draggedDoc[Width](), - height: draggedDoc[Height](), + width: NumCast(draggedDoc._width), + height: NumCast(draggedDoc._height), title: 'dashDoc', docId: draggedDoc[Id], float: 'unset', @@ -704,14 +714,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps @action toggleSidebar = (preview: boolean = false) => { + const defaultSidebar = 250; const prevWidth = 1 - this.sidebarWidth() / Number(getComputedStyle(this._ref.current!).width.replace('px', '')); if (preview) this._showSidebar = true; else { this.layoutDoc[this.SidebarKey + '_freeform_scale_max'] = 1; - this.layoutDoc._layout_showSidebar = (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? '50%' : '0%') !== '0%'; + this.layoutDoc._layout_showSidebar = + (this.layoutDoc._layout_sidebarWidthPercent = StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%') === '0%' ? `${(defaultSidebar / (NumCast(this.layoutDoc._width) + defaultSidebar)) * 100}%` : '0%') !== '0%'; } - this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth); + this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) + defaultSidebar : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth); }; sidebarDown = (e: React.PointerEvent) => { const batch = UndoManager.StartBatch('toggle sidebar'); @@ -733,7 +745,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); const sidebarWidth = (NumCast(this.layoutDoc._width) * Number(this.layout_sidebarWidthPercent.replace('%', ''))) / 100; - const width = this.layoutDoc[Width]() + localDelta[0]; + const width = NumCast(this.layoutDoc._width) + localDelta[0]; this.layoutDoc._layout_sidebarWidthPercent = Math.max(0, (sidebarWidth + localDelta[0]) / width) * 100 + '%'; this.layoutDoc.width = width; this.layoutDoc._layout_showSidebar = this.layoutDoc._layout_sidebarWidthPercent !== '0%'; @@ -853,29 +865,27 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps icon: !FormattedTextBox._globalHighlights.has(option) ? 'highlighter' : 'remove-format', }) ); + const appearance = cm.findByDescription('Appearance...'); + const appearanceItems: ContextMenuProps[] = appearance && 'subitems' in appearance ? appearance.subitems : []; - const uicontrols: ContextMenuProps[] = []; - uicontrols.push({ + appearanceItems.push({ description: !this.Document._layout_noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._layout_noSidebar = !this.layoutDoc._layout_noSidebar), icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye', }); - uicontrols.push({ + appearanceItems.push({ description: (this.Document._layout_enableAltContentUI ? 'Hide' : 'Show') + ' Alt Content UI', event: () => (this.layoutDoc._layout_enableAltContentUI = !this.layoutDoc._layout_enableAltContentUI), icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', }); - !Doc.noviceMode && uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); + !Doc.noviceMode && appearanceItems.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && - uicontrols.push({ + appearanceItems.push({ description: 'Broadcast Message', event: () => DocServer.GetRefField('rtfProto').then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: 'expand-arrows-alt', }); - cm.addItem({ description: 'UI Controls...', subitems: uicontrols, icon: 'asterisk' }); - const appearance = cm.findByDescription('Appearance...'); - const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; appearanceItems.push({ description: 'Change Style...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' }); // this.rootDoc.isTemplateDoc && appearanceItems.push({ description: "Make Default Layout", event: async () => Doc.UserDoc().defaultTextLayout = new PrefetchProxy(this.rootDoc), icon: "eye" }); @@ -909,7 +919,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }, icon: 'eye', }); - cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); + !appearance && appearanceItems.length && cm.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); const options = cm.findByDescription('Options...'); const optionItems = options && 'subitems' in options ? options.subitems : []; @@ -1054,7 +1064,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); this.dataDoc[UpdatingFromServer] = this.dataDoc[ForceServerWrite] = false; anchor.text = selectedText; + anchor.text_html = this._selectionHTML ?? selectedText; anchor.title = selectedText.substring(0, 30); + anchor.presentation_zoomText = true; return anchor; } return anchorDoc ?? this.rootDoc; @@ -1407,7 +1419,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [ view.state.schema.marks.linkAnchor.create({ allAnchors: [{ href: `/doc/${this.rootDoc[Id]}`, title: this.rootDoc.title, anchorId: `${this.rootDoc[Id]}` }], - location: 'add:right', title: `from: ${DocCast(pdfAnchor.embedContainer).title}`, noPreview: true, docref: false, @@ -1440,7 +1451,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const self = this; return new Plugin({ view(newView) { - runInAction(() => self.props.isSelected(true) && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); + runInAction(() => self.props.isSelected() && RichTextMenu.Instance && (RichTextMenu.Instance.view = newView)); return new RichTextMenuPlugin({ editorProps: this.props }); }, }); @@ -1613,7 +1624,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._downX = e.clientX; this._downY = e.clientY; FormattedTextBoxComment.textBox = this; - if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.button === 0 && (this.props.rootSelected() || this.props.isSelected()) && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar, otherwise nested boxes will lose focus to outer boxes. e.stopPropagation(); // if the text box's content is active, then it consumes all down events @@ -1642,15 +1653,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps let target = e.target as any; // hrefs are stored on the dataset of the <a> node that wraps the hyerlink <span> while (target && !target.dataset?.targethrefs) target = target.parentElement; FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs, target?.dataset.linkdoc, target?.dataset.nopreview === 'true'); - if (pcords && pcords.inside > 0 && state.doc.nodeAt(pcords.inside)?.type === state.schema.nodes.dashDoc) { - return; - } } }; @action onDoubleClick = (e: React.MouseEvent): void => { FormattedTextBoxComment.textBox = this; - if (e.button === 0 && this.props.isSelected(true) && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar e.stopPropagation(); // if the text box is selected, then it consumes all click events @@ -1661,7 +1669,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } FormattedTextBoxComment.Hide(); - if (e.buttons === 1 && this.props.isSelected(true) && !e.altKey) { + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } }; @@ -1707,7 +1715,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos))); } } - if (this.props.isSelected(true)) { + if (this.props.isSelected()) { // if text box is selected, then it consumes all click events (e.nativeEvent as any).handledByInnerReactInstance = true; this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey); @@ -1721,7 +1729,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); const clickPos = this._editorView!.posAtCoords({ left: x, top: y }); let olistPos = clickPos?.pos; - if (clickPos && olistPos && this.props.isSelected(true)) { + if (clickPos && olistPos && this.props.isSelected()) { const clickNode = this._editorView?.state.doc.nodeAt(olistPos); const nodeBef = this._editorView?.state.doc.nodeAt(Math.max(0, olistPos - 1)); olistPos = nodeBef?.type === this._editorView?.state.schema.nodes.ordered_list ? olistPos - 1 : olistPos; @@ -1770,7 +1778,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps tr && this._editorView.dispatch(tr); } } - if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected(true)) { + if (RichTextMenu.Instance?.view === this._editorView && !this.props.isSelected()) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } FormattedTextBox._hadSelection = window.getSelection()?.toString() !== ''; @@ -1937,7 +1945,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const color = !annotated ? Colors.WHITE : Colors.BLACK; const backgroundColor = !annotated ? (this.sidebarWidth() ? Colors.MEDIUM_BLUE : Colors.BLACK) : this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ':annotated' : '')); - return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging()) ? null : ( + return !annotated && (!this.props.isContentActive() || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None) ? null : ( <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} @@ -2050,7 +2058,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } @computed get fieldKey() { const usePath = StrCast(this.rootDoc[`${this.props.fieldKey}_usePath`]); - return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `_${usePath.replace(':hover', '')}` : ''); + return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering || this.props.isContentActive()) ? `_${usePath.replace(':hover', '')}` : ''); } @observable _isHovering = false; onPassiveWheel = (e: WheelEvent) => { @@ -2067,7 +2075,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (this.props.isContentActive()) { // prevent default if selected || child is active but this doc isn't scrollable if ( - (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(this.props.PanelHeight()) && // + (this._scrollRef.current?.scrollHeight ?? 0) <= Math.ceil(this.props.PanelHeight() / (this.props.NativeDimScaling?.() || 1)) && // (this.props.isSelected() || this.isAnyChildContentActive()) ) { e.preventDefault(); diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index e3ac4fb9d..712be91c8 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -181,7 +181,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveAlignment() { - if (this.view && this.TextView?.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected()) { const path = (this.view.state.selection.$from as any).path; for (let i = path.length - 3; i < path.length && i >= 0; i -= 3) { if (path[i]?.type === this.view.state.schema.nodes.paragraph || path[i]?.type === this.view.state.schema.nodes.heading) { @@ -194,7 +194,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // finds font sizes and families in selection getActiveListStyle() { - if (this.view && this.TextView?.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected()) { const path = (this.view.state.selection.$from as any).path; for (let i = 0; i < path.length; i += 3) { if (path[i].type === this.view.state.schema.nodes.ordered_list) { @@ -214,7 +214,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const activeSizes = new Set<string>(); const activeColors = new Set<string>(); const activeHighlights = new Set<string>(); - if (this.view && this.TextView?.props.isSelected(true)) { + if (this.view && this.TextView?.props.isSelected()) { const state = this.view.state; const pos = this.view.state.selection.$from; const marks: Mark[] = [...(state.storedMarks ?? [])]; @@ -249,7 +249,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { //finds all active marks on selection in given group getActiveMarksOnSelection() { let activeMarks: MarkType[] = []; - if (!this.view || !this.TextView?.props.isSelected(true)) return activeMarks; + if (!this.view || !this.TextView?.props.isSelected()) return activeMarks; const markGroup = [schema.marks.noAutoLinkAnchor, schema.marks.strong, schema.marks.em, schema.marks.underline, schema.marks.strikethrough, schema.marks.superscript, schema.marks.subscript]; if (this.view.state.storedMarks) return this.view.state.storedMarks.map(mark => mark.type); @@ -286,7 +286,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } destroy() { - !this.TextView?.props.isSelected(true) && this.fadeOut(true); + !this.TextView?.props.isSelected() && this.fadeOut(true); } @action @@ -443,7 +443,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { } align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { - if (this.TextView?.props.isSelected(true)) { + if (this.TextView?.props.isSelected()) { var tr = view.state.tr; view.state.doc.nodesBetween(view.state.selection.from, view.state.selection.to, (node, pos, parent, index) => { if ([schema.nodes.paragraph, schema.nodes.heading].includes(node.type)) { diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index 6f07588b3..4faef26e2 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -28,7 +28,6 @@ export const marks: { [index: string]: MarkSpec } = { autoLinkAnchor: { attrs: { allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, - location: { default: null }, title: { default: null }, }, inclusive: false, @@ -37,7 +36,6 @@ export const marks: { [index: string]: MarkSpec } = { tag: 'a[href]', getAttrs(dom: any) { return { - location: dom.getAttribute('location'), title: dom.getAttribute('title'), }; }, @@ -46,7 +44,7 @@ export const marks: { [index: string]: MarkSpec } = { toDOM(node: any) { const targethrefs = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.href : item.href), ''); const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); - return ['a', { class: anchorids, 'data-targethrefs': targethrefs, 'data-noPreview': 'true', 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, location: node.attrs.location, style: `background: lightBlue` }, 0]; + return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; }, }, noAutoLinkAnchor: { @@ -73,7 +71,6 @@ export const marks: { [index: string]: MarkSpec } = { linkAnchor: { attrs: { allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, - location: { default: null }, title: { default: null }, noPreview: { default: false }, docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text @@ -84,7 +81,6 @@ export const marks: { [index: string]: MarkSpec } = { tag: 'a[href]', getAttrs(dom: any) { return { - location: dom.getAttribute('location'), title: dom.getAttribute('title'), noPreview: dom.getAttribute('noPreview'), }; @@ -108,7 +104,7 @@ export const marks: { [index: string]: MarkSpec } = { node.attrs.title, ], ] - : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, location: node.attrs.location, style: `text-decoration: underline; cursor: default` }, 0]; + : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0]; }, }, diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index f27fb18e2..4cd2cee52 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -212,7 +212,6 @@ export const nodes: { [index: string]: NodeSpec } = { alt: { default: null }, title: { default: null }, float: { default: 'left' }, - location: { default: 'add:right' }, docId: { default: '' }, }, group: 'inline', diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 3d7c68bcd..789509784 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -27,7 +27,6 @@ import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm'; import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; import { CollectionView } from '../../collections/CollectionView'; -import { TabDocView } from '../../collections/TabDocView'; import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; @@ -204,16 +203,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); this._disposers.editing = reaction( () => this.layoutDoc.presentation_status === PresStatus.Edit, - editing => { - if (editing) { - this.childDocs.forEach(doc => { - if (doc.presentation_indexed !== undefined) { - this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined)); - doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1); - } - }); - } - } + editing => editing && + this.childDocs.filter(doc => doc.presentation_indexed !== undefined).forEach(doc => { + this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined)); + doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1); + }) // prettier-ignore ); } @@ -283,19 +277,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; // go to documents chain - runSubroutines = (childrenToRun: Doc[], normallyNextSlide: Doc) => { - console.log(childrenToRun, normallyNextSlide, 'runSUBFUNC'); - if (childrenToRun === undefined) { - console.log('children undefined'); - return; + runSubroutines = (childrenToRun: Opt<Doc[]>, normallyNextSlide: Doc) => { + if (childrenToRun && childrenToRun[0] !== normallyNextSlide) { + childrenToRun.forEach(child => DocumentManager.Instance.showDocument(child, {})); } - if (childrenToRun[0] === normallyNextSlide) { - return; - } - - childrenToRun.forEach(child => { - DocumentManager.Instance.showDocument(child, {}); - }); }; // Called when the user activates 'next' - to move to the next part of the pres. trail @@ -453,7 +438,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc); const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext; if (context) { - const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView; if (ffview?.childDocs) { PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime); ffview.rootDoc._currentFrame = NumCast(activeFrame); @@ -626,7 +611,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { changed = true; const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height); activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale); - dv.ComponentView?.brushView?.(viewport, transTime); + dv.ComponentView?.brushView?.(viewport, transTime, 2500); } } else { if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) { @@ -782,6 +767,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection); }; + public static PanelName = 'PRESBOX'; + static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) { if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); @@ -796,7 +783,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime), effect: activeItem, noSelect: true, - openLocation: OpenWhere.addLeft, + openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft, anchorDoc: activeItem, easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, zoomTextSelections: BoolCast(activeItem.presentation_zoomText), @@ -806,7 +793,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (activeItem.presentation_openInLightbox) { const context = DocCast(targetDoc.annotationOn) ?? targetDoc; if (!DocumentManager.Instance.getLightboxDocumentView(context)) { - LightboxView.SetLightboxDoc(context); + LightboxView.Instance.SetLightboxDoc(context); } } if (targetDoc) { @@ -815,7 +802,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => { // if target or the doc it annotates is not in the lightbox, then close the lightbox if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { - LightboxView.SetLightboxDoc(undefined); + LightboxView.Instance.SetLightboxDoc(undefined); } DocumentManager.Instance.showDocument(targetDoc, options, finished); }); @@ -909,7 +896,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { break; } }); - LightboxView.SetLightboxDoc(undefined); + LightboxView.Instance.SetLightboxDoc(undefined); Doc.RemFromMyOverlay(this.rootDoc); return PresStatus.Edit; }; @@ -1082,7 +1069,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */)); audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */)); audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd; - TabDocView.PinDoc(audio, { audioRange: true }); + this.props.pinToPres(audio, { audioRange: true }); setTimeout(() => this.removeDocument(doc), 0); return false; } @@ -1653,8 +1640,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { let dataField = Doc.LayoutFieldKey(tagDoc); if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations'; - if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn["${dataField}"]`); - else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc["${dataField}"]`); + if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc.annotationOn?.["${dataField}"]`); + else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc?.["${dataField}"]`); }} checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false} /> @@ -2237,7 +2224,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const config_data = Cast(this.rootDoc.data, listSpec(Doc)); if (data && config_data) { data.push(doc); - TabDocView.PinDoc(doc, {}); + this.props.pinToPres(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { this.props.addDocTab(doc, OpenWhere.addRight); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 82ed9e8d5..c667f126f 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -3,7 +3,6 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; -import { Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; @@ -15,6 +14,7 @@ import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; import { undoable, undoBatch } from '../../../util/UndoManager'; +import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; @@ -25,9 +25,6 @@ import { PresBox } from './PresBox'; import './PresElementBox.scss'; import { PresMovement } from './PresEnums'; import React = require('react'); -import { TreeView } from '../../collections/TreeView'; -import { BranchingTrailManager } from '../../../util/BranchingTrailManager'; -import { MultiToggle, Type } from 'browndash-components'; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. @@ -331,7 +328,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { }; @computed get recordingIsInOverlay() { - return DocListCast(Doc.MyOverlayDocs.data).some(doc => doc.slides === this.rootDoc); + return Doc.MyOverlayDocs.some(doc => doc.slides === this.rootDoc); } // a previously recorded video will have timecode defined @@ -341,14 +338,12 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { }; removeAllRecordingInOverlay = () => { - DocListCast(Doc.MyOverlayDocs.data) - .filter(doc => doc.slides === this.rootDoc) - .forEach(Doc.RemFromMyOverlay); + Doc.MyOverlayDocs.filter(doc => doc.slides === this.rootDoc).forEach(Doc.RemFromMyOverlay); }; static removeEveryExistingRecordingInOverlay = () => { // Remove every recording that already exists in overlay view - DocListCast(Doc.MyOverlayDocs.data).forEach(doc => { + Doc.MyOverlayDocs.forEach(doc => { if (doc.slides !== null) { // if it's a recording video, don't remove from overlay (user can lose data) if (PresElementBox.videoIsRecorded(DocCast(doc.slides))) { @@ -407,8 +402,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { activeItem.recording = recording; // make recording box appear in the bottom right corner of the screen - recording.overlayX = window.innerWidth - recording[Width]() - 20; - recording.overlayY = window.innerHeight - recording[Height]() - 20; + recording.overlayX = window.innerWidth - NumCast(recording._width) - 20; + recording.overlayY = window.innerHeight - NumCast(recording._height) - 20; Doc.AddToMyOverlay(recording); } }; @@ -551,7 +546,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { return ( <div - className={`presItem-container`} + className='presItem-container' key={this.props.Document[Id] + this.indexInPres} ref={this._itemRef} style={{ |
