diff options
Diffstat (limited to 'src/client/views/nodes')
65 files changed, 1447 insertions, 2000 deletions
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 8e83cf121..6558d215a 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -233,7 +233,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this.dataDoc[this.fieldKey + '-recordingStart'] = new DateField(); DocUtils.ActiveRecordings.push(this); this._recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(e.data); + const [{ result }] = await Networking.UploadFilesToServer({file: e.data}); if (!(result instanceof Error)) { this.props.Document[this.fieldKey] = new AudioField(result.accessPaths.agnostic.client); } @@ -362,10 +362,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp Doc.GetProto(newDoc).recordingSource = this.dataDoc; Doc.GetProto(newDoc).recordingStart = ComputedField.MakeFunction(`self.recordingSource["${this.fieldKey}-recordingStart"]`); Doc.GetProto(newDoc).mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); - if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) { + if (Doc.IsInMyOverlay(this.rootDoc)) { newDoc.overlayX = this.rootDoc.x; newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc); + Doc.AddToMyOverlay(newDoc); } else { this.props.addDocument?.(newDoc); } @@ -447,7 +447,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight); - setPlayheadTime = (time: number) => (this._ele!.currentTime = this.layoutDoc._layout_currentTimecode = time); + setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time); playing = () => this.mediaState === media_state.Playing; diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 330cc3971..aae759702 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -2,7 +2,8 @@ import React = require('react'); import { action } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; -import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; +import { Doc } from '../../../fields/Doc'; +import { Height, Width } from '../../../fields/DocSymbols'; import { InkTool } from '../../../fields/InkField'; import { StrCast } from '../../../fields/Types'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -48,7 +49,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps>() { } render() { - const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]()); + const scaling = Math.min(this.layoutDoc.layout_fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[Height](), this.props.PanelWidth() / this.rootDoc[Width]()); return ( <div className={`colorBox-container${this.props.isContentActive() ? '-interactive' : ''}`} diff --git a/src/client/views/nodes/ComparisonBox.scss b/src/client/views/nodes/ComparisonBox.scss index 660045a6f..a12f1c12b 100644 --- a/src/client/views/nodes/ComparisonBox.scss +++ b/src/client/views/nodes/ComparisonBox.scss @@ -93,4 +93,4 @@ display: flex; } } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 3be2d9a77..ca5ec9389 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -2,11 +2,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, Opt } from '../../../fields/Doc'; -import { Cast, NumCast, StrCast } from '../../../fields/Types'; +import { DocCast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; -import { Docs } from '../../documents/Documents'; +import { Docs, DocUtils } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; -import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { StyleProp } from '../StyleProvider'; @@ -32,48 +31,56 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl get clipWidthKey() { return '_' + this.props.fieldKey + '_clipWidth'; } - componentDidMount() { this.props.setContentView?.(this); } - protected createDropTarget = (ele: HTMLDivElement | null, fieldKey: string, disposerId: number) => { this._disposers[disposerId]?.(); if (ele) { - // create disposers identified by disposerId to remove drag & drop listeners - this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.dropHandler(e, dropEvent, fieldKey), this.layoutDoc); + this._disposers[disposerId] = DragManager.MakeDropTarget(ele, (e, dropEvent) => this.internalDrop(e, dropEvent, fieldKey), this.layoutDoc); } }; @undoBatch - private dropHandler = (event: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { + private internalDrop = (e: Event, dropEvent: DragManager.DropEvent, fieldKey: string) => { if (dropEvent.complete.docDragData) { - event.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place const droppedDocs = dropEvent.complete.docDragData?.droppedDocuments; - if (droppedDocs?.length) { - this.dataDoc[fieldKey] = droppedDocs[0]; - } + const added = dropEvent.complete.docDragData.moveDocument?.(droppedDocs, this.rootDoc, (doc: Doc | Doc[]) => this.addDoc(doc instanceof Doc ? doc : doc.lastElement(), fieldKey)); + Doc.SetContainer(droppedDocs.lastElement(), this.dataDoc); + !added && e.preventDefault(); + e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place + return added; } }; private registerSliding = (e: React.PointerEvent<HTMLDivElement>, targetWidth: number) => { - e.button !== 2 && + if (e.button !== 2) { setupMoveUpEvents( this, e, this.onPointerMove, emptyFunction, + action((e, doubleTap) => { + if (doubleTap) { + this._isAnyChildContentActive = true; + if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + } + }), + false, + undefined, action(() => { - // on click, animate slider movement to the targetWidth + if (this._isAnyChildContentActive) return; this._animating = 'all 200ms'; + // on click, animate slider movement to the targetWidth this.layoutDoc[this.clipWidthKey] = (targetWidth * 100) / this.props.PanelWidth(); setTimeout( action(() => (this._animating = '')), 200 ); - }), - false + }) ); + } }; @action @@ -86,7 +93,12 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl }; getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - const anchor = Docs.Create.ImageanchorDocument({ title: 'ImgAnchor:' + this.rootDoc.title, presTransition: 1000, unrendered: true, annotationOn: this.rootDoc }); + const anchor = Docs.Create.ConfigDocument({ + title: 'CompareAnchor:' + this.rootDoc.title, + // set presentation timing properties for restoring view + presTransition: 1000, + annotationOn: this.rootDoc, + }); if (anchor) { if (!addAsAnnotation) anchor.backgroundColor = 'transparent'; /* addAsAnnotation &&*/ this.addDocument(anchor); @@ -101,12 +113,29 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl e.stopPropagation; // prevent click event action (slider movement) in registerSliding delete this.dataDoc[fieldKey]; }; + moveDoc = (doc: Doc, addDocument: (document: Doc | Doc[]) => boolean, which: string) => this.remDoc(doc, which) && addDocument(doc); + addDoc = (doc: Doc, which: string) => { + this.dataDoc[which] = doc; + return true; + }; + remDoc = (doc: Doc, which: string) => { + if (this.dataDoc[which] === doc) { + this.dataDoc[which] = undefined; + return true; + } + return false; + }; + + whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive)); docStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => { if (property === StyleProp.PointerEvents) return 'none'; return this.props.styleProvider?.(doc, props, property); }; - + moveDoc1 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_1'), true); + moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true); + remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true); + remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true); render() { const clearButton = (which: string) => { return ( @@ -114,36 +143,35 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl className={`clear-button ${which}`} onPointerDown={e => e.stopPropagation()} // prevent triggering slider movement in registerSliding onClick={e => this.clearDoc(e, which)}> - <FontAwesomeIcon className={`clear-button ${which}`} icon={'times'} size="sm" /> + <FontAwesomeIcon className={`clear-button ${which}`} icon="times" size="sm" /> </div> ); }; const displayDoc = (which: string) => { - const whichDoc = Cast(this.dataDoc[which], Doc, null); - // if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null); - const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc; - return whichDoc ? ( + const whichDoc = DocCast(this.dataDoc[which]); + const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); + return targetDoc ? ( <> <DocumentView - ref={r => { - //whichDoc !== targetDoc && r?.focus(whichDoc, { instant: true }); - }} {...this.props} + Document={targetDoc} + DataDoc={undefined} + moveDocument={which.endsWith('1') ? this.moveDoc1 : this.moveDoc2} + removeDocument={which.endsWith('1') ? this.remDoc1 : this.remDoc2} NativeWidth={returnZero} NativeHeight={returnZero} - isContentActive={returnFalse} + isContentActive={emptyFunction} isDocumentActive={returnFalse} - styleProvider={this.docStyleProvider} - Document={targetDoc} - DataDoc={undefined} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} + styleProvider={this._isAnyChildContentActive ? this.props.styleProvider : this.docStyleProvider} hideLinkButton={true} - pointerEvents={returnNone} + pointerEvents={this._isAnyChildContentActive ? undefined : returnNone} /> {clearButton(which)} </> // placeholder image if doc is missing ) : ( <div className="placeholder"> - <FontAwesomeIcon className="upload-icon" icon={'cloud-upload-alt'} size="lg" /> + <FontAwesomeIcon className="upload-icon" icon="cloud-upload-alt" size="lg" /> </div> ); }; @@ -156,7 +184,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl }; return ( - <div className={`comparisonBox${this.props.isContentActive() || SnappingManager.GetIsDragging() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}> + <div className={`comparisonBox${this.props.isContentActive() ? '-interactive' : ''}` /* change className to easily disable/enable pointer events in CSS */}> {displayBox(`${this.fieldKey}_2`, 1, this.props.PanelWidth() - 3)} <div className="clip-div" style={{ width: this.clipWidth + '%', transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, 'gray') }}> {displayBox(`${this.fieldKey}_1`, 0, 0)} @@ -168,7 +196,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl left: `calc(${this.clipWidth + '%'} - 0.5px)`, cursor: this.clipWidth < 5 ? 'e-resize' : this.clipWidth / 100 > (this.props.PanelWidth() - 5) / this.props.PanelWidth() ? 'w-resize' : undefined, }} - onPointerDown={e => this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ + onPointerDown={e => !this._isAnyChildContentActive && this.registerSliding(e, this.props.PanelWidth() / 2)} /* if clicked, return slide-bar to center */ > <div className="slide-handle" /> </div> diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 5876efb01..d548ab9f1 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -71,14 +71,14 @@ export class DataVizBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { }; getAnchor = (addAsAnnotation?: boolean, pinProps?: PinProps) => { - const anchor = - this._chartRenderer?.getAnchor(pinProps) ?? - Docs.Create.TextanchorDocument({ - unrendered: true, - // when we clear selection -> we should have it so chartBox getAnchor returns undefined - // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) - /*put in some options*/ - }); + const anchor = !pinProps + ? this.rootDoc + : this._chartRenderer?.getAnchor(pinProps) ?? + Docs.Create.ConfigDocument({ + // when we clear selection -> we should have it so chartBox getAnchor returns undefined + // this is for when we want the whole doc (so when the chartBox getAnchor returns without a marker) + /*put in some options*/ + }); anchor.presDataVizView = this.dataVizView; anchor.presDataVizAxes = this.axes.length ? new List<string>(this.axes) : undefined; diff --git a/src/client/views/nodes/DataVizBox/components/LineChart.tsx b/src/client/views/nodes/DataVizBox/components/LineChart.tsx index 11f62a61f..6b564b0c9 100644 --- a/src/client/views/nodes/DataVizBox/components/LineChart.tsx +++ b/src/client/views/nodes/DataVizBox/components/LineChart.tsx @@ -163,7 +163,10 @@ export class LineChart extends React.Component<LineChartProps> { // create a document anchor that stores whatever is needed to reconstruct the viewing state (selection,zoom,etc) getAnchor = (pinProps?: PinProps) => { - const anchor = Docs.Create.TextanchorDocument({ title: 'line doc selection' + this._currSelected?.x, unrendered: true }); + const anchor = Docs.Create.ConfigDocument({ + // + title: 'line doc selection' + this._currSelected?.x, + }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: pinProps?.pinData }, this.props.dataDoc); anchor.presDataVizSelection = this._currSelected ? new List<number>([this._currSelected.x, this._currSelected.y]) : undefined; return anchor; diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 3816bddfa..d84e34d52 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,7 +1,7 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { AnimationSym, Doc } from '../../../../../fields/Doc'; +import { Doc } from '../../../../../fields/Doc'; import { Id } from '../../../../../fields/FieldSymbols'; import { List } from '../../../../../fields/List'; import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../../../Utils'; @@ -57,7 +57,7 @@ export class TableBox extends React.Component<TableBoxProps> { DragManager.StartAnchorAnnoDrag([header.current!], new DragManager.AnchorAnnoDragData(this.props.docView()!, sourceAnchorCreator, targetCreator), downX, downY, { dragComplete: e => { if (!e.aborted && e.annoDragData && e.annoDragData.linkSourceDoc && e.annoDragData.dropDocument && e.linkDocument) { - e.linkDocument.layout_linkDisplay = true; + e.linkDocument.link_displayLine = true; // e.annoDragData.linkSourceDoc.followLinkToggle = e.annoDragData.dropDocument.annotationOn === this.props.rootDoc; // e.annoDragData.linkSourceDoc.followLinkZoom = false; } diff --git a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts index e1ff6f8eb..10bfb0c64 100644 --- a/src/client/views/nodes/DataVizBox/utils/D3Utils.ts +++ b/src/client/views/nodes/DataVizBox/utils/D3Utils.ts @@ -34,7 +34,6 @@ export const createLineGenerator = (xScale: d3.ScaleLinear<number, number, never }; export const xAxisCreator = (g: d3.Selection<SVGGElement, unknown, null, undefined>, height: number, xScale: d3.ScaleLinear<number, number, never>) => { - console.log('x axis creator being called'); g.attr('class', 'x-axis').attr('transform', `translate(0,${height})`).call(d3.axisBottom(xScale).tickSize(15)); }; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 9546a6d38..8ac9d6804 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,6 +1,7 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; -import { AclPrivate, Doc, Opt } from '../../../fields/Doc'; +import { Doc, Opt } from '../../../fields/Doc'; +import { AclPrivate } from '../../../fields/DocSymbols'; import { ScriptField } from '../../../fields/ScriptField'; import { Cast, StrCast } from '../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; @@ -17,7 +18,6 @@ import { SearchBox } from '../search/SearchBox'; import { DashWebRTCVideo } from '../webcam/DashWebRTCVideo'; import { YoutubeBox } from './../../apis/youtube/YoutubeBox'; import { AudioBox } from './AudioBox'; -import { FontIconBox } from './button/FontIconBox'; import { ColorBox } from './ColorBox'; import { ComparisonBox } from './ComparisonBox'; import { DataVizBox } from './DataVizBox/DataVizBox'; @@ -25,9 +25,11 @@ import { DocumentViewProps } from './DocumentView'; import './DocumentView.scss'; import { EquationBox } from './EquationBox'; import { FieldView, FieldViewProps } from './FieldView'; +import { FontIconBox } from './FontIconBox/FontIconBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { FunctionPlotBox } from './FunctionPlotBox'; import { ImageBox } from './ImageBox'; +import { ImportElementBox } from './importBox/ImportElementBox'; import { KeyValueBox } from './KeyValueBox'; import { LabelBox } from './LabelBox'; import { LinkAnchorBox } from './LinkAnchorBox'; @@ -35,6 +37,7 @@ import { LinkBox } from './LinkBox'; import { LoadingBox } from './LoadingBox'; import { MapBox } from './MapBox/MapBox'; import { PDFBox } from './PDFBox'; +import { PhysicsSimulationBox } from './PhysicsBox/PhysicsSimulationBox'; import { RecordingBox } from './RecordingBox'; import { ScreenshotBox } from './ScreenshotBox'; import { ScriptingBox } from './ScriptingBox'; @@ -266,7 +269,9 @@ export class DocumentContentsView extends React.Component< HTMLtag, ComparisonBox, LoadingBox, + PhysicsSimulationBox, SchemaRowBox, + ImportElementBox, MapPushpinBox, }} bindings={bindings} diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index d5ca30957..7723a088d 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -9,7 +9,7 @@ import { DocUtils } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Hypothesis } from '../../util/HypothesisUtils'; import { LinkManager } from '../../util/LinkManager'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import './DocumentLinksButton.scss'; import { DocumentView } from './DocumentView'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; @@ -71,14 +71,15 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp e, this.onLinkButtonMoved, emptyFunction, - action((e, doubleTap) => doubleTap && DocumentView.showBackLinks(this.props.View.rootDoc)), + action((e, doubleTap) => { + doubleTap && DocumentView.showBackLinks(this.props.View.rootDoc); + }), undefined, undefined, action(() => (DocumentLinksButton.LinkEditorDocView = this.props.View)) ); }; - @undoBatch onLinkButtonDown = (e: React.PointerEvent): void => { setupMoveUpEvents( this, @@ -123,17 +124,14 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp e, returnFalse, emptyFunction, - undoBatch( - action(e => { - DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View); - }) - ) + action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)) ); }; - public static finishLinkClick = undoBatch( - action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) => { - if (startLink === endLink) { + @undoBatch + public static finishLinkClick(screenX: number, screenY: number, startLink: Doc | undefined, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) { + runInAction(() => { + if (startLink === endLink || !startLink) { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; DocumentLinksButton.AnnotationId = undefined; @@ -185,8 +183,8 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp ); } } - }) - ); + }); + } @action clearLinks() { DocumentLinksButton.StartLink = undefined; @@ -195,7 +193,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp @computed get filteredLinks() { const results = [] as Doc[]; - const filters = this.props.View.props.docFilters(); + const filters = this.props.View.props.childFilters(); Array.from(new Set<Doc>(this.props.View.allLinks)).forEach(link => { if (DocUtils.FilterDocs([link], filters, []).length || DocUtils.FilterDocs([link.link_anchor_2 as Doc], filters, []).length || DocUtils.FilterDocs([link.link_anchor_1 as Doc], filters, []).length) { results.push(link); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 1265651ad..b25540dd3 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -24,7 +24,7 @@ width: 100%; height: 100%; border-radius: inherit; - transition: outline 0.3s linear; + // transition: outline 0.3s linear; // background: $white; //overflow: hidden; transform-origin: center; @@ -115,7 +115,10 @@ width: 100%; height: 100%; transition: inherit; - + display: flex; + justify-content: center; + align-items: center; + .sharingIndicator { height: 30px; width: 30px; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index d8494e58e..bb9f45bdd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -3,10 +3,12 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; -import { AclPrivate, AnimationSym, DataSym, Doc, DocListCast, Field, Opt, StrListCast, WidthSym } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc'; +import { AclPrivate, Animation, DocData, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; +import { RefField } from '../../../fields/RefField'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; @@ -35,8 +37,10 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from '../DocComponent'; import { EditableView } from '../EditableView'; import { GestureOverlay } from '../GestureOverlay'; +import { InkingStroke } from '../InkingStroke'; import { LightboxView } from '../LightboxView'; import { StyleProp } from '../StyleProvider'; +import { UndoStack } from '../UndoStack'; import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; @@ -47,8 +51,7 @@ import { LinkAnchorBox } from './LinkAnchorBox'; import { PresEffect, PresEffectDirection } from './trails'; import { PinProps, PresBox } from './trails/PresBox'; import React = require('react'); -import { InkingStroke } from '../InkingStroke'; -import { RefField } from '../../../fields/RefField'; +import { SettingsManager } from '../../util/SettingsManager'; const { Howl } = require('howler'); interface Window { @@ -66,9 +69,7 @@ export enum OpenWhere { addLeft = 'add:left', addRight = 'add:right', addBottom = 'add:bottom', - dashboard = 'dashboard', close = 'close', - fullScreen = 'fullScreen', toggle = 'toggle', toggleRight = 'toggle:right', replace = 'replace', @@ -115,6 +116,7 @@ export interface DocComponentView { brushView?: (view: { width: number; height: number; panX: number; panY: number }) => void; 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) reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height. However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling. shrinkWrap?: () => void; // requests a document to display all of its contents with no white space. currently only implemented (needed?) for freeform views select?: (ctrlKey: boolean, shiftKey: boolean) => void; @@ -157,16 +159,18 @@ export interface DocumentViewSharedProps { CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; PanelWidth: () => number; PanelHeight: () => number; + shouldNotScale?: () => boolean; docViewPath: () => DocumentView[]; 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; layout_fitWidth?: (doc: Doc) => boolean | undefined; - docFilters: () => string[]; - docRangeFilters: () => string[]; + childFilters: () => string[]; + childFiltersByRanges: () => string[]; searchFilterDocs: () => Doc[]; layout_showTitle?: () => string; whenChildContentsActiveChanged: (isActive: boolean) => void; @@ -179,7 +183,7 @@ export interface DocumentViewSharedProps { pinToPres: (document: Doc, pinProps: PinProps) => void; ScreenToLocalTransform: () => Transform; bringToFront: (doc: Doc, sendToBack?: boolean) => void; - canEmbedOnDrag?: boolean; + dragAction?: dropActionType; treeViewDoc?: Doc; xPadding?: number; yPadding?: number; @@ -189,9 +193,8 @@ export interface DocumentViewSharedProps { hideCaptions?: boolean; ignoreAutoHeight?: boolean; forceAutoHeight?: boolean; - disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. + disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. onClickScriptDisable?: 'never' | 'always'; // undefined = only when selected - enableDragWhenActive?: boolean; waitForDoubleClickToClick?: () => 'never' | 'always' | undefined; defaultDoubleClick?: () => 'default' | 'ignore' | undefined; pointerEvents?: () => Opt<string>; @@ -276,7 +279,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps return Doc.LayoutFieldKey(this.layoutDoc); } @computed get layout_showTitle() { - return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.layout_showTitle) as Opt<string>; + return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt<string>; } @computed get NativeDimScaling() { return this.props.NativeDimScaling?.() || 1; @@ -306,7 +309,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.HeaderMargin) || 0; } @computed get layout_showCaption() { - return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.showCaption) || 0; + return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.ShowCaption) || 0; } @computed get titleHeight() { return this.props?.styleProvider?.(this.layoutDoc, this.props, StyleProp.TitleHeight) || 0; @@ -329,8 +332,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps return ( DocumentView.LongPress || onScriptDisable === 'always' || - (onScriptDisable !== 'never' && (this.rootSelected() || this.props.isSelected())) || - this._componentView?.isAnyChildContentActive?.() + (onScriptDisable !== 'never' && (this.rootSelected() || this._componentView?.isAnyChildContentActive?.())) ); } @computed get onClickHandler() { @@ -352,11 +354,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps componentDidMount() { this.setupHandlers(); } - + preDropFunc = (e: Event, de: DragManager.DropEvent) => { + const dropAction = this.layoutDoc.dropAction as dropActionType; + if (de.complete.docDragData && this.isContentActive() && !this.props.treeViewDoc) { + dropAction && (de.complete.docDragData.dropAction = dropAction); + e.stopPropagation(); + } + }; setupHandlers() { this.cleanupHandlers(false); if (this._mainCont.current) { - this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, this.drop.bind(this), this.props.Document); + 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)); } @@ -384,7 +392,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps dragData.treeViewDoc = this.props.treeViewDoc; dragData.removeDocument = this.props.removeDocument; dragData.moveDocument = this.props.moveDocument; - dragData.canEmbed = this.props.canEmbedOnDrag; + dragData.canEmbed = this.rootDoc.dragAction ?? this.props.dragAction ? true : false; const ffview = this.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; ffview && runInAction(() => (ffview.ChildDrag = this.props.DocumentView())); DragManager.StartDocumentDrag( @@ -402,7 +410,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps defaultRestoreTargetView = (docView: DocumentView, anchor: Doc, focusSpeed: number, options: DocFocusOptions) => { const targetMatch = Doc.AreProtosEqual(anchor, this.rootDoc) || // anchor is this document, so anchor's properties apply to this document - (DocCast(anchor)?.unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.rootDoc)) // the anchor is an unrendered annotation on this document, so anchor properties apply to this document + (DocCast(anchor)?.layout_unrendered && Doc.AreProtosEqual(DocCast(anchor.annotationOn), this.rootDoc)) // the anchor is an layout_unrendered annotation on this document, so anchor properties apply to this document ? true : false; return targetMatch && PresBox.restoreTargetDocView(docView, anchor, focusSpeed) ? focusSpeed : undefined; @@ -456,27 +464,26 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps // instead of in the global lightbox const oldFunc = DocumentViewInternal.addDocTabFunc; DocumentViewInternal.addDocTabFunc = this.props.addDocTab; - const res = - this.onClickHandler?.script.run( - { - this: this.layoutDoc, - self: this.rootDoc, - _readOnly_: false, - scriptContext: this.props.scriptContext, - documentView: this.props.DocumentView(), - clientX, - clientY, - shiftKey, - altKey, - metaKey, - }, - console.log - ).result?.select === true - ? this.props.select(false) - : ''; + this.onClickHandler?.script.run( + { + this: this.layoutDoc, + self: this.rootDoc, + _readOnly_: false, + scriptContext: this.props.scriptContext, + documentView: this.props.DocumentView(), + clientX, + clientY, + shiftKey, + altKey, + metaKey, + }, + console.log + ).result?.select === true + ? this.props.select(false) + : ''; DocumentViewInternal.addDocTabFunc = oldFunc; }; - clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); + clickFunc = () => UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title); } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { @@ -484,13 +491,18 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } preventDefault = false; } - - this._singleClickFunc = clickFunc ?? (() => this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey)); + const sendToBack = e.altKey; + this._singleClickFunc = + clickFunc ?? + (() => + sendToBack + ? this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.bringToFront(this.rootDoc, true) + : this._componentView?.select?.(e.ctrlKey || e.metaKey, e.shiftKey) ?? this.props.select(e.ctrlKey || e.metaKey || e.shiftKey)); const waitFordblclick = this.props.waitForDoubleClickToClick?.() ?? this.Document.waitForDoubleClickToClick; if ((clickFunc && waitFordblclick !== 'never') || waitFordblclick === 'always') { this._doubleClickTimeout && clearTimeout(this._doubleClickTimeout); this._doubleClickTimeout = setTimeout(this._singleClickFunc, 300); - } else { + } else if (!DocumentView.LongPress) { this._singleClickFunc(); this._singleClickFunc = undefined; } @@ -502,7 +514,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @action onPointerDown = (e: React.PointerEvent): void => { - this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000); + this._longPressSelector = setTimeout(() => { + if (DocumentView.LongPress) { + if (this.rootDoc.undoIgnoreFields) { + runInAction(() => (UndoStack.HideInline = !UndoStack.HideInline)); + } else { + this.props.select(false); + } + } + }, 1000); if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView(); this._downX = e.clientX; @@ -513,19 +533,19 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps // if this is part of a template, let the event go up to the template root unless right/ctrl clicking if ( // prettier-ignore - this.props.isDocumentActive?.() && + (this.props.isDocumentActive?.() || this.props.isContentActive?.()) && !this.props.onBrowseClick?.() && !this.Document.ignoreClick && e.button === 0 && this.pointerEvents !== 'none' && - !DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc) + !Doc.IsInMyOverlay(this.layoutDoc) ) { e.stopPropagation(); // don't preventDefault anymore. Goldenlayout, PDF text selection and RTF text selection all need it to go though - //if (this.props.isSelected(true) && this.rootDoc.type !== DocumentType.PDF && this.layoutDoc._viewType !== CollectionViewType.Docking) e.preventDefault(); + //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() || this.props.enableDragWhenActive || this.rootDoc.enableDragWhenActive)) { + if (!this.layoutDoc._lockedPosition && (!this.isContentActive() || BoolCast(this.rootDoc._dragWhenActive))) { document.addEventListener('pointermove', this.onPointerMove); } } @@ -539,7 +559,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (!Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, Date.now())) { this.cleanupPointerEvents(); - this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dropAction || this.props.dropAction || undefined) as dropActionType)); + this._longPressSelector && clearTimeout(this._longPressSelector); + this.startDragging(this._downX, this._downY, ((e.ctrlKey || e.altKey) && 'embed') || ((this.Document.dragAction || this.props.dragAction || undefined) as dropActionType)); } }; @@ -557,8 +578,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (this.onPointerUpHandler?.script) { this.onPointerUpHandler.script.run({ self: this.rootDoc, this: this.layoutDoc }, console.log); } else if (e.button === 0 && Utils.isClick(e.clientX, e.clientY, this._downX, this._downY, this._downTime)) { - this._doubleTap = Date.now() - this._lastTap < Utils.CLICK_TIME; - if (!this.props.isSelected(true)) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected + this._doubleTap = (this.onDoubleClickHandler?.script || this.rootDoc.defaultDoubleClick !== 'ignore') && Date.now() - this._lastTap < Utils.CLICK_TIME; + if (!this.isContentActive()) this._lastTap = Date.now(); // don't want to process the start of a double tap if the doucment is selected } if (DocumentView.LongPress) e.preventDefault(); }; @@ -598,16 +619,17 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return; + if (this.props.dontRegisterView || this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return false; if (this.props.Document === Doc.ActiveDashboard) { + e.stopPropagation(); + e.preventDefault(); alert((e.target as any)?.closest?.('*.lm_content') ? "You can't perform this move most likely because you don't have permission to modify the destination." : 'Linking to document tabs not yet supported. Drop link on document content.'); - return; + return true; } const linkdrag = de.complete.annoDragData ?? de.complete.linkDragData; if (linkdrag) { linkdrag.linkSourceDoc = linkdrag.linkSourceGetAnchor(); if (linkdrag.linkSourceDoc) { - e.stopPropagation(); if (de.complete.annoDragData && !de.complete.annoDragData.dropDocument) { de.complete.annoDragData.dropDocument = de.complete.annoDragData.dropDocCreator(undefined); } @@ -615,8 +637,11 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const dropDoc = de.complete.annoDragData?.dropDocument ?? this._componentView?.getAnchor?.(true) ?? this.rootDoc; de.complete.linkDocument = DocUtils.MakeLink(linkdrag.linkSourceDoc, dropDoc, {}, undefined, [de.x, de.y - 50]); } + e.stopPropagation(); + return true; } } + return false; }; @undoBatch @@ -654,7 +679,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @action onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => { - if (e && this.rootDoc._hideContextMenu && Doc.noviceMode) { + if (e && this.rootDoc._layout_hideContextMenu && Doc.noviceMode) { e.preventDefault(); e.stopPropagation(); //!this.props.isSelected(true) && SelectionManager.SelectView(this.props.DocumentView(), false); @@ -702,10 +727,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps const templateDoc = Cast(this.props.Document[StrCast(this.props.Document.layout_fieldKey)], Doc, null); const appearance = cm.findByDescription('UI Controls...'); 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' }); + } !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' }); - if (!Doc.IsSystem(this.rootDoc) && this.rootDoc.type !== DocumentType.PRES && ![CollectionViewType.Docking, CollectionViewType.Tree].includes(this.rootDoc._viewType as any)) { + 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...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; @@ -725,18 +754,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onClicks.push({ description: 'Enter Portal', event: this.makeIntoPortal, icon: 'window-restore' }); !Doc.noviceMode && onClicks.push({ description: 'Toggle Detail', event: this.setToggleDetail, icon: 'concierge-bell' }); - if (!this.Document.annotationOn) { - const options = cm.findByDescription('Options...'); - const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; - !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); - - onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); - onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); - !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); - } else if (LinkManager.Links(this.Document).length) { - onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' }); - onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); - !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' }); + if (!this.props.treeViewDoc) { + if (!this.Document.annotationOn) { + const options = cm.findByDescription('Options...'); + const optionItems: ContextMenuProps[] = options && 'subitems' in options ? options.subitems : []; + !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'compass' }); + + onClicks.push({ description: this.onClickHandler ? 'Remove Click Behavior' : 'Follow Link', event: () => this.toggleFollowLink(false, false), icon: 'link' }); + onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.props.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); + !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); + } else if (LinkManager.Links(this.Document).length) { + onClicks.push({ description: 'Select on Click', event: () => this.noOnClick(), icon: 'link' }); + onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); + !existingOnClick && cm.addItem({ description: 'OnClick...', subitems: onClicks, icon: 'mouse-pointer' }); + } } } @@ -767,23 +798,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps !more && moreItems.length && cm.addItem({ description: 'More...', subitems: moreItems, icon: 'compass' }); } const constantItems: ContextMenuProps[] = []; - constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); if (!Doc.IsSystem(this.rootDoc)) { - constantItems.push({ description: 'Export as Zip file', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); - constantItems.push({ description: 'Import Zipped file', icon: 'upload', event: ({ x, y }) => this.importDocument() }); - (this.rootDoc._viewType !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); + constantItems.push({ description: 'Zip Export', icon: 'download', event: async () => Doc.Zip(this.props.Document) }); + (this.rootDoc._type_collection !== CollectionViewType.Docking || !Doc.noviceMode) && constantItems.push({ description: 'Share', event: () => SharingManager.Instance.open(this.props.DocumentView()), icon: 'users' }); if (this.props.removeDocument && Doc.ActiveDashboard !== this.props.Document) { // need option to gray out menu items ... preferably with a '?' that explains why they're grayed out (eg., no permissions) constantItems.push({ description: 'Close', event: this.deleteClicked, icon: 'times' }); } } - cm.addItem({ description: 'General...', noexpand: !Doc.IsSystem(this.rootDoc), subitems: constantItems, icon: 'question' }); + constantItems.push({ description: 'Show Metadata', event: () => this.props.addDocTab(this.props.Document, (OpenWhere.addRight.toString() + 'KeyValue') as OpenWhere), icon: 'layer-group' }); + cm.addItem({ description: 'General...', noexpand: false, subitems: constantItems, icon: 'question' }); const help = cm.findByDescription('Help...'); const helpItems: ContextMenuProps[] = help && 'subitems' in help ? help.subitems : []; !Doc.noviceMode && helpItems.push({ description: 'Text Shortcuts Ctrl+/', event: () => this.props.addDocTab(Docs.Create.PdfDocument('/assets/cheat-sheet.pdf', { _width: 300, _height: 300 }), OpenWhere.addRight), icon: 'keyboard' }); !Doc.noviceMode && helpItems.push({ description: 'Print Document in Console', event: () => console.log(this.props.Document), icon: 'hand-point-right' }); - !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DataSym]), icon: 'hand-point-right' }); + !Doc.noviceMode && helpItems.push({ description: 'Print DataDoc in Console', event: () => console.log(this.props.Document[DocData]), icon: 'hand-point-right' }); let documentationDescription: string | undefined = undefined; let documentationLink: string | undefined = undefined; @@ -818,7 +848,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps break; } // Add link to help documentation - if (documentationDescription && documentationLink) { + if (!this.props.treeViewDoc && documentationDescription && documentationLink) { helpItems.push({ description: documentationDescription, event: () => window.open(documentationLink, '_blank'), @@ -832,44 +862,50 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); }; - rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; + @computed get _rootSelected() { + return this.props.isSelected(false) || (this.props.Document.rootDocument && this.props.rootSelected?.(false)) || false; + } + rootSelected = (outsideReaction?: boolean) => this._rootSelected; panelHeight = () => this.props.PanelHeight() - this.headerMargin; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); onClickFunc: any = () => (this.disableClickScriptFunc ? undefined : this.onClickHandler); setHeight = (height: number) => (this.layoutDoc._height = height); setContentView = action((view: { getAnchor?: (addAsAnnotation: boolean) => Doc; forward?: () => boolean; back?: () => boolean }) => (this._componentView = view)); - isContentActive = (outsideReaction?: boolean): boolean | undefined => { - return this.props.isContentActive() === false + @computed get _isContentActive() { + // true - if the document has been activated directly or indirectly (by having its children selected) + // false - if its pointer events are explicitly turned off or if it's container tells it that it's inactive + // undefined - it is not active, but it should be responsive to actions that might active it or its contents (eg clicking) + return this.props.isContentActive() === false || this.props.pointerEvents?.() === 'none' ? false - : Doc.ActiveTool !== InkTool.None || - SnappingManager.GetIsDragging() || - this.rootSelected() || - this.rootDoc.forceActive || - this.props.isSelected(outsideReaction) || - this._componentView?.isAnyChildContentActive?.() || - this.props.isContentActive() + : Doc.ActiveTool !== InkTool.None || SnappingManager.GetIsDragging() || this.rootSelected() || this.rootDoc.forceActive || this._componentView?.isAnyChildContentActive?.() || this.props.isContentActive() ? true : undefined; - }; + } + isContentActive = (): boolean | undefined => this._isContentActive; @observable _retryThumb = 1; - thumbShown = () => { - const childHighlighted = () => - Array.from(Doc.highlightedDocs.keys()) - .concat(Array.from(Doc.brushManager.BrushedDoc.keys())) - .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc)); + @computed get _thumbShown() { + const childHighlighted = () => false; + // Array.from(Doc.highlightedDocs.keys()) + // .concat(Array.from(Doc.brushManager.BrushedDoc.keys())) + // .some(doc => Doc.AreProtosEqual(DocCast(doc.annotationOn), this.rootDoc)); const childOverlayed = () => Array.from(DocumentManager._overlayViews).some(view => Doc.AreProtosEqual(view.rootDoc, this.rootDoc)); return !this.props.LayoutTemplateString && - !this.props.isSelected() && + !this.isContentActive() && LightboxView.LightboxDoc !== this.rootDoc && this.thumb && !Doc.AreProtosEqual(DocumentLinksButton.StartLink, this.rootDoc) && - ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._viewType === CollectionViewType.Docking) && - !this._componentView?.isAnyChildContentActive?.() + ((!childHighlighted() && !childOverlayed() && !Doc.isBrushedHighlightedDegree(this.rootDoc)) || this.rootDoc._type_collection === CollectionViewType.Docking) ? true : false; - }; - docFilters = () => [...this.props.docFilters(), ...StrListCast(this.layoutDoc.docFilters)]; - contentPointerEvents = () => (!this.disableClickScriptFunc && this.onClickHandler ? 'none' : this.pointerEvents); + } + thumbShown = () => this._thumbShown; + childFilters = () => [...this.props.childFilters(), ...StrListCast(this.layoutDoc.childFilters)]; + + /// disable pointer events on content when there's an enabled onClick script (but not the browse script) and the contents aren't forced active, or if contents are marked inactive + @computed get _contentPointerEvents() { + return (!this.disableClickScriptFunc && this.onClickHandler && !this.props.onBrowseClick?.() && this.isContentActive() !== true) || this.isContentActive() === false ? 'none' : this.pointerEvents; + } + contentPointerEvents = () => this._contentPointerEvents; @computed get contents() { TraceMobx(); const isInk = StrCast(this.layoutDoc.layout).includes(InkingStroke.name) && !this.props.LayoutTemplateString; @@ -877,7 +913,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps <div className="documentView-contentsView" style={{ - pointerEvents: (isInk ? 'none' : this.pointerEvents) ?? 'all', + pointerEvents: (isInk ? 'none' : this.contentPointerEvents()) ?? 'all', height: this.headerMargin ? `calc(100% - ${this.headerMargin}px)` : undefined, }}> {!this._retryThumb || !this.thumbShown() ? null : ( @@ -900,7 +936,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps docViewPath={this.props.viewPath} thumbShown={this.thumbShown} setContentView={this.setContentView} - docFilters={this.docFilters} + childFilters={this.childFilters} NativeDimScaling={this.props.NativeDimScaling} PanelHeight={this.panelHeight} setHeight={!this.props.suppressSetHeight ? this.setHeight : undefined} @@ -912,7 +948,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps setTitleFocus={this.setTitleFocus} layout_fieldKey={this.finalLayoutKey} /> - {this.layoutDoc.hideAllLinks ? null : this.allLinkEndpoints} + {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints} </div> ); } @@ -922,14 +958,14 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => { // prettier-ignore switch (property.split(':')[0]) { - case StyleProp.layout_showTitle: return ''; + case StyleProp.ShowTitle: return ''; case StyleProp.PointerEvents: return 'none'; case StyleProp.Highlighting: return undefined; } return this.props.styleProvider?.(doc, props, property); }; // We need to use allrelatedLinks to get not just links to the document as a whole, but links to - // anchors that are not rendered as DocumentViews (marked as 'unrendered' with their 'annotationOn' set to this document). e.g., + // anchors that are not rendered as DocumentViews (marked as 'layout_unrendered' with their 'annotationOn' set to this document). e.g., // - PDF text regions are rendered as an Annotations without generating a DocumentView, ' // - RTF selections are rendered via Prosemirror and have a mark which contains the Document ID for the annotation link // - and links to PDF/Web docs at a certain scroll location never create an explicit view. @@ -940,20 +976,20 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps link => Doc.AreProtosEqual(link.link_anchor_1 as Doc, this.rootDoc) || Doc.AreProtosEqual(link.link_anchor_2 as Doc, this.rootDoc) || - ((link.link_anchor_1 as Doc)?.unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.rootDoc)) || - ((link.link_anchor_2 as Doc)?.unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.rootDoc)) + ((link.link_anchor_1 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_1 as Doc)?.annotationOn as Doc, this.rootDoc)) || + ((link.link_anchor_2 as Doc)?.layout_unrendered && Doc.AreProtosEqual((link.link_anchor_2 as Doc)?.annotationOn as Doc, this.rootDoc)) ); } @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); } - hideLink = computedFn((link: Doc) => () => (link.layout_linkDisplay = false)); + hideLink = computedFn((link: Doc) => () => (link.link_displayLine = false)); @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links TraceMobx(); - if (this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.unrendered) return null; - const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters?.() ?? [], []).filter(d => d.layout_linkDisplay); + if (this.props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this.props.dontRegisterView || this.layoutDoc.layout_unrendered) return null; + const filtered = DocUtils.FilterDocs(this.directLinks, this.props.childFilters?.() ?? [], []).filter(d => d.link_displayLine); return filtered.map(link => ( <div className="documentView-anchorCont" key={link[Id]}> <DocumentView @@ -1001,7 +1037,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps gumStream = stream; recorder = new MediaRecorder(stream); recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(e.data); + const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); if (!(result instanceof Error)) { const audioField = new AudioField(result.accessPaths.agnostic.client); const audioAnnos = Cast(dataDoc[field + '-audioAnnotations'], listSpec(AudioField), null); @@ -1046,13 +1082,13 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps @computed get innards() { TraceMobx(); const ffscale = () => this.props.DocumentView().props.CollectionFreeFormDocumentView?.().props.ScreenToLocalTransform().Scale || 1; - const layout_showTitle = this.layout_showTitle?.split(':')[0]; - const layout_showTitleHover = this.layout_showTitle?.includes(':hover'); + const showTitle = this.layout_showTitle?.split(':')[0]; + const showTitleHover = this.layout_showTitle?.includes(':hover'); const captionView = !this.layout_showCaption ? null : ( <div className="documentView-captionWrapper" style={{ - pointerEvents: this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined, + pointerEvents: this.rootDoc.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined, minWidth: 50 * ffscale(), maxHeight: `max(100%, ${20 * ffscale()}px)`, }}> @@ -1071,27 +1107,27 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps /> </div> ); - const targetDoc = layout_showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc; + const targetDoc = showTitle?.startsWith('_') ? this.layoutDoc : this.rootDoc; const background = StrCast( - SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.sharingDoc.userColor, - Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().userColor) : 'rgba(0,0,0,0.4)' + SharingManager.Instance.users.find(u => u.user.email === this.dataDoc.author)?.sharingDoc.headingColor, + Doc.UserDoc().layout_showTitle && [DocumentType.RTF, DocumentType.COL].includes(this.rootDoc.type as any) ? StrCast(Doc.SharingDoc().headingColor) : SettingsManager.Instance.userVariantColor ); - const layout_sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); - const titleView = !layout_showTitle ? null : ( + const sidebarWidthPercent = +StrCast(this.layoutDoc.layout_sidebarWidthPercent).replace('%', ''); + const titleView = !showTitle ? null : ( <div - className={`documentView-titleWrapper${layout_showTitleHover ? '-hover' : ''}`} + className={`documentView-titleWrapper${showTitleHover ? '-hover' : ''}`} key="title" style={{ position: this.headerMargin ? 'relative' : 'absolute', height: this.titleHeight, - width: !this.headerMargin ? `calc(${layout_sidebarWidthPercent || 100}% - 18px)` : (layout_sidebarWidthPercent || 100) + '%', // leave room for annotation button + width: !this.headerMargin ? `calc(${sidebarWidthPercent || 100}% - 18px)` : (sidebarWidthPercent || 100) + '%', // leave room for annotation button color: lightOrDark(background), background, pointerEvents: (!this.disableClickScriptFunc && this.onClickHandler) || this.Document.ignoreClick ? 'none' : this.isContentActive() || this.props.isDocumentActive?.() ? 'all' : undefined, }}> <EditableView ref={this._titleRef} - contents={layout_showTitle + contents={showTitle .split(';') .map(field => field.trim()) .map(field => targetDoc[field]?.toString()) @@ -1100,27 +1136,27 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps fontSize={10} GetValue={() => { this.props.select(false); - return layout_showTitle.split(';').length === 1 ? layout_showTitle + '=' + Field.toString(targetDoc[layout_showTitle.split(';')[0]] as any as Field) : '#' + layout_showTitle; + return showTitle.split(';').length === 1 ? showTitle + '=' + Field.toString(targetDoc[showTitle.split(';')[0]] as any as Field) : '#' + showTitle; }} SetValue={undoBatch((input: string) => { if (input?.startsWith('#')) { if (this.props.layout_showTitle) { this.rootDoc._layout_showTitle = input?.substring(1) ? input.substring(1) : undefined; } else { - Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'creationDate'; + Doc.UserDoc().layout_showTitle = input?.substring(1) ? input.substring(1) : 'author_date'; } } else { - var value = input.replace(new RegExp(layout_showTitle + '='), '') as string | number; - if (layout_showTitle !== 'title' && Number(value).toString() === value) value = Number(value); - if (layout_showTitle.includes('Date') || layout_showTitle === 'author') return true; - Doc.SetInPlace(targetDoc, layout_showTitle, value, true); + var value = input.replace(new RegExp(showTitle + '='), '') as string | number; + if (showTitle !== 'title' && Number(value).toString() === value) value = Number(value); + if (showTitle.includes('Date') || showTitle === 'author') return true; + Doc.SetInPlace(targetDoc, showTitle, value, true); } return true; })} /> </div> ); - return this.props.hideTitle || (!layout_showTitle && !this.layout_showCaption) ? ( + return this.props.hideTitle || (!showTitle && !this.layout_showCaption) ? ( this.contents ) : ( <div className="documentView-styleWrapper"> @@ -1135,7 +1171,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps renderDoc = (style: object) => { TraceMobx(); - return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DataSym]) === AclPrivate + return !DocCast(this.Document) || GetEffectiveAcl(this.Document[DocData]) === AclPrivate ? null : this.docContents ?? ( <div @@ -1147,8 +1183,8 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps opacity: this.opacity, cursor: Doc.ActiveTool === InkTool.None ? 'grab' : 'crosshair', color: StrCast(this.layoutDoc.color, 'inherit'), - fontFamily: StrCast(this.Document._fontFamily, 'inherit'), - fontSize: Cast(this.Document._fontSize, 'string', null), + fontFamily: StrCast(this.Document._text_fontFamily, 'inherit'), + fontSize: Cast(this.Document._text_fontSize, 'string', null), transform: this._animateScalingTo ? `scale(${this._animateScalingTo})` : undefined, transition: !this._animateScalingTo ? StrCast(this.Document.dataTransition) : `transform ${this.animateScaleTime / 1000}s ease-${this._animateScalingTo < 1 ? 'in' : 'out'}`, }}> @@ -1187,16 +1223,22 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps case PresEffect.Lightspeed: return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>; } } + @computed get highlighting() { + return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting); + } + @computed get borderPath() { + return this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath); + } render() { TraceMobx(); - const highlighting = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.Highlighting); - const borderPath = this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BorderPath); + const highlighting = this.highlighting; + const borderPath = this.borderPath; const boxShadow = this.props.treeViewDoc || !highlighting ? this.boxShadow : highlighting && this.borderRounding && highlighting.highlightStyle !== 'dashed' ? `0 0 0 ${highlighting.highlightIndex}px ${highlighting.highlightColor}` - : this.boxShadow || (this.props.Document.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined); + : this.boxShadow || (this.rootDoc.isTemplateForField ? 'black 0.2vw 0.2vw 0.8vw' : undefined); const renderDoc = this.renderDoc({ borderRadius: this.borderRounding, outline: highlighting && !this.borderRounding && !highlighting.highlightStroke ? `${highlighting.highlightColor} ${highlighting.highlightStyle} ${highlighting.highlightIndex}px` : 'solid 0px', @@ -1212,15 +1254,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick} - onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)} - onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.props.Document)} - onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.props.Document)} + onPointerEnter={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)} + onPointerOver={e => (!SnappingManager.GetIsDragging() || DragManager.CanEmbed) && Doc.BrushDoc(this.rootDoc)} + onPointerLeave={e => !isParentOf(this.ContentDiv, document.elementFromPoint(e.nativeEvent.x, e.nativeEvent.y)) && Doc.UnBrushDoc(this.rootDoc)} style={{ borderRadius: this.borderRounding, pointerEvents: this.pointerEvents === 'visiblePainted' ? 'none' : this.pointerEvents, }}> <> - {DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[AnimationSym], this.rootDoc)} + {DocumentViewInternal.AnimationEffect(renderDoc, this.rootDoc[Animation], this.rootDoc)} {borderPath?.jsx} </> </div> @@ -1254,8 +1296,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { public setAnimEffect = (presEffect: Doc, timeInMs: number, afterTrans?: () => void) => { this.AnimEffectTimer && clearTimeout(this.AnimEffectTimer); - this.rootDoc[AnimationSym] = presEffect; - this.AnimEffectTimer = setTimeout(() => (this.rootDoc[AnimationSym] = undefined), timeInMs); + this.rootDoc[Animation] = presEffect; + this.AnimEffectTimer = setTimeout(() => (this.rootDoc[Animation] = undefined), timeInMs); }; public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => { this.rootDoc._viewTransition = `${transProp} ${timeInMs}ms`; @@ -1284,13 +1326,13 @@ export class DocumentView extends React.Component<DocumentViewProps> { } // shows a stacking view collection (by default, but the user can change) of all documents linked to the source - public static showBackLinks(linkSource: Doc) { - const docId = Doc.CurrentUserEmail + Doc.GetProto(linkSource)[Id] + '-pivotish'; + public static showBackLinks(linkAnchor: Doc) { + const docId = Doc.CurrentUserEmail + Doc.GetProto(linkAnchor)[Id] + '-pivotish'; // prettier-ignore - DocServer.GetRefField(docId).then(docx => docx instanceof Doc && + DocServer.GetRefField(docId).then(docx => LightboxView.SetLightboxDoc( - docx || // reuse existing pivot view of documents, or else create a new collection - Docs.Create.StackingDocument([], { title: linkSource.title + '-pivot', _width: 500, _height: 500, linkSource, updateContentsScript: ScriptField.MakeScript('updateLinkCollection(self)') }, docId) + (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) ) ); } @@ -1326,7 +1368,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.rootDoc['link_anchor_2']) : this.props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.rootDoc['link_anchor_1']) : undefined; } @computed get hideLinkButton() { - return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.isSelected() ? ':selected' : '')); + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.isSelected() ? ':selected' : '')); } @computed get linkCountView() { const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton; @@ -1345,7 +1387,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { return this.docView?._componentView?.reverseNativeScaling?.() ? 0 : returnVal(this.props.NativeHeight?.(), Doc.NativeHeight(this.layoutDoc, this.props.DataDoc, !this.layout_fitWidth)); } @computed get shouldNotScale() { - return (this.layout_fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._viewType as any); + return this.props.shouldNotScale?.() || (this.layout_fitWidth && !this.nativeWidth) || [CollectionViewType.Docking].includes(this.Document._type_collection as any); } @computed get effectiveNativeWidth() { return this.shouldNotScale ? 0 : this.nativeWidth || NumCast(this.layoutDoc.width); @@ -1478,8 +1520,8 @@ export class DocumentView extends React.Component<DocumentViewProps> { // increase max auto height if document has been resized to be greater than current max () => NumCast(this.layoutDoc._height), action(height => { - const docMax = NumCast(this.layoutDoc.docMaxAutoHeight); - if (docMax && docMax < height) this.layoutDoc.docMaxAutoHeight = height; + const docMax = NumCast(this.layoutDoc.layout_maxAutoHeight); + if (docMax && docMax < height) this.layoutDoc.layout_maxAutoHeight = height; }) ); !BoolCast(this.props.Document.dontRegisterView, this.props.dontRegisterView) && DocumentManager.Instance.AddView(this); @@ -1565,22 +1607,23 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff else dv.switchViews(true, detailLayoutKeySuffix, undefined, true); }); -ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc) { - const linkSource = Cast(linkCollection.linkSource, Doc, null); +ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) { const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data); - let wid = linkSource[WidthSym](); + let wid = linkSource[Width](); + let embedding: Doc | undefined; const links = LinkManager.Links(linkSource); links.forEach(link => { const other = LinkManager.getOppositeAnchor(link, linkSource); - const otherdoc = !other ? undefined : other.annotationOn ? Cast(other.annotationOn, Doc, null) : other; + const otherdoc = DocCast(other?.annotationOn ?? other); if (otherdoc && !collectedLinks?.some(d => Doc.AreProtosEqual(d, otherdoc))) { - const embedding = Doc.MakeEmbedding(otherdoc); + embedding = Doc.MakeEmbedding(otherdoc); embedding.x = wid; embedding.y = 0; embedding._lockedPosition = false; - wid += otherdoc[WidthSym](); + wid += otherdoc[Width](); Doc.AddDocToList(Doc.GetProto(linkCollection), 'data', embedding); } }); + embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise return links; }); diff --git a/src/client/views/nodes/EquationBox.scss b/src/client/views/nodes/EquationBox.scss index 9714e1bd0..f5871db22 100644 --- a/src/client/views/nodes/EquationBox.scss +++ b/src/client/views/nodes/EquationBox.scss @@ -1,8 +1,9 @@ -@import "../global/globalCssVariables.scss"; +@import '../global/globalCssVariables.scss'; .equationBox-cont { - transform-origin: top left; + transform-origin: center; + background-color: #e7e7e7; > span { - width: 100%; + width: 100%; } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/EquationBox.tsx b/src/client/views/nodes/EquationBox.tsx index f17ab06e7..a77e4bdd1 100644 --- a/src/client/views/nodes/EquationBox.tsx +++ b/src/client/views/nodes/EquationBox.tsx @@ -2,7 +2,7 @@ import EquationEditor from 'equation-editor-react'; import { action, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { WidthSym } from '../../../fields/Doc'; +import { Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { NumCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; @@ -26,7 +26,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { this.props.select(false); this._ref.current!.mathField.focus(); - this._ref.current!.mathField.select(); + this.rootDoc.text === 'x' && this._ref.current!.mathField.select(); + EquationBox.SelectOnLoad = ''; } reaction( () => StrCast(this.dataDoc.text), @@ -35,6 +36,7 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { this._ref.current!.mathField.latex(text); } } + //{ fireImmediately: true } ); reaction( () => this.props.isSelected(), @@ -53,9 +55,8 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { const _height = Number(getComputedStyle(this._ref.current!.element.current).height.replace('px', '')); const _width = Number(getComputedStyle(this._ref.current!.element.current).width.replace('px', '')); if (e.key === 'Enter') { - const nextEq = Docs.Create.EquationDocument({ + const nextEq = Docs.Create.EquationDocument(e.shiftKey ? StrCast(this.dataDoc.text) : 'x', { title: '# math', - text: StrCast(this.dataDoc.text), _width, _height: 25, x: NumCast(this.layoutDoc.x), @@ -67,7 +68,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[WidthSym](), + x: NumCast(this.layoutDoc.x) + this.layoutDoc[Width](), y: NumCast(this.layoutDoc.y), _width: 400, _height: 300, @@ -101,10 +102,10 @@ export class EquationBox extends ViewBoxBaseComponent<FieldViewProps>() { width: 'fit-content', // `${100 / scale}%`, height: `${100 / scale}%`, pointerEvents: !this.props.isSelected() ? 'none' : undefined, - fontSize: StrCast(this.rootDoc._fontSize), + fontSize: StrCast(this.rootDoc._text_fontSize), }} onKeyDown={e => e.stopPropagation()}> - <EquationEditor ref={this._ref} value={this.dataDoc.text || 'x'} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" /> + <EquationEditor ref={this._ref} value={StrCast(this.dataDoc.text, 'x')} spaceBehavesLikeTab={true} onChange={this.onChange} autoCommands="pi theta sqrt sum prod alpha beta gamma rho" autoOperatorNames="sin cos tan" /> </div> ); } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 85dd779fc..4ebf22ddf 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -45,7 +45,7 @@ export interface FieldViewProps extends DocumentViewSharedProps { @observer export class FieldView extends React.Component<FieldViewProps> { public static LayoutString(fieldType: { name: string }, fieldStr: string) { - return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={"data} />" + return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "<ImageBox {...props} fieldKey={'data'} />" } @computed diff --git a/src/client/views/nodes/button/ButtonInterface.ts b/src/client/views/nodes/FontIconBox/ButtonInterface.ts index 0aa2ac8e1..0aa2ac8e1 100644 --- a/src/client/views/nodes/button/ButtonInterface.ts +++ b/src/client/views/nodes/FontIconBox/ButtonInterface.ts diff --git a/src/client/views/nodes/button/FontIconBadge.scss b/src/client/views/nodes/FontIconBox/FontIconBadge.scss index 78f506e57..2ff5c651f 100644 --- a/src/client/views/nodes/button/FontIconBadge.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBadge.scss @@ -1,11 +1,12 @@ .fontIconBadge { - background: red; + background: lightgreen; width: 15px; height: 15px; top: 8px; + color: black; display: block; position: absolute; right: 5; border-radius: 50%; text-align: center; -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/FontIconBox/FontIconBadge.tsx b/src/client/views/nodes/FontIconBox/FontIconBadge.tsx new file mode 100644 index 000000000..b50588ce2 --- /dev/null +++ b/src/client/views/nodes/FontIconBox/FontIconBadge.tsx @@ -0,0 +1,37 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import './FontIconBadge.scss'; + +interface FontIconBadgeProps { + value: string | undefined; +} + +@observer +export class FontIconBadge extends React.Component<FontIconBadgeProps> { + _notifsRef = React.createRef<HTMLDivElement>(); + + // onPointerDown = (e: React.PointerEvent) => { + // setupMoveUpEvents(this, e, + // (e: PointerEvent) => { + // const dragData = new DragManager.DocumentDragData([this.props.collection!]); + // DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); + // return true; + // }, + // returnFalse, emptyFunction, false); + // } + + render() { + if (this.props.value === undefined) return null; + return ( + <div className="fontIconBadge-container" ref={this._notifsRef}> + <div + className="fontIconBadge" + style={{ display: 'initial' }} + // onPointerDown={this.onPointerDown} + > + {this.props.value} + </div> + </div> + ); + } +} diff --git a/src/client/views/nodes/button/FontIconBox.scss b/src/client/views/nodes/FontIconBox/FontIconBox.scss index f3b43501b..9d9fa26b0 100644 --- a/src/client/views/nodes/button/FontIconBox.scss +++ b/src/client/views/nodes/FontIconBox/FontIconBox.scss @@ -18,7 +18,7 @@ .fontIconBox-label { color: $white; - bottom: 0; + bottom: -1; position: absolute; text-align: center; font-size: 7px; @@ -27,7 +27,7 @@ border-radius: 8px; padding: 0; width: 100%; - font-family: 'ROBOTO'; + font-family: 'system-ui'; text-transform: uppercase; font-weight: bold; transition: 0.15s; diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx new file mode 100644 index 000000000..91eac675f --- /dev/null +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -0,0 +1,403 @@ +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Button, MultiToggle, ColorPicker, Dropdown, DropdownType, EditableText, IconButton, IListItemProps, NumberDropdown, NumberDropdownType, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; +import { ScriptField } from '../../../../fields/ScriptField'; +import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; +import { SelectionManager } from '../../../util/SelectionManager'; +import { undoable, UndoManager } from '../../../util/UndoManager'; +import { ContextMenu } from '../../ContextMenu'; +import { DocComponent } from '../../DocComponent'; +import { EditableView } from '../../EditableView'; +import { Colors } from '../../global/globalEnums'; +import { StyleProp } from '../../StyleProvider'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { OpenWhere } from '../DocumentView'; +import { RichTextMenu } from '../formattedText/RichTextMenu'; +import './FontIconBox.scss'; +import { SelectedDocView } from '../../selectedDoc'; +import { Utils } from '../../../../Utils'; +import { FaAlignCenter, FaAlignJustify, FaAlignLeft, FaAlignRight } from 'react-icons/fa'; + +export enum ButtonType { + TextButton = 'textBtn', + MenuButton = 'menuBtn', + DropdownList = 'dropdownList', + DropdownButton = 'dropdownBtn', + ClickButton = 'clickBtn', + ToggleButton = 'toggleBtn', + ColorButton = 'colorBtn', + ToolButton = 'toolBtn', + MultiToggleButton = 'multiToggleBtn', + NumberSliderButton = 'numSliderBtn', + NumberDropdownButton = 'numDropdownBtn', + NumberInlineButton = 'numInlineBtn', + EditableText = 'editableText', +} + +export interface ButtonProps extends FieldViewProps { + type?: ButtonType; +} +@observer +export class FontIconBox extends DocComponent<ButtonProps>() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(FontIconBox, fieldKey); + } + @observable noTooltip = false; + showTemplate = (): void => { + const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); + dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight); + }; + dragAsTemplate = (): void => { + this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); + }; + useAsPrototype = (): void => { + this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); + }; + + specificContextMenu = (): void => { + if (!Doc.noviceMode) { + const cm = ContextMenu.Instance; + cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' }); + cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' }); + cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' }); + } + }; + + static GetShowLabels() { + return BoolCast(Doc.UserDoc()._showLabel); + } + static SetShowLabels(show: boolean) { + Doc.UserDoc()._showLabel = show; + } + static GetRecognizeGestures() { + return BoolCast(Doc.UserDoc()._recognizeGestures); + } + static SetRecognizeGestures(show: boolean) { + Doc.UserDoc()._recognizeGestures = show; + } + + // Determining UI Specs + @computed get label() { + return StrCast(this.rootDoc.icon_label, StrCast(this.rootDoc.title)); + } + Icon = (color: string, iconFalse?: boolean) => { + let icon; + if (iconFalse) { + icon = StrCast(this.dataDoc[this.fieldKey ?? 'iconFalse'] ?? this.dataDoc.icon, 'user') as any; + if (icon) return <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />; + else return null; + } + icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; + const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />; + return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />; + }; + @computed get dropdown() { + return BoolCast(this.rootDoc.dropDownOpen); + } + @computed get buttonList() { + return StrListCast(this.rootDoc.btnList); + } + @computed get type() { + return StrCast(this.rootDoc.btnType); + } + + /** + * Types of buttons in dash: + * - Main menu button (LHS) + * - Tool button + * - Expandable button (CollectionLinearView) + * - Button inside of CollectionLinearView vs. outside of CollectionLinearView + * - Action button + * - Dropdown button + * - Color button + * - Dropdown list + * - Number button + **/ + + _batch: UndoManager.Batch | undefined = undefined; + /** + * Number button + */ + @computed get numberDropdown() { + let type: NumberDropdownType; + switch (this.type) { + case ButtonType.NumberDropdownButton: + type = 'dropdown'; + break; + case ButtonType.NumberInlineButton: + type = 'input'; + break; + case ButtonType.NumberSliderButton: + default: + type = 'slider'; + break; + } + const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: value === undefined }); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + // Script for checking the outcome of the toggle + const checkResult = Number(Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3))); + const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>; + + return ( + <NumberDropdown + color={color} + numberDropdownType={type} + showPlusMinus={false} + tooltip={this.label} + type={Type.PRIM} + min={NumCast(this.rootDoc.numBtnMin, 0)} + max={NumCast(this.rootDoc.numBtnMax, 100)} + number={checkResult} + setNumber={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)} + fillWidth + /> + ); + } + + /** + * Dropdown button + */ + @computed get dropdownButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + return ( + <div + className={`menuButton ${this.type} ${active}`} + style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} + onClick={action(() => { + this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; + this.noTooltip = this.rootDoc.dropDownOpen; + Doc.UnBrushAllDocs(); + })}> + {this.Icon(color)} + {!this.label || !FontIconBox.GetShowLabels() ? null : ( + <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> + {' '} + {this.label}{' '} + </div> + )} + <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}> + <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" /> + </div> + {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null} + </div> + ); + } + + /** + * Dropdown list + */ + @computed get dropdownListButton() { + const active: string = StrCast(this.rootDoc.dropDownOpen); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + const script = ScriptCast(this.rootDoc.script); + + let noviceList: string[] = []; + let text: string | undefined; + let dropdown = true; + let getStyle: (val: string) => any = () => {}; + let icon: IconProp = 'caret-down'; + let isViewDropdown: boolean = script?.script.originalScript.startsWith('setView'); + try { + if (isViewDropdown) { + const selectedDocs: Doc[] = SelectionManager.Docs(); + const selected = SelectionManager.Docs().lastElement(); + if (selected) { + if (StrCast(selected.type) === DocumentType.COL) { + text = StrCast(selected._type_collection); + } else { + dropdown = false; + if (selectedDocs.length > 1) { + text = selectedDocs.length + ' selected'; + } else { + text = Utils.cleanDocumentType(StrCast(selected.type) as DocumentType); + icon = Doc.toIcon(selected); + } + return <Popup icon={<FontAwesomeIcon size={'1x'} icon={icon} />} text={text} type={Type.TERT} color={color} popup={<SelectedDocView selectedDocs={selectedDocs} />} fillWidth />; + } + } else { + dropdown = false; + return <Button text={`None Selected`} type={Type.TERT} color={color} fillWidth inactive />; + } + noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; + } else { + text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); + getStyle = (val: string) => { + return { fontFamily: val }; + }; + } + } catch (e) { + console.log(e); + } + + // Get items to place into the list + const list: IListItemProps[] = this.buttonList + .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value)) + .map(value => ({ + text: value.charAt(0).toUpperCase() + value.slice(1), + val: value, + style: getStyle(value), + onClick: undoable(() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value }), value), + // shortcut: '#', + })); + + return ( + <Dropdown + selectedVal={text} + setSelectedVal={undoable(val => script.script.run({ this: this.layoutDoc, self: this.rootDoc, val }), `dropdown select ${this.label}`)} + color={color} + type={isViewDropdown ? Type.TERT : Type.PRIM} + dropdownType={DropdownType.SELECT} + items={list} + tooltip={this.label} + fillWidth + /> + ); + } + + @computed get colorScript() { + return ScriptCast(this.rootDoc.script); + } + + /** + * Color button + */ + @computed get colorButton() { + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const curColor = this.colorScript?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; + const tooltip: string = StrCast(this.rootDoc.toolTip); + + return ( + <ColorPicker + setSelectedColor={value => { + const s = this.colorScript; + s && undoable(() => s.script.run({ this: this.layoutDoc, self: this.rootDoc, value: value, _readOnly_: false }).result, `Set ${tooltip} to ${value}`)(); + }} + selectedColor={curColor} + type={Type.PRIM} + color={color} + icon={this.Icon(color)!} + tooltip={tooltip} + label={this.label} + /> + ); + } + @computed get multiToggleButton() { + // Determine the type of toggle button + const tooltip: string = StrCast(this.rootDoc.toolTip); + + const script = ScriptCast(this.rootDoc.onClick); + const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; + // Colors + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const items = DocListCast(this.rootDoc.data); + return ( + <MultiToggle + tooltip={`Toggle ${tooltip}`} + type={Type.PRIM} + color={color} + label={this.label} + items={DocListCast(this.rootDoc.data).map(item => ({ + icon: <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={StrCast(item.icon) as any} color={color} />, + tooltip: StrCast(item.toolTip), + val: StrCast(item.toolType), + }))} + selectedVal={StrCast(items.find(itemDoc => ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: undefined, _readOnly_: true }).result)?.toolType)} + setSelectedVal={(val: string | number) => { + const itemDoc = items.find(item => item.toolType === val); + itemDoc && ScriptCast(itemDoc.onClick).script.run({ this: itemDoc, self: itemDoc, value: val, _readOnly_: false }); + }} + /> + ); + } + + @computed get toggleButton() { + // Determine the type of toggle button + const buttonText: string = StrCast(this.rootDoc.buttonText); + const tooltip: string = StrCast(this.rootDoc.toolTip); + + const script = ScriptCast(this.rootDoc.onClick); + const toggleStatus = script ? script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: undefined, _readOnly_: true }).result : false; + // Colors + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + + return ( + <Toggle + tooltip={`Toggle ${tooltip}`} + toggleType={ToggleType.BUTTON} + type={Type.PRIM} + toggleStatus={toggleStatus} + text={buttonText} + color={color} + icon={this.Icon(color)!} + label={this.label} + onPointerDown={() => script.script.run({ this: this.layoutDoc, self: this.rootDoc, value: !toggleStatus, _readOnly_: false })} + /> + ); + } + + /** + * Default + */ + @computed get defaultButton() { + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const tooltip: string = StrCast(this.rootDoc.toolTip); + + return <IconButton tooltip={tooltip} icon={this.Icon(color)!} label={this.label} />; + } + + @computed get editableText() { + // Script for running the toggle + const script = ScriptCast(this.rootDoc.script); + // Function to run the script + const checkResult = script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result; + + const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value, _readOnly_: false }).result; + + return <EditableText editing={false} setEditing={(editing: boolean) => {}} />; + + return ( + <div className="menuButton editableText"> + <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} /> + <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}> + <EditableView GetValue={() => script?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} /> + </div> + </div> + ); + } + + render() { + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); + const tooltip = StrCast(this.rootDoc.toolTip); + const scriptFunc = () => ScriptCast(this.rootDoc.onClick)?.script.run({ this: this.layoutDoc, self: this.rootDoc, _readOnly_: false }); + const btnProps = { tooltip, icon: this.Icon(color)!, label: this.label }; + // prettier-ignore + switch (this.type) { + case ButtonType.NumberDropdownButton: + case ButtonType.NumberInlineButton: + case ButtonType.NumberSliderButton: return this.numberDropdown; + case ButtonType.EditableText: return this.editableText; + case ButtonType.DropdownList: return this.dropdownListButton; + case ButtonType.ColorButton: return this.colorButton; + case ButtonType.DropdownButton: return this.dropdownButton; + case ButtonType.MultiToggleButton: return this.multiToggleButton; + case ButtonType.ToggleButton: return this.toggleButton; + case ButtonType.ClickButton: + case ButtonType.ToolButton: return <IconButton {...btnProps} size={Size.LARGE} color={color} />; + case ButtonType.TextButton: return <Button {...btnProps} text={StrCast(this.rootDoc.buttonText)}/>; + case ButtonType.MenuButton: return <IconButton {...btnProps} color={color} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />; + } + return this.defaultButton; + } +} diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index db72209ab..61711417f 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -43,7 +43,10 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps> ); } getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { - const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); + const anchor = Docs.Create.ConfigDocument({ + // + annotationOn: this.rootDoc, + }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), datarange: true } }, this.rootDoc); anchor.presXRange = new List<number>(Array.from(this._plot.options.xAxis.domain)); anchor.presYRange = new List<number>(Array.from(this._plot.options.yAxis.domain)); @@ -77,9 +80,10 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent<FieldViewProps> @undoBatch drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData?.droppedDocuments.length) { + const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc), true); + !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place - de.complete.docDragData.droppedDocuments.map(doc => Doc.AddDocToList(this.dataDoc, this.props.fieldKey, doc)); - return false; + return added; } return false; }; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 7d1d50cc7..d763753a5 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -3,7 +3,8 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; -import { DataSym, Doc, DocListCast, Opt, WidthSym } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { DocData, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -77,13 +78,12 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { const anchor = this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor - Docs.Create.ImageanchorDocument({ + Docs.Create.ConfigDocument({ title: 'ImgAnchor:' + this.rootDoc.title, presPanX: NumCast(this.layoutDoc._freeform_panX), presPanY: NumCast(this.layoutDoc._freeform_panY), presViewScale: Cast(this.layoutDoc._freeform_scale, 'number', null), presTransition: 1000, - unrendered: true, annotationOn: this.rootDoc, }); if (anchor) { @@ -107,7 +107,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp ); const layoutDoc = this.layoutDoc; this._disposers.path = reaction( - () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[WidthSym]() }), + () => ({ nativeSize: this.nativeSize, width: this.layoutDoc[Width]() }), ({ nativeSize, width }) => { if (layoutDoc === this.layoutDoc || !this.layoutDoc._height) { this.layoutDoc._height = (width * nativeSize.nativeHeight) / nativeSize.nativeWidth; @@ -135,31 +135,32 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData) { + let added: boolean | undefined = undefined; const targetIsBullseye = (ele: HTMLElement): boolean => { if (!ele) return false; if (ele === this._overlayIconRef.current) return true; return targetIsBullseye(ele.parentElement as HTMLElement); }; if (de.metaKey || targetIsBullseye(e.target as HTMLElement)) { - de.complete.docDragData.droppedDocuments.forEach( - action((drop: Doc) => { - Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop); - this.rootDoc[this.fieldKey + '_usePath'] = 'alternate:hover'; - e.stopPropagation(); - }) - ); + added = de.complete.docDragData.droppedDocuments.reduce((last: boolean, drop: Doc) => { + this.rootDoc[this.fieldKey + '_usePath'] = 'alternate:hover'; + return last && Doc.AddDocToList(this.dataDoc, this.fieldKey + '-alternates', drop); + }, true); } else if (de.altKey || !this.dataDoc[this.fieldKey]) { const layoutDoc = de.complete.docDragData?.draggedDocuments[0]; const targetField = Doc.LayoutFieldKey(layoutDoc); - const targetDoc = layoutDoc[DataSym]; + const targetDoc = layoutDoc[DocData]; if (targetDoc[targetField] instanceof ImageField) { this.dataDoc[this.fieldKey] = ObjectField.MakeCopy(targetDoc[targetField] as ImageField); Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(targetDoc), this.fieldKey); Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(targetDoc), this.fieldKey); - e.stopPropagation(); } } + added === false && e.preventDefault(); + added !== undefined && e.stopPropagation(); + return added; } + return false; }; @undoBatch @@ -169,14 +170,13 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp setNativeSize = action(() => { const scaling = (this.props.DocumentView?.().props.ScreenToLocalTransform().Scale || 1) / NumCast(this.rootDoc._freeform_scale, 1); const nscale = NumCast(this.props.PanelWidth()) / scaling; - const nh = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']); const nw = nscale / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']); - this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nh; - this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nh; - this.rootDoc._freeform_panX = nh * NumCast(this.rootDoc._freeform_panX); - this.rootDoc._freeform_panY = nh * NumCast(this.rootDoc._freeform_panY); - this.dataDoc._freeform_panXMax = this.dataDoc._freeform_panXMax ? nh * NumCast(this.dataDoc._freeform_panXMax) : undefined; - this.dataDoc._freeform_panXMin = this.dataDoc._freeform_panXMin ? nh * NumCast(this.dataDoc._freeform_panXMin) : undefined; + this.dataDoc[this.fieldKey + '_nativeHeight'] = NumCast(this.dataDoc[this.fieldKey + '_nativeHeight']) * nw; + this.dataDoc[this.fieldKey + '_nativeWidth'] = NumCast(this.dataDoc[this.fieldKey + '_nativeWidth']) * nw; + this.rootDoc._freeform_panX = nw * NumCast(this.rootDoc._freeform_panX); + this.rootDoc._freeform_panY = nw * NumCast(this.rootDoc._freeform_panY); + this.dataDoc._freeform_panXMax = this.dataDoc._freeform_panXMax ? nw * NumCast(this.dataDoc._freeform_panXMax) : undefined; + this.dataDoc._freeform_panXMin = this.dataDoc._freeform_panXMin ? nw * NumCast(this.dataDoc._freeform_panXMin) : undefined; this.dataDoc._freeform_panYMax = this.dataDoc._freeform_panYMax ? nw * NumCast(this.dataDoc._freeform_panYMax) : undefined; this.dataDoc._freeform_panYMin = this.dataDoc._freeform_panYMin ? nw * NumCast(this.dataDoc._freeform_panYMin) : undefined; }); @@ -231,7 +231,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp croppingProto.freeform_panYMax = anchh / viewScale; if (addCrop) { DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' }); - cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[WidthSym](); + cropping.x = NumCast(this.rootDoc.x) + this.rootDoc[Width](); cropping.y = NumCast(this.rootDoc.y); this.props.addDocTab(cropping, OpenWhere.inParent); } @@ -295,7 +295,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp choosePath(url: URL) { const lower = url.href.toLowerCase(); if (url.protocol === 'data') return url.href; - if (url.href.indexOf(window.location.origin) === -1) return Utils.CorsProxy(url.href); + if (url.href.indexOf(window.location.origin) === -1 && url.href.indexOf("dashblobstore") === -1) return Utils.CorsProxy(url.href); if (!/\.(png|jpg|jpeg|gif|webp)$/.test(lower)) return `/assets/unknown-file-icon-hi.png`; const ext = extname(url.href); @@ -303,7 +303,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } considerGooglePhotosLink = () => { - const remoteUrl = this.dataDoc.googlePhotosUrl; + const remoteUrl = StrCast(this.dataDoc.googlePhotosUrl); // bcz: StrCast or URLCast??? return !remoteUrl ? null : <img draggable={false} style={{ transformOrigin: 'bottom right' }} id={'google-photos'} src={'/assets/google_photos.png'} onClick={() => window.open(remoteUrl)} />; }; @@ -387,7 +387,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp ref={this._overlayIconRef} onPointerDown={e => setupMoveUpEvents(e.target, e, returnFalse, emptyFunction, e => (this.rootDoc[`_${this.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined))} style={{ - display: (SnappingManager.GetIsDragging() && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none', + display: (this.props.isContentActive() !== false && DragManager.DocDragData?.canEmbed) || DocListCast(this.dataDoc[this.fieldKey + '-alternates']).length ? 'block' : 'none', width: 'min(10%, 25px)', height: 'min(10%, 25px)', background: usePath === undefined ? 'white' : usePath === 'alternate' ? 'black' : 'gray', @@ -505,7 +505,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp } })} style={{ - display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined, + display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined, width: this.props.PanelWidth() ? undefined : `100%`, height: this.props.PanelWidth() ? undefined : `100%`, pointerEvents: this.layoutDoc._lockedPosition ? 'none' : undefined, diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 88a82e8e6..a6712a3db 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -84,7 +84,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean { const { script, type, onDelegate } = kvpScript; //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates - const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : doc.proto || doc; + const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc); let field: Field; if (type === 'computed') { field = new ComputedField(script); @@ -213,18 +213,18 @@ export class KeyValueBox extends React.Component<FieldViewProps> { document.addEventListener('pointerup', this.onDividerUp); }; - getFieldView = async () => { + getFieldView = () => { const rows = this.rows.filter(row => row.isChecked); if (rows.length > 1) { - const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document.data).title}`, _chromeHidden: true }); + const parent = Docs.Create.StackingDocument([], { _layout_autoHeight: true, _width: 300, title: `field views for ${DocCast(this.props.Document).title}`, _chromeHidden: true }); for (const row of rows) { - const field = this.createFieldView(DocCast(this.props.Document.data), row); + const field = this.createFieldView(DocCast(this.props.Document), row); field && Doc.AddDocToList(parent, 'data', field); row.uncheck(); } return parent; } - return rows.length ? this.createFieldView(DocCast(this.props.Document.data), rows.lastElement()) : undefined; + return rows.length ? this.createFieldView(DocCast(this.props.Document), rows.lastElement()) : undefined; }; createFieldView = (templateDoc: Doc, row: KeyValuePair) => { diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss index 2b4867321..57d36932e 100644 --- a/src/client/views/nodes/KeyValuePair.scss +++ b/src/client/views/nodes/KeyValuePair.scss @@ -19,7 +19,6 @@ position: relative; margin: 0; } - .keyValuePair-keyFieldMod, .keyValuePair-keyField { width: 100%; margin-left: 20px; @@ -28,9 +27,6 @@ overflow: auto; display: inline; } - .keyValuePair-keyFieldMod { - margin-left: 35px; - } } } .keyValuePair-td-value { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 509bb667e..01acdccb7 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -13,6 +13,7 @@ import { KeyValueBox } from './KeyValueBox'; import './KeyValueBox.scss'; import './KeyValuePair.scss'; import React = require('react'); +import { DocCast } from '../../../fields/Types'; // Represents one row in a key value plane @@ -56,8 +57,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { const props: FieldViewProps = { Document: this.props.doc, DataDoc: this.props.doc, - docFilters: returnEmptyFilter, - docRangeFilters: returnEmptyFilter, + childFilters: returnEmptyFilter, + childFiltersByRanges: returnEmptyFilter, searchFilterDocs: returnEmptyDoclist, styleProvider: DefaultStyleProvider, docViewPath: returnEmptyDoclist, @@ -66,7 +67,6 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { isSelected: returnFalse, setHeight: returnFalse, select: emptyFunction, - dropAction: 'embed', bringToFront: emptyFunction, renderDepth: 1, isContentActive: returnFalse, @@ -87,7 +87,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { break; } protoCount++; - doc = doc.proto; + doc = DocCast(doc.proto); } const parenCount = Math.max(0, protoCount - 1); const keyStyle = protoCount === 0 ? 'black' : 'blue'; @@ -104,12 +104,12 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { onClick={undoBatch(() => { if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) { delete props.Document[props.fieldKey]; - } else delete props.Document.proto![props.fieldKey]; + } else delete DocCast(props.Document.proto)?.[props.fieldKey]; })}> X </button> <input className="keyValuePair-td-key-check" type="checkbox" style={hover} onChange={this.handleCheck} ref={this.checkbox} /> - <div className={`keyValuePair-keyField${props.fieldKey.includes('_') ? 'Mod' : ''}`} style={{ color: keyStyle }}> + <div className="keyValuePair-keyField" style={{ marginLeft: 20 * (props.fieldKey.match(/_/g)?.length || 0), color: keyStyle }}> {'('.repeat(parenCount)} {props.fieldKey} {')'.repeat(parenCount)} diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 32026ea9c..4439be0cd 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -74,7 +74,9 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp if (docDragData && missingParams?.includes((e.target as any).textContent)) { this.paramsDoc[(e.target as any).textContent] = new List<Doc>(docDragData.droppedDocuments.map((d, i) => (d.onDragStart ? docDragData.draggedDocuments[i] : d))); e.stopPropagation(); + return true; } + return false; }; @observable _mouseOver = false; @@ -87,8 +89,8 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp const params = { rotateText: null, fontSizeFactor: 1, - minimumFontSize: NumCast(this.rootDoc._minFontSize, 8), - maximumFontSize: NumCast(this.rootDoc._maxFontSize, 1000), + minimumFontSize: NumCast(this.rootDoc._label_minFontSize, 8), + maximumFontSize: NumCast(this.rootDoc._label_maxFontSize, 1000), limitingDimension: 'both', horizontalAlign: 'center', verticalAlign: 'center', @@ -130,9 +132,9 @@ export class LabelBox extends ViewBoxBaseComponent<FieldViewProps & LabelBoxProp className="labelBox-mainButton" style={{ backgroundColor: this.hoverColor, - fontSize: StrCast(this.layoutDoc._fontSize), + fontSize: StrCast(this.layoutDoc._text_fontSize), color: StrCast(this.layoutDoc._color), - fontFamily: StrCast(this.layoutDoc._fontFamily) || 'inherit', + fontFamily: StrCast(this.layoutDoc._text_fontFamily) || 'inherit', letterSpacing: StrCast(this.layoutDoc.letterSpacing), textTransform: StrCast(this.layoutDoc.textTransform) as any, paddingLeft: NumCast(this.rootDoc._xPadding), diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index f38ef634c..9bcd04cf5 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -47,13 +47,13 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { if (separation > 100) { const dragData = new DragManager.DocumentDragData([this.rootDoc]); dragData.dropAction = 'embed'; - dragData.removeDropProperties = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick']; + dragData.dropPropertiesToRemove = ['link_anchor_1_x', 'link_anchor_1_y', 'link_anchor_2_x', 'link_anchor_2_y', 'onClick']; DragManager.StartDocumentDrag([this._ref.current!], dragData, pt[0], pt[1]); return true; } else { this.rootDoc[this.fieldKey + '_x'] = ((pt[0] - bounds.left) / bounds.width) * 100; this.rootDoc[this.fieldKey + '_y'] = ((pt[1] - bounds.top) / bounds.height) * 100; - this.rootDoc.layout_autoMoveAnchors = false; + this.rootDoc.link_autoMoveAnchors = false; } } return false; @@ -68,7 +68,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps>() { const y = NumCast(this.rootDoc[this.fieldKey + '_y'], 100); const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ':anchor'); const anchor = this.fieldKey === 'link_anchor_1' ? 'link_anchor_2' : 'link_anchor_1'; - const anchorScale = !this.dataDoc[this.fieldKey + '_useLinkSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25; + const anchorScale = !this.dataDoc[this.fieldKey + '_useSmallAnchor'] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : 0.25; const targetTitle = StrCast((this.dataDoc[anchor] as Doc)?.title); const selView = SelectionManager.Views().lastElement()?.props.LayoutTemplateString?.includes('link_anchor_1') ? 'link_anchor_1' diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index b5b8e660f..767f0291b 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -1,3 +1,7 @@ -.linkBox-container-interactive { +.linkBox-container-interactive { pointer-events: all; -}
\ No newline at end of file + width: 100%; +} +.linkBox-container { + width: 100%; +} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index c6172ee01..d69009415 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -3,11 +3,12 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import wiki from 'wikijs'; -import { Doc, DocCastAsync, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Doc, DocCastAsync, Opt } from '../../../fields/Doc'; +import { DirectLinks, Height, Width } from '../../../fields/DocSymbols'; 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, DocUtils } from '../../documents/Documents'; +import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; @@ -123,7 +124,6 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { this._markerTargetDoc = linkTarget; this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } - this._toolTipText = 'link to ' + this._targetDoc?.title; if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink(); } }) @@ -184,17 +184,17 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { width = () => { if (!this._targetDoc) return 225; - if (this._targetDoc[WidthSym]() < this._targetDoc?.[HeightSym]()) { - return (Math.min(225, this._targetDoc[HeightSym]()) * this._targetDoc[WidthSym]()) / this._targetDoc[HeightSym](); + if (this._targetDoc[Width]() < this._targetDoc?.[Height]()) { + return (Math.min(225, this._targetDoc[Height]()) * this._targetDoc[Width]()) / this._targetDoc[Height](); } - return Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)); + return Math.min(225, NumCast(this._targetDoc?.[Width](), 225)); }; height = () => { if (!this._targetDoc) return 225; - if (this._targetDoc[WidthSym]() > this._targetDoc?.[HeightSym]()) { - return (Math.min(225, this._targetDoc[WidthSym]()) * this._targetDoc[HeightSym]()) / this._targetDoc[WidthSym](); + if (this._targetDoc[Width]() > this._targetDoc?.[Height]()) { + return (Math.min(225, this._targetDoc[Width]()) * this._targetDoc[Height]()) / this._targetDoc[Width](); } - return Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); + return Math.min(225, NumCast(this._targetDoc?.[Height](), 225)); }; @computed get previewHeader() { return !this._linkDoc || !this._markerTargetDoc || !this._targetDoc || !this._linkSrc ? null : ( @@ -268,8 +268,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { addDocTab={returnFalse} pinToPres={returnFalse} dontRegisterView={true} - docFilters={returnEmptyFilter} - docRangeFilters={returnEmptyFilter} + childFilters={returnEmptyFilter} + childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} renderDepth={0} suppressSetHeight={true} @@ -296,7 +296,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { className="linkDocPreview" ref={this._linkDocRef} onPointerDown={this.followLinkPointerDown} - style={{ display: !this._toolTipText ? 'none' : undefined, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> + style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> {this.docPreview} </div> ); diff --git a/src/client/views/nodes/LoadingBox.scss b/src/client/views/nodes/LoadingBox.scss index 4c3b8dabe..d4a7e18f2 100644 --- a/src/client/views/nodes/LoadingBox.scss +++ b/src/client/views/nodes/LoadingBox.scss @@ -12,6 +12,10 @@ text-overflow: ellipsis; max-width: 80%; text-align: center; + display: flex; + flex-direction: column; + gap: 8px; + align-items: center; } } diff --git a/src/client/views/nodes/LoadingBox.tsx b/src/client/views/nodes/LoadingBox.tsx index 8c5255f80..fcbd0128d 100644 --- a/src/client/views/nodes/LoadingBox.tsx +++ b/src/client/views/nodes/LoadingBox.tsx @@ -8,6 +8,7 @@ import { Networking } from '../../Network'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; import './LoadingBox.scss'; +import { Id } from '../../../fields/FieldSymbols'; /** * LoadingBox Class represents a placeholder doc for documents that are currently @@ -43,7 +44,7 @@ export class LoadingBox extends ViewBoxAnnotatableComponent<FieldViewProps>() { this.rootDoc.loadingError = 'Upload interrupted, please try again'; } else { const updateFunc = async () => { - const result = await Networking.QueryYoutubeProgress(StrCast(this.rootDoc.title)); + const result = await Networking.QueryYoutubeProgress(StrCast(this.rootDoc[Id])); // We use the guid of the overwriteDoc to track file uploads. runInAction(() => (this.progress = result.progress)); this._timer = setTimeout(updateFunc, 1000); }; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index bfb0a6161..15cf22953 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -62,6 +62,12 @@ const mapOptions = { const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS=<your apikey> +const script = document.createElement('script'); +script.defer = true; +script.async = true; +script.src = `https://maps.googleapis.com/maps/api/js?key=${apiKey}&libraries=places,drawing`; +document.head.appendChild(script); + /** * Consider integrating later: allows for drawing, circling, making shapes on map */ @@ -473,7 +479,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); + return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); } /** @@ -544,9 +550,8 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this.addDocument(doc, annotationKey); }; - pointerEvents = () => { - return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'; - }; + pointerEvents = () => (this.props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none'); + @computed get annotationLayer() { return ( <div className="mapBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer}> @@ -583,8 +588,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.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; + opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()]; infoWidth = () => this.props.PanelWidth() / 5; infoHeight = () => this.props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; @@ -905,7 +910,7 @@ export class MapBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } render() { - const renderAnnotations = (docFilters?: () => string[]) => null; + const renderAnnotations = (childFilters?: () => string[]) => null; return ( <div className="mapBox" ref={this._ref}> <div diff --git a/src/client/views/nodes/MapBox/MapBox2.tsx b/src/client/views/nodes/MapBox/MapBox2.tsx index f75b3a6a6..643aeaee0 100644 --- a/src/client/views/nodes/MapBox/MapBox2.tsx +++ b/src/client/views/nodes/MapBox/MapBox2.tsx @@ -3,7 +3,8 @@ import { Autocomplete, GoogleMap, GoogleMapProps, Marker } from '@react-google-m import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc'; +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'; @@ -367,7 +368,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps .ScreenToLocalTransform() .scale(this.props.NativeDimScaling?.() || 1) .transformDirection(delta[0], delta[1]); - const fullWidth = this.layoutDoc[WidthSym](); + const fullWidth = this.layoutDoc[Width](); const mapWidth = fullWidth - this.sidebarWidth(); if (this.sidebarWidth() + localDelta[0] > 0) { this._showSidebar = true; @@ -390,7 +391,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); + return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.props.fieldKey + '_backgroundColor'], '#e4e4e4')); } /** @@ -509,7 +510,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; pointerEvents = () => { - return this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'; + return this.props.isContentActive() === false ? 'none' : this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'; }; @computed get annotationLayer() { return ( @@ -546,8 +547,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.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.IsOpaqueFilter()]; + transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; + opaqueFilter = () => [...this.props.childFilters(), Utils.IsOpaqueFilter()]; infoWidth = () => this.props.PanelWidth() / 5; infoHeight = () => this.props.PanelHeight() / 5; anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; @@ -557,7 +558,7 @@ export class MapBox2 extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return (window as any).Microsoft.Maps; } render() { - const renderAnnotations = (docFilters?: () => string[]) => null; + const renderAnnotations = (childFilters?: () => string[]) => null; return ( <div className="MapBox2" ref={this._ref}> <div diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx index 04cb12ade..66c47d131 100644 --- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx +++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx @@ -61,7 +61,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi fieldKey="data" NativeWidth={returnZero} NativeHeight={returnZero} - docFilters={returnEmptyFilter} + childFilters={returnEmptyFilter} setHeight={emptyFunction} isAnnotationOverlay={false} select={emptyFunction} @@ -76,7 +76,7 @@ export class MapBoxInfoWindow extends React.Component<MapBoxInfoWindowProps & Vi removeDocument={this.removeDoc} addDocument={this.addDoc} renderDepth={this.props.renderDepth + 1} - viewType={CollectionViewType.Stacking} + type_collection={CollectionViewType.Stacking} pointerEvents={returnAll} /> </div> diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index edeaba322..fd4c6366b 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -3,7 +3,8 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction import { observer } from 'mobx-react'; import * as Pdfjs from 'pdfjs-dist'; import 'pdfjs-dist/web/pdf_viewer.css'; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +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 +64,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[WidthSym]() * (nh / nw)); + !this.Document._layout_fitWidth && (this.Document._height = 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 +105,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[WidthSym]().toString(); - newDiv.style.height = this.layoutDoc[HeightSym]().toString(); + newDiv.style.width = this.layoutDoc[Width]().toString(); + newDiv.style.height = 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[WidthSym]() * (this.props.NativeDimScaling?.() || 1); - const anchh = cropping[HeightSym]() * (this.props.NativeDimScaling?.() || 1); + const anchw = cropping[Width]() * (this.props.NativeDimScaling?.() || 1); + const anchh = 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 +170,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps CollectionFreeFormView.UpdateIcon( filename, docViewContent, - this.layoutDoc[WidthSym](), - this.layoutDoc[HeightSym](), + this.layoutDoc[Width](), + this.layoutDoc[Height](), this.props.PanelWidth(), this.props.PanelHeight(), NumCast(this.layoutDoc._layout_scrollTop), @@ -237,14 +238,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ele = document.createElement('div'); ele.append(this._pdfViewer.selectionContent()!); } - const docAnchor = () => { - const anchor = Docs.Create.TextanchorDocument({ + const docAnchor = () => + Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc.title + '@' + NumCast(this.layoutDoc._layout_scrollTop)?.toFixed(0)), - unrendered: true, annotationOn: this.rootDoc, }); - return anchor; - }; const annoAnchor = this._pdfViewer?._getAnchor(this._pdfViewer.savedAnnotations(), true); const anchor = annoAnchor ?? docAnchor(); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true, pannable: true } }, this.rootDoc); @@ -261,8 +259,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[WidthSym]() / (Doc.NativeAspect(this.dataDoc) || 1); - !this.Document._layout_fitWidth && (this.Document._height = this.Document[WidthSym]() * (nh / nw)); + this.layoutDoc._height = this.layoutDoc[Width]() / (Doc.NativeAspect(this.dataDoc) || 1); + !this.Document._layout_fitWidth && (this.Document._height = this.Document[Width]() * (nh / nw)); }; public search = action((searchString: string, bwd?: boolean, clear: boolean = false) => { @@ -337,7 +335,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[WidthSym]() + localDelta[0]); + onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]); this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; @@ -358,11 +356,11 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { this._previewNativeWidth = nativeWidth * sideratio; - this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth; + this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth; this._showSidebar = true; } else { this.layoutDoc.nativeWidth = nativeWidth * pdfratio; - this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth; + this.layoutDoc._width = (this.layoutDoc[Width]() * nativeWidth * pdfratio) / curNativeWidth; this.layoutDoc._show_sidebar = nativeWidth !== this.layoutDoc._nativeWidth; } }); @@ -441,7 +439,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return PDFBox.sidebarResizerWidth + nativeDiff * (this.props.NativeDimScaling?.() || 1); }; @undoBatch - toggleSidebarType = () => (this.rootDoc.sidebarViewType = this.rootDoc.sidebarViewType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform); + toggleSidebarType = () => (this.rootDoc.sidebar_collectionType = this.rootDoc.sidebar_collectionType === CollectionViewType.Freeform ? CollectionViewType.Stacking : CollectionViewType.Freeform); specificContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; const options = cm.findByDescription('Options...'); @@ -557,11 +555,10 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps }; return ( <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: '100%', right: 0, backgroundColor: `white` }}> - {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} + {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))} </div> ); } - isPdfContentActive = () => this.isAnyChildContentActive() || this.props.isSelected() || (this.props.renderDepth === 0 && LightboxView.IsLightboxDocView(this.props.docViewPath())); @computed get renderPdfView() { TraceMobx(); const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; @@ -594,7 +591,6 @@ export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps pdf={this._pdf} focus={this.focus} url={this.pdfUrl!.url.pathname} - isContentActive={this.isPdfContentActive} anchorMenuClick={this.anchorMenuClick} loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} setPdfViewer={this.setPdfViewer} diff --git a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx index 2c06282ed..cd1ff17dd 100644 --- a/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx +++ b/src/client/views/nodes/PhysicsBox/PhysicsSimulationBox.tsx @@ -1,11 +1,11 @@ -// import ArrowLeftIcon from '@mui/icons-material/ArrowLeft'; -// import ArrowRightIcon from '@mui/icons-material/ArrowRight'; -// import PauseIcon from '@mui/icons-material/Pause'; -// import PlayArrowIcon from '@mui/icons-material/PlayArrow'; -// import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; -// import ReplayIcon from '@mui/icons-material/Replay'; -// import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material'; -// import Typography from '@mui/material/Typography'; +import ArrowLeftIcon from '@mui/icons-material/ArrowLeft'; +import ArrowRightIcon from '@mui/icons-material/ArrowRight'; +import PauseIcon from '@mui/icons-material/Pause'; +import PlayArrowIcon from '@mui/icons-material/PlayArrow'; +import QuestionMarkIcon from '@mui/icons-material/QuestionMark'; +import ReplayIcon from '@mui/icons-material/Replay'; +import { Box, Button, Checkbox, Dialog, DialogActions, DialogContent, DialogContentText, DialogTitle, FormControl, FormControlLabel, FormGroup, IconButton, LinearProgress, Stack } from '@mui/material'; +import Typography from '@mui/material/Typography'; import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NumListCast } from '../../../../fields/Doc'; @@ -928,12 +928,12 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP <Stack direction="row" spacing={1}> {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( <IconButton onClick={() => (this.dataDoc.simulation_paused = false)}> - {/* <PlayArrowIcon /> */} + <PlayArrowIcon /> </IconButton> )} {!this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( <IconButton onClick={() => (this.dataDoc.simulation_paused = true)}> - {/* <PauseIcon /> */} + <PauseIcon /> </IconButton> )} {this.dataDoc.simulation_paused && this.simulationMode != 'Tutorial' && ( @@ -1180,7 +1180,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude; }} disabled={this.dataDoc.tutorial_stepNumber == 0}> - {/* <ArrowLeftIcon /> */} + <ArrowLeftIcon /> </IconButton> <div> <h3> @@ -1199,7 +1199,7 @@ export class PhysicsSimulationBox extends ViewBoxAnnotatableComponent<FieldViewP this.dataDoc.simulation_showForceMagnitudes = this.tutorial.steps[step].showMagnitude; }} disabled={this.dataDoc.tutorial_stepNumber === this.tutorial.steps.length - 1}> - {/* <ArrowRightIcon /> */} + <ArrowRightIcon /> </IconButton> </div> <div> diff --git a/src/client/views/nodes/QueryBox.scss b/src/client/views/nodes/QueryBox.scss deleted file mode 100644 index b5f90aa1e..000000000 --- a/src/client/views/nodes/QueryBox.scss +++ /dev/null @@ -1,5 +0,0 @@ -.queryBox, .queryBox-dragging { - width: 100%; - height: 100%; - position: absolute; -}
\ No newline at end of file diff --git a/src/client/views/nodes/QueryBox.tsx b/src/client/views/nodes/QueryBox.tsx deleted file mode 100644 index 1b6056be6..000000000 --- a/src/client/views/nodes/QueryBox.tsx +++ /dev/null @@ -1,38 +0,0 @@ -// import React = require("react"); -// import { IReactionDisposer } from "mobx"; -// import { observer } from "mobx-react"; -// import { documentSchema } from "../../../new_fields/documentSchemas"; -// import { Id } from '../../../new_fields/FieldSymbols'; -// import { makeInterface, listSpec } from "../../../new_fields/Schema"; -// import { StrCast, Cast } from "../../../new_fields/Types"; -// import { ViewBoxAnnotatableComponent } from '../DocComponent'; -// import { SearchBox } from "../search/SearchBox"; -// import { FieldView, FieldViewProps } from './FieldView'; -// import "./QueryBox.scss"; -// import { List } from "../../../new_fields/List"; -// import { SnappingManager } from "../../util/SnappingManager"; - -// type QueryDocument = makeInterface<[typeof documentSchema]>; -// const QueryDocument = makeInterface(documentSchema); - -// @observer -// export class QueryBox extends ViewBoxAnnotatableComponent<FieldViewProps, QueryDocument>(QueryDocument) { -// public static LayoutString(fieldKey: string) { return FieldView.LayoutString(QueryBox, fieldKey); } -// _docListChangedReaction: IReactionDisposer | undefined; -// componentDidMount() { -// } - -// componentWillUnmount() { -// this._docListChangedReaction?.(); -// } - -// render() { -// const dragging = !SnappingManager.GetIsDragging() ? "" : "-dragging"; -// return <div className={`queryBox${dragging}`} onWheel={(e) => e.stopPropagation()} > - -// <SearchBox Document={this.props.Document} /> -// </div >; -// } -// } - -// //<SearchBox id={this.props.Document[Id]} sideBar={side} Document={this.props.Document} searchQuery={StrCast(this.dataDoc.searchQuery)} filterQuery={this.dataDoc.filterQuery} /> diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx index 80b12b96e..04f11a5df 100644 --- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx @@ -11,6 +11,7 @@ import { DocumentType } from '../../../documents/DocumentTypes'; import { Presentation } from '../../../util/TrackMovements'; import { Doc } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; +import { DocCast } from '../../../../fields/Types'; @observer export class RecordingBox extends ViewBoxBaseComponent() { @@ -57,7 +58,7 @@ export class RecordingBox extends ViewBoxBaseComponent() { render() { return ( <div className="recordingBox" ref={this._ref}> - {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={this.rootDoc.proto?.[Id] || ''} />} + {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={DocCast(this.rootDoc.proto)?.[Id] || ''} />} </div> ); } diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx index 424ebc384..51eb774e2 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.tsx +++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx @@ -67,7 +67,7 @@ export function RecordingView(props: IRecordingViewProps) { const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() })); // upload the segments to the server and get their server access paths - const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles)).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server)); + const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles.map(file => ({file})))).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server)); // concat the segments together using post call const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths); diff --git a/src/client/views/nodes/ScreenshotBox.scss b/src/client/views/nodes/ScreenshotBox.scss index 6fb5ea7b3..1e9b64a0b 100644 --- a/src/client/views/nodes/ScreenshotBox.scss +++ b/src/client/views/nodes/ScreenshotBox.scss @@ -1,6 +1,5 @@ .screenshotBox { transform-origin: top left; - background: white; color: black; // .screenshotBox-viewer { // opacity: 0.99; // hack! overcomes some kind of Chrome weirdness where buttons (e.g., snapshot) disappear at some point as the video is resized larger @@ -12,46 +11,39 @@ #CANCAN { canvas { - width:100% !important; + width: 100% !important; height: 100% !important; } } -.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-cont-fullScreen { +.screenshotBox-content, +.screenshotBox-content-interactive, +.screenshotBox-cont-fullScreen { width: 100%; z-index: -1; // 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt position: absolute; } -.screenshotBox-content, .screenshotBox-content-interactive, .screenshotBox-content-fullScreen { - height: Auto; +.screenshotBox-content, +.screenshotBox-content-interactive, +.screenshotBox-content-fullScreen { + height: Auto; } .screenshotBox-uiButtons { - background:dimgray; - border: orange solid 1px; position: absolute; right: 25; top: 0; - width:25; + width: 22; height: 25; - .screenshotBox-snapshot{ - color : white; - top :0px; - right : 5px; - position: absolute; - background-color:rgba(50, 50, 50, 0.2); - transform-origin: left top; - pointer-events:all; - } - .screenshotBox-recorder{ - color : white; - top :0px; + .screenshotBox-recorder { + color: white; + top: 4px; left: 5px; position: absolute; - background-color:rgba(50, 50, 50, 0.2); + background-color: rgba(50, 50, 50, 0.2); transform-origin: left top; - pointer-events:all; + pointer-events: all; } } diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index 7bf765042..271ff3cf8 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -5,10 +5,11 @@ import { computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; // import { BufferAttribute, Camera, Vector2, Vector3 } from 'three'; import { DateField } from '../../../fields/DateField'; -import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; +import { Doc } from '../../../fields/Doc'; +import { Height, Width } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, DocCast, NumCast } from '../../../fields/Types'; import { AudioField, VideoField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnFalse, returnOne, returnZero } from '../../../Utils'; @@ -24,6 +25,8 @@ import { FieldView, FieldViewProps } from './FieldView'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import './ScreenshotBox.scss'; import { VideoBox } from './VideoBox'; +import { SettingsManager } from '../../util/SettingsManager'; + declare class MediaRecorder { constructor(e: any, options?: any); // whatever MediaRecorder has } @@ -140,7 +143,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[WidthSym]() || 0) / aspect; + this.layoutDoc._height = (this.layoutDoc[Width]() || 0) / aspect; } }; @@ -222,7 +225,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl const aud_chunks: any = []; this._audioRec.ondataavailable = (e: any) => aud_chunks.push(e.data); this._audioRec.onstop = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(aud_chunks); + const [{ result }] = await Networking.UploadFilesToServer(aud_chunks.map((file: any) => ({ file }))); if (!(result instanceof Error)) { this.dataDoc[this.props.fieldKey + '-audio'] = new AudioField(result.accessPaths.agnostic.client); } @@ -233,9 +236,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '-recordingStart'] = new DateField(new Date())); this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data); this._videoRec.onstop = async (e: any) => { - console.log('screenshotbox: upload'); const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() }); - const [{ result }] = await Networking.UploadFilesToServer(file); + const [{ result }] = await Networking.UploadFilesToServer({ file }); this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000; if (!(result instanceof Error)) { // convert this screenshotBox into normal videoBox @@ -277,7 +279,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl dictationTextProto.mediaState = ComputedField.MakeFunction('self.recordingSource.mediaState'); this.dataDoc[this.fieldKey + '-dictation'] = dictationText; }; - videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[HeightSym]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[WidthSym]())) * this.props.PanelWidth(); + videoPanelHeight = () => (NumCast(this.dataDoc[this.fieldKey + '_nativeHeight'], this.layoutDoc[Height]()) / NumCast(this.dataDoc[this.fieldKey + '_nativeWidth'], this.layoutDoc[Width]())) * this.props.PanelWidth(); formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight()); render() { TraceMobx(); @@ -311,11 +313,11 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl </> </CollectionFreeFormView> </div> - <div style={{ background: 'white', position: 'relative', height: this.formattedPanelHeight() }}> + <div style={{ background: SettingsManager.Instance.userColor, position: 'relative', height: this.formattedPanelHeight() }}> {!(this.dataDoc[this.fieldKey + '-dictation'] instanceof Doc) ? null : ( <FormattedTextBox {...this.props} - Document={this.dataDoc[this.fieldKey + '-dictation']} + Document={DocCast(this.dataDoc[this.fieldKey + '-dictation'])} fieldKey={'text'} PanelHeight={this.formattedPanelHeight} select={emptyFunction} @@ -333,8 +335,8 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl </div> </div> {!this.props.isSelected() ? null : ( - <div className="screenshotBox-uiButtons"> - <div className="screenshotBox-recorder" key="snap" onPointerDown={this.toggleRecording}> + <div className="screenshotBox-uiButtons" style={{ background: SettingsManager.Instance.userColor }}> + <div className="screenshotBox-recorder" style={{ color: SettingsManager.Instance.userBackgroundColor, background: SettingsManager.Instance.userVariantColor }} key="snap" onPointerDown={this.toggleRecording}> <FontAwesomeIcon icon="file" size="lg" /> </div> </div> diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index 37fda14fc..7c8a1849e 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -270,8 +270,12 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable // sets field of the corresponding field key (param name) to be dropped document @action onDrop = (e: Event, de: DragManager.DropEvent, fieldKey: string) => { - Doc.SetInPlace(this.rootDoc, fieldKey, de.complete.docDragData?.droppedDocuments[0], true); - e.stopPropagation(); + if (de.complete.docDragData) { + de.complete.docDragData.droppedDocuments.forEach(doc => Doc.SetInPlace(this.rootDoc, fieldKey, doc, true)); + e.stopPropagation(); + return true; + } + return false; }; // deletes a param from all areas in which it is stored @@ -606,7 +610,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable ' ': { dataProvider: (token: any) => this.handleToken(token), component: (blob: any) => { - console.log('Blob', blob); return this.renderFuncListElement(blob.entity); }, output: (item: any, trigger: any) => { @@ -617,7 +620,6 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatable '.': { dataProvider: (token: any) => this.handleToken(token), component: (blob: any) => { - console.log('Blob', blob); return this.renderFuncListElement(blob.entity); }, output: (item: any, trigger: any) => { diff --git a/src/client/views/nodes/SliderBox.tsx b/src/client/views/nodes/SliderBox.tsx index b96977f32..430b20eb5 100644 --- a/src/client/views/nodes/SliderBox.tsx +++ b/src/client/views/nodes/SliderBox.tsx @@ -12,50 +12,56 @@ import { FieldView, FieldViewProps } from './FieldView'; import { Handle, Tick, TooltipRail, Track } from './SliderBox-components'; import './SliderBox.scss'; - @observer export class SliderBox extends ViewBoxBaseComponent<FieldViewProps>() { - public static LayoutString(fieldKey: string) { return FieldView.LayoutString(SliderBox, fieldKey); } + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(SliderBox, fieldKey); + } - get minThumbKey() { return this.fieldKey + "-minThumb"; } - get maxThumbKey() { return this.fieldKey + "-maxThumb"; } - get minKey() { return this.fieldKey + "-min"; } - get maxKey() { return this.fieldKey + "-max"; } + get minThumbKey() { + return this.fieldKey + '-minThumb'; + } + get maxThumbKey() { + return this.fieldKey + '-maxThumb'; + } + get minKey() { + return this.fieldKey + '-min'; + } + get maxKey() { + return this.fieldKey + '-max'; + } specificContextMenu = (e: React.MouseEvent): void => { const funcs: ContextMenuProps[] = []; - funcs.push({ description: "Edit Thumb Change Script", icon: "edit", event: (obj: any) => ScriptBox.EditButtonScript("On Thumb Change ...", this.props.Document, "onThumbChange", obj.x, obj.y) }); - ContextMenu.Instance.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" }); - } - onChange = (values: readonly number[]) => runInAction(() => { - this.dataDoc[this.minThumbKey] = values[0]; - this.dataDoc[this.maxThumbKey] = values[1]; - ScriptCast(this.layoutDoc.onThumbChanged, null)?.script.run({ - self: this.rootDoc, - scriptContext: this.props.scriptContext, range: values, this: this.layoutDoc + funcs.push({ description: 'Edit Thumb Change Script', icon: 'edit', event: (obj: any) => ScriptBox.EditButtonScript('On Thumb Change ...', this.props.Document, 'onThumbChange', obj.x, obj.y) }); + ContextMenu.Instance.addItem({ description: 'Options...', subitems: funcs, icon: 'asterisk' }); + }; + onChange = (values: readonly number[]) => + runInAction(() => { + this.dataDoc[this.minThumbKey] = values[0]; + this.dataDoc[this.maxThumbKey] = values[1]; + ScriptCast(this.layoutDoc.onThumbChanged, null)?.script.run({ + self: this.rootDoc, + scriptContext: this.props.scriptContext, + range: values, + this: this.layoutDoc, + }); }); - }) render() { const domain = [NumCast(this.layoutDoc[this.minKey]), NumCast(this.layoutDoc[this.maxKey])]; const defaultValues = [NumCast(this.dataDoc[this.minThumbKey]), NumCast(this.dataDoc[this.maxThumbKey])]; - return domain[1] <= domain[0] ? (null) : ( - <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()} - style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}> - <div className="sliderBox-mainButton" - onContextMenu={this.specificContextMenu} style={{ + return domain[1] <= domain[0] ? null : ( + <div className="sliderBox-outerDiv" onContextMenu={this.specificContextMenu} onPointerDown={e => e.stopPropagation()} style={{ boxShadow: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BoxShadow) }}> + <div + className="sliderBox-mainButton" + onContextMenu={this.specificContextMenu} + style={{ background: StrCast(this.layoutDoc.backgroundColor), - color: StrCast(this.layoutDoc.color, "black"), - fontSize: StrCast(this.layoutDoc._fontSize), letterSpacing: StrCast(this.layoutDoc.letterSpacing) - }} > - <Slider - mode={2} - step={Math.min(1, .1 * (domain[1] - domain[0]))} - domain={domain} - rootStyle={{ position: "relative", width: "100%" }} - onChange={this.onChange} - values={defaultValues} - > - + color: StrCast(this.layoutDoc.color, 'black'), + fontSize: StrCast(this.layoutDoc._text_fontSize), + letterSpacing: StrCast(this.layoutDoc.letterSpacing), + }}> + <Slider mode={2} step={Math.min(1, 0.1 * (domain[1] - domain[0]))} domain={domain} rootStyle={{ position: 'relative', width: '100%' }} onChange={this.onChange} values={defaultValues}> <Rail>{railProps => <TooltipRail {...railProps} />}</Rail> <Handles> {({ handles, activeHandleID, getHandleProps }) => ( @@ -64,13 +70,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps>() { const value = i === 0 ? defaultValues[0] : defaultValues[1]; return ( <div title={String(value)}> - <Handle - key={handle.id} - handle={handle} - domain={domain} - isActive={handle.id === activeHandleID} - getHandleProps={getHandleProps} - /> + <Handle key={handle.id} handle={handle} domain={domain} isActive={handle.id === activeHandleID} getHandleProps={getHandleProps} /> </div> ); })} @@ -81,13 +81,7 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps>() { {({ tracks, getTrackProps }) => ( <div className="slider-tracks"> {tracks.map(({ id, source, target }) => ( - <Track - key={id} - source={source} - target={target} - disabled={false} - getTrackProps={getTrackProps} - /> + <Track key={id} source={source} target={target} disabled={false} getTrackProps={getTrackProps} /> ))} </div> )} @@ -95,13 +89,8 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps>() { <Ticks count={5}> {({ ticks }) => ( <div className="slider-ticks"> - {ticks.map((tick) => ( - <Tick - key={tick.id} - tick={tick} - count={ticks.length} - format={(val: number) => val.toString()} - /> + {ticks.map(tick => ( + <Tick key={tick.id} tick={tick} count={ticks.length} format={(val: number) => val.toString()} /> ))} </div> )} @@ -111,4 +100,4 @@ export class SliderBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> ); } -}
\ No newline at end of file +} diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index e00cb8618..1f52c2d92 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -3,7 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, untracked } from 'mobx'; import { observer } from 'mobx-react'; import { basename } from 'path'; -import { Doc, HeightSym, StrListCast, WidthSym } from '../../../fields/Doc'; +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'; @@ -14,6 +15,7 @@ import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { Networking } from '../../Network'; import { DocumentManager } from '../../util/DocumentManager'; +import { FollowLinkScript } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ReplayMovements } from '../../util/ReplayMovements'; import { SelectionManager } from '../../util/SelectionManager'; @@ -33,8 +35,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import { RecordingBox } from './RecordingBox'; import { PinProps, PresBox } from './trails'; import './VideoBox.scss'; -import { ScriptField } from '../../../fields/ScriptField'; -import { FollowLinkScript } from '../../util/LinkFollower'; const path = require('path'); /** @@ -111,7 +111,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp // returns the presentation data if it exists, null otherwise @computed get presentation() { const data = this.dataDoc[this.fieldKey + '-presentation']; - return data ? JSON.parse(data) : null; + return data ? JSON.parse(StrCast(data)) : null; } @computed private get timeline() { @@ -354,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[WidthSym](); - this.dataDoc.icon_nativeHeight = this.layoutDoc[HeightSym](); + this.dataDoc.icon_nativeWidth = this.layoutDoc[Width](); + this.dataDoc.icon_nativeHeight = this.layoutDoc[Height](); }; this.Snapshot(undefined, undefined, makeIcon); }; @@ -465,7 +465,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp { fireImmediately: true } ); - (!this.dataDoc[this.fieldKey + '_thumbnails'] || this.dataDoc[this.fieldKey + '_thumbnails'].length != VideoBox.numThumbnails) && this.getVideoThumbnails(); + (!this.dataDoc[this.fieldKey + '_thumbnails'] || StrListCast(this.dataDoc[this.fieldKey + '_thumbnails']).length != VideoBox.numThumbnails) && this.getVideoThumbnails(); } }; @@ -870,6 +870,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp }), returnFalse, () => MarqueeAnnotator.clearAnnotations(this._savedAnnotations), + false, false ); } @@ -912,7 +913,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp marqueeFitScaling = () => ((this.props.NativeDimScaling?.() || 1) * this.heightPercent) / 100; marqueeOffset = () => [((this.panelWidth() / 2) * (1 - this.heightPercent / 100)) / (this.heightPercent / 100), 0]; - timelineDocFilter = () => [`_timelineLabel:true,${Utils.noRecursionHack}:x`]; + timelineDocFilter = () => [`_isTimelineLabel:true,${Utils.noRecursionHack}:x`]; // renders video controls componentUI = (boundsLeft: number, boundsTop: number) => { @@ -1026,13 +1027,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp croppingProto.videoCrop = true; croppingProto.layout_currentTimecode = this.layoutDoc._layout_currentTimecode; croppingProto.freeform_scale = viewScale; - croppingProto.freeform_scaleMin = viewScale; + croppingProto.freeform_scale_min = viewScale; croppingProto.freeform_ = anchx / viewScale; croppingProto.freeform_panY = anchy / viewScale; - croppingProto.freeform_panXMin = anchx / viewScale; - croppingProto.freeform_panXMax = anchw / viewScale; - croppingProto.freeform_panYMin = anchy / viewScale; - croppingProto.freeform_panYMax = anchh / viewScale; + croppingProto.freeform_panX_min = anchx / viewScale; + croppingProto.freeform_panX_max = anchw / viewScale; + croppingProto.freeform_panY_min = anchy / viewScale; + croppingProto.freeform_panY_max = anchh / viewScale; if (addCrop) { DocUtils.MakeLink(region, cropping, { link_relationship: 'cropped image' }); } @@ -1081,8 +1082,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp PanelHeight={this.panelHeight} isAnyChildContentActive={returnFalse} ScreenToLocalTransform={this.screenToLocalTransform} - docFilters={this.timelineDocFilter} + childFilters={this.timelineDocFilter} select={emptyFunction} + focus={emptyFunction} NativeDimScaling={returnOne} whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.removeDocument} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 3da5d8f17..f5df42161 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -2,16 +2,18 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as WebRequest from 'web-request'; -import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +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'; import { List } from '../../../fields/List'; +import { RefField } from '../../../fields/RefField'; import { listSpec } from '../../../fields/Schema'; import { Cast, ImageCast, NumCast, StrCast, WebCast } from '../../../fields/Types'; import { ImageField, WebField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, removeStyleSheetRule, returnFalse, returnOne, returnTrue, returnZero, setupMoveUpEvents, smoothScroll, StopEvent, Utils } from '../../../Utils'; +import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, getWordAtPoint, returnFalse, returnOne, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; @@ -37,7 +39,6 @@ import { LinkDocPreview } from './LinkDocPreview'; import { PinProps, PresBox } from './trails'; import './WebBox.scss'; import React = require('react'); -import { RefField } from '../../../fields/RefField'; const { CreateImage } = require('./WebBoxRenderer'); const _global = (window /* browser */ || global) /* node */ as any; const htmlToText = require('html-to-text'); @@ -84,7 +85,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps return this._url ? WebBox.urlHash(this._url) + '' : ''; } @computed get scrollHeight() { - return Math.max(this.layoutDoc[HeightSym](), this._scrollHeight); + return Math.max(this.layoutDoc[Height](), this._scrollHeight); } @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); @@ -124,11 +125,15 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._searchRef.current?.setRangeText(searchString); }); } - if (clear) { - this._iframe?.contentWindow?.getSelection()?.empty(); - } - if (searchString) { - (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true); + try { + if (clear) { + this._iframe?.contentWindow?.getSelection()?.empty(); + } + if (searchString) { + (this._iframe?.contentWindow as any)?.find(searchString, false, bwd, true); + } + } catch (e) { + console.log("WebBox search error", e) } return true; }; @@ -214,7 +219,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[WidthSym]() / youtubeaspect; + this.layoutDoc._height = this.layoutDoc[Width]() / youtubeaspect; } } // else it's an HTMLfield } else if (this.webField && !this.dataDoc.text) { @@ -281,7 +286,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[HeightSym](), NumCast(this.layoutDoc._layout_scrollTop), windowHeight, windowHeight * 0.1, Math.max(NumCast(anchor.y) + anchor[HeightSym](), this._scrollHeight)); + 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)); if (scrollTo !== undefined) { if (this._initialScroll === undefined) { const focusTime = options.zoomTime ?? 500; @@ -296,8 +301,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps @action getView = (doc: Doc) => { + if (Doc.AreProtosEqual(doc, this.rootDoc)) return new Promise<Opt<DocumentView>>(res => res(this.props.DocumentView?.())); if (this.rootDoc.layout_fieldKey === 'layout_icon') this.props.DocumentView?.().iconify(); - if (this._url && WebCast(doc.presData).url.href !== this._url) this.setData(WebCast(doc.presData).url.href); + const webUrl = WebCast(doc.presData)?.url; + if (this._url && webUrl && webUrl.href !== this._url) this.setData(webUrl.href); if (this._sidebarRef?.current?.makeDocUnfiltered(doc) && !this.SidebarShown) this.toggleSidebar(false); return new Promise<Opt<DocumentView>>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; @@ -320,10 +327,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps } catch (e) {} const anchor = this._getAnchor(this._savedAnnotations, false) ?? - Docs.Create.WebanchorDocument({ + Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop), y: NumCast(this.layoutDoc._layout_scrollTop), - unrendered: true, annotationOn: this.rootDoc, }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.rootDoc); @@ -459,7 +465,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps this._scrollHeight = Math.max(this._scrollHeight, (iframeContent.body.children[0] as any)?.scrollHeight || 0); if (this._scrollHeight) { this.rootDoc.nativeHeight = Math.min(NumCast(this.rootDoc.nativeHeight), this._scrollHeight); - this.layoutDoc.height = Math.min(this.layoutDoc[HeightSym](), (this.layoutDoc[WidthSym]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth)); + this.layoutDoc.height = Math.min(this.layoutDoc[Height](), (this.layoutDoc[Width]() * NumCast(this.rootDoc.nativeHeight)) / NumCast(this.rootDoc.nativeWidth)); } }; initHeights(); @@ -472,13 +478,13 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps 'click', undoBatch( action((e: MouseEvent) => { - const batch = UndoManager.StartBatch('webclick'); let href = ''; for (let ele = e.target as any; ele; ele = ele.parentElement) { href = (typeof ele.href === 'string' ? ele.href : ele.href?.baseVal) || ele.parentElement?.href || href; } const origin = this.webField?.origin; if (href && origin) { + const batch = UndoManager.StartBatch('webclick'); e.stopPropagation(); setTimeout(() => { this.setData(href.replace(Utils.prepend(''), origin)); @@ -509,7 +515,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps if (this._innerCollectionView) { this._innerCollectionView.zoom(e.screenX, e.screenY, e.deltaY); const offset = e.clientY - NumCast(this.layoutDoc._layout_scrollTop); - this.layoutDoc.panY = offset - offset / NumCast(this.layoutDoc._freeform_scale) + NumCast(this.layoutDoc._layout_scrollTop) - NumCast(this.layoutDoc._layout_scrollTop) / NumCast(this.layoutDoc._freeform_scale); + this.layoutDoc.freeform_panY = offset - offset / NumCast(this.layoutDoc._freeform_scale) + NumCast(this.layoutDoc._layout_scrollTop) - NumCast(this.layoutDoc._layout_scrollTop) / NumCast(this.layoutDoc._freeform_scale); } e.preventDefault(); } @@ -744,6 +750,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ref={action((r: HTMLIFrameElement | null) => (this._iframe = r))} 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. // the 'allow-top-navigation' and 'allow-top-navigation-by-user-activation' attributes are left out to prevent iframes from redirecting the top-level Dash page // sandbox={"allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin allow-scripts"} />; sandbox={`${this.layoutDoc.allowScripts ? 'allow-scripts' : ''} allow-forms allow-modals allow-orientation-lock allow-pointer-lock allow-popups allow-popups-to-escape-sandbox allow-presentation allow-same-origin`} @@ -782,7 +789,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[WidthSym]() + localDelta[0]); + onButton && (this.layoutDoc._width = this.layoutDoc[Width]() + localDelta[0]); this.layoutDoc._layout_showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; } return false; @@ -818,9 +825,9 @@ 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[WidthSym](); + const defaultNativeWidth = this.rootDoc[this.fieldKey] instanceof WebField ? 850 : this.Document[Width](); Doc.SetNativeWidth(this.dataDoc, Doc.NativeWidth(this.dataDoc) || defaultNativeWidth); - Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[HeightSym]() / this.Document[WidthSym]()) * defaultNativeWidth); + Doc.SetNativeHeight(this.dataDoc, Doc.NativeHeight(this.dataDoc) || (this.Document[Height]() / 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; @@ -828,11 +835,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); if (preview) { this._previewNativeWidth = nativeWidth * sideratio; - this._previewWidth = (this.layoutDoc[WidthSym]() * nativeWidth * sideratio) / curNativeWidth; + this._previewWidth = (this.layoutDoc[Width]() * nativeWidth * sideratio) / curNativeWidth; this._showSidebar = true; } else { this.layoutDoc._layout_showSidebar = !this.layoutDoc._layout_showSidebar; - this.layoutDoc._width = (this.layoutDoc[WidthSym]() * nativeWidth * pdfratio) / curNativeWidth; + this.layoutDoc._width = (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 { @@ -897,7 +904,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps const previewScale = this._previewNativeWidth ? 1 - this.sidebarWidth() / this._previewNativeWidth : 1; const pointerEvents = this.layoutDoc._lockedPosition ? 'none' : (this.props.pointerEvents?.() as any); const scale = previewScale * (this.props.NativeDimScaling?.() || 1); - const renderAnnotations = (docFilters: () => string[]) => ( + const renderAnnotations = (childFilters: () => string[]) => ( <CollectionFreeFormView {...this.props} setContentView={this.setInnerContent} @@ -915,8 +922,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps ScreenToLocalTransform={this.scrollXf} NativeDimScaling={returnOne} focus={this.focus} - dropAction="embed" - docFilters={docFilters} + childFilters={childFilters} select={emptyFunction} isAnyChildContentActive={returnFalse} bringToFront={emptyFunction} @@ -924,7 +930,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} - addDocument={this.addDocument} + addDocument={this.addDocumentWrapper} childPointerEvents={this.props.isContentActive() ? 'all' : undefined} pointerEvents={this.annotationPointerEvents} /> @@ -992,16 +998,19 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps 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.docFilters(), Utils.IsTransparentFilter()]; - opaqueFilter = () => [...this.props.docFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; + transparentFilter = () => [...this.props.childFilters(), Utils.IsTransparentFilter()]; + opaqueFilter = () => [...this.props.childFilters(), Utils.noDragsDocFilter, ...(DragManager.docsBeingDragged.length ? [] : [Utils.IsOpaqueFilter()])]; childStyleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string): any => { if (doc instanceof Doc && property === StyleProp.PointerEvents) { - if (doc.textInlineAnnotations) return 'none'; + if (this.inlineTextAnnotations.includes(doc)) return 'none'; } return this.props.styleProvider?.(doc, props, property); }; - pointerEvents = () => (!this._draggingSidebar && this.props.isContentActive() && this.props.pointerEvents?.() !== 'none' && !MarqueeOptionsMenu.Instance?.isShown() ? 'all' : SnappingManager.GetIsDragging() ? undefined : 'none'); - annotationPointerEvents = () => (this._isAnnotating || SnappingManager.GetIsDragging() || Doc.ActiveTool !== InkTool.None ? 'all' : 'none'); + pointerEvents = () => + !this._draggingSidebar && this.props.isContentActive() && !MarqueeOptionsMenu.Instance?.isShown() + ? 'all' // + : 'none'; + annotationPointerEvents = () => (this.props.isContentActive() && (this._isAnnotating || 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); @@ -1010,7 +1019,11 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps <div className="webBox" ref={this._mainCont} - style={{ pointerEvents: this.pointerEvents(), position: SnappingManager.GetIsDragging() ? 'absolute' : undefined, display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined }}> + style={{ + pointerEvents: this.pointerEvents(), // + position: SnappingManager.GetIsDragging() ? 'absolute' : undefined, + display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined, + }}> <div className="webBox-background" style={{ backgroundColor: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor) }} /> <div className="webBox-container" diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx deleted file mode 100644 index 3b5aac221..000000000 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import { observer } from "mobx-react"; -import * as React from "react"; -import { AclPrivate, Doc, DocListCast } from "../../../../fields/Doc"; -import { GetEffectiveAcl } from "../../../../fields/util"; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils"; -import { DragManager } from "../../../util/DragManager"; -import "./FontIconBadge.scss"; - -interface FontIconBadgeProps { - value: string | undefined; -} - -@observer -export class FontIconBadge extends React.Component<FontIconBadgeProps> { - _notifsRef = React.createRef<HTMLDivElement>(); - - // onPointerDown = (e: React.PointerEvent) => { - // setupMoveUpEvents(this, e, - // (e: PointerEvent) => { - // const dragData = new DragManager.DocumentDragData([this.props.collection!]); - // DragManager.StartDocumentDrag([this._notifsRef.current!], dragData, e.x, e.y); - // return true; - // }, - // returnFalse, emptyFunction, false); - // } - - render() { - if (this.props.value === undefined) return (null); - return <div className="fontIconBadge-container" ref={this._notifsRef}> - <div className="fontIconBadge" style={{ "display": "initial" }} - // onPointerDown={this.onPointerDown} - > - {this.props.value} - </div> - </div>; - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx deleted file mode 100644 index 5e615f2c1..000000000 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ /dev/null @@ -1,960 +0,0 @@ -import { IconProp } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Tooltip } from '@material-ui/core'; -import { action, computed, observable, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { ColorState, SketchPicker } from 'react-color'; -import { Doc, HeightSym, StrListCast, WidthSym } from '../../../../fields/Doc'; -import { InkTool } from '../../../../fields/InkField'; -import { ScriptField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { WebField } from '../../../../fields/URLField'; -import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, Utils } from '../../../../Utils'; -import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; -import { LinkManager } from '../../../util/LinkManager'; -import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; -import { SelectionManager } from '../../../util/SelectionManager'; -import { undoBatch, UndoManager } from '../../../util/UndoManager'; -import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; -import { ContextMenu } from '../../ContextMenu'; -import { DocComponent } from '../../DocComponent'; -import { EditableView } from '../../EditableView'; -import { GestureOverlay } from '../../GestureOverlay'; -import { Colors } from '../../global/globalEnums'; -import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../../InkingStroke'; -import { InkTranscription } from '../../InkTranscription'; -import { StyleProp } from '../../StyleProvider'; -import { FieldView, FieldViewProps } from '.././FieldView'; -import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; -import { OpenWhere } from '../DocumentView'; -import { RichTextMenu } from '../formattedText/RichTextMenu'; -import { WebBox } from '../WebBox'; -import { FontIconBadge } from './FontIconBadge'; -import './FontIconBox.scss'; - -export enum ButtonType { - TextButton = 'textBtn', - MenuButton = 'menuBtn', - DropdownList = 'drpdownList', - DropdownButton = 'drpdownBtn', - ClickButton = 'clickBtn', - DoubleButton = 'dblBtn', - ToggleButton = 'tglBtn', - ColorButton = 'colorBtn', - ToolButton = 'toolBtn', - NumberSliderButton = 'numSliderBtn', - NumberDropdownButton = 'numDropdownBtn', - NumberInlineButton = 'numInlineBtn', - EditableText = 'editableText', -} - -export interface ButtonProps extends FieldViewProps { - type?: ButtonType; -} -@observer -export class FontIconBox extends DocComponent<ButtonProps>() { - public static LayoutString(fieldKey: string) { - return FieldView.LayoutString(FontIconBox, fieldKey); - } - @observable noTooltip = false; - showTemplate = (): void => { - const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); - dragFactory && this.props.addDocTab(dragFactory, OpenWhere.addRight); - }; - dragAsTemplate = (): void => { - this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); - }; - useAsPrototype = (): void => { - this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); - }; - - specificContextMenu = (): void => { - if (!Doc.noviceMode) { - const cm = ContextMenu.Instance; - cm.addItem({ description: 'Show Template', event: this.showTemplate, icon: 'tag' }); - cm.addItem({ description: 'Use as Render Template', event: this.dragAsTemplate, icon: 'tag' }); - cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' }); - } - }; - - static GetShowLabels() { - return BoolCast(Doc.UserDoc()._showLabel); - } - static SetShowLabels(show: boolean) { - Doc.UserDoc()._showLabel = show; - } - static GetRecognizeGestures() { - return BoolCast(Doc.UserDoc()._recognizeGestures); - } - static SetRecognizeGestures(show: boolean) { - Doc.UserDoc()._recognizeGestures = show; - } - - // Determining UI Specs - @computed get label() { - return StrCast(this.rootDoc.label, StrCast(this.rootDoc.title)); - } - Icon = (color: string) => { - const icon = StrCast(this.dataDoc[this.fieldKey ?? 'icon'] ?? this.dataDoc.icon, 'user') as any; - const trailsIcon = () => <img src={`/assets/${'presTrails.png'}`} style={{ width: 30, height: 30, filter: `invert(${color === Colors.DARK_GRAY ? '0%' : '100%'})` }} />; - return !icon ? null : icon === 'pres-trail' ? trailsIcon() : <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />; - }; - @computed get dropdown() { - return BoolCast(this.rootDoc.dropDownOpen); - } - @computed get buttonList() { - return StrListCast(this.rootDoc.btnList); - } - @computed get type() { - return StrCast(this.rootDoc.btnType); - } - - /** - * Types of buttons in dash: - * - Main menu button (LHS) - * - Tool button - * - Expandable button (CollectionLinearView) - * - Button inside of CollectionLinearView vs. outside of CollectionLinearView - * - Action button - * - Dropdown button - * - Color button - * - Dropdown list - * - Number button - **/ - - _batch: UndoManager.Batch | undefined = undefined; - /** - * Number button - */ - @computed get numberSliderButton() { - const numScript = ScriptCast(this.rootDoc.script); - const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value'); - - // Script for checking the outcome of the toggle - const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); - - const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>; - - const dropdown = ( - <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()}> - <input - type="range" - step="1" - min={NumCast(this.rootDoc.numBtnMin, 0)} - max={NumCast(this.rootDoc.numBtnMax, 100)} - value={checkResult} - className="menu-slider" - onPointerDown={() => (this._batch = UndoManager.StartBatch('presDuration'))} - onPointerUp={() => this._batch?.end()} - onChange={e => { - e.stopPropagation(); - setValue(Number(e.target.value)); - }} - /> - </div> - ); - return ( - <div - className="menuButton numBtn slider" - onPointerDown={e => e.stopPropagation()} - onClick={action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - })}> - {checkResult} - {label} - {this.rootDoc.dropDownOpen ? dropdown : null} - </div> - ); - } - /** - * Number button - */ - @computed get numberDropdownButton() { - const numScript = ScriptCast(this.rootDoc.script); - const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value'); - - // Script for checking the outcome of the toggle - const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); - - const label = !FontIconBox.GetShowLabels() ? null : <div className="fontIconBox-label">{this.label}</div>; - - const items: number[] = []; - for (let i = 0; i < 100; i++) { - if (i % 2 === 0) { - items.push(i); - } - } - const list = items.map(value => { - return ( - <div - className="list-item" - key={`${value}`} - style={{ - backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined, - }} - onClick={() => setValue(value)}> - {value} - </div> - ); - }); - return ( - <div className="menuButton numBtn list"> - <div className="button" onClick={action(e => setValue(Number(checkResult) - 1))}> - <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'minus'} /> - </div> - <div - className={`button ${'number'}`} - onPointerDown={e => { - e.stopPropagation(); - e.preventDefault(); - }} - onClick={action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - })}> - <input style={{ width: 30 }} className="button-input" type="number" value={checkResult} onChange={undoBatch(action(e => setValue(Number(e.target.value))))} /> - </div> - <div className={`button`} onClick={action(e => setValue(Number(checkResult) + 1))}> - <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'plus'} /> - </div> - - {this.rootDoc.dropDownOpen ? ( - <div> - <div className="menuButton-dropdownList" style={{ left: '25%' }}> - {list} - </div> - <div - className="dropbox-background" - onClick={action(e => { - e.stopPropagation(); - this.rootDoc.dropDownOpen = false; - this.noTooltip = false; - Doc.UnBrushAllDocs(); - })} - /> - </div> - ) : null} - </div> - ); - } - /** - * Number button - */ - @computed get numberInlineButton() { - return <div />; - } - - /** - * Dropdown button - */ - @computed get dropdownButton() { - const active: string = StrCast(this.rootDoc.dropDownOpen); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - return ( - <div - className={`menuButton ${this.type} ${active}`} - style={{ color: color, backgroundColor: backgroundColor, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} - onClick={action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - })}> - {this.Icon(color)} - {!this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> - {' '} - {this.label}{' '} - </div> - )} - <div className="menuButton-dropdown" style={{ borderBottomRightRadius: this.dropdown ? 0 : undefined }}> - <FontAwesomeIcon icon={'caret-down'} color={color} size="sm" /> - </div> - {this.rootDoc.dropDownOpen ? <div className="menuButton-dropdownBox">{/* DROPDOWN BOX CONTENTS */}</div> : null} - </div> - ); - } - - /** - * Dropdown list - */ - @computed get dropdownListButton() { - const active: string = StrCast(this.rootDoc.dropDownOpen); - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - - const script = ScriptCast(this.rootDoc.script); - - let noviceList: string[] = []; - let text: string | undefined; - let dropdown = true; - let icon: IconProp = 'caret-down'; - try { - if (script?.script.originalScript.startsWith('setView')) { - const selected = SelectionManager.Docs().lastElement(); - if (selected) { - if (StrCast(selected.type) === DocumentType.COL) { - text = StrCast(selected._viewType); - } else { - dropdown = false; - text = selected.type === DocumentType.RTF ? 'Text' : StrCast(selected.type); - icon = Doc.toIcon(selected); - } - } else { - dropdown = false; - icon = 'globe-asia'; - text = 'User Default'; - } - noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking]; - } else text = StrCast((RichTextMenu.Instance?.TextView?.EditorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - } catch (e) { - console.log(e); - } - - // Get items to place into the list - const list = this.buttonList - .filter(value => !Doc.noviceMode || !noviceList.length || noviceList.includes(value)) - .map(value => ( - <div - className="list-item" - key={`${value}`} - style={{ - fontFamily: script.script.originalScript.startsWith('{ return setFont') ? value : undefined, - backgroundColor: value === text ? Colors.LIGHT_BLUE : undefined, - }} - onClick={undoBatch(() => script.script.run({ self: this.rootDoc, value }))}> - {value[0].toUpperCase() + value.slice(1)} - </div> - )); - - const label = - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ bottom: 0, position: 'absolute', color: color, backgroundColor: backgroundColor }}> - {this.label} - </div> - ); - - return ( - <div - className={`menuButton ${this.type} ${active}`} - style={{ backgroundColor: this.rootDoc.dropDownOpen ? Colors.MEDIUM_BLUE : backgroundColor, color: color, display: dropdown ? undefined : 'flex' }} - onClick={ - dropdown - ? action(() => { - this.rootDoc.dropDownOpen = !this.rootDoc.dropDownOpen; - this.noTooltip = this.rootDoc.dropDownOpen; - Doc.UnBrushAllDocs(); - }) - : undefined - }> - {dropdown ? null : <FontAwesomeIcon style={{ marginLeft: 5 }} className={`fontIconBox-icon-${this.type}`} icon={icon} color={color} />} - <div className="menuButton-dropdown-header">{text && text[0].toUpperCase() + text.slice(1)}</div> - {label} - {!dropdown ? null : ( - <div className="menuButton-dropDown"> - <FontAwesomeIcon icon={icon} color={color} size="sm" /> - </div> - )} - {this.rootDoc.dropDownOpen ? ( - <div> - <div className="menuButton-dropdownList" style={{ left: 0 }}> - {list} - </div> - <div - className="dropbox-background" - onClick={action(e => { - e.stopPropagation(); - this.rootDoc.dropDownOpen = false; - this.noTooltip = false; - Doc.UnBrushAllDocs(); - })} - /> - </div> - ) : null} - </div> - ); - } - - @observable colorPickerClosed: boolean = true; - @computed get colorScript() { - return ScriptCast(this.rootDoc.script); - } - - colorPicker = (curColor: string) => { - const change = (value: ColorState, ev: MouseEvent) => { - ev.preventDefault(); - ev.stopPropagation(); - const s = this.colorScript; - s && undoBatch(() => s.script.run({ self: this.rootDoc, value: Utils.colorString(value), _readOnly_: false }).result)(); - }; - const presets = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']; - return <SketchPicker onChange={change as any /* SketchPicker passes the mouse event to the callback, but the type system doesn't know that */} color={curColor} presetColors={presets} />; - }; - /** - * Color button - */ - @computed get colorButton() { - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const curColor = this.colorScript?.script.run({ self: this.rootDoc, value: undefined, _readOnly_: true }).result ?? 'transparent'; - - const label = - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color, backgroundColor }}> - {this.label} - </div> - ); - - return ( - <div - className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')} ${this.colorPickerClosed}`} - style={{ color: color, borderBottomLeftRadius: this.dropdown ? 0 : undefined }} - onClick={action(e => { - this.colorPickerClosed = !this.colorPickerClosed; - this.noTooltip = !this.colorPickerClosed; - setTimeout(() => Doc.UnBrushAllDocs()); - e.stopPropagation(); - })} - onPointerDown={e => e.stopPropagation()}> - {this.Icon(color)} - <div className="colorButton-color" style={{ backgroundColor: curColor }} /> - {label} - {/* {dropdownCaret} */} - {this.colorPickerClosed ? null : ( - <div> - <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> - {this.colorPicker(curColor)} - </div> - <div - className="dropbox-background" - onPointerDown={action(e => { - e.preventDefault(); - e.stopPropagation(); - this.colorPickerClosed = true; - this.noTooltip = false; - Doc.UnBrushAllDocs(); - })} - /> - </div> - )} - </div> - ); - } - - @computed get toggleButton() { - // Determine the type of toggle button - const switchToggle: boolean = BoolCast(this.rootDoc.switchToggle); - const buttonText: string = StrCast(this.rootDoc.buttonText); - // Colors - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - - // Button label - const label = - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color, backgroundColor }}> - {this.label} - </div> - ); - - if (switchToggle) { - return ( - <div className={`menuButton ${this.type} ${'switch'}`}> - {buttonText ? buttonText : null} - <label className="switch"> - <input type="checkbox" checked={backgroundColor === Colors.MEDIUM_BLUE} /> - <span className="slider round" /> - </label> - </div> - ); - } else { - return ( - <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ opacity: 1, backgroundColor, color }}> - {this.Icon(color)} - {label} - </div> - ); - } - } - - /** - * Default - */ - @computed get defaultButton() { - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - return ( - <div className={`menuButton ${this.type}`} onContextMenu={this.specificContextMenu} style={{ backgroundColor: 'transparent', borderBottomLeftRadius: this.dropdown ? 0 : undefined }}> - <div className="menuButton-wrap"> - {this.Icon(color)} - {!this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color: color, backgroundColor: backgroundColor }}> - {' '} - {this.label}{' '} - </div> - )} - </div> - </div> - ); - } - - @computed get editableText() { - // Script for running the toggle - const script = ScriptCast(this.rootDoc.script); - // Function to run the script - const checkResult = script?.script.run({ value: '', _readOnly_: true }).result; - - const setValue = (value: string, shiftDown?: boolean): boolean => script?.script.run({ value, _readOnly_: false }).result; - return ( - <div className="menuButton editableText"> - <FontAwesomeIcon className={`fontIconBox-icon-${this.type}`} icon={'lock'} /> - <div style={{ width: 'calc(100% - .875em)', paddingLeft: '4px' }}> - <EditableView GetValue={() => script?.script.run({ value: '', _readOnly_: true }).result} SetValue={setValue} oneLine={true} contents={checkResult} /> - </div> - </div> - ); - } - - render() { - const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); - const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); - const label = (noBackground: boolean = false) => - !this.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color, backgroundColor: noBackground ? 'transparent' : backgroundColor }}> - {this.label} - </div> - ); - // TODO:glr Add label of button type - let button: JSX.Element = this.defaultButton; - - // prettier-ignore - switch (this.type) { - case ButtonType.EditableText: return this.editableText; - case ButtonType.DropdownList: button = this.dropdownListButton; break; - case ButtonType.ColorButton: button = this.colorButton; break; - case ButtonType.NumberDropdownButton: button = this.numberDropdownButton; break; - case ButtonType.NumberInlineButton: button = this.numberInlineButton; break; - case ButtonType.NumberSliderButton: button = this.numberSliderButton; break; - case ButtonType.DropdownButton: button = this.dropdownButton; break; - case ButtonType.ToggleButton: button = this.toggleButton; break; - case ButtonType.TextButton: - // Script for checking the outcome of the toggle - const script = ScriptCast(this.rootDoc.script); - const checkResult = script?.script.run({ _readOnly_: true }).result; - button = ( - <div className={`menuButton ${this.type}`} style={{ color, backgroundColor:checkResult ?? backgroundColor, opacity: 1, gridAutoColumns: `${NumCast(this.rootDoc._height)} auto` }}> - {this.Icon(color)} - {StrCast(this.rootDoc.buttonText) ? <div className="button-text">{StrCast(this.rootDoc.buttonText)}</div> : null} - {label()} - </div> - ); - break; - case ButtonType.ClickButton: - case ButtonType.ToolButton: button = ( - <div className={`menuButton ${this.type + (FontIconBox.GetShowLabels() ? 'Label' : '')}`} style={{ backgroundColor, color, opacity: 1 }}> - {this.Icon(color)} - {label()} - </div> - ); - break; - case ButtonType.MenuButton: button = ( - <div className={`menuButton ${this.type}`} style={{ color, backgroundColor }}> - {this.Icon(color)} - {label(true)} - <FontIconBadge value={Cast(this.Document.badgeValue, 'string', null)} /> - </div> - ); - break; - } - - return !this.layoutDoc.toolTip || this.noTooltip ? button : <Tooltip title={<div className="dash-tooltip">{StrCast(this.layoutDoc.toolTip)}</div>}>{button}</Tooltip>; - } -} - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setView(view: string) { - const selected = SelectionManager.Docs().lastElement(); - selected ? (selected._viewType = view) : console.log('[FontIconBox.tsx] changeView failed'); -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: boolean) { - const selectedViews = SelectionManager.Views(); - if (Doc.ActiveTool !== InkTool.None) { - if (checkResult) { - return ActiveFillColor(); - } - SetActiveFillColor(color ?? 'transparent'); - } else if (selectedViews.length) { - if (checkResult) { - const selView = selectedViews.lastElement(); - const fieldKey = selView.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; - const layoutFrameNumber = Cast(selView.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values - const contentFrameNumber = Cast(selView.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - return CollectionFreeFormDocumentView.getStringValues(selView?.rootDoc, contentFrameNumber)[fieldKey] ?? 'transparent'; - } - selectedViews.forEach(dv => { - const fieldKey = dv.rootDoc.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; - const layoutFrameNumber = Cast(dv.props.docViewPath().lastElement()?.rootDoc?._currentFrame, 'number'); // frame number that container is at which determines layout frame values - const contentFrameNumber = Cast(dv.rootDoc?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - if (contentFrameNumber !== undefined) { - CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color }); - } else { - dv.rootDoc['_' + fieldKey] = color; - } - }); - } else { - const selected = SelectionManager.Docs().length ? SelectionManager.Docs() : LinkManager.currentLink ? [LinkManager.currentLink] : []; - if (checkResult) { - return selected.lastElement()?._backgroundColor ?? 'transparent'; - } - selected.forEach(doc => (doc._backgroundColor = color)); - } -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { - if (checkResult) { - return Doc.SharingDoc().userColor; - } - Doc.SharingDoc().userColor = undefined; - Doc.GetProto(Doc.SharingDoc()).userColor = color; - Doc.UserDoc().layout_showTitle = color === 'transparent' ? undefined : StrCast(Doc.UserDoc().layout_showTitle, 'creationDate'); -}); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { - const selected = SelectionManager.Views().length ? SelectionManager.Views()[0] : undefined; - if (checkResult) { - if (NumCast(selected?.Document.z) >= 1) return Colors.MEDIUM_BLUE; - return 'transparent'; - } - selected ? selected.props.CollectionFreeFormDocumentView?.().float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); -}); - -ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - // prettier-ignore - const map: Map<'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ - ['grid', { - undo: false, - checkResult: (doc:Doc) => doc._freeform_backgroundGrid, - setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, - }], - ['snaplines', { - undo: false, - checkResult: (doc:Doc) => doc._freeform_snapLines, - setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines, - }], - ['viewAll', { - undo: false, - checkResult: (doc:Doc) => doc._freeform_fitContentsToBox, - setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox, - }], - ['clusters', { - undo: false, - checkResult: (doc:Doc) => doc._freeform_useClusters, - setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters, - }], - ['arrange', { - undo: true, - checkResult: (doc:Doc) => doc._autoArrange, - setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange, - }], - ]); - - if (checkResult) { - return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent'; - } - const batch = map.get(attr)?.undo ? UndoManager.StartBatch('set feature') : { end: () => {} }; - SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv)); - setTimeout(() => batch.end(), 100); -}); -ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highlight' | 'fontSize', value: any, checkResult?: boolean) { - const editorView = RichTextMenu.Instance?.TextView?.EditorView; - const selected = SelectionManager.Docs().lastElement(); - // prettier-ignore - const map: Map<'font'|'fontColor'|'highlight'|'fontSize', { checkResult: () => any; setDoc: () => void;}> = new Map([ - ['font', { - checkResult: () => RichTextMenu.Instance?.fontFamily, - setDoc: () => value && RichTextMenu.Instance.setFontFamily(value), - }], - ['highlight', { - checkResult: () =>(selected ?? Doc.UserDoc())._fontHighlight, - setDoc: () => value && RichTextMenu.Instance.setHighlight(value), - }], - ['fontColor', { - checkResult: () => RichTextMenu.Instance?.fontColor, - setDoc: () => value && RichTextMenu.Instance.setColor(value), - }], - ['fontSize', { - checkResult: () => RichTextMenu.Instance?.fontSize.replace('px', ''), - setDoc: () => { - if (typeof value === 'number') value = value.toString(); - if (value && Number(value).toString() === value) value += 'px'; - RichTextMenu.Instance.setFontSize(value); - }, - }], - ]); - - if (checkResult) { - return map.get(attr)?.checkResult(); - } - map.get(attr)?.setDoc?.(); -}); - -type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'bullet' | 'decimal'; -type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; -ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { - const textView = RichTextMenu.Instance?.TextView; - const editorView = textView?.EditorView; - // prettier-ignore - const alignments:attrfuncs[] = (['left','right','center'] as ("left"|"center"|"right")[]).map((where) => - [ where, { checkResult: () =>(editorView ? (RichTextMenu.Instance.textAlign ===where): (Doc.UserDoc().textAlign ===where) ? true:false), - toggle: () => (editorView?.state ? RichTextMenu.Instance.align(editorView, editorView.dispatch, where):(Doc.UserDoc().textAlign = where))}]); - // prettier-ignore - const listings:attrfuncs[] = (['bullet','decimal'] as attrname[]).map(list => - [ list, { checkResult: () => (editorView ? RichTextMenu.Instance.getActiveListStyle() === list:false), - toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]); - // prettier-ignore - const attrs:attrfuncs[] = [ - ['dictation', { checkResult: () => textView?._recording ? true:false, - toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }], - ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), - toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], - ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleBold : () => (Doc.UserDoc().fontWeight = Doc.UserDoc().fontWeight === 'bold' ? undefined : 'bold')}], - ['italics', { checkResult: () => (editorView ? RichTextMenu.Instance.italics : (Doc.UserDoc().fontStyle === 'italics') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleItalics : () => (Doc.UserDoc().fontStyle = Doc.UserDoc().fontStyle === 'italics' ? undefined : 'italics')}], - ['underline', { checkResult: () => (editorView ? RichTextMenu.Instance.underline : (Doc.UserDoc().textDecoration === 'underline') ? true:false), - toggle: editorView ? RichTextMenu.Instance.toggleUnderline : () => (Doc.UserDoc().textDecoration = Doc.UserDoc().textDecoration === 'underline' ? undefined : 'underline') }]] - - const map = new Map(attrs.concat(alignments).concat(listings)); - if (checkResult) return map.get(charStyle)?.checkResult() ? Colors.MEDIUM_BLUE : 'transparent'; - map.get(charStyle)?.toggle(); -}); - -export function checkInksToGroup() { - // console.log("getting here to inks group"); - if (Doc.ActiveTool === InkTool.Write) { - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those - // find all inkDocs in ffView.unprocessedDocs that are within 200 pixels of each other - const inksToGroup = ffView.unprocessedDocs.filter(inkDoc => { - // console.log(inkDoc.x, inkDoc.y); - }); - }); - } -} - -export function createInkGroup(inksToGroup?: Doc[], isSubGroup?: boolean) { - // TODO nda - if document being added to is a inkGrouping then we can just add to that group - if (Doc.ActiveTool === InkTool.Write) { - CollectionFreeFormView.collectionsWithUnprocessedInk.forEach(ffView => { - // TODO: nda - will probably want to go through ffView unprocessed docs and then see if any of the inksToGroup docs are in it and only use those - const selected = ffView.unprocessedDocs; - // loop through selected an get the bound - const bounds: { x: number; y: number; width?: number; height?: number }[] = []; - - selected.map( - action(d => { - const x = NumCast(d.x); - const y = NumCast(d.y); - const width = d[WidthSym](); - const height = d[HeightSym](); - bounds.push({ x, y, width, height }); - }) - ); - - const aggregBounds = aggregateBounds(bounds, 0, 0); - const marqViewRef = ffView._marqueeViewRef.current; - - // set the vals for bounds in marqueeView - if (marqViewRef) { - marqViewRef._downX = aggregBounds.x; - marqViewRef._downY = aggregBounds.y; - marqViewRef._lastX = aggregBounds.r; - marqViewRef._lastY = aggregBounds.b; - } - - selected.map( - action(d => { - const dx = NumCast(d.x); - const dy = NumCast(d.y); - delete d.x; - delete d.y; - delete d.activeFrame; - delete d._timecodeToShow; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - delete d._timecodeToHide; // bcz: this should be automatic somehow.. along with any other properties that were logically associated with the original collection - // calculate pos based on bounds - if (marqViewRef?.Bounds) { - d.x = dx - marqViewRef.Bounds.left - marqViewRef.Bounds.width / 2; - d.y = dy - marqViewRef.Bounds.top - marqViewRef.Bounds.height / 2; - } - return d; - }) - ); - ffView.props.removeDocument?.(selected); - // TODO: nda - this is the code to actually get a new grouped collection - const newCollection = marqViewRef?.getCollection(selected, undefined, true); - if (newCollection) { - newCollection.height = newCollection[HeightSym](); - newCollection.width = newCollection[WidthSym](); - } - - // nda - bug: when deleting a stroke before leaving writing mode, delete the stroke from unprocessed ink docs - newCollection && ffView.props.addDocument?.(newCollection); - // TODO: nda - will probably need to go through and only remove the unprocessed selected docs - ffView.unprocessedDocs = []; - - InkTranscription.Instance.transcribeInk(newCollection, selected, false); - }); - } - CollectionFreeFormView.collectionsWithUnprocessedInk.clear(); -} - -function setActiveTool(tool: InkTool | GestureUtils.Gestures, keepPrim: boolean, checkResult?: boolean) { - InkTranscription.Instance?.createInkGroup(); - if (checkResult) { - return (Doc.ActiveTool === tool && !GestureOverlay.Instance?.InkShape) || GestureOverlay.Instance?.InkShape === tool - ? GestureOverlay.Instance?.KeepPrimitiveMode || ![GestureUtils.Gestures.Circle, GestureUtils.Gestures.Line, GestureUtils.Gestures.Rectangle].includes(tool as GestureUtils.Gestures) - ? Colors.MEDIUM_BLUE - : Colors.MEDIUM_BLUE_ALT - : 'transparent'; - } - runInAction(() => { - if (GestureOverlay.Instance) { - GestureOverlay.Instance.KeepPrimitiveMode = keepPrim; - } - if (Object.values(GestureUtils.Gestures).includes(tool as any)) { - if (GestureOverlay.Instance.InkShape === tool && !keepPrim) { - Doc.ActiveTool = InkTool.None; - GestureOverlay.Instance.InkShape = undefined; - } else { - Doc.ActiveTool = InkTool.Pen; - GestureOverlay.Instance.InkShape = tool as GestureUtils.Gestures; - } - } else if (tool) { - // pen or eraser - if (Doc.ActiveTool === tool && !GestureOverlay.Instance.InkShape && !keepPrim) { - Doc.ActiveTool = InkTool.None; - } else { - Doc.ActiveTool = tool as any; - GestureOverlay.Instance.InkShape = undefined; - } - } else { - Doc.ActiveTool = InkTool.None; - } - }); -} - -ScriptingGlobals.add(setActiveTool, 'sets the active ink tool mode'); - -// toggle: Set overlay status of selected document -ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', value: any, checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - // prettier-ignore - const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ - ['inkMask', { - checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask()) ? Colors.MEDIUM_BLUE : 'transparent'), - setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask), - setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), - }], - ['fillColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"), - setInk: (doc: Doc) => (doc.fillColor = StrCast(value)), - setMode: () => SetActiveFillColor(StrCast(value)), - }], - [ 'strokeWidth', { - checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()), - setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)), - setMode: () => SetActiveInkWidth(value.toString()), - }], - ['strokeColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()), - setInk: (doc: Doc) => (doc.color = String(value)), - setMode: () => SetActiveInkColor(StrCast(value)), - }], - ]); - - if (checkResult) { - return map.get(option)?.checkResult(); - } - map.get(option)?.setMode(); - SelectionManager.Docs() - .filter(doc => doc.type === DocumentType.INK) - .map(doc => map.get(option)?.setInk(doc)); -}); - -/** WEB - * webSetURL - **/ -ScriptingGlobals.add(function webSetURL(url: string, checkResult?: boolean) { - const selected = SelectionManager.Views().lastElement(); - if (selected?.rootDoc.type === DocumentType.WEB) { - if (checkResult) { - return StrCast(selected.rootDoc.data, Cast(selected.rootDoc.data, WebField, null)?.url?.href); - } - selected.ComponentView?.setData?.(url); - //selected.rootDoc.data = new WebField(url); - } -}); -ScriptingGlobals.add(function webForward(checkResult?: boolean) { - const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox; - if (checkResult) { - return selected?.forward(checkResult) ? undefined : 'lightGray'; - } - selected?.forward(); -}); -ScriptingGlobals.add(function webBack(checkResult?: boolean) { - const selected = SelectionManager.Views().lastElement()?.ComponentView as WebBox; - if (checkResult) { - return selected?.back(checkResult) ? undefined : 'lightGray'; - } - selected?.back(); -}); - -/** Schema - * toggleSchemaPreview - **/ -ScriptingGlobals.add(function toggleSchemaPreview(checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - if (checkResult && selected) { - const result: boolean = NumCast(selected.schema_previewWidth) > 0; - if (result) return Colors.MEDIUM_BLUE; - else return 'transparent'; - } else if (selected) { - if (NumCast(selected.schema_previewWidth) > 0) { - selected.schema_previewWidth = 0; - } else { - selected.schema_previewWidth = 200; - } - } -}); -ScriptingGlobals.add(function toggleSingleLineSchema(checkResult?: boolean) { - const selected = SelectionManager.Docs().lastElement(); - if (checkResult && selected) { - return NumCast(selected._schema_singleLine) > 0 ? Colors.MEDIUM_BLUE : 'transparent'; - } - if (selected) { - selected._schema_singleLine = !selected._schema_singleLine; - } -}); - -/** STACK - * groupBy - */ -ScriptingGlobals.add(function setGroupBy(key: string, checkResult?: boolean) { - SelectionManager.Docs().map(doc => (doc._fontFamily = key)); - const editorView = RichTextMenu.Instance.TextView?.EditorView; - if (checkResult) { - return StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily); - } - if (editorView) RichTextMenu.Instance.setFontFamily(key); - else Doc.UserDoc().fontFamily = key; -}); diff --git a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx b/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx deleted file mode 100644 index 74c3c563c..000000000 --- a/src/client/views/nodes/button/colorDropdown/ColorDropdown.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { Component } from 'react'; -import { BoolCast, StrCast } from '../../../../../fields/Types'; -import { IButtonProps } from '../ButtonInterface'; -import { ColorState, SketchPicker } from 'react-color'; -import { ScriptField } from '../../../../../fields/ScriptField'; -import { Doc } from '../../../../../fields/Doc'; -import { FontIconBox } from '../FontIconBox'; - -export class ColorDropdown extends Component<IButtonProps> { - render() { - const active: string = StrCast(this.props.rootDoc.dropDownOpen); - - const script: string = StrCast(this.props.rootDoc.script); - const scriptCheck: string = script + '(undefined, true)'; - const boolResult = ScriptField.MakeScript(scriptCheck)?.script.run().result; - - const stroke: boolean = false; - // if (script === "setStrokeColor") { - // stroke = true; - // const checkWidth = ScriptField.MakeScript("setStrokeWidth(0, true)")?.script.run().result; - // const width = 20 + (checkWidth / 100) * 70; - // const height = 20 + (checkWidth / 100) * 70; - // strokeIcon = (<div style={{ borderRadius: "100%", width: width + '%', height: height + '%', backgroundColor: boolResult ? boolResult : "#FFFFFF" }} />); - // } - - const colorOptions: string[] = ['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb']; - - const colorBox = (func: (color: ColorState) => void) => <SketchPicker disableAlpha={!stroke} onChange={func} color={boolResult ? boolResult : '#FFFFFF'} presetColors={colorOptions} />; - const label = - !this.props.label || !FontIconBox.GetShowLabels() ? null : ( - <div className="fontIconBox-label" style={{ color: this.props.color, backgroundColor: this.props.backgroundColor, position: 'absolute' }}> - {this.props.label} - </div> - ); - - const dropdownCaret = ( - <div className="menuButton-dropDown" style={{ borderBottomRightRadius: active ? 0 : undefined }}> - <FontAwesomeIcon icon={'caret-down'} color={this.props.color} size="sm" /> - </div> - ); - - const click = (value: ColorState) => { - const hex: string = value.hex; - const s = ScriptField.MakeScript(script + '("' + hex + '", false)'); - if (s) { - s.script.run().result; - } - }; - return ( - <div - className={`menuButton ${this.props.type} ${active}`} - style={{ color: this.props.color, borderBottomLeftRadius: active ? 0 : undefined }} - onClick={() => (this.props.rootDoc.dropDownOpen = !this.props.rootDoc.dropDownOpen)} - onPointerDown={e => e.stopPropagation()}> - <FontAwesomeIcon className={`fontIconBox-icon-${this.props.type}`} icon={this.props.icon} color={this.props.color} /> - <div className="colorButton-color" style={{ backgroundColor: boolResult ? boolResult : '#FFFFFF' }} /> - {label} - {/* {dropdownCaret} */} - {this.props.rootDoc.dropDownOpen ? ( - <div> - <div className="menuButton-dropdownBox" onPointerDown={e => e.stopPropagation()} onClick={e => e.stopPropagation()}> - {colorBox(click)} - </div> - <div - className="dropbox-background" - onClick={e => { - e.stopPropagation(); - this.props.rootDoc.dropDownOpen = false; - }} - /> - </div> - ) : null} - </div> - ); - } -} diff --git a/src/client/views/nodes/button/colorDropdown/index.ts b/src/client/views/nodes/button/colorDropdown/index.ts deleted file mode 100644 index 1147d6457..000000000 --- a/src/client/views/nodes/button/colorDropdown/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ColorDropdown';
\ No newline at end of file diff --git a/src/client/views/nodes/button/textButton/TextButton.tsx b/src/client/views/nodes/button/textButton/TextButton.tsx deleted file mode 100644 index 5d7d55863..000000000 --- a/src/client/views/nodes/button/textButton/TextButton.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { Component } from 'react'; -import { BoolCast } from '../../../../../fields/Types'; -import { IButtonProps } from '../ButtonInterface'; - -export class TextButton extends Component<IButtonProps> { - render() { - const type = this.props.type; - // Determine the type of toggle button - const buttonText: boolean = BoolCast(this.props.rootDoc.switchToggle); - - return ( - <div - className={`menuButton ${this.props.type}`} - style={{ - opacity: 1, - backgroundColor: this.props.backgroundColor, - color: this.props.color, - }} - > - <FontAwesomeIcon - className={`fontIconBox-icon-${this.props.type}`} - icon={this.props.icon} - color={this.props.color} - /> - {this.props.label} - </div> - ); - } -} diff --git a/src/client/views/nodes/button/textButton/index.ts b/src/client/views/nodes/button/textButton/index.ts deleted file mode 100644 index 01d62eb7e..000000000 --- a/src/client/views/nodes/button/textButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './TextButton';
\ No newline at end of file diff --git a/src/client/views/nodes/button/toggleButton/ToggleButton.tsx b/src/client/views/nodes/button/toggleButton/ToggleButton.tsx deleted file mode 100644 index dca6487d8..000000000 --- a/src/client/views/nodes/button/toggleButton/ToggleButton.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import React, { Component } from 'react'; -import { BoolCast } from '../../../../../fields/Types'; -import { Colors } from '../../../global/globalEnums'; -import { IButtonProps } from '../ButtonInterface'; - -export class ToggleButton extends Component<IButtonProps> { - render() { - const type = this.props.type; - // Determine the type of toggle button - const switchToggle: boolean = BoolCast(this.props.rootDoc.switchToggle); - - if (switchToggle) { - return ( - <div className={`menuButton ${type} ${'switch'}`}> - <label className="switch"> - <input type="checkbox" - checked={this.props.backgroundColor === Colors.MEDIUM_BLUE} - /> - <span className="slider round"></span> - </label> - </div> - ); - } else { - return ( - <div className={`menuButton ${type}`} - style={{ opacity: 1, backgroundColor: this.props.backgroundColor, color: this.props.color }}> - <FontAwesomeIcon className={`fontIconBox-icon-${type}`} icon={this.props.icon} color={this.props.color} /> - {this.props.label} - </div> - ); - } - } -}
\ No newline at end of file diff --git a/src/client/views/nodes/button/toggleButton/index.ts b/src/client/views/nodes/button/toggleButton/index.ts deleted file mode 100644 index cdb9c527c..000000000 --- a/src/client/views/nodes/button/toggleButton/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './ToggleButton';
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index c929b7ff3..48f4c2afd 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -2,8 +2,9 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { NodeSelection } from 'prosemirror-state'; import * as ReactDOM from 'react-dom/client'; -import { Doc, HeightSym, WidthSym } from '../../../../fields/Doc'; -import { Cast, NumCast, StrCast } from '../../../../fields/Types'; +import { Doc } from '../../../../fields/Doc'; +import { Height, Width } from '../../../../fields/DocSymbols'; +import { NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -210,14 +211,14 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { addDocTab={this._textBox.props.addDocTab} pinToPres={returnFalse} renderDepth={this._textBox.props.renderDepth + 1} - PanelWidth={this._finalLayout[WidthSym]} - PanelHeight={this._finalLayout[HeightSym]} + PanelWidth={this._finalLayout[Width]} + PanelHeight={this._finalLayout[Height]} focus={this.outerFocus} whenChildContentsActiveChanged={returnFalse} bringToFront={emptyFunction} dontRegisterView={false} - docFilters={this.props.tbox?.props.docFilters} - docRangeFilters={this.props.tbox?.props.docRangeFilters} + childFilters={this.props.tbox?.props.childFilters} + childFiltersByRanges={this.props.tbox?.props.childFiltersByRanges} searchFilterDocs={this.props.tbox?.props.searchFilterDocs} /> </div> diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 6c61f6709..d5ad128fe 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; -import { DataSym, Doc, Field } from '../../../../fields/Doc'; +import { Doc } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; @@ -17,6 +17,7 @@ import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; import React = require('react'); +import { Transform } from '../../../util/Transform'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -113,6 +114,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna componentWillUnmount() { this._reactionDisposer?.(); } + return100 = () => 100; // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { @@ -123,7 +125,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna col={0} deselectCell={emptyFunction} selectCell={emptyFunction} - maxWidth={this.props.hideKey ? undefined : () => 100} + maxWidth={this.props.hideKey ? undefined : this.return100} columnWidth={this.props.hideKey ? () => this.props.tbox.props.PanelWidth() - 20 : returnZero} selectedCell={() => [this._dashDoc!, 0]} fieldKey={this._fieldKey} @@ -135,6 +137,8 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna allowCRs={true} oneLine={!this._expanded} finishEdit={action(() => (this._expanded = false))} + transform={Transform.Identity} + menuTarget={null} /> </div> ); @@ -144,7 +148,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna let container = this.props.tbox.props.DocumentView?.().props.docViewPath().lastElement(); if (container) { const embedding = Doc.MakeEmbedding(container.rootDoc); - embedding._viewType = CollectionViewType.Time; + embedding._type_collection = CollectionViewType.Time; const colHdrKey = '_' + container.LayoutFieldKey + '_columnHeaders'; let list = Cast(embedding[colHdrKey], listSpec(SchemaHeaderField)); if (!list) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 109b62e6f..348bdd79e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -84,7 +84,6 @@ audiotag:hover { height: 11; } -.formattedTextBox-outer-selected, .formattedTextBox-outer { position: relative; overflow: auto; @@ -92,9 +91,6 @@ audiotag:hover { width: 100%; height: unset; } -.formattedTextBox-outer-selected { - cursor: text; -} .formattedTextBox-sidebar-handle { position: absolute; @@ -148,10 +144,8 @@ audiotag:hover { } .formattedTextBox-inner-rounded, -.formattedTextBox-inner-rounded-selected, .formattedTextBox-inner, -.formattedTextBox-inner-minimal, -.formattedTextBox-inner-selected { +.formattedTextBox-inner-minimal { height: 100%; white-space: pre-wrap; .ProseMirror:hover { @@ -169,17 +163,6 @@ audiotag:hover { border-width: 1px; } } -.formattedTextBox-inner-rounded-selected, -.formattedTextBox-inner-selected { - > .ProseMirror { - padding: 10px; - } -} -.formattedTextBox-outer-selected { - > .ProseMirror:hover { - background: unset; - } -} .gpt-typing-wrapper { padding: 10px; @@ -640,7 +623,6 @@ footnote::before { } } - .formattedTextBox-outer-selected, .formattedTextBox-outer { position: relative; overflow: auto; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 023814a9d..1dcc445e8 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -12,9 +12,11 @@ import { Fragment, Mark, Node, Slice } from 'prosemirror-model'; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from 'prosemirror-state'; import { EditorView } from 'prosemirror-view'; import { DateField } from '../../../../fields/DateField'; -import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, CssSym, Doc, DocListCast, Field, ForceServerWrite, HeightSym, Opt, StrListCast, UpdatingFromServer, WidthSym } from '../../../../fields/Doc'; +import { Doc, DocListCast, StrListCast, Field, Opt } from '../../../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, Height, Width, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; +import { List } from '../../../../fields/List'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from '../../../../fields/RichTextField'; import { RichTextUtils } from '../../../../fields/RichTextUtils'; @@ -32,8 +34,8 @@ import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { MakeTemplate } from '../../../util/DropConverter'; -import { IsFollowLinkScript } from '../../../util/LinkFollower'; import { LinkManager } from '../../../util/LinkManager'; +import { RTFMarkup } from '../../../util/RTFMarkup'; import { SelectionManager } from '../../../util/SelectionManager'; import { SnappingManager } from '../../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; @@ -68,8 +70,6 @@ import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; import applyDevTools = require('prosemirror-dev-tools'); import React = require('react'); -import { RTFMarkup } from '../../../util/RTFMarkup'; -import { List } from '../../../../fields/List'; const translateGoogleApi = require('translate-google-api'); export const GoogleRef = 'googleDocId'; type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void; @@ -96,6 +96,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps private _scrollRef: React.RefObject<HTMLDivElement> = React.createRef(); private _editorView: Opt<EditorView>; public _applyingChange: string = ''; + private _finishingLink = false; private _searchIndex = 0; private _lastTimedMark: Mark | undefined = undefined; private _cachedLinks: Doc[] = []; @@ -131,7 +132,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return this._showSidebar ? '20%' : StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebarColor, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4')); + return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this.fieldKey + '_backgroundColor'], '#e4e4e4')); } @computed get layout_autoHeight() { return (this.props.forceAutoHeight || this.layoutDoc._layout_autoHeight) && !this.props.ignoreAutoHeight; @@ -243,9 +244,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { if (!pinProps && this._editorView?.state.selection.empty) return this.rootDoc; - const anchor = Docs.Create.TextanchorDocument({ annotationOn: this.rootDoc, unrendered: true }); + const anchor = Docs.Create.ConfigDocument({ title: StrCast(this.rootDoc.title), annotationOn: this.rootDoc }); this.addDocument(anchor); + this._finishingLink = true; this.makeLinkAnchor(anchor, OpenWhere.addRight, undefined, 'Anchored Selection', false, addAsAnnotation); + this._finishingLink = false; PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: true } }, this.rootDoc); return anchor; }; @@ -296,21 +299,21 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps }; dispatchTransaction = (tx: Transaction) => { - if (this._editorView) { + if (this._editorView && (this._editorView as any).docView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc; - const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); - const curTemp = this.layoutDoc.resolvedDataDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField) : undefined; // the actual text in the text box - const curProto = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype - const curLayout = this.rootDoc !== this.layoutDoc ? Cast(this.layoutDoc[this.fieldKey], RichTextField, null) : undefined; // the default text stored in a layout template - const json = JSON.stringify(state.toJSON()); + const newText = state.doc.textBetween(0, state.doc.content.size, ' \n'); + const newJson = JSON.stringify(state.toJSON()); + const prevData = Cast(this.layoutDoc[this.fieldKey], RichTextField, null); // the actual text in the text box + const templateData = this.rootDoc !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template + const protoData = Cast(Cast(dataDoc.proto, Doc, null)?.[this.fieldKey], RichTextField, null); // the default text inherited from a prototype const effectiveAcl = GetEffectiveAcl(dataDoc); - const removeSelection = (json: string | undefined) => (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); + const removeSelection = (json: string | undefined) => json?.replace(/"selection":.*/, ''); - if ([AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl)) { + if ([AclEdit, AclAdmin, AclSelfEdit, AclAugment].includes(effectiveAcl)) { const accumTags = [] as string[]; state.tr.doc.nodesBetween(0, state.doc.content.size, (node: any, pos: number, parent: any) => { if (node.type === schema.nodes.dashField && node.attrs.fieldKey.startsWith('#')) { @@ -320,29 +323,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps dataDoc.tags = accumTags.length ? new List<string>(Array.from(new Set<string>(accumTags))) : undefined; let unchanged = true; - if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { + if (this._applyingChange !== this.fieldKey && removeSelection(newJson) !== removeSelection(prevData?.Data)) { this._applyingChange = this.fieldKey; - const textChange = curText !== Cast(dataDoc[this.fieldKey], RichTextField)?.Text; + const textChange = newText !== prevData?.Text; textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); - if ((!curTemp && !curProto) || curText || json.includes('dash')) { + if ((!prevData && !protoData) || newText || (!newText && !templateData)) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (removeSelection(json) !== removeSelection(curLayout?.Data)) { + if ((this._finishingLink || this.props.isContentActive()) && removeSelection(newJson) !== removeSelection(prevData?.Data)) { const numstring = NumCast(dataDoc[this.fieldKey], null); - if (numstring !== undefined) { - dataDoc[this.fieldKey] = Number(curText); - } else { - dataDoc[this.fieldKey] = new RichTextField(json, curText); - } - dataDoc[this.fieldKey + '_noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited - textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); + dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText); + dataDoc[this.fieldKey + '_noTemplate'] = true; // mark the data field as being split from the template if it has been edited + textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText }); unchanged = false; } } else { // if we've deleted all the text in a note driven by a template, then restore the template data dataDoc[this.fieldKey] = undefined; - this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data))); dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have - ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); + ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText }); unchanged = false; } this._applyingChange = ''; @@ -418,6 +417,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps DocListCast(Doc.MyPublishedDocs.data).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,8 +460,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (node.firstChild === null && !node.marks.find((m: Mark) => m.type.name === schema.marks.noAutoLinkAnchor.name) && node.marks.find((m: Mark) => m.type.name === schema.marks.splitter.name)) { alink = alink ?? - (LinkManager.Links(this.Document).find(link => Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target)) || - DocUtils.MakeLink(this.props.Document, target, { link_relationship: LinkManager.AutoKeywords })!); + (LinkManager.Links(this.rootDoc).find( + link => + Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), this.rootDoc) && // + Doc.AreProtosEqual(Cast(link.link_anchor_2, Doc, null), target) + ) || + DocUtils.MakeLink(this.rootDoc, target, { link_relationship: LinkManager.AutoKeywords })!); newAutoLinks.add(alink); 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 ?? [])); @@ -534,36 +538,50 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - if (de.complete.annoDragData) de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true); + if (de.complete.annoDragData) { + de.complete.annoDragData.dropDocCreator = () => this.getAnchor(true); + e.stopPropagation(); + return true; + } const dragData = de.complete.docDragData; if (dragData) { - const draggedDoc = dragData.draggedDocuments.length && dragData.draggedDocuments[0]; - // replace text contents whend dragging with Alt - if (draggedDoc && draggedDoc.type === DocumentType.RTF && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.altKey) { - if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.fieldKey] = new RichTextField(draggedDoc.data.Data, draggedDoc.data.Text); - e.stopPropagation(); - } - // embed document when dragg marked as embed - } else if (de.embedKey) { - const target = dragData.droppedDocuments[0]; - const node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), - height: target[HeightSym](), - title: 'dashDoc', - docId: target[Id], - float: 'unset', - }); - if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) { - dragData.removeDocument?.(dragData.draggedDocuments[0]); + const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc; + const effectiveAcl = GetEffectiveAcl(dataDoc); + let added = [AclEdit, AclAdmin, AclSelfEdit].includes(effectiveAcl); + const draggedDoc = dragData.draggedDocuments.lastElement(); + if (added) { + // replace text contents when dragging with Alt + if (de.altKey) { + const fieldKey = Doc.LayoutFieldKey(draggedDoc); + if (draggedDoc[fieldKey] instanceof RichTextField && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) { + Doc.GetProto(this.dataDoc)[this.fieldKey] = Field.Copy(draggedDoc[fieldKey]); + } + + // embed document when drag marked as embed + } else if (de.embedKey) { + const node = schema.nodes.dashDoc.create({ + width: draggedDoc[Width](), + height: draggedDoc[Height](), + title: 'dashDoc', + docId: draggedDoc[Id], + float: 'unset', + }); + if (!['embed', 'copy'].includes((dragData.dropAction ?? '') as any)) { + added = dragData.removeDocument?.(draggedDoc) ? true : false; + } + if (added) { + draggedDoc._freeform_fitContentsToBox = true; + Doc.SetContainer(draggedDoc, this.rootDoc); + const view = this._editorView!; + view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); + } } - target._freeform_fitContentsToBox = true; - target.embedContainer = this.rootDoc; - const view = this._editorView!; - view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); - e.stopPropagation(); } // otherwise, fall through to outer collection to handle drop + !added && e.preventDefault(); + e.stopPropagation(); + return added; } + return false; }; getNodeEndpoints(context: Node, node: Node): { from: number; to: number } | null { @@ -650,7 +668,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const hr = Math.round(Date.now() / 1000 / 60 / 60); numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } - this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView) + this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView) }; @observable _showSidebar = false; @@ -667,7 +685,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this.layoutDoc._width = !preview && this.SidebarShown ? NumCast(this.layoutDoc._width) * 2 : Math.max(20, NumCast(this.layoutDoc._width) * prevWidth); }; sidebarDown = (e: React.PointerEvent) => { - const batch = UndoManager.StartBatch('sidebar'); + const batch = UndoManager.StartBatch('toggle sidebar'); setupMoveUpEvents( this, e, @@ -686,7 +704,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[WidthSym]() + localDelta[0]; + const width = 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%'; @@ -694,13 +712,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return false; }; - @undoBatch deleteAnnotation = (anchor: Doc) => { + const batch = UndoManager.StartBatch('delete link'); LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]); // const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]); // this.props.dataDoc[this.fieldKey] = new List<Doc>(docAnnotations.filter(a => a !== this.annoTextRegion)); // AnchorMenu.Instance.fadeOut(true); this.props.select(false); + setTimeout(batch.end); // wait for reaction to remove link from document }; @undoBatch @@ -733,17 +752,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps .split(' ') .filter(h => h); const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0]; + const deleteMarkups = undoBatch(() => { + const sel = editor.state.selection; + editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor)); + }); e.persist(); anchorDoc && DocServer.GetRefField(anchorDoc).then( action(anchor => { + anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc); AnchorMenu.Instance.Status = 'annotation'; - AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc); + AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc); AnchorMenu.Instance.Pinned = false; - AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc); - AnchorMenu.Instance.MakeTargetToggle = () => this.makeTargetToggle(anchor as Doc); - AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(anchor as Doc); - AnchorMenu.Instance.IsTargetToggler = () => this.isTargetToggler(anchor as Doc); + AnchorMenu.Instance.PinToPres = !anchor ? returnFalse : () => this.pinToPres(anchor as Doc); + AnchorMenu.Instance.MakeTargetToggle = !anchor ? returnFalse : () => this.makeTargetToggle(anchor as Doc); + AnchorMenu.Instance.ShowTargetTrail = !anchor ? returnFalse : () => this.showTargetTrail(anchor as Doc); + AnchorMenu.Instance.IsTargetToggler = !anchor ? returnFalse : () => this.isTargetToggler(anchor as Doc); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); }) ); @@ -808,9 +832,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps icon: !this.Document._layout_noSidebar ? 'eye-slash' : 'eye', }); uicontrols.push({ - description: (this.Document._layout_altContentUI ? 'Hide' : 'Show') + ' Alt Content UI', - event: () => (this.layoutDoc._layout_altContentUI = !this.layoutDoc._layout_altContentUI), - icon: !this.Document._layout_altContentUI ? 'eye-slash' : 'eye', + 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', }); uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' }); !Doc.noviceMode && @@ -823,7 +847,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const appearance = cm.findByDescription('Appearance...'); const appearanceItems = appearance && 'subitems' in appearance ? appearance.subitems : []; - appearanceItems.push({ description: 'Change Perspective...', noexpand: true, subitems: changeItems, icon: 'external-link-alt' }); + 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" }); !Doc.noviceMode && @@ -862,11 +886,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const optionItems = options && 'subitems' in options ? options.subitems : []; optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); - optionItems.push({ - description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', - event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR), - icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars', - }); + this.props.renderDepth && + optionItems.push({ + description: !this.Document._createDocOnCR ? 'Create New Doc on Carriage Return' : 'Allow Carriage Returns', + event: () => (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR), + icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars', + }); !Doc.noviceMode && optionItems.push({ description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`, @@ -906,7 +931,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps let image_url = await gptImageCall((this.dataDoc.text as RichTextField)?.Text); if (image_url) { const [result] = await Networking.PostToServer('/uploadRemoteImage', { sources: [image_url] }); - const source = Utils.prepend(result.accessPaths.agnostic.client); + const source = result.accessPaths.agnostic.client; const newDoc = Docs.Create.ImageDocument(source, { x: NumCast(this.rootDoc.x) + NumCast(this.layoutDoc._width) + 10, y: NumCast(this.rootDoc.y), @@ -915,10 +940,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps data_nativeWidth: result.nativeWidth, data_nativeHeight: result.nativeHeight, }); - if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc)) { + if (Doc.IsInMyOverlay(this.rootDoc)) { newDoc.overlayX = this.rootDoc.x; newDoc.overlayY = NumCast(this.rootDoc.y) + NumCast(this.rootDoc._height); - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, newDoc); + Doc.AddToMyOverlay(newDoc); } else { this.props.addDocument?.(newDoc); } @@ -960,7 +985,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps if (this._editorView && this._recordingStart) { if (this._break) { const textanchorFunc = () => { - const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor', unrendered: true }); + const tanch = Docs.Create.ConfigDocument({ title: 'dictation anchor' }); return this.addDocument(tanch) ? tanch : undefined; }; const link = DocUtils.MakeLinkToActiveAudio(textanchorFunc, false).lastElement(); @@ -998,7 +1023,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const splitter = state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); let tr = state.tr.addMark(sel.from, sel.to, splitter); if (sel.from !== sel.to) { - const anchor = anchorDoc ?? Docs.Create.TextanchorDocument({ title: '#' + this._editorView?.state.doc.textBetween(sel.from, sel.to), annotationOn: this.dataDoc, unrendered: true }); + const anchor = + anchorDoc ?? + Docs.Create.ConfigDocument({ + // + title: 'text(' + this._editorView?.state.doc.textBetween(sel.from, sel.to) + ')', + annotationOn: this.dataDoc, + }); const href = targetHref ?? Doc.localServerPath(anchor); if (anchor !== anchorDoc && addAsAnnotation) this.addDocument(anchor); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { @@ -1014,6 +1045,7 @@ 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.title = selectedText.substring(0, 30); return anchor; } return anchorDoc ?? this.rootDoc; @@ -1022,7 +1054,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } getView = async (doc: Doc) => { - if (DocListCast(this.rootDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.unrendered ? DocCast(doc.annotationOn) : doc, anno))) { + if (DocListCast(this.rootDoc[this.SidebarKey]).find(anno => Doc.AreProtosEqual(doc.layout_unrendered ? DocCast(doc.annotationOn) : doc, anno))) { !this.SidebarShown && this.toggleSidebar(false); setTimeout(() => this._sidebarRef?.current?.makeDocUnfiltered(doc)); } @@ -1129,11 +1161,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, layout_autoHeight: this.layout_autoHeight, marginsHeight: this.layout_autoHeightMargins }), ({ sidebarHeight, textHeight, layout_autoHeight, marginsHeight }) => { const newHeight = this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight)); - if (layout_autoHeight && newHeight && newHeight !== this.rootDoc.height && !this.props.dontRegisterView) { + if ( + (!Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') || this.props.isSelected()) && // + layout_autoHeight && + newHeight && + newHeight !== this.rootDoc.height && + !this.props.dontRegisterView + ) { this.props.setHeight?.(newHeight); } }, - { fireImmediately: true } + { fireImmediately: !Array.from(FormattedTextBox._globalHighlights).includes('Bold Text') } ); this._disposers.links = reaction( () => LinkManager.Links(this.dataDoc), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks @@ -1155,7 +1193,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps () => { const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc; const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc; - return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(whichDoc[this.fieldKey]) }; + return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey])) }; }, incomingValue => { if (this._editorView && this._applyingChange !== this.fieldKey) { @@ -1199,15 +1237,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps this._disposers.selected = reaction( () => this.props.isSelected(), action(selected => { + selected && this.prepareForTyping(); if (FormattedTextBox._globalHighlights.has('Bold Text')) { - this.layoutDoc[CssSym] = this.layoutDoc[CssSym] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed + this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css change happens outside of mobx/react, so this will notify anyone interested in the layout that it has changed } if (RichTextMenu.Instance?.view === this._editorView && !selected) { RichTextMenu.Instance?.updateMenu(undefined, undefined, undefined); } if (this._editorView && selected) { RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); - this.autoLink(); + setTimeout(this.autoLink, 20); } // Accessing editor and text doc for gpt assisted text edits if (this._editorView && selected) { @@ -1474,11 +1513,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } const selectOnLoad = this.rootDoc[Id] === FormattedTextBox.SelectOnLoad && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(this.props.docViewPath())); - if (selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { + if (this._editorView && selectOnLoad && !this.props.dontRegisterView && !this.props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { const selLoadChar = FormattedTextBox.SelectOnLoadChar; FormattedTextBox.SelectOnLoad = ''; this.props.select(false); - if (selLoadChar && this._editorView) { + if (selLoadChar) { const $from = this._editorView.state.selection.anchor ? this._editorView.state.doc.resolve(this._editorView.state.selection.anchor - 1) : undefined; const mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }); const curMarks = this._editorView.state.storedMarks ?? $from?.marksAcross(this._editorView.state.selection.$head) ?? []; @@ -1488,30 +1527,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps .insertText(FormattedTextBox.SelectOnLoadChar, this._editorView.state.doc.content.size - 1, this._editorView.state.doc.content.size) .setStoredMarks(storedMarks); this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(tr.doc.content.size)))); - } else if (this._editorView && curText && !FormattedTextBox.DontSelectInitialText) { + } else if (curText && !FormattedTextBox.DontSelectInitialText) { selectAll(this._editorView.state, this._editorView?.dispatch); - this.startUndoTypingBatch(); - } else if (this._editorView) { - this._editorView.dispatch(this._editorView.state.tr.addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); } } selectOnLoad && this._editorView!.focus(); - // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. + if (this.props.isContentActive()) this.prepareForTyping(); if (this._editorView) { const tr = this._editorView.state.tr; const { from, to } = tr.selection; - // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selectoin after the document has ben fully instantiated. + // for some reason, the selection is sometimes lost in the sidebar view when prosemirror syncs the seledtion with the dom, so reset the selection after the document has ben fully instantiated. if (FormattedTextBox.DontSelectInitialText) setTimeout(() => this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250); - this._editorView.state.storedMarks = [ - ...(this._editorView.state.storedMarks ?? []), - ...(!this._editorView.state.storedMarks?.some(mark => mark.type === schema.marks.user_mark) ? [schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })] : []), - ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), - ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), - ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), - ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), - ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), - ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), - ]; + if (FormattedTextBox.PasteOnLoad) { const pdfAnchorId = FormattedTextBox.PasteOnLoad.clipboardData?.getData('dash/pdfAnchor'); FormattedTextBox.PasteOnLoad = undefined; @@ -1521,9 +1548,31 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps FormattedTextBox.DontSelectInitialText = false; } + // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. + prepareForTyping = () => { + this._editorView?.dispatch( + this._editorView?.state.tr.setStoredMarks([ + ...(this._editorView.state.storedMarks?.filter(mark => ![schema.marks.em, schema.marks.underline, schema.marks.pFontFamily, schema.marks.pFontSize, schema.marks.strong, schema.marks.pFontColor].includes(mark.type)) ?? []), + ...[schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) })], + ...(Doc.UserDoc().fontColor !== 'transparent' && Doc.UserDoc().fontColor ? [schema.mark(schema.marks.pFontColor, { color: StrCast(Doc.UserDoc().fontColor) })] : []), + ...(Doc.UserDoc().fontStyle === 'italics' ? [schema.mark(schema.marks.em)] : []), + ...(Doc.UserDoc().textDecoration === 'underline' ? [schema.mark(schema.marks.underline)] : []), + ...(Doc.UserDoc().fontFamily ? [schema.mark(schema.marks.pFontFamily, { family: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontFamily) })] : []), + ...(Doc.UserDoc().fontSize ? [schema.mark(schema.marks.pFontSize, { fontSize: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.FontSize) })] : []), + ...(Doc.UserDoc().fontWeight === 'bold' ? [schema.mark(schema.marks.strong)] : []), + ]) + ); + }; + + @action componentWillUnmount() { + if (this._recording) { + this._recording = !this._recording; + } Object.values(this._disposers).forEach(disposer => disposer?.()); this.endUndoTypingBatch(); + FormattedTextBox.LiveTextUndo?.end(); + FormattedTextBox.LiveTextUndo = undefined; this.unhighlightSearchTerms(); this._editorView?.destroy(); RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); @@ -1558,8 +1607,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } if (this._recording && !e.ctrlKey && e.button === 0) { this.breakupDictation(); - e.preventDefault(); - e.stopPropagation(); } this._downX = e.clientX; this._downY = e.clientY; @@ -1624,7 +1671,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps onFocused = (e: React.FocusEvent): void => { //applyDevTools.applyDevTools(this._editorView); this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); - this.startUndoTypingBatch(); + e.stopPropagation(); }; onClick = (e: React.MouseEvent): void => { @@ -1699,13 +1746,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps } } startUndoTypingBatch() { - !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('undoTyping')); + !this._undoTyping && (this._undoTyping = UndoManager.StartBatch('text edits on ' + this.rootDoc.title)); } public endUndoTypingBatch() { - const wasUndoing = this._undoTyping; this._undoTyping?.end(); this._undoTyping = undefined; - return wasUndoing; } @action @@ -1733,7 +1778,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps const state = this._editorView!.state; const curText = state.doc.textBetween(0, state.doc.content.size, ' \n'); - if (this.layoutDoc.sidebarViewType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) { + if (this.layoutDoc.sidebar_collectionType === 'translation' && !this.fieldKey.includes('translation') && curText.endsWith(' ') && curText !== this._lastText) { try { translateGoogleApi(curText, { from: 'en', to: 'es' }).then((result1: any) => { setTimeout( @@ -1799,8 +1844,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps default: if (this._lastTimedMark?.attrs.userid === Doc.CurrentUserEmail) break; case ' ': - [AclEdit, AclAdmin, AclSelfEdit].includes(GetEffectiveAcl(this.dataDoc)) && - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); + if (e.code !== 'Space') { + [AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.rootDoc)) && + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(Date.now() / 1000) }))); + } + break; } this.startUndoTypingBatch(); }; @@ -1822,17 +1870,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps tryUpdateScrollHeight = () => { const margins = 2 * NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; - if (children) { + if (children && !SnappingManager.GetIsDragging()) { const toNum = (val: string) => Number(val.replace('px', '').replace('auto', '0')); const toHgt = (node: Element) => { const { height, marginTop, marginBottom } = getComputedStyle(node); return toNum(height) + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom)); }; const proseHeight = !this.ProseRef ? 0 : children.reduce((p, child) => p + toHgt(child), margins); - const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.docMaxAutoHeight, proseHeight), proseHeight); + const scrollHeight = this.ProseRef && Math.min(NumCast(this.layoutDoc.layout_maxAutoHeight, proseHeight), proseHeight); if (this.props.setHeight && scrollHeight && !this.props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation const setScrollHeight = () => (this.rootDoc[this.fieldKey + '_scrollHeight'] = scrollHeight); + if (this.rootDoc === this.layoutDoc || this.layoutDoc.resolvedDataDoc) { setScrollHeight(); } else { @@ -1941,25 +1990,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps fitContentsToBox={this.fitContentsToBox} noSidebar={true} treeViewHideTitle={true} - fieldKey={this.layoutDoc.sidebarViewType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`} + fieldKey={this.layoutDoc.sidebar_collectionType === 'translation' ? `${this.fieldKey}_translation` : `${this.fieldKey}_sidebar`} /> </div> ); }; return ( <div className={'formattedTextBox-sidebar' + (Doc.ActiveTool !== InkTool.None ? '-inking' : '')} style={{ width: `${this.layout_sidebarWidthPercent}`, backgroundColor: `${this.sidebarColor}` }}> - {renderComponent(StrCast(this.layoutDoc.sidebarViewType))} + {renderComponent(StrCast(this.layoutDoc.sidebar_collectionType))} </div> ); } cycleAlternateText = () => { - if (this.layoutDoc._layout_altContentUI) { - const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`]; + if (this.layoutDoc._layout_enableAltContentUI) { + const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`]; this.rootDoc[`_${this.props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; } }; @computed get overlayAlternateIcon() { - const usePath = this.rootDoc[`${this.props.fieldKey}_usePath`]; + const usePath = this.rootDoc[`_${this.props.fieldKey}_usePath`]; return ( <Tooltip title={ @@ -1995,62 +2044,69 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps return this.props.fieldKey + (usePath && (!usePath.includes(':hover') || this._isHovering) ? `_${usePath.replace(':hover', '')}` : ''); } @observable _isHovering = false; + onPassiveWheel = (e: WheelEvent) => { + // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) + if (this.props.isContentActive() && !this.props.allowScroll) { + if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault(); + e.stopPropagation(); + } + }; + _oldWheel: any; + @computed get fontColor() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color); + } + @computed get fontSize() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize); + } + @computed get fontFamily() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily); + } + @computed get fontWeight() { + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight); + } render() { TraceMobx(); - const active = this.props.isContentActive() || this.props.isSelected(); - const selected = active; const scale = (this.props.NativeDimScaling?.() || 1) * NumCast(this.layoutDoc._freeform_scale, 1); - const rounded = StrCast(this.layoutDoc.borderRounding) === '100%' ? '-rounded' : ''; - const interactive = (Doc.ActiveTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || !this.layoutDoc._lockedPosition); - if (!selected && FormattedTextBoxComment.textBox === this) setTimeout(FormattedTextBoxComment.Hide); - const minimal = this.props.ignoreAutoHeight; + const rounded = StrCast(this.layoutDoc.layout_borderRounding) === '100%' ? '-rounded' : ''; + setTimeout(() => !this.props.isContentActive() && FormattedTextBoxComment.textBox === this && FormattedTextBoxComment.Hide); const paddingX = NumCast(this.layoutDoc._xMargin, this.props.xPadding || 0); const paddingY = NumCast(this.layoutDoc._yMargin, this.props.yPadding || 0); - const selPad = (selected && !this.layoutDoc._createDocOnCR) || minimal ? Math.min(paddingY, Math.min(paddingX, 10)) : 0; - const selPaddingClass = selected && !this.layoutDoc._createDocOnCR && paddingY >= 10 ? '-selected' : ''; const styleFromLayoutString = Doc.styleFromLayoutString(this.rootDoc, this.layoutDoc, this.props, scale); // this converts any expressions in the format string to style props. e.g., <FormattedTextBox height='{this._headerHeight}px' > return styleFromLayoutString?.height === '0px' ? null : ( <div className="formattedTextBox" onPointerEnter={action(() => (this._isHovering = true))} onPointerLeave={action(() => (this._isHovering = false))} - ref={r => - r?.addEventListener( - 'wheel', // if scrollTop is 0, then don't let wheel trigger scroll on any container (which it would since onScroll won't be triggered on this) - (e: WheelEvent) => { - if (this.props.isContentActive() && !this.props.allowScroll) { - if (!NumCast(this.layoutDoc._layout_scrollTop) && e.deltaY <= 0) e.preventDefault(); - e.stopPropagation(); - } - }, - { passive: false } - ) - } + ref={r => { + this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); + this._oldWheel = r; + r?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); + }} style={{ ...(this.props.dontScale ? {} : { transform: `scale(${scale})`, - transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%`, }), - display: !SnappingManager.GetIsDragging() && this.props.thumbShown?.() ? 'none' : undefined, + // display: !this.props.isContentActive() && this.props.thumbShown?.() ? 'none' : undefined, transition: 'inherit', // overflowY: this.layoutDoc._layout_autoHeight ? "hidden" : undefined, - color: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.Color), - fontSize: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontSize), - fontFamily: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontFamily), - fontWeight: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.FontWeight), + color: this.fontColor, + fontSize: this.fontSize, + fontFamily: this.fontFamily, + fontWeight: this.fontWeight, ...styleFromLayoutString, }}> <div className="formattedTextBox-cont" ref={this._ref} style={{ + cursor: this.props.isContentActive() ? 'text' : undefined, overflow: this.layout_autoHeight && this.props.CollectionFreeFormDocumentView?.() ? 'hidden' : undefined, //x this breaks viewing an layout_autoHeight doc in its own tab, or in the lightbox height: this.props.height || (this.layout_autoHeight && this.props.renderDepth && !this.props.suppressSetHeight ? 'max-content' : undefined), - pointerEvents: interactive ? undefined : 'none', + pointerEvents: Doc.ActiveTool === InkTool.None && !this.props.onBrowseClick?.() ? undefined : 'none', }} onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyDown} @@ -2062,32 +2118,30 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps onPointerDown={this.onPointerDown} onDoubleClick={this.onDoubleClick}> <div - className={`formattedTextBox-outer${selected ? '-selected' : ''}`} + className={`formattedTextBox-outer`} ref={this._scrollRef} style={{ width: this.props.dontSelectOnLoad ? '100%' : `calc(100% - ${this.layout_sidebarWidthPercent})`, - pointerEvents: !active && !SnappingManager.GetIsDragging() ? 'none' : undefined, overflow: this.layoutDoc._createDocOnCR ? 'hidden' : this.layoutDoc._layout_autoHeight ? 'visible' : undefined, }} onScroll={this.onScroll} onDrop={this.ondrop}> <div - className={minimal ? 'formattedTextBox-minimal' : `formattedTextBox-inner${rounded}${selPaddingClass}`} + className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ padding: StrCast(this.layoutDoc._textBoxPadding), - paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`), - paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX - selPad}px`), - paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`), - paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY - selPad}px`), - pointerEvents: !active && !SnappingManager.GetIsDragging() ? (IsFollowLinkScript(this.layoutDoc.onClick) ? 'none' : undefined) : undefined, + paddingLeft: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`), + paddingRight: StrCast(this.layoutDoc._textBoxPaddingX, `${paddingX}px`), + paddingTop: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`), + paddingBottom: StrCast(this.layoutDoc._textBoxPaddingY, `${paddingY}px`), }} /> </div> {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.layout_sidebarWidthPercent === '0%' ? null : this.sidebarCollection} {this.noSidebar || this.Document._layout_noSidebar || this.props.dontSelectOnLoad || this.Document._createDocOnCR ? null : this.sidebarHandle} {this.audioHandle} - {this.layoutDoc._layout_altContentUI ? this.overlayAlternateIcon : null} + {this.layoutDoc._layout_enableAltContentUI ? this.overlayAlternateIcon : null} </div> </div> ); diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 4dfe07b24..112a0d87e 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -4,7 +4,7 @@ import { Schema } from 'prosemirror-model'; import { splitListItem, wrapInList } from 'prosemirror-schema-list'; import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state'; import { liftTarget } from 'prosemirror-transform'; -import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit } from '../../../../fields/DocSymbols'; import { GetEffectiveAcl } from '../../../../fields/util'; import { Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; @@ -12,6 +12,7 @@ import { RTFMarkup } from '../../../util/RTFMarkup'; import { SelectionManager } from '../../../util/SelectionManager'; import { OpenWhere } from '../DocumentView'; import { liftListItem, sinkListItem } from './prosemirrorPatches.js'; +import { Doc } from '../../../../fields/Doc'; const mac = typeof navigator !== 'undefined' ? /Mac/.test(navigator.platform) : false; @@ -48,15 +49,11 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey const canEdit = (state: any) => { switch (GetEffectiveAcl(props.DataDoc)) { case AclAugment: - return false; - case AclSelfEdit: - for (var i = state.selection.from; i < state.selection.to; i++) { - const marks = state.doc.resolve(i)?.marks?.(); - if (marks?.some((mark: any) => mark.type === schema.marks.user_mark && mark.attrs.userid !== Doc.CurrentUserEmail)) { - return false; - } + const prevNode = state.selection.$cursor.nodeBefore; + const prevUser = !prevNode ? Doc.CurrentUserEmail : prevNode.marks[prevNode.marks.length - 1].attrs.userid; + if (prevUser != Doc.CurrentUserEmail) { + return false; } - break; } return true; }; @@ -337,7 +334,7 @@ export function buildKeymap<S extends Schema<any>>(schema: S, props: any, mapKey //Command to create a blank space bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { - if (!canEdit(state)) return true; + if (GetEffectiveAcl(props.DataDoc) != AclEdit && GetEffectiveAcl(props.DataDoc) != AclAugment && GetEffectiveAcl(props.DataDoc) != AclAdmin) return true; const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); dispatch(splitMetadata(marks, state.tr)); return false; diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index 5e0041b84..7c3e4baad 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -138,7 +138,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.activeListType = this.getActiveListStyle(); this._activeAlignment = this.getActiveAlignment(); - this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document.fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; + this._activeFontFamily = !activeFamilies.length ? StrCast(this.TextView?.Document._text_fontFamily, StrCast(Doc.UserDoc().fontFamily, 'Arial')) : activeFamilies.length === 1 ? String(activeFamilies[0]) : 'various'; this._activeFontSize = !activeSizes.length ? StrCast(this.TextView?.Document.fontSize, StrCast(Doc.UserDoc().fontSize, '10px')) : activeSizes[0]; this._activeFontColor = !activeColors.length ? StrCast(this.TextView?.Document.fontColor, StrCast(Doc.UserDoc().fontColor, 'black')) : activeColors.length > 0 ? String(activeColors[0]) : '...'; this._activeHighlightColor = !activeHighlights.length ? '' : activeHighlights.length > 0 ? String(activeHighlights[0]) : '...'; @@ -221,7 +221,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { m.type === state.schema.marks.marker && activeHighlights.add(String(m.attrs.highlight)); }); } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) { - SelectionManager.Views().forEach(dv => StrCast(dv.rootDoc._fontSize) && activeSizes.add(StrCast(dv.rootDoc._fontSize))); + SelectionManager.Views().forEach(dv => StrCast(dv.rootDoc._text_fontSize) && activeSizes.add(StrCast(dv.rootDoc._text_fontSize))); } return { activeFamilies: Array.from(activeFamilies), activeSizes: Array.from(activeSizes), activeColors: Array.from(activeColors), activeHighlights: Array.from(activeHighlights) }; } @@ -345,8 +345,8 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { this.view.focus(); } } else if (SelectionManager.Views().some(dv => dv.ComponentView instanceof EquationBox)) { - SelectionManager.Views().forEach(dv => (dv.rootDoc._fontSize = fontSize)); - } else Doc.UserDoc()._fontSize = fontSize; + SelectionManager.Views().forEach(dv => (dv.rootDoc._text_fontSize = fontSize)); + } else Doc.UserDoc().fontSize = fontSize; this.updateMenu(this.view, undefined, this.props); }; @@ -355,7 +355,7 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { const fmark = this.view.state.schema.marks.pFontFamily.create({ family: family }); this.setMark(fmark, this.view.state, (tx: any) => this.view!.dispatch(tx.addStoredMark(fmark)), true); this.view.focus(); - } else Doc.UserDoc()._fontFamily = family; + } else Doc.UserDoc().fontFamily = family; this.updateMenu(this.view, undefined, this.props); }; @@ -758,11 +758,11 @@ export class RichTextMenu extends AntimodeMenu<AntimodeMenuProps> { // <div className="collectionMenu-divider" key="divider 3" /> // {[this.createMarksDropdown(this.activeFontSize, this.fontSizeOptions, "font size", action((val: string) => { // this.activeFontSize = val; - // SelectionManager.Views().map(dv => dv.props.Document._fontSize = val); + // SelectionManager.Views().map(dv => dv.props.Document._text_fontSize = val); // })), // this.createMarksDropdown(this.activeFontFamily, this.fontFamilyOptions, "font family", action((val: string) => { // this.activeFontFamily = val; - // SelectionManager.Views().map(dv => dv.props.Document._fontFamily = val); + // SelectionManager.Views().map(dv => dv.props.Document._text_fontFamily = val); // })), // <div className="collectionMenu-divider" key="divider 4" />, // this.createNodesDropdown(this.activeListType, this.listTypeOptions, "list type", () => ({})), diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 104aed058..8bafc2cef 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,11 +1,11 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; import { NodeSelection, TextSelection } from 'prosemirror-state'; -import { DataSym, Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; +import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { ComputedField } from '../../../../fields/ScriptField'; -import { NumCast, StrCast } from '../../../../fields/Types'; -import { normalizeEmail } from '../../../../fields/util'; +import { NumCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -76,12 +76,21 @@ export class RichTextRules { //Create annotation to a field on the text document new InputRule(new RegExp(/>>$/), (state, match, start, end) => { - const textDoc = this.Document[DataSym]; + const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); textDoc.inlineTextCount = numInlines + 1; const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to const inlineLayoutKey = 'layout_' + inlineFieldKey; // the field holding the layout string that will render the inline annotation - const textDocInline = Docs.Create.TextDocument('', { _layout_fieldKey: inlineLayoutKey, _width: 75, _height: 35, annotationOn: textDoc, _layout_fitWidth: true, _layout_autoHeight: true, _fontSize: '9px', title: 'inline comment' }); + const textDocInline = Docs.Create.TextDocument('', { + _layout_fieldKey: inlineLayoutKey, + _width: 75, + _height: 35, + annotationOn: textDoc, + _layout_fitWidth: true, + _layout_autoHeight: true, + _text_fontSize: '9px', + title: 'inline comment', + }); textDocInline.title = inlineFieldKey; // give the annotation its own title textDocInline.title_custom = true; // And make sure that it's 'custom' so that editing text doesn't change the title of the containing doc textDocInline.isTemplateForField = inlineFieldKey; // this is needed in case the containing text doc is converted to a template at some point @@ -234,13 +243,13 @@ export class RichTextRules { // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // [[<fieldKey> : <Doc>]] - // [[:Doc]] => hyperlink + // [[:docTitle]] => hyperlink // [[fieldKey]] => show field // [[fieldKey=value]] => show field and also set its value - // [[fieldKey:Doc]] => show field of doc + // [[fieldKey:docTitle]] => show field of doc new InputRule(new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { const fieldKey = match[1]; - const docId = match[3]?.replace(':', ''); + const docTitle = match[3]?.replace(':', ''); const value = match[2]?.substring(1); const linkToDoc = (target: Doc) => { const rstate = this.TextBox.EditorView?.state; @@ -257,21 +266,25 @@ export class RichTextRules { } }; if (!fieldKey) { - if (docId) { - const target = DocServer.QUERY_SERVER_CACHE(docId); - if (target) setTimeout(() => linkToDoc(target)); - else DocServer.GetRefField(docId).then(docx => linkToDoc((docx instanceof Doc && docx) || Docs.Create.FreeformDocument([], { title: docId + '(auto)', _width: 500, _height: 500 }, docId))); - - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); + if (docTitle) { + const target = DocServer.FindDocByTitle(docTitle); + if (target) { + setTimeout(() => linkToDoc(target)); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); + } } return state.tr; } if (value !== '' && value !== undefined) { const num = value.match(/^[0-9.]$/); - this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + } + const target = DocServer.FindDocByTitle(docTitle); + if (target) { + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target[Id], hideKey: false }); + return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); } - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId, hideKey: false }); - return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); + return state.tr; }), // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document @@ -303,11 +316,11 @@ export class RichTextRules { new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - //this.Document[DataSym]['#' + tag] = '#' + tag; - const tags = StrListCast(this.Document[DataSym].tags); + //this.Document[DocData]['#' + tag] = '#' + tag; + const tags = StrListCast(this.Document[DocData].tags); if (!tags.includes(tag)) { tags.push(tag); - this.Document[DataSym].tags = new List<string>(tags); + this.Document[DocData].tags = new List<string>(tags); } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); return state.tr diff --git a/src/client/views/nodes/importBox/ImportElementBox.tsx b/src/client/views/nodes/importBox/ImportElementBox.tsx new file mode 100644 index 000000000..58f0b29e4 --- /dev/null +++ b/src/client/views/nodes/importBox/ImportElementBox.tsx @@ -0,0 +1,38 @@ +import { computed } from 'mobx'; +import { observer } from 'mobx-react'; +import { Doc } from '../../../../fields/Doc'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue } from '../../../../Utils'; +import { Transform } from '../../../util/Transform'; +import { ViewBoxBaseComponent } from '../../DocComponent'; +import { DefaultStyleProvider } from '../../StyleProvider'; +import { DocumentView, DocumentViewInternal } from '../DocumentView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import React = require('react'); + +@observer +export class ImportElementBox extends ViewBoxBaseComponent<FieldViewProps>() { + public static LayoutString(fieldKey: string) { + return FieldView.LayoutString(ImportElementBox, fieldKey); + } + + screenToLocalXf = () => this.props.ScreenToLocalTransform().scale(1 * (this.props.NativeDimScaling?.() || 1)); + @computed get mainItem() { + return ( + <div style={{ backgroundColor: 'pink' }}> + <DocumentView + {...this.props} // + LayoutTemplateString={undefined} + Document={this.rootDoc} + isContentActive={returnFalse} + DataDoc={undefined} + addDocument={returnFalse} + ScreenToLocalTransform={this.screenToLocalXf} + hideResizeHandles={true} + /> + </div> + ); + } + render() { + return !(this.rootDoc instanceof Doc) ? null : this.mainItem; + } +} diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss index eb91c82f3..bf56b4d9e 100644 --- a/src/client/views/nodes/trails/PresBox.scss +++ b/src/client/views/nodes/trails/PresBox.scss @@ -951,6 +951,7 @@ margin-right: unset; height: 100%; position: relative; + user-select: none; } select { diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0a907c958..c0cd3ab70 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -3,7 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { Animation } from '../../../../fields/DocSymbols'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -41,8 +42,7 @@ export interface pinDataTypes { scrollable?: boolean; dataviz?: number[]; pannable?: boolean; - map?:boolean; - viewType?: boolean; + type_collection?: boolean; inkable?: boolean; filters?: boolean; pivot?: boolean; @@ -104,10 +104,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @observable _presKeyEvents: boolean = false; @observable _forceKeyEvents: boolean = false; @computed get isTreeOrStack() { - return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any); + return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any); } @computed get isTree() { - return this.layoutDoc._viewType === CollectionViewType.Tree; + return this.layoutDoc._type_collection === CollectionViewType.Tree; } @computed get presFieldKey() { return StrCast(this.layoutDoc.presFieldKey, 'data'); @@ -129,14 +129,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } public static targetRenderedDoc = (doc: Doc) => { const targetDoc = Cast(doc?.presentationTargetDoc, Doc, null); - return targetDoc?.unrendered ? DocCast(targetDoc.annotationOn) : targetDoc; + return targetDoc?.layout_unrendered ? DocCast(targetDoc.annotationOn) : targetDoc; }; @computed get scrollable() { - if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._viewType === CollectionViewType.Stacking) return true; + if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._type_collection === CollectionViewType.Stacking) return true; return false; } @computed get panable() { - if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; + if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._type_collection === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; return false; } @computed get selectedDocumentView() { @@ -197,7 +197,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.turnOffEdit(true); this._disposers.selection = reaction( () => SelectionManager.Views(), - views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation() + views => (!PresBox.Instance || views.some(view => view.props.Document === this.rootDoc)) && this.updateCurrentPresentation(), + { fireImmediately: true } ); this._disposers.editing = reaction( () => this.layoutDoc.presStatus === PresStatus.Edit, @@ -275,7 +276,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetList = PresBox.targetRenderedDoc(doc); if (doc.presIndexed !== undefined && targetList) { const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']); - return listItems.filter(doc => !doc.unrendered); + return listItems.filter(doc => !doc.layout_unrendered); } }; // Called when the user activates 'next' - to move to the next part of the pres. trail @@ -299,8 +300,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { listItemDoc.presTransition = 500; targetView?.setAnimEffect(listItemDoc, 500); if (targetView?.docView && this.activeItem.presBulletExpand) { - targetView.docView._animateScalingTo = 1.1; - Doc.AddUnHighlightWatcher(() => (targetView!.docView!._animateScalingTo = 0)); + targetView.docView._animateScalingTo = 1.2; + targetView.docView._animateScaleTime = 400; + Doc.AddUnHighlightWatcher(() => { + targetView.docView!._animateScaleTime = undefined; + targetView!.docView!._animateScalingTo = 0; + }); } listItemDoc.opacity = undefined; this.activeItem.presIndexed = presIndexed + 1; @@ -331,7 +336,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action back = () => { const activeItem: Doc = this.activeItem; - const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); let prevSelected = this.itemIndex; // Functionality for group with up let didZoom = activeItem.presMovement; @@ -377,26 +381,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { static pinDataTypes(target?: Doc): pinDataTypes { const targetType = target?.type as any; const inkable = [DocumentType.INK].includes(targetType); - const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._viewType === CollectionViewType.Stacking; - const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform); + const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking; + const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._type_collection === CollectionViewType.Freeform); const map = [DocumentType.MAP].includes(targetType); const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType); const clippable = [DocumentType.COMPARISON].includes(targetType); const datarange = [DocumentType.FUNCPLOT].includes(targetType); const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined; - const viewType = targetType === DocumentType.COL; + const type_collection = targetType === DocumentType.COL; const filters = true; const pivot = true; const dataannos = false; - return { scrollable, pannable, inkable, viewType, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; + return { scrollable, pannable, inkable, type_collection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; } @action playAnnotation = (anno: AudioField) => {}; @action static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.presPinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) { - const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc); + const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc); if (!bestTarget || activeItem === bestTarget) return; let changed = false; if (pinDocLayout) { @@ -499,16 +503,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { changed = true; } } - if ((pinDataTypes?.viewType && activeItem.presViewType !== undefined) || (!pinDataTypes && activeItem.presViewType !== undefined)) { - if (bestTarget._viewType !== activeItem.presViewType) { - bestTarget._viewType = activeItem.presViewType; + if ((pinDataTypes?.type_collection && activeItem.presViewType !== undefined) || (!pinDataTypes && activeItem.presViewType !== undefined)) { + if (bestTarget._type_collection !== activeItem.presViewType) { + bestTarget._type_collection = activeItem.presViewType; changed = true; } } if ((pinDataTypes?.filters && activeItem.presDocFilters !== undefined) || (!pinDataTypes && activeItem.presDocFilters !== undefined)) { - if (bestTarget.docFilters !== activeItem.presDocFilters) { - bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presDocFilters as ObjectField) || new List<string>([]); + if (bestTarget.childFilters !== activeItem.presDocFilters) { + bestTarget.childFilters = ObjectField.MakeCopy(activeItem.presDocFilters as ObjectField) || new List<string>([]); changed = true; } } @@ -537,13 +541,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.presAnnotations !== undefined)) { const fkey = Doc.LayoutFieldKey(bestTarget); - const oldItems = DocListCast(bestTarget[fkey + '_annotations']).filter(doc => doc.unrendered); + const oldItems = DocListCast(bestTarget[fkey + '_annotations']).filter(doc => doc.layout_unrendered); const newItems = DocListCast(activeItem.presAnnotations).map(doc => { doc.hidden = false; return doc; }); const hiddenItems = DocListCast(bestTarget[fkey + '_annotations']) - .filter(doc => !doc.unrendered && !newItems.includes(doc)) + .filter(doc => !doc.layout_unrendered && !newItems.includes(doc)) .map(doc => { doc.hidden = true; return doc; @@ -623,7 +627,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { pinProps.pinData.scrollable || pinProps.pinData.temporal || pinProps.pinData.pannable || - pinProps.pinData.viewType || + pinProps.pinData.type_collection || pinProps.pinData.clippable || pinProps.pinData.datarange || pinProps.pinData.dataview || @@ -636,7 +640,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } if (pinProps.pinData.dataannos) { const fkey = Doc.LayoutFieldKey(targetDoc); - pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.unrendered)); + pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '_annotations']).filter(doc => !doc.layout_unrendered)); } if (pinProps.pinData.inkable) { pinDoc.presFillColor = targetDoc.fillColor; @@ -673,8 +677,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }) ) ); - if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; - if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); + if (pinProps.pinData.type_collection) pinDoc.presViewType = targetDoc._type_collection; + if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField); if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; if (pinProps.pinData.pannable) { pinDoc.presPanX = NumCast(targetDoc._freeform_panX); @@ -711,7 +715,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const finished = () => { afterNav?.(); console.log('Finish Slide Nav: ' + targetDoc.title); - targetDoc[AnimationSym] = undefined; + targetDoc[Animation] = undefined; }; const selViewCache = Array.from(this.selectedArray); const dragViewCache = Array.from(this._dragArray); @@ -757,7 +761,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } } if (targetDoc) { - if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[AnimationSym] = undefined; + if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[Animation] = undefined; DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => { // if target or the doc it annotates is not in the lightbox, then close the lightbox @@ -821,7 +825,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const savedStates = docs.map(doc => { switch (doc.type) { case DocumentType.COL: - if (doc._viewType === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) }; + if (doc._type_collection === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) }; break; case DocumentType.INK: if (doc.data instanceof InkField) { @@ -839,8 +843,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { case CollectionViewType.Freeform: { const { x, y, s, doc } = savedState!; - doc._panX = x; - doc._panY = y; + doc._freeform_panX = x; + doc._freeform_panY = y; doc._freeform_scale = s; } break; @@ -857,7 +861,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } }); LightboxView.SetLightboxDoc(undefined); - Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); + Doc.RemFromMyOverlay(this.rootDoc); return PresStatus.Edit; }; }; @@ -953,8 +957,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return PresBox.OpenPresMinimized(this.rootDoc, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]); }; exitMinimize = () => { - if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { - Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); + if (Doc.IsInMyOverlay(this.layoutDoc)) { + Doc.RemFromMyOverlay(this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, OpenWhereMod.right); } return PresStatus.Edit; @@ -966,7 +970,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { doc.overlayY = pt[1]; doc._height = 30; doc._width = PresBox.minimizedWidth; - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + Doc.AddToMyOverlay(doc); PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex); return (doc.presStatus = PresStatus.Manual); } @@ -978,11 +982,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @undoBatch viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore - const viewType = e.target.selectedOptions[0].value as CollectionViewType; - this.layoutDoc.presFieldKey = this.fieldKey + (viewType === CollectionViewType.Tree ? '-linearized' : ''); + const type_collection = e.target.selectedOptions[0].value as CollectionViewType; + this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === CollectionViewType.Tree ? '-linearized' : ''); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here - [CollectionViewType.Tree || CollectionViewType.Stacking].includes(viewType) && (this.rootDoc._pivotField = undefined); - this.rootDoc._viewType = viewType; + [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.rootDoc._pivotField = undefined); + this.rootDoc._type_collection = type_collection; if (this.isTreeOrStack) { this.layoutDoc._gridGap = 0; } @@ -1044,13 +1048,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }); return true; }; - childLayoutTemplate = () => (!this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement)); + childLayoutTemplate = () => (!this.isTreeOrStack ? DocCast(Doc.UserDoc().presElement) : DocCast(Doc.UserDoc().presElement)); removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(outsideReaction); - //.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; - /** * For sorting the array so that the order is maintained when it is dropped. */ @@ -1183,7 +1184,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } break; case 'Escape': - if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { + if (Doc.IsInMyOverlay(this.layoutDoc)) { this.exitClicked(); } else if (this.layoutDoc.presStatus === PresStatus.Edit) { this.clearSelectedArray(); @@ -1452,6 +1453,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { min={min} max={max} value={value} + readOnly={true} style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }} className={`toolbar-slider ${active ? '' : 'none'}`} onPointerDown={e => { @@ -1525,7 +1527,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="ribbon-doubleButton"> <div className="presBox-subheading">Slide Duration</div> <div className="ribbon-property"> - <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s + <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s </div> <div className="ribbon-propertyUpDown"> <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}> @@ -1678,7 +1680,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> <div className="presBox-subheading">Zoom (% screen filled)</div> <div className="ribbon-property"> - <input className="presBox-input" type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />% + <input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />% </div> <div className="ribbon-propertyUpDown"> <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}> @@ -1693,7 +1695,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> <div className="presBox-subheading">Transition Time</div> <div className="ribbon-property"> - <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s + <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}> @@ -1780,6 +1782,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { className="presBox-input" style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} type="number" + readOnly={true} value={NumCast(activeItem.presStartTime).toFixed(2)} onKeyDown={e => e.stopPropagation()} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { @@ -1806,6 +1809,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { onKeyDown={e => e.stopPropagation()} style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} type="number" + readOnly={true} value={NumCast(activeItem.presEndTime).toFixed(2)} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presEndTime = Number(e.target.value); @@ -2088,20 +2092,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { createTemplate = (layout: string, input?: string) => { const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0; const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0; - const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' }); - const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' }); - const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' }); - const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' }); - const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' }); - const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' }); - const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' }); + const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _text_fontSize: '24pt' }); + const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _text_fontSize: '16pt' }); + const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _text_fontSize: '20pt' }); + const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _text_fontSize: '24pt' }); + const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _text_fontSize: '14pt' }); + const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _text_fontSize: '14pt' }); + const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' }); // prettier-ignore switch (layout) { case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y }); - case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y }); - case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y }); - case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y }); - case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _layoutFitContentsToBox: true, x, y }) + case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y }); + case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y }); + case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y }); + case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y }) } }; @@ -2150,12 +2154,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get toolbar() { const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; - const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; + const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; - return mode === CollectionViewType.Carousel3D || inOverlay ? null : ( + return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.rootDoc) ? null : ( <div id="toolbarContainer" className={'presBox-toolbar'}> {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> <FontAwesomeIcon icon={"plus"} /> @@ -2194,11 +2197,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { * presentPanel: The button to start the presentation / open minimized view of the presentation */ @computed get topPanel() { - const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; + const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); return ( - <div className={`presBox-buttons${inOverlay ? ' inOverlay' : ''}`} style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, display: !this.rootDoc._chromeHidden ? 'none' : undefined }}> + <div + className={`presBox-buttons${Doc.IsInMyOverlay(this.rootDoc) ? ' inOverlay' : ''}`} + style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, display: !this.rootDoc._chromeHidden ? 'none' : undefined }}> {isMini ? null : ( <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presStatus === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}> <option onPointerDown={StopEvent} value={CollectionViewType.Stacking}> @@ -2248,7 +2252,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get playButtons() { const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0)); const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; - const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); + const inOverlay = Doc.IsInMyOverlay(this.rootDoc); // Case 1: There are still other frames and should go through all frames before going to next slide return ( <div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== 'edit' ? 'inline-flex' : 'none' }}> @@ -2419,10 +2423,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { render() { // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); - const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; + const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType; const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0)); const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0; - const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}> <div @@ -2465,7 +2468,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> </div> ) : ( - <div className="presBox-cont" style={{ minWidth: inOverlay ? PresBox.minimizedWidth : undefined }}> + <div className="presBox-cont" style={{ minWidth: Doc.IsInMyOverlay(this.rootDoc) ? PresBox.minimizedWidth : undefined }}> {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} @@ -2479,9 +2482,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { childIgnoreNativeSize={true} moveDocument={returnFalse} ignoreUnrendered={true} + childDragAction="move" //childLayoutFitWidth={returnTrue} childOpacity={returnOne} - //childLayoutString={PresElementBox.LayoutString('data')} childClickScript={PresBox.navigateToDocScript} childLayoutTemplate={this.childLayoutTemplate} childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined} diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index e48d2d674..f31cf6147 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -1,19 +1,20 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; +import { Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from '../../../../Utils'; -import { Docs, DocUtils } from '../../../documents/Documents'; +import { Docs } from '../../../documents/Documents'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; -import { undoBatch } from '../../../util/UndoManager'; +import { undoable, undoBatch } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; @@ -106,8 +107,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { ScreenToLocalTransform={Transform.Identity} renderDepth={this.props.renderDepth + 1} docViewPath={returnEmptyDoclist} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} + childFilters={this.props.childFilters} + childFiltersByRanges={this.props.childFiltersByRanges} searchFilterDocs={this.props.searchFilterDocs} rootSelected={returnTrue} addDocument={returnFalse} @@ -191,13 +192,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { const dragArray = this.presBoxView?._dragArray ?? []; const dragData = new DragManager.DocumentDragData(this.presBoxView?.sortArray() ?? []); if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc); - dragData.dropAction = 'move'; - dragData.treeViewDoc = this.presBox?._viewType === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc; + dragData.treeViewDoc = this.presBox?._type_collection === CollectionViewType.Tree ? this.presBox : undefined; // this.props.DocumentView?.()?.props.treeViewDoc; dragData.moveDocument = this.props.moveDocument; const dragItem: HTMLElement[] = []; + const classesToRestore = new Map<HTMLElement, string>(); if (dragArray.length === 1) { const doc = this._itemRef.current || dragArray[0]; if (doc) { + classesToRestore.set(doc, doc.className); doc.className = miniView ? 'presItem-miniSlide' : 'presItem-slide'; dragItem.push(doc); } @@ -211,16 +213,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { dragItem.push(doc); } - // const dropEvent = () => runInAction(() => this._dragging = false); if (activeItem) { + runInAction(() => (this._dragging = true)); DragManager.StartDocumentDrag( dragItem.map(ele => ele), dragData, e.clientX, e.clientY, - undefined + undefined, + action(() => { + Array.from(classesToRestore).forEach(pair => (pair[0].className = pair[1])); + this._dragging = false; + }) ); - // runInAction(() => this._dragging = true); return true; } return false; @@ -263,16 +268,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { } }; - @undoBatch - removeItem = action((e: React.MouseEvent) => { + removePresentationItem = undoable((e: React.MouseEvent) => { e.stopPropagation(); if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) { - this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1; + runInAction(() => (this.presBox!.itemIndex = (this.presBoxView?.itemIndex || 0) - 1)); } this.props.removeDocument?.(this.rootDoc); this.presBoxView?.removeFromSelectedArray(this.rootDoc); this.removeAllRecordingInOverlay(); - }); + }, 'Remove doc from pres trail'); // set the value/title of the individual pres element @undoBatch @@ -323,11 +327,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { }; removeAllRecordingInOverlay = () => { - DocListCast(Doc.MyOverlayDocs.data).forEach(doc => { - if (doc.slides === this.rootDoc) { - Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc); - } - }); + DocListCast(Doc.MyOverlayDocs.data) + .filter(doc => doc.slides === this.rootDoc) + .forEach(Doc.RemFromMyOverlay); }; static removeEveryExistingRecordingInOverlay = () => { @@ -335,9 +337,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { DocListCast(Doc.MyOverlayDocs.data).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))) return; - - Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, doc); + if (PresElementBox.videoIsRecorded(DocCast(doc.slides))) { + Doc.RemFromMyOverlay(doc); + } } }); }; @@ -357,7 +359,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { if (!iconClick) PresElementBox.removeEveryExistingRecordingInOverlay(); if (activeItem.recording) { - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, DocCast(activeItem.recording)); + Doc.AddToMyOverlay(DocCast(activeItem.recording)); } }; @@ -369,7 +371,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { // if we already have an existing recording this.showRecording(activeItem, true); // // if we already have an existing recording - // Doc.AddDocToList(Doc.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); + // Doc.AddToMyOverlay(Cast(activeItem.recording, Doc, null)); } else { // Remove every recording that already exists in overlay view // this is a design decision to clear to focus in on the recoding mode @@ -379,9 +381,9 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { const recording = Docs.Create.WebCamDocument('', { _width: 384, _height: 216, - hideDocumentButtonBar: true, + layout_hideDocumentButtonBar: true, layout_hideDecorationTitle: true, - hideOpenButton: true, + layout_hideOpenButton: true, // hideDeleteButton: true, cloneFieldFilter: new List<string>(['isSystem']), }); @@ -391,9 +393,9 @@ 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[WidthSym]() - 20; - recording.overlayY = window.innerHeight - recording[HeightSym]() - 20; - Doc.AddDocToList(Doc.MyOverlayDocs, undefined, recording); + recording.overlayX = window.innerWidth - recording[Width]() - 20; + recording.overlayY = window.innerHeight - recording[Height]() - 20; + Doc.AddToMyOverlay(recording); } }; @@ -476,7 +478,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { ); items.push( <Tooltip key="trash" title={<div className="dash-tooltip">Remove from presentation</div>}> - <div className={'slideButton'} onClick={this.removeItem}> + <div className={'slideButton'} onClick={this.removePresentationItem}> <FontAwesomeIcon icon={'trash'} onPointerDown={e => e.stopPropagation()} /> </div> </Tooltip> @@ -524,7 +526,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { style={{ display: 'infline-block', backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor), - //boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined, + //layout_boxShadow: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? '0 0 0px 1.5px' + presBoxColor : undefined) : undefined, border: presBoxColor && presBoxColor !== 'white' && presBoxColor !== 'transparent' ? (isCurrent ? presBoxColor + ' solid 2.5px' : undefined) : undefined, }}> <div @@ -538,7 +540,6 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presItem-number" title="select without navigation" - style={{ pointerEvents: this.presBoxView?.isContentActive() ? 'all' : undefined }} onPointerDown={e => { e.stopPropagation(); if (this._itemRef.current && this._dragRef.current) { @@ -546,7 +547,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps>() { } }} onClick={e => e.stopPropagation()}>{`${this.indexInPres + 1}. `}</div> - <EditableView ref={this._titleRef} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} /> + <EditableView ref={this._titleRef} oneLine={true} editing={!isSelected ? false : undefined} contents={activeItem.title} overflow={'ellipsis'} GetValue={() => StrCast(activeItem.title)} SetValue={this.onSetValue} /> </div> {/* <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.transition}</div></Tooltip> */} {/* <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presItem-time" style={{ display: showMore ? "block" : "none" }}>{this.duration}</div></Tooltip> */} |
