From 5b5730a7df073659cbb6c326f748f7fcbe6625e8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 18 Jan 2024 15:05:53 -0500 Subject: lots of changes to try to simplify API for viewPaths and related --- src/client/views/global/globalScripts.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/global') diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index e57ef4871..3084a7972 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -43,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b if (checkResult) { const selView = selectedViews.lastElement(); const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; - const layoutFrameNumber = Cast(selView._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent'; } selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent'); selectedViews.forEach(dv => { const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; - const layoutFrameNumber = Cast(dv._props.docViewPath().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values + const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed if (contentFrameNumber !== undefined) { const obj: { [key: string]: Opt } = {}; -- cgit v1.2.3-70-g09d2 From e3709b4445732791f696cdf26274ab09294ce208 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 27 Jan 2024 04:21:08 -0500 Subject: made dataViz nodes linked to schema nodes update automatically as cahnges are made. fixed user created templates from disappearing from menu, and made them work. added toJavascriptString and made DashField views convert to text. added support for turning text into javascript rendering (paint) code. --- src/client/documents/Documents.ts | 2 +- src/client/util/CurrentUserUtils.ts | 12 +++-- src/client/util/DropConverter.ts | 1 + src/client/util/UndoManager.ts | 2 +- src/client/views/ContextMenuItem.tsx | 38 ++++++------- src/client/views/DocComponent.tsx | 6 +-- src/client/views/DocumentDecorations.tsx | 16 ++++-- src/client/views/EditableView.tsx | 9 ++-- .../collectionFreeForm/CollectionFreeFormView.scss | 48 +++++++++-------- .../collectionFreeForm/CollectionFreeFormView.tsx | 21 +++++++- .../collectionSchema/CollectionSchemaView.tsx | 3 +- .../views/global/globalCssVariables.module.scss | 2 + .../global/globalCssVariables.module.scss.d.ts | 1 + src/client/views/linking/LinkMenuItem.tsx | 2 +- src/client/views/nodes/DataVizBox/DataVizBox.tsx | 63 +++++++++++----------- .../views/nodes/DataVizBox/components/Chart.scss | 1 - src/client/views/nodes/DocumentView.tsx | 6 +-- src/client/views/nodes/FontIconBox/FontIconBox.tsx | 8 ++- .../views/nodes/formattedText/DashFieldView.tsx | 13 +++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++++++++-- src/client/views/nodes/formattedText/nodes_rts.ts | 3 ++ src/fields/CursorField.ts | 5 +- src/fields/DateField.ts | 5 +- src/fields/Doc.ts | 39 +++++++++----- src/fields/FieldSymbols.ts | 1 + src/fields/HtmlField.ts | 15 +++--- src/fields/IconField.ts | 17 +++--- src/fields/InkField.ts | 5 +- src/fields/List.ts | 5 +- src/fields/ObjectField.ts | 3 +- src/fields/Proxy.ts | 5 +- src/fields/RefField.ts | 11 ++-- src/fields/RichTextField.ts | 5 +- src/fields/SchemaHeaderField.ts | 5 +- src/fields/ScriptField.ts | 5 +- src/fields/URLField.ts | 8 ++- 36 files changed, 273 insertions(+), 152 deletions(-) (limited to 'src/client/views/global') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ee3bf0d82..f1a0b37b3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -211,7 +211,7 @@ export class DocumentOptions { author?: string; // STRt = new StrInfo('creator of document'); // bcz: don't change this. Otherwise, the userDoc's field Infos will have a FieldInfo assigned to its author field which will render it unreadable author_date?: DATEt = new DateInfo('date the document was created', true); annotationOn?: DOCt = new DocInfo('document annotated by this document', false); - _embedContainer?: DOCt = new DocInfo('document that displays (contains) this discument', false); + _embedContainer?: DOCt = new DocInfo('document that displays (contains) this document', false); rootDocument?: DOCt = new DocInfo('document that supplies the information needed for a rendering template (eg, pres slide for PresElement)'); color?: STRt = new StrInfo('foreground color data doc', false); hidden?: BOOLt = new BoolInfo('whether the document is not rendered by its collection', false); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 6ddb65941..8f46f844c 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -61,7 +61,7 @@ export let resolvedPorts: { server: number, socket: number }; export class CurrentUserUtils { // initializes experimental advanced template views - slideView, headerView - static setupExperimentalTemplateButtons(doc: Doc, tempDocs?:Doc) { + static setupExperimentalTemplateButtons(doc: Doc, tempDocs:Opt, userBtns:Doc[]) { const requiredTypeNameFields:{btnOpts:DocumentOptions, templateOpts:DocumentOptions, template:(opts:DocumentOptions) => Doc}[] = [ { btnOpts: { title: "slide", icon: "address-card" }, @@ -83,7 +83,7 @@ export class CurrentUserUtils { ) }, ]; - const requiredTypes = requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => { + const requiredTypes = [...requiredTypeNameFields.map(({ btnOpts, template, templateOpts }) => { const tempBtn = DocListCast(tempDocs?.data)?.find(doc => doc.title === btnOpts.title); const reqdScripts = { onDragStart: '{ return copyDragFactory(this.dragFactory,this.openFactoryAsDelegate); }' }; const assignBtnAndTempOpts = (templateBtn:Opt, btnOpts:DocumentOptions, templateOptions:DocumentOptions) => { @@ -94,7 +94,7 @@ export class CurrentUserUtils { return templateBtn; }; return DocUtils.AssignScripts(assignBtnAndTempOpts(tempBtn, btnOpts, templateOpts) ?? this.createToolButton( {...btnOpts, dragFactory: MakeTemplate(template(templateOpts))}), reqdScripts); - }); + }), ...userBtns]; const reqdOpts:DocumentOptions = { title: "Experimental Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, @@ -201,7 +201,7 @@ export class CurrentUserUtils { makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}), makeIconTemplate(DocumentType.PDF, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "pink"}), makeIconTemplate(DocumentType.WEB, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "brown"}), - makeIconTemplate(DocumentType.RTF, "text", { iconTemplate:DocumentType.LABEL, _layout_showTitle: "author_date"}), + makeIconTemplate(DocumentType.RTF, "text", { iconTemplate:DocumentType.LABEL, _layout_showTitle: "title"}), makeIconTemplate(DocumentType.IMG, "data", { iconTemplate:DocumentType.IMG, _height: undefined}), makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}), @@ -465,7 +465,9 @@ export class CurrentUserUtils { static setupToolsBtnPanel(doc: Doc, field:string) { const myTools = DocCast(doc[field]); const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, DocListCast(myTools?.data)?.length ? DocListCast(myTools.data)[0]:undefined); - const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc,DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined); + const tempBtns = DocListCast(myTools?.data)?.length > 1 ? DocListCast(myTools.data)[1]:undefined; + const userTemplateBtns = DocListCast(tempBtns?.data).filter(btn => !btn.isSystem); + const templateBtns = CurrentUserUtils.setupExperimentalTemplateButtons(doc, tempBtns, userTemplateBtns); const reqdToolOps:DocumentOptions = { title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0", layout_explainer: "This is a palette of documents that can be created.", diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index f62ec8f83..8c3b56452 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -80,6 +80,7 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { title: StrCast(layoutDoc.title), btnType: ButtonType.ClickButton, icon: 'bolt', + isSystem: false, }); dbox.dragFactory = layoutDoc; dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 9a6719ea5..421855bf3 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -102,7 +102,7 @@ export namespace UndoManager { 'UndoEvent : ' + event.prop + ' = ' + - (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toScriptString(val)).join(',') : Field.toScriptString(value)) + (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value)) ); currentBatch.push(event); tempEvents?.push(event); diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 3c9d821a9..b2076e1a5 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -38,16 +38,16 @@ export class ContextMenuItem extends ObservableReactComponent (this._items.length = 0)); - if ((this.props as SubmenuProps)?.subitems) { - (this.props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i))); + if ((this._props as SubmenuProps)?.subitems) { + (this._props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i))); } } handleEvent = async (e: React.MouseEvent) => { - if ('event' in this.props) { - this.props.closeMenu?.(); - const batch = this.props.undoable !== false ? UndoManager.StartBatch(`Click Menu item: ${this.props.description}`) : undefined; - await this.props.event({ x: e.clientX, y: e.clientY }); + if ('event' in this._props) { + this._props.closeMenu?.(); + const batch = this._props.undoable !== false ? UndoManager.StartBatch(`Click Menu item: ${this._props.description}`) : undefined; + await this._props.event({ x: e.clientX, y: e.clientY }); batch?.end(); } }; @@ -91,11 +91,11 @@ export class ContextMenuItem extends ObservableReactComponent - {this.props.icon ? {this.isJSXElement(this.props.icon) ? this.props.icon : } : null} -
{this.props.description.replace(':', '')}
+
+ {this._props.icon ? {this.isJSXElement(this._props.icon) ? this._props.icon : } : null} +
{this._props.description.replace(':', '')}
); - } else if ('subitems' in this.props) { + } else if ('subitems' in this._props) { const where = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? 'flex-start' : this._overPosY > (window.innerHeight * 2) / 3 ? 'flex-end' : 'center'; const marginTop = !this.overItem ? '' : this._overPosY < window.innerHeight / 3 ? '20px' : this._overPosY > (window.innerHeight * 2) / 3 ? '-20px' : ''; @@ -118,32 +118,32 @@ export class ContextMenuItem extends ObservableReactComponent {this._items.map(prop => ( - + ))}
); - if (!(this.props as SubmenuProps).noexpand) { + if (!(this._props as SubmenuProps).noexpand) { return (
{this._items.map(prop => ( - + ))}
); } return (
- {this.props.icon ? ( + {this._props.icon ? ( - + ) : null}
- {this.props.description} + {this._props.description}
() { const recent = this.Document !== Doc.MyRecentlyClosed ? Doc.MyRecentlyClosed : undefined; toRemove.forEach(doc => { leavePushpin && DocUtils.LeavePushpin(doc, annotationKey ?? this.annotationKey); - Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc); - Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc); + Doc.RemoveDocFromList(targetDataDoc, annotationKey ?? this.annotationKey, doc, true); + Doc.RemoveDocFromList(doc[DocData], 'proto_embeddings', doc, true); doc.embedContainer = undefined; if (recent && !dontAddToRemoved) { doc.type !== DocumentType.LOADING && Doc.AddDocToList(recent, 'data', doc, undefined, true, true); @@ -243,7 +243,7 @@ export function ViewBoxAnnotatableComponent

() { inheritParentAcls(targetDataDoc, doc, true); }); - const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List; + const annoDocs = Doc.Get(targetDataDoc, annotationKey ?? this.annotationKey, true) as List; // get the dataDoc directly ... when using templates there may be some default items already there, but we can't change them. maybe we should copy them over, though... if (annoDocs instanceof List) annoDocs.push(...added.filter(add => !annoDocs.includes(add))); else targetDataDoc[annotationKey ?? this.annotationKey] = new List(added); targetDataDoc[(annotationKey ?? this.annotationKey) + '_modificationDate'] = new DateField(); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 5ef62b2c5..2193acf62 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -18,7 +18,6 @@ import { DocumentType } from '../documents/DocumentTypes'; import { Docs } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; -import { LinkFollower } from '../util/LinkFollower'; import { SelectionManager } from '../util/SelectionManager'; import { SettingsManager } from '../util/SettingsManager'; import { SnappingManager } from '../util/SnappingManager'; @@ -112,7 +111,6 @@ export class DocumentDecorations extends ObservableReactComponent { - this._editingTitle = false; if (this._accumulatedTitle.startsWith('#') || this._accumulatedTitle.startsWith('=')) { this._titleControlString = this._accumulatedTitle; } else if (this._titleControlString.startsWith('#')) { @@ -616,7 +614,7 @@ export class DocumentDecorations extends ObservableReactComponent (this._showNothing = true))); + setTimeout( + action(() => { + this._editingTitle = false; + this._showNothing = true; + }) + ); return null; } @@ -726,7 +729,10 @@ export class DocumentDecorations extends ObservableReactComponent !hideTitle && this.titleBlur()} + onBlur={action((e: React.FocusEvent) => { + this._editingTitle = false; + !hideTitle && this.titleBlur(); + })} onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))} onKeyDown={hideTitle ? emptyFunction : this.titleEntered} onPointerDown={e => e.stopPropagation()} diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index c6666d79d..4508d00a7 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -1,4 +1,4 @@ -import { action, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { action, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as Autosuggest from 'react-autosuggest'; @@ -97,9 +97,10 @@ export class EditableView extends ObservableReactComponent { super.componentDidUpdate(prevProps); if (this._editing && this._props.editing === false) { this._inputref?.value && this.finalizeEdit(this._inputref.value, false, true, false); - } else if (this._props.editing !== undefined) { - this._editing = this._props.editing; - } + } else + runInAction(() => { + if (this._props.editing !== undefined) this._editing = this._props.editing; + }); } componentWillUnmount() { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 7d3acaea7..9e7d364ea 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -176,6 +176,11 @@ // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions. touch-action: none; transform-origin: top left; + > svg { + height: 100%; + width: 100%; + margin: auto; + } .collectionfreeformview-placeholder { background: gray; @@ -270,34 +275,31 @@ padding: 10px; .msg { - position: relative; - // display: block; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; - + position: relative; + // display: block; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; } - + .gif-container { - position: relative; - margin-top: 5px; - // display: block; - - justify-content: center; - align-items: center; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; - - + position: relative; + margin-top: 5px; + // display: block; + + justify-content: center; + align-items: center; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -o-user-select: none; + user-select: none; + .gif { background-color: transparent; height: 300px; } } - } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 53dc389b4..be19fc50f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -39,7 +39,7 @@ import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewWrapper } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; import { DocumentView, OpenWhere } from '../../nodes/DocumentView'; -import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView'; +import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../../nodes/trails/PresBox'; import { CreateImage } from '../../nodes/WebBoxRenderer'; @@ -73,6 +73,14 @@ export class CollectionFreeFormView extends CollectionSubView { ${paintFunc} })()`; + } constructor(props: any) { super(props); makeObservable(this); @@ -1227,6 +1235,7 @@ export class CollectionFreeFormView extends CollectionSubView ({ code: this.paintFunc, first: this._firstRender, width: this.Document._width, height: this.Document._height }), + ({ code, first }) => code && !first && eval(code), + { fireImmediately: true } + ); + this._disposers.layoutElements = reaction( // layoutElements can't be a computed value because doLayoutComputation() is an action that has side effect of updating clusters () => this.doInternalLayoutComputation, @@ -1847,6 +1863,7 @@ export class CollectionFreeFormView extends CollectionSubView { this.createDashEventsTarget(r); this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); @@ -1868,7 +1885,7 @@ export class CollectionFreeFormView extends CollectionSubView - {this._lightboxDoc ? ( + {this.paintFunc ? null : this._lightboxDoc ? (

(); @observable _menuKeys: string[] = []; diff --git a/src/client/views/global/globalCssVariables.module.scss b/src/client/views/global/globalCssVariables.module.scss index 44e8efe23..ad0c5c21d 100644 --- a/src/client/views/global/globalCssVariables.module.scss +++ b/src/client/views/global/globalCssVariables.module.scss @@ -64,6 +64,7 @@ $mainTextInput-zindex: 999; // then text input overlay so that it's context menu $docDecorations-zindex: 998; // then doc decorations appear over everything else $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right? $SCHEMA_DIVIDER_WIDTH: 4; +$SCHEMA_NEW_NODE_HEIGHT: 30; $MINIMIZED_ICON_SIZE: 24; $MAX_ROW_HEIGHT: 44px; $DFLT_IMAGE_NATIVE_DIM: 900px; @@ -78,6 +79,7 @@ $DATA_VIZ_TABLE_ROW_HEIGHT: 30; :export { contextMenuZindex: $contextMenu-zindex; + SCHEMA_NEW_NODE_HEIGHT: $SCHEMA_NEW_NODE_HEIGHT; SCHEMA_DIVIDER_WIDTH: $SCHEMA_DIVIDER_WIDTH; MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE; MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT; diff --git a/src/client/views/global/globalCssVariables.module.scss.d.ts b/src/client/views/global/globalCssVariables.module.scss.d.ts index bcbb1f068..12cc3fc89 100644 --- a/src/client/views/global/globalCssVariables.module.scss.d.ts +++ b/src/client/views/global/globalCssVariables.module.scss.d.ts @@ -1,5 +1,6 @@ interface IGlobalScss { contextMenuZindex: string; // context menu shows up over everything + SCHEMA_NEW_NODE_HEIGHT: string; SCHEMA_DIVIDER_WIDTH: string; MINIMIZED_ICON_SIZE: string; MAX_ROW_HEIGHT: string; diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index dc4aee1ca..ad6deeefb 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -136,7 +136,7 @@ export class LinkMenuItem extends ObservableReactComponent { deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc)))); @observable _hover = false; - docView = () => this.props.docView; + docView = () => this._props.docView; render() { const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp; diff --git a/src/client/views/nodes/DataVizBox/DataVizBox.tsx b/src/client/views/nodes/DataVizBox/DataVizBox.tsx index 8365f4770..e453bcee0 100644 --- a/src/client/views/nodes/DataVizBox/DataVizBox.tsx +++ b/src/client/views/nodes/DataVizBox/DataVizBox.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Colors, Toggle, ToggleType, Type } from 'browndash-components'; -import { ObservableMap, action, computed, makeObservable, observable, runInAction } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnEmptyString, returnFalse, returnOne, setupMoveUpEvents } from '../../../../Utils'; @@ -40,9 +40,9 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() im private _mainCont: React.RefObject = React.createRef(); private _marqueeref = React.createRef(); private _annotationLayer: React.RefObject = React.createRef(); + private _disposers: { [name: string]: IReactionDisposer } = {}; anchorMenuClick?: () => undefined | ((anchor: Doc) => void); crop: ((region: Doc | undefined, addCrop?: boolean) => Doc | undefined) | undefined; - @observable _schemaDataVizChildren: any = undefined; @observable _marqueeing: number[] | undefined = undefined; @observable _savedAnnotations = new ObservableMap(); @@ -256,12 +256,39 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() im componentDidMount() { this._props.setContentViewBox?.(this); if (!DataVizBox.dataset.has(CsvCast(this.dataDoc[this.fieldKey]).url.href)) this.fetchData(); + this._disposers.datavis = reaction( + () => { + const getFrom = DocCast(this.layoutDoc.dataViz_asSchema); + const keys = Cast(getFrom?.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text'); + if (!keys) return; + const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]); + var current: { [key: string]: string }[] = []; + children + .filter(child => child) + .forEach(child => { + const row: { [key: string]: string } = {}; + keys.forEach(key => { + var cell = child[key]; + if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, ''); + row[key] = StrCast(cell); + }); + current.push(row); + }); + return current; + }, + current => { + current && DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current); + }, + { fireImmediately: true } + ); } fetchData = () => { - DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests - fetch('/csvData?uri=' + this.dataUrl?.url.href) // - .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res)))); + if (!this.Document.dataViz_asSchema) { + DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, []); // assign temporary dataset as a lock to prevent duplicate server requests + fetch('/csvData?uri=' + this.dataUrl?.url.href) // + .then(res => res.json().then(action(res => !res.errno && DataVizBox.dataset.set(CsvCast(this.dataDoc[this.fieldKey]).url.href, res)))); + } }; // toggles for user to decide which chart type to view the data in @@ -327,33 +354,7 @@ export class DataVizBox extends ViewBoxAnnotatableComponent() im GPTPopup.Instance.addDoc = this.sidebarAddDocument; }; - @action - updateSchemaViz = () => { - const getFrom = DocCast(this.layoutDoc.dataViz_asSchema); - const keys = Cast(getFrom.schema_columnKeys, listSpec('string'))?.filter(key => key != 'text'); - if (!keys) return; - const children = DocListCast(getFrom[Doc.LayoutFieldKey(getFrom)]); - var current: { [key: string]: string }[] = []; - for (let i = 0; i < children.length; i++) { - var row: { [key: string]: string } = {}; - if (children[i]) { - for (let j = 0; j < keys.length; j++) { - var cell = children[i][keys[j]]; - if (cell && (cell as string)) cell = cell.toString().replace(/\,/g, ''); - row[keys[j]] = StrCast(cell); - } - } - current.push(row); - } - DataVizBox.dataset.set(CsvCast(this.Document[this.fieldKey]).url.href, current); - }; - render() { - if (this.layoutDoc && this.layoutDoc.dataViz_asSchema) { - this._schemaDataVizChildren = DocListCast(DocCast(this.layoutDoc.dataViz_asSchema)[Doc.LayoutFieldKey(DocCast(this.layoutDoc.dataViz_asSchema))]).length; - this.updateSchemaViz(); - } - const scale = this._props.NativeDimScaling?.() || 1; return !this.records.length ? ( // displays how to get data into the DataVizBox if its empty diff --git a/src/client/views/nodes/DataVizBox/components/Chart.scss b/src/client/views/nodes/DataVizBox/components/Chart.scss index 2f7dd0487..116a45623 100644 --- a/src/client/views/nodes/DataVizBox/components/Chart.scss +++ b/src/client/views/nodes/DataVizBox/components/Chart.scss @@ -93,7 +93,6 @@ display: flex; flex-direction: column; cursor: default; - margin-top: 30px; height: calc(100% - 40px); // bcz: hack 40px is the size of the button rows .tableBox-container { overflow: scroll; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a82580ddb..2e1ba2a7e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -592,13 +592,9 @@ export class DocumentViewInternal extends DocComponent this.toggleFollowLink(false, false), icon: 'link' }); !Doc.noviceMode && onClicks.push({ description: 'Edit onClick Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onClick'), 'edit onClick'), icon: 'terminal' }); - !existingOnClick && cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); + cm.addItem({ description: 'OnClick...', noexpand: true, subitems: onClicks, icon: 'mouse-pointer' }); } else if (LinkManager.Links(this.Document).length) { onClicks.push({ description: 'Restore On Click default', event: () => this.noOnClick(), icon: 'link' }); onClicks.push({ description: 'Follow Link on Click', event: () => this.followLinkOnClick(), icon: 'link' }); diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index cf07d98be..8290e102c 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -73,7 +73,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { }; specificContextMenu = (): void => { - if (!Doc.noviceMode) { + if (!Doc.noviceMode && Cast(this.layoutDoc.dragFactory, Doc, null)) { 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' }); @@ -366,7 +366,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { ); } - render() { + renderButton = () => { const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); const tooltip = StrCast(this.Document.toolTip); const scriptFunc = () => ScriptCast(this.Document.onClick)?.script.run({ this: this.Document, self: this.Document, _readOnly_: false }); @@ -389,5 +389,9 @@ export class FontIconBox extends ViewBoxBaseComponent() { background={SettingsManager.userBackgroundColor} size={Size.LARGE} tooltipPlacement='right' onPointerDown={scriptFunc} />; } return this.defaultButton; + }; + + render() { + return
{this.renderButton()}
; } } diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index dc9914637..18286267a 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,10 +1,10 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { Doc } from '../../../../fields/Doc'; +import { Doc, Field } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; @@ -114,6 +114,13 @@ export class DashFieldViewInternal extends ObservableReactComponent (this._dashDoc ? Field.toKeyValueString(this._dashDoc, this._props.fieldKey) : undefined), + keyvalue => keyvalue && this._props.tbox.tryUpdateDoc(true) + ); + } + componentWillUnmount() { this._reactionDisposer?.(); } @@ -184,7 +191,7 @@ export class DashFieldViewInternal extends ObservableReactComponent {this._props.hideKey ? null : ( - {this._fieldKey} + {(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey} )} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 731ab1d53..6f9c2893e 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -67,6 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; +import { CollectionView } from '../../collections/CollectionView'; // import * as applyDevTools from 'prosemirror-dev-tools'; @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -325,13 +326,26 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + if (node.type === this._editorView?.state.schema.nodes.dashField) { + const refDoc = !node.attrs.docId ? this.Document : (DocServer.GetCachedRefField(node.attrs.docId as string) as Doc); + return Field.toJavascriptString(refDoc[node.attrs.fieldKey as string] as Field); + } + return ''; + }; dispatchTransaction = (tx: Transaction) => { if (this._editorView && (this._editorView as any).docView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); + this.tryUpdateDoc(false); + } + }; + tryUpdateDoc = (force: boolean) => { + if (this._editorView && (this._editorView as any).docView) { + const state = this._editorView.state; const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc.proto), this.fieldKey) ? DocCast(this.layoutDoc.proto) : this.dataDoc; - const newText = state.doc.textBetween(0, state.doc.content.size, ' \n'); + const newText = state.doc.textBetween(0, state.doc.content.size, ' \n', this.leafText); 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.Document !== this.layoutDoc ? prevData : undefined; // the default text stored in a layout template @@ -350,13 +364,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent(Array.from(new Set(accumTags))) : undefined; let unchanged = true; - if (this._applyingChange !== this.fieldKey && removeSelection(newJson) !== removeSelection(prevData?.Data)) { + if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) { this._applyingChange = this.fieldKey; const textChange = newText !== prevData?.Text; textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); 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 ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data)) { + if (force || ((this._finishingLink || this._props.isContentActive() || this._inDrop) && removeSelection(newJson) !== removeSelection(prevData?.Data))) { const numstring = NumCast(dataDoc[this.fieldKey], null); 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 @@ -480,7 +494,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { const splitter = editorView.state.schema.marks.splitter.create({ id: Utils.GenerateGuid() }); - if (!sel.$anchor.pos || editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim() === autoLinkTerm) { + if (!sel.$anchor.pos || [autoLinkTerm, StrCast(target.title)].includes(editorView.state.doc.textBetween(sel.$anchor.pos - 1, sel.$to.pos).trim())) { tr = tr.addMark(sel.from, sel.to, splitter); tr.doc.nodesBetween(sel.from, sel.to, (node: any, pos: number, parent: any) => { 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)) { @@ -919,6 +933,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._createDocOnCR = !this.layoutDoc._createDocOnCR), icon: !this.Document._createDocOnCR ? 'grip-lines' : 'bars', }); + optionItems.push({ + description: 'Make Paint Function', + event: () => { + this.dataDoc.layout_painted = CollectionView.LayoutString('painted'); + this.layoutDoc.layout_fieldKey = 'layout_painted'; + this.layoutDoc.type_collection = CollectionViewType.Freeform; + this.DocumentView?.().setToggleDetail(); + this.dataDoc.paintFunc = ComputedField.MakeFunction(`toJavascriptString(this['${this.fieldKey}']?.Text)`); + }, + icon: !this.Document._layout_enableAltContentUI ? 'eye-slash' : 'eye', + }); !Doc.noviceMode && optionItems.push({ description: `${this.Document._layout_autoHeight ? 'Lock' : 'Auto'} Height`, @@ -1682,7 +1707,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { this.dataDoc.title_custom = true; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index d023020e1..31f001b11 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -2,6 +2,8 @@ import * as React from 'react'; import { DOMOutputSpec, Node, NodeSpec } from 'prosemirror-model'; import { listItem, orderedList } from 'prosemirror-schema-list'; import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from './ParagraphNodeSpec'; +import { DocServer } from '../../../DocServer'; +import { Doc, Field } from '../../../../fields/Doc'; const blockquoteDOM: DOMOutputSpec = ['blockquote', 0], hrDOM: DOMOutputSpec = ['hr'], @@ -264,6 +266,7 @@ export const nodes: { [index: string]: NodeSpec } = { hideKey: { default: false }, editable: { default: true }, }, + leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field), group: 'inline', draggable: false, toDOM(node) { diff --git a/src/fields/CursorField.ts b/src/fields/CursorField.ts index 84917ae53..870dfbeac 100644 --- a/src/fields/CursorField.ts +++ b/src/fields/CursorField.ts @@ -1,6 +1,6 @@ import { createSimpleSchema, object, serializable } from 'serializr'; import { Deserializable } from '../client/util/SerializationHelper'; -import { Copy, FieldChanged, ToScriptString, ToString } from './FieldSymbols'; +import { Copy, FieldChanged, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { ObjectField } from './ObjectField'; export type CursorPosition = { @@ -55,6 +55,9 @@ export default class CursorField extends ObjectField { return new CursorField(this.data); } + [ToJavascriptString]() { + return 'invalid'; + } [ToScriptString]() { return 'invalid'; } diff --git a/src/fields/DateField.ts b/src/fields/DateField.ts index 2ea619bd9..1e5222fb6 100644 --- a/src/fields/DateField.ts +++ b/src/fields/DateField.ts @@ -1,7 +1,7 @@ import { Deserializable } from '../client/util/SerializationHelper'; import { serializable, date } from 'serializr'; import { ObjectField } from './ObjectField'; -import { Copy, ToScriptString, ToString } from './FieldSymbols'; +import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; @scriptingGlobal @@ -23,6 +23,9 @@ export class DateField extends ObjectField { return `${this.date.toLocaleString()}`; } + [ToJavascriptString]() { + return `"${this.date.toISOString()}"`; + } [ToScriptString]() { return `new DateField(new Date("${this.date.toISOString()}"))`; } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ff416bbe7..f4141cf46 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -18,7 +18,7 @@ import { DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight, Initializing, Self, SelfProxy, UpdatingFromServer, Width } from './DocSymbols'; // prettier-ignore -import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToScriptString, ToString } from './FieldSymbols'; +import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { InkField, InkTool } from './InkField'; import { List, ListFieldName } from './List'; import { ObjectField } from './ObjectField'; @@ -52,6 +52,23 @@ export namespace Field { default: return field?.[ToScriptString]?.() ?? 'null'; } // prettier-ignore } + export function toJavascriptString(field: Field) { + var rawjava = ''; + + switch (typeof field) { + case 'string': + case 'number': + case 'boolean':rawjava = String(field); + break; + default: rawjava = field?.[ToJavascriptString]?.() ?? 'null'; + } // prettier-ignore + var script = rawjava; + Doc.MyPublishedDocs.forEach(doc => { + const regex = new RegExp(`^\\^${doc.title}\\s`, 'm'); + script = script.replace(regex, Cast(doc.text, RichTextField, null)?.Text ?? ''); + }); + return script; + } export function toString(field: Field) { if (typeof field === 'string' || typeof field === 'number' || typeof field === 'boolean') return String(field); return field?.[ToString]?.() || ''; @@ -287,6 +304,7 @@ export class Doc extends RefField { public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any); public [Width] = () => NumCast(this[SelfProxy]._width); public [Height] = () => NumCast(this[SelfProxy]._height); + public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here? public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`; public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`; public get [DocLayout]() { return this[SelfProxy].__LAYOUT__; } // prettier-ignore @@ -508,12 +526,9 @@ export namespace Doc { * Removes doc from the list of Docs at listDoc[fieldKey] * @returns true if successful, false otherwise. */ - export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) { + export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, ignoreProto = false) { const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); - if (listDoc[key] === undefined) { - listDoc[DocData][key] = new List(); - } - const list = Cast(listDoc[key], listSpec(Doc)); + const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List()) : Cast(listDoc[key], listSpec(Doc)); if (list) { const ind = list.indexOf(doc); if (ind !== -1) { @@ -528,12 +543,9 @@ export namespace Doc { * Adds doc to the list of Docs stored at listDoc[fieldKey]. * @returns true if successful, false otherwise. */ - export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { + export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean, ignoreProto?: boolean) { const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); - if (listDoc[key] === undefined) { - listDoc[DocData][key] = new List(); - } - const list = Cast(listDoc[key], listSpec(Doc)); + const list = Doc.Get(listDoc, key, ignoreProto) === undefined ? (listDoc[DocData][key] = new List()) : Cast(listDoc[key], listSpec(Doc)); if (list) { if (!allowDuplicates) { const pind = list.findIndex(d => d instanceof Doc && d[Id] === doc[Id]); @@ -1020,7 +1032,7 @@ export namespace Doc { // export function MakeMetadataFieldTemplate(templateField: Doc, templateDoc: Opt): boolean { // find the metadata field key that this template field doc will display (indicated by its title) - const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, ''); + const metadataFieldKey = StrCast(templateField.isTemplateForField) || StrCast(templateField.title).replace(/^-/, '') || Doc.LayoutFieldKey(templateField); // update the original template to mark it as a template templateField.isTemplateForField = metadataFieldKey; @@ -1640,3 +1652,6 @@ ScriptingGlobals.add(function setDocFilter(container: Doc, key: string, value: a ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, range: number[]) { Doc.setDocRangeFilter(container, key, range); }); +ScriptingGlobals.add(function toJavascriptString(str: string) { + return Field.toJavascriptString(str as Field); +}); diff --git a/src/fields/FieldSymbols.ts b/src/fields/FieldSymbols.ts index 0dbeb064b..0c44d2417 100644 --- a/src/fields/FieldSymbols.ts +++ b/src/fields/FieldSymbols.ts @@ -5,5 +5,6 @@ export const Parent = Symbol('FieldParent'); export const Copy = Symbol('FieldCopy'); export const ToValue = Symbol('FieldToValue'); export const ToScriptString = Symbol('FieldToScriptString'); +export const ToJavascriptString = Symbol('FieldToJavascriptString'); export const ToPlainText = Symbol('FieldToPlainText'); export const ToString = Symbol('FieldToString'); diff --git a/src/fields/HtmlField.ts b/src/fields/HtmlField.ts index 6e8bba977..b67f0f7e9 100644 --- a/src/fields/HtmlField.ts +++ b/src/fields/HtmlField.ts @@ -1,9 +1,9 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, primitive } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString} from "./FieldSymbols"; +import { Deserializable } from '../client/util/SerializationHelper'; +import { serializable, primitive } from 'serializr'; +import { ObjectField } from './ObjectField'; +import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; -@Deserializable("html") +@Deserializable('html') export class HtmlField extends ObjectField { @serializable(primitive()) readonly html: string; @@ -17,8 +17,11 @@ export class HtmlField extends ObjectField { return new HtmlField(this.html); } + [ToJavascriptString]() { + return 'invalid'; + } [ToScriptString]() { - return "invalid"; + return 'invalid'; } [ToString]() { return this.html; diff --git a/src/fields/IconField.ts b/src/fields/IconField.ts index 76c4ddf1b..4d2badb68 100644 --- a/src/fields/IconField.ts +++ b/src/fields/IconField.ts @@ -1,9 +1,9 @@ -import { Deserializable } from "../client/util/SerializationHelper"; -import { serializable, primitive } from "serializr"; -import { ObjectField } from "./ObjectField"; -import { Copy, ToScriptString, ToString } from "./FieldSymbols"; +import { Deserializable } from '../client/util/SerializationHelper'; +import { serializable, primitive } from 'serializr'; +import { ObjectField } from './ObjectField'; +import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; -@Deserializable("icon") +@Deserializable('icon') export class IconField extends ObjectField { @serializable(primitive()) readonly icon: string; @@ -17,10 +17,13 @@ export class IconField extends ObjectField { return new IconField(this.icon); } + [ToJavascriptString]() { + return 'invalid'; + } [ToScriptString]() { - return "invalid"; + return 'invalid'; } [ToString]() { - return "ICONfield"; + return 'ICONfield'; } } diff --git a/src/fields/InkField.ts b/src/fields/InkField.ts index 22bea3927..b3e01229a 100644 --- a/src/fields/InkField.ts +++ b/src/fields/InkField.ts @@ -2,7 +2,7 @@ import { Bezier } from 'bezier-js'; import { alias, createSimpleSchema, list, object, serializable } from 'serializr'; import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { Deserializable } from '../client/util/SerializationHelper'; -import { Copy, ToScriptString, ToString } from './FieldSymbols'; +import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { ObjectField } from './ObjectField'; // Helps keep track of the current ink tool in use. @@ -85,6 +85,9 @@ export class InkField extends ObjectField { return new InkField(this.inkData); } + [ToJavascriptString]() { + return '[' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + ']'; + } [ToScriptString]() { return 'new InkField([' + this.inkData.map(i => `{X: ${i.X}, Y: ${i.Y}}`) + '])'; } diff --git a/src/fields/List.ts b/src/fields/List.ts index b8ad552d2..9458a9611 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -5,7 +5,7 @@ import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { Deserializable, afterDocDeserialize, autoObject } from '../client/util/SerializationHelper'; import { Field } from './Doc'; import { FieldTuples, Self, SelfProxy } from './DocSymbols'; -import { Copy, FieldChanged, Parent, ToScriptString, ToString } from './FieldSymbols'; +import { Copy, FieldChanged, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { ObjectField } from './ObjectField'; import { ProxyField } from './Proxy'; import { RefField } from './RefField'; @@ -310,6 +310,9 @@ class ListImpl extends ObjectField { private [Self] = this; private [SelfProxy]: List; // also used in utils.ts even though it won't be found using find all references + [ToJavascriptString]() { + return `[${(this as any).map((field: any) => Field.toScriptString(field))}]`; + } [ToScriptString]() { return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; } diff --git a/src/fields/ObjectField.ts b/src/fields/ObjectField.ts index b5bc2952a..e1b5b036c 100644 --- a/src/fields/ObjectField.ts +++ b/src/fields/ObjectField.ts @@ -1,5 +1,5 @@ import { RefField } from './RefField'; -import { FieldChanged, Parent, Copy, ToScriptString, ToString } from './FieldSymbols'; +import { FieldChanged, Parent, Copy, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols'; import { ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { Field } from './Doc'; @@ -12,6 +12,7 @@ export abstract class ObjectField { public [Parent]?: RefField | ObjectField; abstract [Copy](): ObjectField; + abstract [ToJavascriptString](): string; abstract [ToScriptString](): string; abstract [ToString](): string; } diff --git a/src/fields/Proxy.ts b/src/fields/Proxy.ts index 3a46e3581..820d9b6ff 100644 --- a/src/fields/Proxy.ts +++ b/src/fields/Proxy.ts @@ -4,7 +4,7 @@ import { DocServer } from '../client/DocServer'; import { scriptingGlobal } from '../client/util/ScriptingGlobals'; import { Deserializable } from '../client/util/SerializationHelper'; import { Field, FieldWaiting, Opt } from './Doc'; -import { Copy, Id, ToScriptString, ToString, ToValue } from './FieldSymbols'; +import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols'; import { ObjectField } from './ObjectField'; import { RefField } from './RefField'; @@ -38,6 +38,9 @@ export class ProxyField extends ObjectField { return new ProxyField(this.fieldId); } + [ToJavascriptString]() { + return Field.toScriptString(this[ToValue](undefined)?.value); + } [ToScriptString]() { return Field.toScriptString(this[ToValue](undefined)?.value); // not sure this is quite right since it doesn't recreate a proxy field, but better than 'invalid' ? } diff --git a/src/fields/RefField.ts b/src/fields/RefField.ts index b6ef69750..01828dd14 100644 --- a/src/fields/RefField.ts +++ b/src/fields/RefField.ts @@ -1,11 +1,11 @@ -import { serializable, primitive, alias } from "serializr"; -import { Utils } from "../Utils"; -import { Id, HandleUpdate, ToScriptString, ToString } from "./FieldSymbols"; -import { afterDocDeserialize } from "../client/util/SerializationHelper"; +import { serializable, primitive, alias } from 'serializr'; +import { Utils } from '../Utils'; +import { Id, HandleUpdate, ToScriptString, ToString, ToJavascriptString } from './FieldSymbols'; +import { afterDocDeserialize } from '../client/util/SerializationHelper'; export type FieldId = string; export abstract class RefField { - @serializable(alias("id", primitive({ afterDeserialize: afterDocDeserialize }))) + @serializable(alias('id', primitive({ afterDeserialize: afterDocDeserialize }))) private __id: FieldId; readonly [Id]: FieldId; @@ -16,6 +16,7 @@ export abstract class RefField { protected [HandleUpdate]?(diff: any): void | Promise; + abstract [ToJavascriptString](): string; abstract [ToScriptString](): string; abstract [ToString](): string; } diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index 3e75a071f..50cfab988 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -1,7 +1,7 @@ import { serializable } from 'serializr'; import { scriptingGlobal } from '../client/util/ScriptingGlobals'; import { Deserializable } from '../client/util/SerializationHelper'; -import { Copy, ToScriptString, ToString } from './FieldSymbols'; +import { Copy, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { ObjectField } from './ObjectField'; @scriptingGlobal @@ -27,6 +27,9 @@ export class RichTextField extends ObjectField { return new RichTextField(this.Data, this.Text); } + [ToJavascriptString]() { + return '`' + this.Text + '`'; + } [ToScriptString]() { return `new RichTextField("${this.Data.replace(/"/g, '\\"')}", "${this.Text}")`; } diff --git a/src/fields/SchemaHeaderField.ts b/src/fields/SchemaHeaderField.ts index 6dde2e5aa..fb4dc4e5b 100644 --- a/src/fields/SchemaHeaderField.ts +++ b/src/fields/SchemaHeaderField.ts @@ -1,7 +1,7 @@ import { Deserializable } from '../client/util/SerializationHelper'; import { serializable, primitive } from 'serializr'; import { ObjectField } from './ObjectField'; -import { Copy, ToScriptString, ToString, FieldChanged } from './FieldSymbols'; +import { Copy, ToScriptString, ToString, FieldChanged, ToJavascriptString } from './FieldSymbols'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { ColumnType } from '../client/views/collections/collectionSchema/CollectionSchemaView'; @@ -114,6 +114,9 @@ export class SchemaHeaderField extends ObjectField { return new SchemaHeaderField(this.heading, this.color, this.type, this.width, this.desc, this.collapsed); } + [ToJavascriptString]() { + return `["${this.heading}","${this.color}",${this.type},${this.width},${this.desc},${this.collapsed}]`; + } [ToScriptString]() { return `schemaHeaderField("${this.heading}","${this.color}",${this.type},${this.width},${this.desc},${this.collapsed})`; } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 62690a9fb..c7fe72ca6 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -6,7 +6,7 @@ import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGloba import { autoObject, Deserializable } from '../client/util/SerializationHelper'; import { numberRange } from '../Utils'; import { Doc, Field, Opt } from './Doc'; -import { Copy, Id, ToScriptString, ToString, ToValue } from './FieldSymbols'; +import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols'; import { List } from './List'; import { ObjectField } from './ObjectField'; import { Cast, StrCast } from './Types'; @@ -113,6 +113,9 @@ export class ScriptField extends ObjectField { return `${this.script.originalScript} + ${this.setterscript?.originalScript}`; } + [ToJavascriptString]() { + return this.script.originalScript; + } [ToScriptString]() { return this.script.originalScript; } diff --git a/src/fields/URLField.ts b/src/fields/URLField.ts index 817b62373..87334ad16 100644 --- a/src/fields/URLField.ts +++ b/src/fields/URLField.ts @@ -1,7 +1,7 @@ import { Deserializable } from '../client/util/SerializationHelper'; import { serializable, custom } from 'serializr'; import { ObjectField } from './ObjectField'; -import { ToScriptString, ToString, Copy } from './FieldSymbols'; +import { ToScriptString, ToString, Copy, ToJavascriptString } from './FieldSymbols'; import { scriptingGlobal } from '../client/util/ScriptingGlobals'; import { Utils } from '../Utils'; @@ -36,6 +36,12 @@ export abstract class URLField extends ObjectField { } return `new ${this.constructor.name}("${this.url?.href}")`; } + [ToJavascriptString]() { + if (Utils.prepend(this.url?.pathname) === this.url?.href) { + return `new ${this.constructor.name}("${this.url.pathname}")`; + } + return `new ${this.constructor.name}("${this.url?.href}")`; + } [ToString]() { if (Utils.prepend(this.url?.pathname) === this.url?.href) { return this.url.pathname; -- cgit v1.2.3-70-g09d2 From a888150f2e220eff4629551aa03813f92aa0b12f Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 5 Feb 2024 22:56:04 -0500 Subject: changed backgroundColor to set on dataDocs. fixed pivoting on tags. fixed link description editing popup. fixed showing link editor in property view - still some weirdness in what is selected. fixed dragging tree view items to set dragData.treeview and be able to drop at bottom of tree. fixed addFolder menu option for TreeViews to add locally.. added a function to collect all docs of a given tag into a collection. fixed setting default font size to update autolayouts. changed dropping link onto same collection to not leave pushpin. fixed minimap thumb updating. added fieldvalue dropdown for dashFieldViews in text. --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 22 ++----- src/client/util/DragManager.ts | 2 +- src/client/util/LinkManager.ts | 4 +- src/client/util/SearchUtil.ts | 7 +- src/client/util/SelectionManager.ts | 4 +- src/client/util/SettingsManager.tsx | 12 +++- src/client/views/DocComponent.tsx | 4 +- src/client/views/DocumentDecorations.tsx | 3 + src/client/views/FilterPanel.tsx | 6 +- .../views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesSection.scss | 26 ++++---- src/client/views/PropertiesView.tsx | 74 ++++++++++++---------- src/client/views/StyleProvider.tsx | 2 +- .../views/collections/CollectionTimeView.tsx | 7 +- .../views/collections/CollectionTreeView.tsx | 73 ++++++++++++++------- src/client/views/collections/TabDocView.tsx | 1 + src/client/views/collections/TreeView.scss | 6 +- src/client/views/collections/TreeView.tsx | 34 +++++----- .../CollectionFreeFormLinkView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 30 +++------ src/client/views/global/globalScripts.ts | 7 +- src/client/views/linking/LinkMenuItem.tsx | 6 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 34 ++++++++-- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/LinkBox.scss | 25 ++++++++ src/client/views/nodes/LinkBox.tsx | 69 +++++++++++++------- src/client/views/nodes/LinkDescriptionPopup.tsx | 18 ++++-- src/client/views/nodes/LinkDocPreview.tsx | 4 +- .../views/nodes/formattedText/DashFieldView.scss | 10 +++ .../views/nodes/formattedText/DashFieldView.tsx | 27 ++++++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 11 ++-- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/fields/Doc.ts | 2 +- src/fields/List.ts | 2 +- 36 files changed, 343 insertions(+), 207 deletions(-) (limited to 'src/client/views/global') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cc983ffa7..95058da22 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -437,7 +437,7 @@ export class DocumentOptions { clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false); onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script) - + tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true); treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view'); treeView_HideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeView_FieldKey tag (presBox)"); treeView_HideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)'); @@ -1459,7 +1459,7 @@ export namespace DocUtils { const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { if (showPopup) { - LinkManager.currentLink = linkDoc; + LinkManager.Instance.currentLink = linkDoc; TaskCompletionBox.textDisplayed = 'Link Created'; TaskCompletionBox.popupX = showPopup[0]; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 31f0308b7..714e33d25 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -17,7 +17,7 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents"; import { DashboardView } from "../views/DashboardView"; import { OverlayView } from "../views/OverlayView"; -import { TreeViewType } from "../views/collections/CollectionTreeView"; +import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView"; import { Colors } from "../views/global/globalEnums"; import { media_state } from "../views/nodes/AudioBox"; import { OpenWhere } from "../views/nodes/DocumentView"; @@ -78,7 +78,7 @@ export class CurrentUserUtils { const tempClicks = DocCast(doc[field]); const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ - { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"}, + { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([this])))"}, { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; @@ -502,29 +502,21 @@ export class CurrentUserUtils { static setupFilesystem(doc: Doc, field:string) { var myFilesystem = DocCast(doc[field]); - const newFolder = `TreeView_addNewFolder()`; const newFolderOpts: DocumentOptions = { - _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List(['treeView_SortCriterion']), + _forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List(['treeView_SortCriterion']), title: "New folder", color: Colors.BLACK, btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true }; - const newFolderScript = { onClick: newFolder}; + const newFolderScript = { onClick: CollectionTreeView.AddTreeFunc}; const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true, title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true, isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true, treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed", - childContextMenuLabels: new List(["Create new folder"]), - childContextMenuIcons: new List(["plus"]), layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." }; const fileFolders = new Set(DocListCast(DocCast(doc[field])?.data)); - myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders)); - const childContextMenuScripts = [newFolder]; - if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { - myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); - } - return myFilesystem; + return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders)); } /// initializes the panel displaying docs that have been recently closed @@ -544,8 +536,8 @@ export class CurrentUserUtils { toolTip: "Empty recently closed",}; DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("this.target")}); - if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("self"))) { - recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("self"))!]) + if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("this"))) { + recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("this"))!]) } return recentlyClosed; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index a6ad0f1b3..071b25a0e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -243,7 +243,7 @@ export namespace DragManager { }; dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, finishDrag); - dragData.draggedViews.forEach(view => view.props.dragStarting?.()); + dragData.draggedViews.forEach(view => view.props.dragStarting?.(dragData)); return true; } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 353f28a92..dd3b9bd07 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -23,8 +23,8 @@ import { ScriptingGlobals } from './ScriptingGlobals'; export class LinkManager { @observable static _instance: LinkManager; @observable.shallow userLinkDBs: Doc[] = []; - @observable public static currentLink: Opt = undefined; - @observable public static currentLinkAnchor: Opt = undefined; + @observable public currentLink: Opt = undefined; + @observable public currentLinkAnchor: Opt = undefined; public static get Instance() { return LinkManager._instance; } diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 218667d3e..fff2737b6 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -8,7 +8,7 @@ import { DocOptions, FInfo } from '../documents/Documents'; export namespace SearchUtil { export type HighlightingResult = { [id: string]: { [key: string]: string[] } }; - export function SearchCollection(collectionDoc: Opt, query: string, matchKeyNames: boolean) { + export function SearchCollection(collectionDoc: Opt, query: string, matchKeyNames: boolean, onlyKeys?: string[]) { const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; const blockedKeys = matchKeyNames ? [] @@ -27,8 +27,9 @@ export namespace SearchUtil { const dtype = StrCast(doc.type) as DocumentType; if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) { const hlights = new Set(); - SearchUtil.documentKeys(doc).forEach( - key => (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))( + (onlyKeys ?? SearchUtil.documentKeys(doc)).forEach( + key => + (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))( matchKeyNames ? key : Field.toString(doc[key] as Field)) && hlights.add(key) ); // prettier-ignore diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index f2a327445..b79281802 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -51,8 +51,8 @@ export class SelectionManager { public static DeselectAll = (except?: Doc): void => { const found = this.Instance.SelectedViews.find(dv => dv.Document === except); - LinkManager.currentLink = undefined; - LinkManager.currentLinkAnchor = undefined; + LinkManager.Instance.currentLink = undefined; + LinkManager.Instance.currentLinkAnchor = undefined; runInAction(() => (this.Instance.SelectedSchemaDocument = undefined)); this.Instance.SelectedViews.forEach(dv => { dv.IsSelected = false; diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 5bf9e5b00..682704770 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -317,7 +317,17 @@ export class SettingsManager extends React.Component<{}> {
{/* */} - {}} /> + (Doc.UserDoc().fontSize = val + 'px')} + /> { return { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 58be41f53..3c772bd42 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -47,7 +47,7 @@ export interface ViewBoxInterface { setData?: (data: Field | Promise) => boolean; componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set) => void; - dragConfig?: (dragData: DragManager.DocumentDragData) => void; + dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag) incrementalRendering?: () => void; infoUI?: () => JSX.Element | null; screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>; @@ -57,7 +57,7 @@ export interface ViewBoxInterface { search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; } /** - * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's + * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's * (note: this should not be used for the 'Box' views that render the contents of Doc views) * Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal) * */ diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index dec109d7b..e9c4d9cc5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -114,6 +114,9 @@ export class DocumentDecorations extends ObservableReactComponent#')) { + SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`))); + } const titleFieldKey = this._titleControlString.substring(1); UndoManager.RunInBatch( () => diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index c4f65a5ca..818c81c9a 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -117,8 +117,8 @@ export class FilterPanel extends ObservableReactComponent { // return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); // } - gatherFieldValues(childDocs: Doc[], facetKey: string) { - const valueSet = new Set(StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1])); + static gatherFieldValues(childDocs: Doc[], facetKey: string, childFilters: string[]) { + const valueSet = new Set(childFilters.map(filter => filter.split(Doc.FilterSep)[1])); let rtFields = 0; let subDocs = childDocs; if (subDocs.length > 0) { @@ -165,7 +165,7 @@ export class FilterPanel extends ObservableReactComponent { @computed get activeRenderedFacetInfos() { return new Set( Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => { - const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); + const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.targetDoc.childFilters)); let nonNumbers = 0; let minVal = Number.MAX_VALUE, diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index 244cd4aa0..cf5105efc 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component LinkManager.currentLink, + () => LinkManager.Instance.currentLink, link => { link && this.CloseAll(); link && (this.openLinks = true); @@ -173,10 +173,14 @@ export class PropertiesView extends ObservableReactComponent(reqdKeys); const docs: Doc[] = SelectionManager.Views.length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); - docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))); + docs.forEach(doc => + Object.keys(doc) + .filter(filter) + .forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key)) + ); // prettier-ignore - Array.from(ids).filter(filter).sort().map(key => { + Array.from(ids).sort().map(key => { const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set()).keys()).length > 1; const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key); const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field); @@ -270,12 +274,12 @@ export class PropertiesView extends ObservableReactComponent; } @computed get linkCount() { - const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc; + const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc; var counter = 0; LinkManager.Links(selAnchor).forEach((l, i) => counter++); @@ -571,19 +575,19 @@ export class PropertiesView extends ObservableReactComponent - {LinkManager.currentLinkAnchor ? ( + {LinkManager.Instance.currentLinkAnchor ? (

<> Anchor: - {LinkManager.currentLinkAnchor.title} + {LinkManager.Instance.currentLinkAnchor.title}

) : null} - {LinkManager.currentLink?.title ? ( + {LinkManager.Instance.currentLink?.title ? (

<> Link: - {LinkManager.currentLink.title} + {LinkManager.Instance.currentLink.title}

) : null} @@ -1222,10 +1226,10 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.currentLink && this.selectedDoc) { + if (LinkManager.Instance.currentLink && this.selectedDoc) { this.setDescripValue(value); } }), @@ -1244,7 +1248,7 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.currentLink && this.selectedDoc) { + if (LinkManager.Instance.currentLink && this.selectedDoc) { this.setlinkRelationshipValue(value); } }), @@ -1253,17 +1257,17 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.currentLink) { - Doc.GetProto(LinkManager.currentLink).link_description = value; + if (LinkManager.Instance.currentLink) { + Doc.GetProto(LinkManager.Instance.currentLink).link_description = value; } }); @undoBatch setlinkRelationshipValue = action((value: string) => { - if (LinkManager.currentLink) { - const prevRelationship = StrCast(LinkManager.currentLink.link_relationship); - LinkManager.currentLink.link_relationship = value; - Doc.GetProto(LinkManager.currentLink).link_relationship = value; + if (LinkManager.Instance.currentLink) { + const prevRelationship = StrCast(LinkManager.Instance.currentLink.link_relationship); + LinkManager.Instance.currentLink.link_relationship = value; + Doc.GetProto(LinkManager.Instance.currentLink).link_relationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList); const linkRelationshipSizes = NumListCast(Doc.UserDoc().link_relationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().link_ColorList); @@ -1347,20 +1351,20 @@ export class PropertiesView extends ObservableReactComponent { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop])))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[prop] = !LinkManager.Instance.currentLink[prop])))); }; @computed get destinationAnchor() { - const ldoc = LinkManager.currentLink; - const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; + const ldoc = LinkManager.Instance.currentLink; + const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor; if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch; return ldoc ? DocCast(ldoc.link_anchor_2) : ldoc; } @computed get sourceAnchor() { - const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; + const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor; - return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); + return selAnchor ?? (LinkManager.Instance.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.Instance.currentLink, this.destinationAnchor) : LinkManager.Instance.currentLink); } toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => { @@ -1386,7 +1390,7 @@ export class PropertiesView extends ObservableReactComponent this.handlelinkRelationshipChange(e.currentTarget.value)} @@ -1403,7 +1407,7 @@ export class PropertiesView extends ObservableReactComponent this.handleDescriptionChange(e.currentTarget.value)} @@ -1425,7 +1429,7 @@ export class PropertiesView extends ObservableReactComponent @@ -1441,7 +1445,7 @@ export class PropertiesView extends ObservableReactComponent

Show link

@@ -1671,7 +1675,7 @@ export class PropertiesView extends ObservableReactComponent @@ -1692,7 +1696,7 @@ export class PropertiesView extends ObservableReactComponent
-
+
Properties
window.open('https://brown-dash.github.io/Dash-Documentation/properties')}> } color={SettingsManager.userColor} /> @@ -1702,12 +1706,12 @@ export class PropertiesView extends ObservableReactComponent{this.editableTitle}
{this.currentType}
+ {this.fieldsSubMenu} {this.optionsSubMenu} {this.linksSubMenu} - {!LinkManager.currentLink || !this.openLinks ? null : this.linkProperties} + {!LinkManager.Instance.currentLink || !this.openLinks ? null : this.linkProperties} {this.inkSubMenu} {this.contextsSubMenu} - {this.fieldsSubMenu} {isNovice ? null : this.sharingSubMenu} {this.filtersSubMenu} {isNovice ? null : this.layoutSubMenu} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index fa0be225e..0e38790b6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -191,7 +191,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : '')); + let docColor: Opt = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : '')); // prettier-ignore switch (doc?.type) { case DocumentType.PRESELEMENT: docColor = docColor || ""; break; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index ee5147428..38f6aa3e7 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -200,8 +200,7 @@ export class CollectionTimeView extends CollectionSubView() { } menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); - const docItems: ContextMenuProps[] = []; - const keySet: Set = new Set(); + const keySet: Set = new Set(['tags']); this.childLayoutPairs.map(pair => this._allFacets @@ -209,7 +208,9 @@ export class CollectionTimeView extends CollectionSubView() { .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0])) .map(fieldKey => keySet.add(fieldKey)) ); - Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); + + const docItems: ContextMenuProps[] = Array.from(keySet).map(fieldKey => + ({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); // prettier-ignore docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' }); ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' }); ContextMenu.Instance.displayMenu(x, y, ':'); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 741013148..786301136 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -8,8 +8,8 @@ import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils'; -import { DocUtils } from '../../documents/Documents'; +import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils'; +import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; @@ -27,6 +27,7 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { @@ -51,6 +52,7 @@ export enum TreeViewType { @observer export class CollectionTreeView extends CollectionSubView>() { + public static AddTreeFunc = 'addTreeFolder(this.embedContainer)'; private _treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; private _titleRef?: HTMLDivElement | HTMLInputElement | null; @@ -140,6 +142,17 @@ export class CollectionTreeView extends CollectionSubView { + if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc); + }); + } + return res; + } + protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData) { @@ -150,9 +163,7 @@ export class CollectionTreeView extends CollectionSubView { - dragData.treeViewDoc = this.Document; - }; + dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document); screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight); @@ -163,34 +174,41 @@ export class CollectionTreeView extends CollectionSubView !docs.includes(v)); if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll(); - if (result.length !== value.length && doc instanceof Doc) { - const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc); - const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1]; - this._props.removeDocument?.(doc); - if (ind > 0 && prev) { - FormattedTextBox.SetSelectOnLoad(prev); - DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false); + if (result.length !== value.length) { + if (doc instanceof Doc) { + const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc); + const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1]; + this._props.removeDocument?.(doc); + if (ind > 0 && prev) { + FormattedTextBox.SetSelectOnLoad(prev); + DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false); + } + return true; } - return true; + return this._props.removeDocument?.(doc) ?? false; } return false; }; @action addDoc = (docs: Doc | Doc[], relativeTo: Opt, before?: boolean): boolean => { - const doAddDoc = (doc: Doc | Doc[]) => - (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => { - const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before); - res && Doc.SetContainer(doc, this.Document); - return res; - }, true); + const doclist = docs instanceof Doc ? [docs] : docs; + const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true); if (this.Document.resolvedDataDoc instanceof Promise) return false; - return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs); + const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs); + res && + doclist.forEach(doc => { + Doc.SetContainer(doc, this.Document); + if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc); + }); + return res; }; onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout + const layoutItems: ContextMenuProps[] = []; + const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc; + menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' }); if (!Doc.noviceMode) { - const layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'), event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient), @@ -198,7 +216,9 @@ export class CollectionTreeView extends CollectionSubView (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' }); layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' }); - ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); + } + ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); + if (!Doc.noviceMode) { const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' }); @@ -467,4 +487,13 @@ export class CollectionTreeView extends CollectionSubView ); } + static addTreeFolder(container: Doc) { + TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; + const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }; + return Doc.AddDocToList(container, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); + } } + +ScriptingGlobals.add(function addTreeFolder(doc: Doc) { + CollectionTreeView.addTreeFolder(doc); +}); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 9bc3ef822..02aa76d82 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -520,6 +520,7 @@ interface TabMiniThumbProps { miniLeft: () => number; } +@observer class TabMiniThumb extends React.Component { render() { return
; diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 09701ddb5..2ab1a5ac1 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -224,13 +224,13 @@ } .treeView-header-above { - border-top: black 1px solid; + border-top: red 1px solid; } .treeView-header-below { - border-bottom: black 1px solid; + border-bottom: red 1px solid; } .treeView-header-inside { - border: black 1px solid; + border: red 1px solid; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index be5737a25..85f7cf7fe 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -383,7 +383,7 @@ export class TreeView extends ObservableReactComponent { makeFolder = () => { const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView }; - return this._props.addDocument(folder); + return this.localAdd(folder); }; preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => { @@ -424,6 +424,16 @@ export class TreeView extends ObservableReactComponent { return false; }; + localAdd = (doc: Doc | Doc[]) => { + const innerAdd = (doc: Doc) => { + const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; + const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); + return added; + }; + return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); + }; + dropping: boolean = false; dropDocuments( droppedDocuments: Doc[], @@ -436,16 +446,8 @@ export class TreeView extends ObservableReactComponent { canEmbed?: boolean ) { const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before); - const localAdd = (doc: Doc | Doc[]) => { - const innerAdd = (doc: Doc) => { - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; - const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); - return added; - }; - return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); - }; - const addDoc = inside ? localAdd : parentAddDoc; + + const addDoc = inside ? this.localAdd : parentAddDoc; const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd; if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) { const move = (!dropAction || canEmbed || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument; @@ -839,14 +841,13 @@ export class TreeView extends ObservableReactComponent { }; contextMenuItems = () => { const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; - const folderOp = this.childDocs?.length ? [makeFolder] : []; const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(this), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' }; const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' }; return [ ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)), ...(this.Document.isFolder - ? folderOp + ? [makeFolder] : Doc.IsSystem(this.Document) ? [] : this.treeView.fileSysMode && this.Document === this.Document[DocData] @@ -993,6 +994,7 @@ export class TreeView extends ObservableReactComponent { onClickScript={this.onChildClick} onDoubleClickScript={this.onChildDoubleClick} dragAction={this._props.dragAction} + dragConfig={this.treeView.dragConfig} moveDocument={this.move} removeDocument={this._props.removeDoc} ScreenToLocalTransform={this.getTransform} @@ -1328,9 +1330,3 @@ export class TreeView extends ObservableReactComponent { }); } } - -ScriptingGlobals.add(function TreeView_addNewFolder() { - TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; - const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }; - return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); -}); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f0a31a8c6..a45a1fb0f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -127,7 +127,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent { SelectionManager.DeselectAll(); SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); - LinkManager.currentLink = this._props.LinkDocs[0]; + LinkManager.Instance.currentLink = this._props.LinkDocs[0]; this.toggleProperties(); // OverlayView.Instance.addElement( // { SelectionManager.DeselectAll(); SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); - LinkManager.currentLink = this._props.LinkDocs[0]; + LinkManager.Instance.currentLink = this._props.LinkDocs[0]; this.toggleProperties(); }; @@ -295,7 +295,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent { this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey]; if (this.dataDoc[this.autoResetFieldKey]) { - this.dataDoc[this.panXFieldKey + '_reset'] = this.dataDoc[this.panXFieldKey]; - this.dataDoc[this.panYFieldKey + '_reset'] = this.dataDoc[this.panYFieldKey]; - this.dataDoc[this.scaleFieldKey + '_reset'] = this.dataDoc[this.scaleFieldKey]; + this.dataDoc[this.panXFieldKey + '_reset'] = this.layoutDoc[this.panXFieldKey]; + this.dataDoc[this.panYFieldKey + '_reset'] = this.layoutDoc[this.panYFieldKey]; + this.dataDoc[this.scaleFieldKey + '_reset'] = this.layoutDoc[this.scaleFieldKey]; } }; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 3084a7972..813cb9338 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -20,6 +20,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { WebBox } from '../nodes/WebBox'; import { VideoBox } from '../nodes/VideoBox'; +import { DocData } from '../../../fields/DocSymbols'; ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; @@ -57,16 +58,16 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b obj[fieldKey] = color; CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj); } else { - dv.Document['_' + fieldKey] = color; + dv.Document[DocData][fieldKey] = color; } }); } else { - const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.currentLink ? [LinkManager.currentLink] : []; + const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : []; if (checkResult) { return selected.lastElement()?._backgroundColor ?? 'transparent'; } SetActiveFillColor(color ?? 'transparent'); - selected.forEach(doc => (doc._backgroundColor = color)); + selected.forEach(doc => (doc[DocData].backgroundColor = color)); } }); diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index ad6deeefb..7427f4310 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -92,8 +92,8 @@ export class LinkMenuItem extends ObservableReactComponent { DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight); } else { SelectionManager.SelectView(this._props.docView, false); - LinkManager.currentLink = this._props.linkDoc === LinkManager.currentLink ? undefined : this._props.linkDoc; - LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined; + LinkManager.Instance.currentLink = this._props.linkDoc === LinkManager.Instance.currentLink ? undefined : this._props.linkDoc; + LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined; if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250))); @@ -159,7 +159,7 @@ export class LinkMenuItem extends ObservableReactComponent { style={{ fontSize: this._hover ? 'larger' : undefined, fontWeight: this._hover ? 'bold' : undefined, - background: LinkManager.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + background: LinkManager.Instance.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, }}>
number; NativeHeight?: () => number; contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[]; + dragConfig?: (data: DragManager.DocumentDragData) => void; dragStarting?: () => void; dragEnding?: () => void; } @@ -288,7 +290,7 @@ export class DocumentViewInternal extends DocComponent dv.ContentDiv!), dragData, @@ -1466,7 +1468,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff }); ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) { - const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data); + const collectedLinks = DocListCast(linkCollection[DocData].data); let wid = NumCast(linkSource._width); let embedding: Doc | undefined; const links = LinkManager.Links(linkSource); @@ -1485,3 +1487,27 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour 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; }); +ScriptingGlobals.add(function updateTagsCollection(collection: Doc) { + const tag = StrCast(collection.title).split('-->')[1]; + const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys()); + const collectionDocs = DocListCast(collection[DocData].data).concat(collection); + let wid = 100; + let created = false; + const matchedDocs = matchedTags + .filter(tagDoc => !Doc.AreProtosEqual(collection, tagDoc)) + .map(tagDoc => { + let embedding = collectionDocs.find(doc => Doc.AreProtosEqual(tagDoc, doc)); + if (!embedding) { + embedding = Doc.MakeEmbedding(tagDoc); + embedding.x = wid; + embedding.y = 0; + embedding._lockedPosition = false; + wid += NumCast(tagDoc._width); + created = true; + } + return embedding; + }); + + created && (collection[DocData].data = new List(matchedDocs)); + return true; +}); diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 10eeff08d..fd3074a88 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -88,7 +88,7 @@ export class LabelBox extends ViewBoxBaseComponent() { @observable _mouseOver = false; @computed get hoverColor() { - return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor); + return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor); } getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index 767f0291b..484fb301e 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -5,3 +5,28 @@ .linkBox-container { width: 100%; } + +.linkBox { + transition: inherit; + pointer-events: none; + position: absolute; + width: 100%; + height: 100%; + path { + transition: inherit; + fill: transparent; + } + svg { + transition: inherit; + overflow: visible; + } + text { + cursor: default; + text-anchor: middle; + font-size: 12; + stroke: black; + } + circle { + cursor: default; + } +} diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 8b6293806..7ade846e7 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils'; +import { aggregateBounds, emptyFunction, returnAlways, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { Transform } from '../../util/Transform'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; @@ -13,6 +13,8 @@ import { StyleProp } from '../StyleProvider'; import { ComparisonBox } from './ComparisonBox'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkBox.scss'; +import { LinkDescriptionPopup } from './LinkDescriptionPopup'; +import { LinkManager } from '../../util/LinkManager'; @observer export class LinkBox extends ViewBoxBaseComponent() { @@ -97,8 +99,8 @@ export class LinkBox extends ViewBoxBaseComponent() { ) { this.layoutDoc.x = params?.lx; this.layoutDoc.y = params?.ty; - this.layoutDoc._width = params.rx - params?.lx; - this.layoutDoc._height = params?.by - params?.ty; + this.layoutDoc._width = Math.max(1, params.rx - params?.lx); + this.layoutDoc._height = Math.max(1, params?.by - params?.ty); } } else { this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width)); @@ -108,6 +110,22 @@ export class LinkBox extends ViewBoxBaseComponent() { { fireImmediately: true } ); } + descriptionDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, () => { + LinkManager.Instance.currentLink = this.Document; + LinkDescriptionPopup.Instance.popupX = e.clientX; + LinkDescriptionPopup.Instance.popupY = e.clientY; + LinkDescriptionPopup.Instance.display = true; + + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { + LinkDescriptionPopup.Instance.popupX -= 190; + } + if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { + LinkDescriptionPopup.Instance.popupY -= 40; + } + }); + }; componentWillUnmount(): void { this.disposer?.(); } @@ -117,19 +135,21 @@ export class LinkBox extends ViewBoxBaseComponent() { const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting); const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined; - const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] }))); - const text = bez.get(0.5); - const linkDesc = StrCast(this.dataDoc.link_description) || 'description'; + const { pts, lx, ty, rx, by } = this.renderProps; + const bez = new Bezier(pts.map(p => ({ x: p[0] - lx, y: p[1] - ty }))); + const { x, y } = bez.get(0.5); + const linkDesc = StrCast(this.dataDoc.link_description) || ' '; const strokeWidth = NumCast(this.dataDoc.stroke_width, 4); const dash = StrCast(this.Document.stroke_dash); const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined; - const { pts, lx, ty, rx, by } = this.renderProps; + const pointerEvents = this._props.pointerEvents?.() === 'none' ? 'none' : 'all'; + const stroke = highlightColor ?? 'lightblue'; return ( -
- +
+ - + @@ -137,26 +157,27 @@ export class LinkBox extends ViewBoxBaseComponent() { - -   - {linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')} -   - + {!linkDesc.trim().length ? ( + + ) : ( + +  {linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}  + + )}
); diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 13f0ac4fc..1645d0813 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -1,10 +1,11 @@ -import { action, makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DocData } from '../../../fields/DocSymbols'; import { LinkManager } from '../../util/LinkManager'; import './LinkDescriptionPopup.scss'; import { TaskCompletionBox } from './TaskCompletedBox'; +import { StrCast } from '../../../fields/Types'; @observer export class LinkDescriptionPopup extends React.Component<{}> { @@ -31,21 +32,26 @@ export class LinkDescriptionPopup extends React.Component<{}> { onDismiss = (add: boolean) => { this.display = false; if (add) { - LinkManager.currentLink && (LinkManager.currentLink[DocData].link_description = this.description); + LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[DocData].link_description = this.description); } + this.description = ''; }; @action onClick = (e: PointerEvent) => { if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) { this.display = false; + this.description = ''; TaskCompletionBox.taskCompleted = false; } }; - @action componentDidMount() { document.addEventListener('pointerdown', this.onClick, true); + reaction( + () => this.display, + display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description)) + ); } componentWillUnmount() { @@ -65,8 +71,10 @@ export class LinkDescriptionPopup extends React.Component<{}> { className="linkDescriptionPopup-input" onKeyDown={e => e.stopPropagation()} onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)} - placeholder={'(Optional) Enter link description...'} - onChange={e => this.descriptionChanged(e)}> + value={this.description} + placeholder={this.description || '(Optional) Enter link description...'} + onChange={e => this.descriptionChanged(e)} + />
this.onDismiss(false)}> {' '} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 3f793b85e..ae25ff179 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -152,8 +152,8 @@ export class LinkDocPreview extends ObservableReactComponent { - LinkManager.currentLink = this._linkDoc; - LinkManager.currentLinkAnchor = this._linkSrc; + LinkManager.Instance.currentLink = this._linkDoc; + LinkManager.Instance.currentLinkAnchor = this._linkSrc; this._props.DocumentView?.().select(false); if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { SettingsManager.Instance.propertiesWidth = 250; diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index 3426ba1a7..7a0ff8776 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -5,6 +5,16 @@ display: inline-flex; align-items: center; + select { + display: none; + } + + &:hover { + select { + display: unset; + } + } + .dashFieldView-enumerables { width: 10px; height: 10px; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 18286267a..ec0b76aa8 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -4,21 +4,24 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { Doc, Field } from '../../../../fields/Doc'; +import Select from 'react-select'; +import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { Cast } from '../../../../fields/Types'; +import { Cast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { Transform } from '../../../util/Transform'; +import { undoable, undoBatch } from '../../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; +import { FilterPanel } from '../../FilterPanel'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -97,7 +100,7 @@ export class DashFieldViewInternal extends ObservableReactComponent(); + _fieldRef = React.createRef(); @observable _dashDoc: Doc | undefined = undefined; @observable _expanded = false; @@ -180,10 +183,22 @@ export class DashFieldViewInternal extends ObservableReactComponent | undefined) => { + event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value); + }; + + @computed get values() { + const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []); + + return vals.strings.map(facet => ({ value: facet, label: facet })); + } + render() { return (
)} - {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} +
); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b82ab4219..f2c4c6c8f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorView } from 'prosemirror-view'; import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; @@ -67,6 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; +import Select from 'react-select'; // import * as applyDevTools from 'prosemirror-dev-tools'; @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -360,7 +361,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent(Array.from(new Set(accumTags))) : undefined; + if (accumTags.some(atag => !StrListCast(dataDoc.tags).includes(atag))) { + dataDoc.tags = new List(Array.from(new Set(accumTags))); + } let unchanged = true; if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) { @@ -1189,8 +1192,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.RecordingEvent, this.breakupDictation); this._disposers.layout_autoHeight = reaction( - () => this.layout_autoHeight, - layout_autoHeight => layout_autoHeight && this.tryUpdateScrollHeight() + () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize }), + (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight()) ); this._disposers.highlights = reaction( () => Array.from(FormattedTextBox._globalHighlights).slice(), diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 2fdd6374a..9bd41f42c 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -264,7 +264,7 @@ export class RichTextRules { return null; }), - // stop using active style + // toggle alternate text UI %/ new InputRule(new RegExp(/%\//), (state, match, start, end) => { setTimeout(this.TextBox.cycleAlternateText); return state.tr.deleteRange(start, end); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ab6d0390b..3cd4efcf7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -390,7 +390,7 @@ export class Doc extends RefField { export namespace Doc { export function SetContainer(doc: Doc, container: Doc) { - doc.embedContainer = container; + container !== Doc.MyRecentlyClosed && (doc.embedContainer = container); } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; diff --git a/src/fields/List.ts b/src/fields/List.ts index 9458a9611..ec31f7dae 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -317,7 +317,7 @@ class ListImpl extends ObjectField { return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; } [ToString]() { - return `List(${(this as any).length})`; + return `[${(this as any).map((field: any) => Field.toString(field))}]`; } } export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; -- cgit v1.2.3-70-g09d2 From e3fde25014d523c5f43a138093718899fe17d108 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 Feb 2024 16:18:16 -0500 Subject: made various render methods in DocumentView computed getters for efficiency and to avoid artifacts (LInkanchorBox dragging) when something else invalidates causing components to regenerate. fixed linklines to animate when doing a zoom transition and to be able to target texts hyperlinks. fixed link lines to share properties with ink and updated the properties panel / menus to allow editing of either. addding toggling link lines on and off from linkitemmenu --- src/client/documents/Documents.ts | 2 +- src/client/views/DocComponent.tsx | 1 + src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/PropertiesView.tsx | 104 ++--------- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/global/globalScripts.ts | 26 +-- src/client/views/linking/LinkMenuItem.tsx | 24 ++- src/client/views/nodes/DocumentView.tsx | 35 ++-- src/client/views/nodes/LinkBox.tsx | 197 +++++++++++++-------- src/client/views/nodes/formattedText/marks_rts.ts | 5 +- src/fields/Doc.ts | 3 +- src/fields/DocSymbols.ts | 1 + 13 files changed, 201 insertions(+), 202 deletions(-) (limited to 'src/client/views/global') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1978c144b..355a4c937 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -599,7 +599,7 @@ export namespace Docs { _width: 1, link: '', link_description: '', - backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area + color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area _dropPropertiesToRemove: new List(['onClick']), }, }, diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index dacd359c5..99b9c3045 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -35,6 +35,7 @@ export interface ViewBoxInterface { addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) select?: (ctrlKey: boolean, shiftKey: boolean) => void; focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt; + viewTransition?: () => Opt; // duration of a view transition animation isAnyChildContentActive?: () => boolean; // is any child content of the document active onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index bba6285c2..cb38ab602 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -411,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { docView.noOnClick(); switch (onClick) { case 'enterPortal': - docView.makeIntoPortal(); + DocUtils.makeIntoPortal(docView.Document, docView.layoutDoc, docView.allLinks); break; case 'toggleDetail': docView.setToggleDetail(); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 07f285eaf..3ae2362a1 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -741,7 +741,7 @@ export class PropertiesView extends ObservableReactComponent void) { @@ -943,19 +949,19 @@ export class PropertiesView extends ObservableReactComponent -
-
-
Width:
-
{this.stkInput}
-
- (this.widthStk = e.target.value))} - onMouseDown={e => { - this._widthUndo = UndoManager.StartBatch('width undo'); - }} - onMouseUp={e => { - this._widthUndo?.end(); - this._widthUndo = undefined; - }} - /> -
+
{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}
+
{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}
-
-
-
Arrow Scale:
- {/*
{this.markScalInput}
*/} -
- (this.markScal = +e.target.value))} - onMouseDown={e => { - this._widthUndo = UndoManager.StartBatch('scale undo'); - }} - onMouseUp={e => { - this._widthUndo?.end(); - this._widthUndo = undefined; - }} - /> -
Arrow Head:
(this.markHead = this.markHead ? '' : 'arrow')))} /> @@ -1442,36 +1406,6 @@ export class PropertiesView extends ObservableReactComponentDescription

{this.editDescription}
-
-

Show link

- -
-
-

Auto-move anchors

- -
-
-

Display arrow

- -
{!hasSelectedAnchor ? null : (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index b7e64d9a8..0794efe4c 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -134,7 +134,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt this._props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; + viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined); fitContentOnce = () => { const vals = this.fitToContentVals; this.layoutDoc._freeform_panX = vals.bounds.cx; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 813cb9338..0541a9ca7 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -43,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b } else if (selectedViews.length) { if (checkResult) { const selView = selectedViews.lastElement(); - const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; + const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent'; } selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent'); selectedViews.forEach(dv => { - const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; + const fieldKey = dv.Document._layout_isSvg ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed if (contentFrameNumber !== undefined) { @@ -344,24 +344,24 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | ' // 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())), - setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask), + checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())), + setInk: (doc: Doc) => (doc[DocData].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)), + checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"), + setInk: (doc: Doc) => (doc[DocData].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()); setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, + checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()), + setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)), + setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, }], ['strokeColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()), - setInk: (doc: Doc) => (doc.color = String(value)), - setMode: () => { SetActiveInkColor(StrCast(value)); setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, + checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()), + setInk: (doc: Doc) => (doc[DocData].color = String(value)), + setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, }], ]); @@ -369,7 +369,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | ' return map.get(option)?.checkResult(); } map.get(option)?.setMode(); - SelectionManager.Docs.filter(doc => doc.type === DocumentType.INK).map(doc => map.get(option)?.setInk(doc)); + SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc)); }); /** WEB diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 7427f4310..92c63cd56 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -74,6 +74,14 @@ export class LinkMenuItem extends ObservableReactComponent { return this._props.sourceDoc; } + onIconDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, () => { + if (!this._props.docView._props.removeDocument?.(this._props.linkDoc)) { + this._props.docView._props.addDocument?.(this._props.linkDoc); + } + }); + }; + onEdit = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -196,12 +204,16 @@ export class LinkMenuItem extends ObservableReactComponent {

) : null}
-
- -
-

- {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)} -

+ Show/Hide Link
}> +
+ +
+ + Follow Link
}> +

+ {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)} +

+
{!this._props.linkDoc.link_description ? null :

{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}

}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5efa028d1..042ae6e55 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -48,6 +48,7 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm'; interface Window { MediaRecorder: MediaRecorder; } @@ -726,7 +727,7 @@ export class DocumentViewInternal extends DocComponent () => (link.link_displayLine = false); - allLinkEndpoints = () => { + @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null; return this.filteredLinks.map(link => ( @@ -750,9 +751,9 @@ export class DocumentViewInternal extends DocComponent
)); - }; + } - viewBoxContents = () => { + @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent'); @@ -778,10 +779,10 @@ export class DocumentViewInternal extends DocComponent - {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()} + {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
); - }; + } captionStyleProvider = (doc: Opt, props: Opt, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => { @@ -814,7 +815,7 @@ export class DocumentViewInternal extends DocComponent { + @computed get titleView() { const showTitle = this.layout_showTitle?.split(':')[0]; const showTitleHover = this.layout_showTitle?.includes(':hover'); @@ -888,9 +889,9 @@ export class DocumentViewInternal extends DocComponent
); - }; + } - captionView = () => { + @computed get captionView() { return !this.layout_showCaption ? null : (
); - }; + } renderDoc = (style: object) => { TraceMobx(); @@ -933,15 +934,15 @@ export class DocumentViewInternal extends DocComponent {this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? ( - this.viewBoxContents() + this.viewBoxContents ) : (
- {this.titleView()} - {this.viewBoxContents()} - {this.captionView()} + {this.titleView} + {this.viewBoxContents} + {this.captionView}
)} {this.widgetDecorations ?? null} @@ -1191,8 +1192,8 @@ export class DocumentView extends DocComponent() { if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined }; } // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox - const transition = this.docViewPath().find((parent: DocumentView) => parent._props.DataTransition?.() || StrCast(parent.Document.dataTransition)); - return { left, top, right, bottom, transition: transition?._props.DataTransition?.() || StrCast(transition?.Document.dataTransition) }; + const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.()); + return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() }; } @computed get nativeWidth() { @@ -1337,6 +1338,7 @@ export class DocumentView extends DocComponent() { } } }; + DataTransition = () => this._props.DataTransition?.() || StrCast(this.Document.dataTransition); ShouldNotScale = () => this.shouldNotScale; NativeWidth = () => this.effectiveNativeWidth; NativeHeight = () => this.effectiveNativeHeight; @@ -1402,6 +1404,7 @@ export class DocumentView extends DocComponent() { () { @@ -43,19 +46,20 @@ export class LinkBox extends ViewBoxBaseComponent() { this.disposer = reaction( () => ({ drag: SnappingManager.IsDragging, a: this.anchor1, b: this.anchor2 }), ({ drag, a, b }) => { - setTimeout( - // need to wait for drag manager to set 'hidden' flag on dragged elements - action(() => { - let a1 = a && document.getElementById(a.Guid); - let a2 = b && document.getElementById(b.Guid); - if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true; - else { - for (; a1 && !a1.hidden; a1 = a1.parentElement); - for (; a2 && !a2.hidden; a2 = a2.parentElement); - this._hide = a1 || a2 ? true : false; - } - }) - ); + !LightboxView.Contains(this.DocumentView?.()) && + setTimeout( + // need to wait for drag manager to set 'hidden' flag on dragged elements + action(() => { + let a1 = a && document.getElementById(a.Guid); + let a2 = b && document.getElementById(b.Guid); + if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true; + else { + for (; a1 && !a1.hidden; a1 = a1.parentElement); + for (; a2 && !a2.hidden; a2 = a2.parentElement); + this._hide = a1 || a2 ? true : false; + } + }) + ); }, { fireImmediately: true } ); @@ -63,89 +67,130 @@ export class LinkBox extends ViewBoxBaseComponent() { select = (ctrlKey: boolean, shiftKey: boolean) => (LinkManager.Instance.currentLink = this.Document); - descriptionDown = (e: React.PointerEvent) => { - setupMoveUpEvents( - this, - e, - returnFalse, - returnFalse, - action(() => { - LinkManager.Instance.currentLink = this.Document; - LinkDescriptionPopup.Instance.popupX = e.clientX; - LinkDescriptionPopup.Instance.popupY = e.clientY; - LinkDescriptionPopup.Instance.display = true; - - const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { - LinkDescriptionPopup.Instance.popupX -= 190; - } - if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { - LinkDescriptionPopup.Instance.popupY -= 40; - } - }), - false - ); - }; render() { - trace(); + if (this._hide) return null; const a = this.anchor1; const b = this.anchor2; this._forceAnimate; - if (a && b && !this._hide) { + if (a && b && !LightboxView.Contains(this.DocumentView?.())) { + // text selection bounds are not directly observable, so we have to + // force an update when anything that could affect them changes (text edits causing reflow, scrolling) + a.Document[a.LayoutFieldKey]; + b.Document[b.LayoutFieldKey]; + a.Document.layout_scrollTop; + b.Document.layout_scrollTop; + const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove) const bxf = b.screenToViewTransform(); const scale = a.CollectionFreeFormView === this.DocumentView?.().CollectionFreeFormView ? axf.Scale : bxf.Scale; const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition - const bt = b.getBounds?.transition; + const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation) + + // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext ), + // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right + // otherwise, we just use the computed nearest point on the document boundary to the target Document + const targetAhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement(); + const targetBhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement(); + + const aid = targetAhyperlink?.id || a.Document[Id]; + const bid = targetBhyperlink?.id || b.Document[Id]; + if (!document.getElementById(aid) || !document.getElementById(bid)) { + setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); + return null; + } + if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting); const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined; + const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily); const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize); - const color = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor)); - const { strokeWidth, stroke_startMarker, stroke_endMarker } = this.Document; - const dash = StrCast(this.Document.stroke_dash); - const stroke = highlightColor ?? 'lightblue'; + const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor)); + const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document; + const strokeWidth = NumCast(stroke_width, 4); const linkDesc = StrCast(this.dataDoc.link_description) || ' '; const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : ''); return ( - - {labelText} -
- } - passProps={{ onPointerDown: this.descriptionDown }} - /> + <> + {!highlightColor ? null : ( + + )} + + linkDesc} + SetValue={action(val => { + this.Document[DocData].link_description = val; + return true; + })} + /> + + {/* (this.Document[DocData].link_description = val))} + fillWidth + /> */} +
+ } + passProps={{}} + /> + ); } - return null; return (
(p ? p + ' ' + item.href : item.href), ''); const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); - return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; + return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; }, }, noAutoLinkAnchor: { @@ -104,7 +105,7 @@ export const marks: { [index: string]: MarkSpec } = { node.attrs.title, ], ] - : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0]; + : ['a', { id: '' + Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0]; }, }, diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 3cd4efcf7..56d50846a 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -16,7 +16,7 @@ import { DateField } from './DateField'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks, DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight, - Initializing, Self, SelfProxy, UpdatingFromServer, Width + Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width } from './DocSymbols'; // prettier-ignore import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { InkField, InkTool } from './InkField'; @@ -309,6 +309,7 @@ export class Doc extends RefField { public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any); public [Width] = () => NumCast(this[SelfProxy]._width); public [Height] = () => NumCast(this[SelfProxy]._height); + public [TransitionTimer]: any = undefined; public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here? public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`; public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`; diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index 9c563abbf..f8a57acd5 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -15,6 +15,7 @@ export const DocLayout = Symbol('DocLayout'); export const DocFields = Symbol('DocFields'); export const DocCss = Symbol('DocCss'); export const DocAcl = Symbol('DocAcl'); +export const TransitionTimer = Symbol('DocTransitionTimer'); export const DirectLinks = Symbol('DocDirectLinks'); export const AclPrivate = Symbol('DocAclOwnerOnly'); export const AclReadonly = Symbol('DocAclReadOnly'); -- cgit v1.2.3-70-g09d2 From 15f23f8b99b2e5ce48e7146c61d4d61b451875ad Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 Feb 2024 18:32:22 -0500 Subject: added back shiftkey to drag and embed. got rid of overlayplane link view. got rid of auto arrange. fixed text edit color for linkBox's --- src/client/util/DocumentManager.ts | 37 +-- src/client/util/DragManager.ts | 2 +- src/client/views/MainView.tsx | 4 +- .../CollectionFreeFormLinkView.scss | 12 - .../CollectionFreeFormLinkView.tsx | 318 --------------------- .../CollectionFreeFormLinksView.scss | 13 - .../CollectionFreeFormLinksView.tsx | 25 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 35 +-- .../views/collections/collectionFreeForm/index.ts | 12 +- src/client/views/global/globalScripts.ts | 5 - src/client/views/nodes/LinkBox.tsx | 2 +- 11 files changed, 17 insertions(+), 448 deletions(-) delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss delete mode 100644 src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx (limited to 'src/client/views/global') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 53d472c66..eada5af75 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,6 +1,6 @@ import { Howl } from 'howler'; import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx'; -import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; @@ -28,8 +28,6 @@ export class DocumentManager { //global holds all of the nodes (regardless of which collection they're in) @observable _documentViews = new Set(); @observable.shallow public CurrentlyLoading: Doc[] = []; - @observable.shallow public LinkAnchorBoxViews: DocumentView[] = []; - @observable.shallow public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = []; @computed public get DocumentViews() { return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view))); } @@ -90,37 +88,14 @@ export class DocumentManager { @action public AddView = (view: DocumentView) => { - if (view._props.LayoutTemplateString?.includes(KeyValueBox.name)) return; - if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { - const viewAnchorIndex = view._props.LayoutTemplateString.includes('link_anchor_2') ? 'link_anchor_2' : 'link_anchor_1'; - const link = view.Document; - this.LinkAnchorBoxViews?.filter(dv => Doc.AreProtosEqual(dv.Document, link) && !dv._props.LayoutTemplateString?.includes(viewAnchorIndex)).forEach(otherView => - this.LinkedDocumentViews.push({ - a: viewAnchorIndex === 'link_anchor_2' ? otherView : view, - b: viewAnchorIndex === 'link_anchor_2' ? view : otherView, - l: link, - }) - ); - this.LinkAnchorBoxViews.push(view); - } else { + if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && + !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { this.AddDocumentView(view); - } - this.callAddViewFuncs(view); + this.callAddViewFuncs(view); + } // prettier-ignore }; public RemoveView = action((view: DocumentView) => { - this.LinkedDocumentViews.slice().forEach( - action(pair => { - if (pair.a === view || pair.b === view) { - const li = this.LinkedDocumentViews.indexOf(pair); - li !== -1 && this.LinkedDocumentViews.splice(li, 1); - } - }) - ); - - if (view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { - const index = this.LinkAnchorBoxViews.indexOf(view); - this.LinkAnchorBoxViews.splice(index, 1); - } else { + if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) { this.DeleteDocumentView(view); } SelectionManager.DeselectView(view); diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index a6ad0f1b3..78356888a 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -504,7 +504,7 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.ctrlKey ? 'embed' : dragData.defaultDropAction; + dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.shiftKey ? 'move' : e.ctrlKey ? 'embed' : dragData.defaultDropAction; } if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header') && dragData.draggedDocuments.length === 1) { if (!startWindowDragTimer) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index eca0aca4c..efe906981 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -10,6 +10,7 @@ import * as React from 'react'; import '../../../node_modules/browndash-components/dist/styles/global.min.css'; import { Utils, emptyFunction, lightOrDark, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents } from '../../Utils'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; import { DocCast, StrCast } from '../../fields/Types'; import { DocServer } from '../DocServer'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; @@ -50,7 +51,6 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionMenu } from './collections/CollectionMenu'; import { TabDocView } from './collections/TabDocView'; import './collections/TreeView.scss'; -import { CollectionFreeFormLinksView } from './collections/collectionFreeForm'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { LinkMenu } from './linking/LinkMenu'; @@ -72,7 +72,6 @@ import { PresBox } from './nodes/trails'; import { AnchorMenu } from './pdf/AnchorMenu'; import { GPTPopup } from './pdf/GPTPopup/GPTPopup'; import { TopBar } from './topbar/TopBar'; -import { DocData } from '../../fields/DocSymbols'; const { default: { LEFT_MENU_WIDTH, TOPBAR_HEIGHT } } = require('./global/globalCssVariables.module.scss'); // prettier-ignore const _global = (window /* browser */ || global) /* node */ as any; @@ -1037,7 +1036,6 @@ export class MainView extends ObservableReactComponent<{}> { {/* */} {this.snapLines} - diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss deleted file mode 100644 index b44acfce8..000000000 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss +++ /dev/null @@ -1,12 +0,0 @@ -.collectionfreeformlinkview-linkLine { - stroke: black; - opacity: 0.8; - stroke-width: 3px; - transition: opacity 0.5s ease-in; - fill: transparent; -} -.collectionfreeformlinkview-linkText { - stroke: rgb(0, 0, 0); - pointer-events: all; - cursor: move; -} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx deleted file mode 100644 index a45a1fb0f..000000000 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ /dev/null @@ -1,318 +0,0 @@ -import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Doc, Field } from '../../../../fields/Doc'; -import { Brushed, DocCss } from '../../../../fields/DocSymbols'; -import { Id } from '../../../../fields/FieldSymbols'; -import { List } from '../../../../fields/List'; -import { Cast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; -import { LinkManager } from '../../../util/LinkManager'; -import { SelectionManager } from '../../../util/SelectionManager'; -import { SettingsManager } from '../../../util/SettingsManager'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { Colors } from '../../global/globalEnums'; -import { DocumentView } from '../../nodes/DocumentView'; -import { ObservableReactComponent } from '../../ObservableReactComponent'; -import './CollectionFreeFormLinkView.scss'; - -export interface CollectionFreeFormLinkViewProps { - A: DocumentView; - B: DocumentView; - LinkDocs: Doc[]; -} - -@observer -export class CollectionFreeFormLinkView extends ObservableReactComponent { - @observable _opacity: number = 0; - @observable _start = 0; - _anchorDisposer: IReactionDisposer | undefined; - _timeout: NodeJS.Timeout | undefined; - constructor(props: any) { - super(props); - makeObservable(this); - } - - componentWillUnmount() { - this._anchorDisposer?.(); - } - @action timeout: any = action(() => Date.now() < this._start++ + 1000 && (this._timeout = setTimeout(this.timeout, 25))); - componentDidMount() { - this._anchorDisposer = reaction( - () => [ - this._props.A.screenToViewTransform(), - Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, - Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss], - this._props.B.screenToViewTransform(), - Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, - Cast(Cast(Cast(this._props.A.Document, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss], - ], - action(() => { - this._start = Date.now(); - this._timeout && clearTimeout(this._timeout); - this._timeout = setTimeout(this.timeout, 25); - setTimeout(this.placeAnchors, 10); // when docs are dragged, their transforms will update before a render has been performed. placeanchors needs to come after a render to find things in the dom. a 0 timeout will still come before the render - }), - { fireImmediately: true } - ); - } - placeAnchors = () => { - const { A, B, LinkDocs } = this._props; - const linkDoc = LinkDocs[0]; - if (SnappingManager.IsDragging || !A.ContentDiv || !B.ContentDiv) return; - setTimeout( - action(() => (this._opacity = 0.75)), - 0 - ); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render() - setTimeout( - action(() => (!LinkDocs.length || !(linkDoc.link_displayLine || Doc.UserDoc().showLinkLines)) && (this._opacity = 0.05)), - 750 - ); // this will unhighlight the link line. - const a = A.ContentDiv.getBoundingClientRect(); - const b = B.ContentDiv.getBoundingClientRect(); - const { left: aleft, top: atop, width: awidth, height: aheight } = A.ContentDiv.parentElement!.getBoundingClientRect(); - const { left: bleft, top: btop, width: bwidth, height: bheight } = B.ContentDiv.parentElement!.getBoundingClientRect(); - const apt = Utils.closestPtBetweenRectangles(aleft, atop, awidth, aheight, bleft, btop, bwidth, bheight, a.left + a.width / 2, a.top + a.height / 2); - const bpt = Utils.closestPtBetweenRectangles(bleft, btop, bwidth, bheight, aleft, atop, awidth, aheight, apt.point.x, apt.point.y); - - // really hacky stuff to make the LinkAnchorBox display where we want it to: - // if there's an element in the DOM with a classname containing a link anchor's id, - // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right - // otherwise, we just use the computed nearest point on the document boundary to the target Document - const targetAhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_1 as Doc)[Id])).lastElement(); - const targetBhyperlink = Array.from(window.document.getElementsByClassName((linkDoc.link_anchor_2 as Doc)[Id])).lastElement(); - if ((!targetAhyperlink && !a.width) || (!targetBhyperlink && !b.width)) return; - if (!targetAhyperlink) { - if (linkDoc.link_autoMoveAnchors) { - linkDoc.link_anchor_1_x = ((apt.point.x - aleft) / awidth) * 100; - linkDoc.link_anchor_1_y = ((apt.point.y - atop) / aheight) * 100; - } - } else { - const m = targetAhyperlink.getBoundingClientRect(); - const mp = A.screenToViewTransform().transformPoint(m.right, m.top + 5); - const mpx = mp[0] / A._props.PanelWidth(); - const mpy = mp[1] / A._props.PanelHeight(); - if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_1_x = mpx * 100; - if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_1_y = mpy * 100; - if (getComputedStyle(targetAhyperlink).fontSize === '0px') linkDoc.opacity = 0; - else linkDoc.opacity = 1; - } - if (!targetBhyperlink) { - if (linkDoc.link_autoMoveAnchors) { - linkDoc.link_anchor_2_x = ((bpt.point.x - bleft) / bwidth) * 100; - linkDoc.link_anchor_2_y = ((bpt.point.y - btop) / bheight) * 100; - } - } else { - const m = targetBhyperlink.getBoundingClientRect(); - const mp = B.screenToViewTransform().transformPoint(m.right, m.top + 5); - const mpx = mp[0] / B._props.PanelWidth(); - const mpy = mp[1] / B._props.PanelHeight(); - if (mpx >= 0 && mpx <= 1) linkDoc.link_anchor_2_x = mpx * 100; - if (mpy >= 0 && mpy <= 1) linkDoc.link_anchor_2_y = mpy * 100; - if (getComputedStyle(targetBhyperlink).fontSize === '0px') linkDoc.opacity = 0; - else linkDoc.opacity = 1; - } - }; - - pointerDown = (e: React.PointerEvent) => { - setupMoveUpEvents( - this, - e, - (e, down, delta) => { - this._props.LinkDocs[0].link_relationship_OffsetX = NumCast(this._props.LinkDocs[0].link_relationship_OffsetX) + delta[0]; - this._props.LinkDocs[0].link_relationship_OffsetY = NumCast(this._props.LinkDocs[0].link_relationship_OffsetY) + delta[1]; - return false; - }, - emptyFunction, - action(() => { - SelectionManager.DeselectAll(); - SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); - LinkManager.Instance.currentLink = this._props.LinkDocs[0]; - this.toggleProperties(); - // OverlayView.Instance.addElement( - // { })} - // />, { x: 300, y: 300 }); - }) - ); - }; - - visibleY = (el: any) => { - let rect = el.getBoundingClientRect(); - const top = rect.top, - height = rect.height; - var el = el.parentNode; - while (el && el !== document.body) { - if (el.className === 'tabDocView-content') break; - rect = el.getBoundingClientRect?.(); - if (rect?.width) { - if (top <= rect.bottom === false && getComputedStyle(el).overflow === 'hidden') return rect.bottom; - // Check if the element is out of view due to a container scrolling - if (top + height <= rect.top && getComputedStyle(el).overflow === 'hidden') return rect.top; - } - el = el.parentNode; - } - // Check its within the document viewport - return top; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden"; - }; - visibleX = (el: any) => { - let rect = el.getBoundingClientRect(); - const left = rect.left, - width = rect.width; - var el = el.parentNode; - while (el && el !== document.body) { - rect = el?.getBoundingClientRect(); - if (rect?.width) { - if (left <= rect.right === false && getComputedStyle(el).overflow === 'hidden') return rect.right; - // Check if the element is out of view due to a container scrolling - if (left + width <= rect.left && getComputedStyle(el).overflow === 'hidden') return rect.left; - } - el = el.parentNode; - } - // Check its within the document viewport - return left; //top <= document.documentElement.clientHeight && getComputedStyle(document.documentElement).overflow === "hidden"; - }; - - @action - toggleProperties = () => { - if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { - SettingsManager.Instance.propertiesWidth = 250; - } - }; - - @action - onClickLine = () => { - SelectionManager.DeselectAll(); - SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); - LinkManager.Instance.currentLink = this._props.LinkDocs[0]; - this.toggleProperties(); - }; - - @computed.struct get renderData() { - this._start; - SnappingManager.IsDragging; - const { A, B, LinkDocs } = this._props; - if (!A.ContentDiv || !B.ContentDiv || !LinkDocs.length) return undefined; - const acont = A.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); - const bcont = B.ContentDiv.getElementsByClassName('linkAnchorBox-cont'); - const adiv = acont.length ? acont[0] : A.ContentDiv; - const bdiv = bcont.length ? bcont[0] : B.ContentDiv; - for (let apdiv = adiv; apdiv; apdiv = apdiv.parentElement as any) if ((apdiv as any).hidden) return; - for (let bpdiv = bdiv; bpdiv; bpdiv = bpdiv.parentElement as any) if ((bpdiv as any).hidden) return; - const a = adiv.getBoundingClientRect(); - const b = bdiv.getBoundingClientRect(); - const atop = this.visibleY(adiv); - const btop = this.visibleY(bdiv); - if (!a.width || !b.width) return undefined; - const aDocBounds = (A._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 }; - const bDocBounds = (B._props as any).DocumentView?.().getBounds || { left: 0, right: 0, top: 0, bottom: 0 }; - const aleft = this.visibleX(adiv); - const bleft = this.visibleX(bdiv); - const aclipped = aleft !== a.left || atop !== a.top; - const bclipped = bleft !== b.left || btop !== b.top; - if (aclipped && bclipped) return undefined; - const clipped = aclipped || bclipped; - const pt1inside = NumCast(LinkDocs[0].link_anchor_1_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_1_y) % 100 !== 0; - const pt2inside = NumCast(LinkDocs[0].link_anchor_2_x) % 100 !== 0 && NumCast(LinkDocs[0].link_anchor_2_y) % 100 !== 0; - const pt1 = [aleft + a.width / 2, atop + a.height / 2]; - const pt2 = [bleft + b.width / 2, btop + b.width / 2]; - const pt2vec = pt2inside ? [-0.7071, 0.7071] : [(bDocBounds.left + bDocBounds.right) / 2 - pt2[0], (bDocBounds.top + bDocBounds.bottom) / 2 - pt2[1]]; - const pt1vec = pt1inside ? [-0.7071, 0.7071] : [(aDocBounds.left + aDocBounds.right) / 2 - pt1[0], (aDocBounds.top + aDocBounds.bottom) / 2 - pt1[1]]; - const pt1len = Math.sqrt(pt1vec[0] * pt1vec[0] + pt1vec[1] * pt1vec[1]); - const pt2len = Math.sqrt(pt2vec[0] * pt2vec[0] + pt2vec[1] * pt2vec[1]); - const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2; - const pt1norm = clipped ? [0, 0] : [-(pt1vec[0] / pt1len) * ptlen, -(pt1vec[1] / pt1len) * ptlen]; - const pt2norm = clipped ? [0, 0] : [-(pt2vec[0] / pt2len) * ptlen, -(pt2vec[1] / pt2len) * ptlen]; - const pt1normlen = Math.sqrt(pt1norm[0] * pt1norm[0] + pt1norm[1] * pt1norm[1]) || 1; - const pt2normlen = Math.sqrt(pt2norm[0] * pt2norm[0] + pt2norm[1] * pt2norm[1]) || 1; - const pt1normalized = [pt1norm[0] / pt1normlen, pt1norm[1] / pt1normlen]; - const pt2normalized = [pt2norm[0] / pt2normlen, pt2norm[1] / pt2normlen]; - const aActive = A.IsSelected || A.Document[Brushed]; - const bActive = B.IsSelected || B.Document[Brushed]; - - const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetX); - const textY = (pt1[1] + pt2[1]) / 2 + NumCast(LinkDocs[0].link_relationship_OffsetY); - const link = this._props.LinkDocs[0]; - return { - a, - b, - pt1norm, - pt2norm, - aActive, - bActive, - textX, - textY, - // fully connected - // pt1, - // pt2, - // this code adds space between links - pt1: link.link_displayArrow ? [pt1[0] + pt1normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt1[1] + pt1normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt1, - pt2: link.link_displayArrow ? [pt2[0] + pt2normalized[0] * 3 * NumCast(link.link_displayArrow_scale, 4), pt2[1] + pt2normalized[1] * 3 * NumCast(link.link_displayArrow_scale, 3)] : pt2, - }; - } - - render() { - if (!this.renderData) return null; - - const link = this._props.LinkDocs[0]; - const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData; - const linkRelationship = Field.toString(link?.link_relationship as any as Field); //get string representing relationship - const linkRelationshipList = Doc.UserDoc().link_relationshipList as List; - const linkColorList = Doc.UserDoc().link_ColorList as List; - const linkRelationshipSizes = Doc.UserDoc().link_relationshipSizes as List; - const currRelationshipIndex = linkRelationshipList.indexOf(linkRelationship); - const linkDescription = Field.toString(link.link_description as any as Field).split('\n')[0]; - - const linkSize = Doc.noviceMode || currRelationshipIndex === -1 || currRelationshipIndex >= linkRelationshipSizes.length ? -1 : linkRelationshipSizes[currRelationshipIndex]; - - //access stroke color using index of the relationship in the color list (default black) - const stroke = currRelationshipIndex === -1 || currRelationshipIndex >= linkColorList.length ? StrCast(link._backgroundColor, 'black') : linkColorList[currRelationshipIndex]; - // const hexStroke = this.rgbToHex(stroke) - - //calculate stroke width/thickness based on the relative importance of the relationshipship (i.e. how many links the relationship has) - //thickness varies linearly from 3px to 12px for increasing link count - const strokeWidth = linkSize === -1 ? '3px' : Math.floor(2 + 10 * (linkSize / Math.max(...linkRelationshipSizes))) + 'px'; - - const arrowScale = NumCast(link.link_displayArrow_scale, 3); - return link.opacity === 0 || !a.width || !b.width || (!(Doc.UserDoc().showLinkLines || link.link_displayLine) && !aActive && !bActive) ? null : ( - <> - - - - - - - - - - - - - - - - - - - - {textX === undefined || !linkDescription ? null : ( - -   - {linkDescription.substring(0, 50) + (linkDescription.length > 50 ? '...' : '')} -   - - )} - - ); - } -} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss deleted file mode 100644 index 4ada1731f..000000000 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.scss +++ /dev/null @@ -1,13 +0,0 @@ -// TODO: change z-index to -1 when a modal is active? - -.collectionfreeformlinksview-svgCanvas { - position: absolute; - top: 0; - left: 0; - width: 100%; - height: 100%; - pointer-events: none; -} -.collectionfreeformlinksview-container { - pointer-events: none; -} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx deleted file mode 100644 index e5b6c366f..000000000 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { computed } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; -import { Id } from '../../../../fields/FieldSymbols'; -import { DocumentManager } from '../../../util/DocumentManager'; -import { LightboxView } from '../../LightboxView'; -import { CollectionFreeFormLinkView } from './CollectionFreeFormLinkView'; -import './CollectionFreeFormLinksView.scss'; - -@observer -export class CollectionFreeFormLinksView extends React.Component { - @computed get uniqueConnections() { - return Array.from(new Set(DocumentManager.Instance.LinkedDocumentViews)) - .filter(c => !LightboxView.LightboxDoc || (LightboxView.Contains(c.a) && LightboxView.Contains(c.b))) - .map(c => ); - } - - render() { - return ( -
- {this.uniqueConnections} -
- ); - } -} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 61fd5fd05..e4c71a086 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -10,6 +10,7 @@ import { DocData, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkField, InkTool, PointData, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; +import { RichTextField } from '../../../../fields/RichTextField'; import { listSpec } from '../../../../fields/Schema'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; @@ -22,8 +23,8 @@ import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; -import { FollowLinkScript } from '../../../util/LinkFollower'; import { ReplayMovements } from '../../../util/ReplayMovements'; +import { CompileScript } from '../../../util/Scripting'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; import { freeformScrollMode } from '../../../util/SettingsManager'; @@ -53,8 +54,6 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; -import { RichTextField } from '../../../../fields/RichTextField'; -import { CompileScript } from '../../../util/Scripting'; export interface collectionFreeformViewProps { NativeWidth?: () => number; @@ -377,28 +376,6 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(a.layout.y) - NumCast(b.layout.y)); - sorted.splice( - sorted.findIndex(pair => pair.layout === refDoc), - 1 - ); - if (sorted.length && refDoc && NumCast(sorted[0].layout.y) < NumCast(refDoc.y)) { - const topIndexed = NumCast(refDoc.y) < NumCast(sorted[0].layout.y) + NumCast(sorted[0].layout._height) / 2; - const deltay = sorted.length > 1 ? NumCast(refDoc.y) - (NumCast(sorted[0].layout.y) + (topIndexed ? 0 : NumCast(sorted[0].layout._height))) : 0; - const deltax = sorted.length > 1 ? NumCast(refDoc.x) - NumCast(sorted[0].layout.x) : 0; - - let lastx = NumCast(refDoc.x); - let lasty = NumCast(refDoc.y) + (topIndexed ? 0 : NumCast(refDoc._height)); - runInAction(() => - sorted.slice(1).forEach((pair, i) => { - lastx = pair.layout.x = lastx + deltax; - lasty = (pair.layout.y = lasty + deltay) + (topIndexed ? 0 : NumCast(pair.layout._height)); - }) - ); - } - } - (docDragData.droppedDocuments.length === 1 || de.shiftKey) && this.updateClusterDocs(docDragData.droppedDocuments); return true; } @@ -1000,7 +977,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).filter(doc => doc instanceof Doc); + const docs = this.childLayoutPairs.map(pair => pair.layout).filter(doc => doc instanceof Doc && doc.type !== DocumentType.LINK); const measuredDocs = docs .map(doc => ({ pos: { x: NumCast(doc.x), y: NumCast(doc.y) }, size: { width: NumCast(doc._width), height: NumCast(doc._height) } })) .filter(({ pos, size }) => pos && size) @@ -1651,12 +1628,6 @@ export class CollectionFreeFormView extends CollectionSubView (this.layoutDoc._autoArrange = !this.layoutDoc._autoArrange), - icon: 'compress-arrows-alt', - }); if (this._props.setContentViewBox === emptyFunction) { !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); return; diff --git a/src/client/views/collections/collectionFreeForm/index.ts b/src/client/views/collections/collectionFreeForm/index.ts index 702dc8d42..9a54ce63a 100644 --- a/src/client/views/collections/collectionFreeForm/index.ts +++ b/src/client/views/collections/collectionFreeForm/index.ts @@ -1,7 +1,5 @@ -export * from "./CollectionFreeFormLayoutEngines"; -export * from "./CollectionFreeFormLinkView"; -export * from "./CollectionFreeFormLinksView"; -export * from "./CollectionFreeFormRemoteCursors"; -export * from "./CollectionFreeFormView"; -export * from "./MarqueeOptionsMenu"; -export * from "./MarqueeView"; \ No newline at end of file +export * from './CollectionFreeFormLayoutEngines'; +export * from './CollectionFreeFormRemoteCursors'; +export * from './CollectionFreeFormView'; +export * from './MarqueeOptionsMenu'; +export * from './MarqueeView'; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 0541a9ca7..51672513b 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -127,11 +127,6 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false), setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_useClusters = !doc._freeform_useClusters, }], - ['arrange', { - waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire - checkResult: (doc:Doc) => BoolCast(doc?._autoArrange, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._autoArrange = !doc._autoArrange, - }], ['flashcards', { checkResult: (doc:Doc) => BoolCast(Doc.UserDoc().defaultToFlashcards, false), setDoc: (doc:Doc,dv:DocumentView) => Doc.UserDoc().defaultToFlashcards = !Doc.UserDoc().defaultToFlashcards, diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 0788e5adc..4f1d12c9b 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -154,7 +154,7 @@ export class LinkBox extends ViewBoxBaseComponent() { paddingRight: 4, paddingTop: 3, paddingBottom: 3, - background: DashColor(highlightColor || color) + background: DashColor((!this.DocumentView?.().isSelected() && highlightColor) || color) .fade(0.5) .toString(), }}> -- cgit v1.2.3-70-g09d2 From 4548ec81efc434c8ee5d89b896b2088e4e61d7a0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 28 Feb 2024 22:22:20 -0500 Subject: extracted field dropdown menu to a components and then cleaned up collectionTimeView, Documentview titles, and FilterPanel to share it --- src/client/documents/Documents.ts | 18 +- src/client/views/FieldsDropdown.tsx | 120 +++++++++++ src/client/views/FilterPanel.tsx | 239 ++++++--------------- .../views/collections/CollectionTimeView.tsx | 55 +---- src/client/views/global/globalScripts.ts | 2 +- src/client/views/nodes/DocumentView.scss | 1 - src/client/views/nodes/DocumentView.tsx | 67 +++--- 7 files changed, 236 insertions(+), 266 deletions(-) create mode 100644 src/client/views/FieldsDropdown.tsx (limited to 'src/client/views/global') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9b17901ca..031560886 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -77,6 +77,7 @@ export enum FInfoFieldType { enumeration, date, list, + rtf, } export class FInfo { description: string = ''; @@ -84,7 +85,7 @@ export class FInfo { fieldType?: FInfoFieldType; values?: Field[]; - filterable?: boolean = true; + filterable?: boolean = true; // can be used as a Filter in FilterPanel // format?: string; // format to display values (e.g, decimal places, $, etc) // parse?: ScriptField; // parse a value from a string constructor(d: string, readOnly?: boolean) { @@ -165,9 +166,19 @@ class DTypeInfo extends FInfo { override searchable = () => false; } class DateInfo extends FInfo { + constructor(d: string, filterable?: boolean) { + super(d, true); + this.filterable = filterable; + } fieldType? = FInfoFieldType.date; values?: DateField[] = []; - filterable = true; +} +class RtfInfo extends FInfo { + constructor(d: string, filterable?: boolean) { + super(d, true); + this.filterable = filterable; + } + fieldType? = FInfoFieldType.rtf; } class ListInfo extends FInfo { fieldType? = FInfoFieldType.list; @@ -178,6 +189,7 @@ type NUMt = NumInfo | number; type STRt = StrInfo | string; type LISTt = ListInfo | List; type DOCt = DocInfo | Doc; +type RTFt = RtfInfo | RichTextField; type DIMt = DimInfo | typeof DimUnit.Pixel | typeof DimUnit.Ratio; type PEVt = PEInfo | 'none' | 'all'; type COLLt = CTypeInfo | CollectionViewType; @@ -191,6 +203,7 @@ export class DocumentOptions { z?: NUMt = new NumInfo('whether document is in overlay (1) or not (0)', false, false, [1, 0]); overlayX?: NUMt = new NumInfo('x coordinate of document in a overlay view', false); overlayY?: NUMt = new NumInfo('y coordinate of document in a overlay view', false); + text?: RTFt = new RtfInfo('rich text of a text doc', true); _dimMagnitude?: NUMt = new NumInfo("magnitude of collectionMulti{row,col} element's width or height", false); _dimUnit?: DIMt = new DimInfo("units of collectionMulti{row,col} element's width or height - 'px' or '*' for pixels or relative units"); latitude?: NUMt = new NumInfo('latitude coordinate for map views', false); @@ -470,7 +483,6 @@ export class DocumentOptions { sidebar_type_collection?: string; // collection type of text sidebar data_dashboards?: List; // list of dashboards used in shareddocs; - text?: string; textTransform?: string; letterSpacing?: string; iconTemplate?: string; // name of icon template style diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx new file mode 100644 index 000000000..5638d34c6 --- /dev/null +++ b/src/client/views/FieldsDropdown.tsx @@ -0,0 +1,120 @@ +/** + * This creates a dropdown menu that's populated with possible field key names (e.g., author, tags) + * + * The set of field names actually displayed is based on searching the prop 'Document' and its descendants : + * The field list will contain all of the fields within the prop Document and all of its children; + * this list is then pruned down to only include fields that are not marked in Documents.ts to be non-filterable + */ + +import { computed, makeObservable, observable, runInAction } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import Select from 'react-select'; +import { Doc } from '../../fields/Doc'; +import { DocOptions, FInfo } from '../documents/Documents'; +import { SearchUtil } from '../util/SearchUtil'; +import { SettingsManager } from '../util/SettingsManager'; +import './FilterPanel.scss'; +import { ObservableReactComponent } from './ObservableReactComponent'; + +interface fieldsDropdownProps { + Document: Doc; // show fields for this Doc if set, otherwise for all docs in dashboard + selectFunc: (value: string) => void; + menuClose?: () => void; + placeholder?: string | (() => string); + showPlaceholder?: true; // if true, then input field always shows the placeholder value; otherwise, it shows the current selection + addedFields?: string[]; +} + +@observer +export class FieldsDropdown extends ObservableReactComponent { + @observable _newField = ''; + constructor(props: any) { + super(props); + makeObservable(this); + } + + @computed get allDescendantDocs() { + const allDocs = new Set(); + SearchUtil.foreachRecursiveDoc([this._props.Document], (depth, doc) => allDocs.add(doc)); + return Array.from(allDocs); + } + + @computed get fieldsOfDocuments() { + const keys = new Set(); + this.allDescendantDocs.forEach(doc => SearchUtil.documentKeys(doc).filter(key => keys.add(key))); + const sortedKeys = Array.from(keys.keys()) + .filter(key => key[0]) + .filter(key => key.indexOf('modificationDate') !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith('_')) || !Doc.noviceMode) + .sort(); + + Array.from(keys).forEach(key => sortedKeys.splice(sortedKeys.indexOf(key), 1)); + + return [...Array.from(keys), ...sortedKeys]; + } + + render() { + const filteredOptions = ['author', ...(this._newField ? [this._newField] : []), ...(this._props.addedFields ?? []), ...this.fieldsOfDocuments.filter(facet => facet[0] === facet.charAt(0).toUpperCase())]; + + Object.entries(DocOptions) + .filter(opts => opts[1].filterable) + .forEach((pair: [string, FInfo]) => filteredOptions.push(pair[0])); + const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet })); + + console.log(options); + return ( + ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - placeholder: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - input: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - }), - option: (baseStyles, state) => ({ - ...baseStyles, - color: SettingsManager.userColor, - background: !state.isFocused ? SettingsManager.userBackgroundColor : SettingsManager.userVariantColor, - }), - menuList: (baseStyles, state) => ({ - ...baseStyles, - backgroundColor: SettingsManager.userBackgroundColor, - }), - }} - placeholder={'add a filter'} - options={options} - isMulti={false} - onChange={val => this.facetClick((val as UserOptions).value)} - onKeyDown={e => e.stopPropagation()} - //onMenuClose={onClose} - value={null} - closeMenuOnSelect={true} - /> - ); - } - render() { return (
-
{this.fieldsDropdown}
+
+ +
{/* THE FOLLOWING CODE SHOULD BE DEVELOPER FOR BOOLEAN EXPRESSION (AND / OR) */} {/*
filter.split(Doc.FilterSep)[0] === facetHeader) ?.split(Doc.FilterSep)[1] } style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} - onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} - onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)} + onBlur={undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} + onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.Document, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)} /> ); case 'checkbox': @@ -399,12 +311,12 @@ export class FilterPanel extends ObservableReactComponent { filter.split(Doc.FilterSep)[0] === facetHeader && filter.split(Doc.FilterSep)[1] == facetValue) ?.split(Doc.FilterSep)[2] ?? '' )} type={type} - onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} + onChange={undoable(e => Doc.setDocFilter(this.Document, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} /> {facetValue}
@@ -414,64 +326,51 @@ export class FilterPanel extends ObservableReactComponent { case 'range': const domain = renderInfoDomain; const range = renderInfoRange; - - if (range) { - console.log('this is info range ' + range[0] + ' , ' + range[1]); - } - if (domain) { - console.log('this is info domain ' + domain[0] + ', ' + domain[1]); - return ( - <> - {/*
- console.log('on change'))} /> -
*/} - -
- Doc.setDocRangeFilter(this.targetDoc, facetHeader, values)} - values={renderInfoRange!}> - {railProps => } - - {({ handles, activeHandleID, getHandleProps }) => ( -
- {handles.map((handle, i) => { - // const value = i === 0 ? defaultValues[0] : defaultValues[1]; - return ( -
- -
- ); - })} -
- )} -
- - {({ tracks, getTrackProps }) => ( -
- {tracks.map(({ id, source, target }) => ( - - ))} -
- )} -
- - {({ ticks }) => ( -
- {ticks.map(tick => ( - val.toString()} /> - ))} -
- )} -
-
-
- +
+ Doc.setDocRangeFilter(this.Document, facetHeader, values)} + values={renderInfoRange!}> + {railProps => } + + {({ handles, activeHandleID, getHandleProps }) => ( +
+ {handles.map((handle, i) => { + // const value = i === 0 ? defaultValues[0] : defaultValues[1]; + return ( +
+ +
+ ); + })} +
+ )} +
+ + {({ tracks, getTrackProps }) => ( +
+ {tracks.map(({ id, source, target }) => ( + + ))} +
+ )} +
+ + {({ ticks }) => ( +
+ {ticks.map(tick => ( + val.toString()} /> + ))} +
+ )} +
+
+
); } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 38f6aa3e7..b92edd165 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,12 +1,10 @@ -import { toUpper } from 'lodash'; import { action, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { emptyFunction, returnEmptyString, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; +import { emptyFunction, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; -import { RichTextField } from '../../../fields/RichTextField'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; @@ -15,7 +13,7 @@ import { DocumentManager } from '../../util/DocumentManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { EditableView } from '../EditableView'; +import { FieldsDropdown } from '../FieldsDropdown'; import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FieldView'; import { PresBox } from '../nodes/trails'; @@ -192,51 +190,6 @@ export class CollectionTimeView extends CollectionSubView() { ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); }; - @computed get _allFacets() { - const facets = new Set(); - this.childDocs.forEach(child => Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); - Doc.AreProtosEqual(this.dataDoc, this.Document) && this.childDocs.forEach(child => Object.keys(child).forEach(key => facets.add(key))); - return Array.from(facets); - } - menuCallback = (x: number, y: number) => { - ContextMenu.Instance.clearItems(); - const keySet: Set = new Set(['tags']); - - this.childLayoutPairs.map(pair => - this._allFacets - .filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof pair.layout[fieldKey] === 'number' || typeof pair.layout[fieldKey] === 'boolean' || typeof pair.layout[fieldKey] === 'string') - .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0])) - .map(fieldKey => keySet.add(fieldKey)) - ); - - const docItems: ContextMenuProps[] = Array.from(keySet).map(fieldKey => - ({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); // prettier-ignore - docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' }); - ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' }); - ContextMenu.Instance.displayMenu(x, y, ':'); - }; - - @computed get pivotKeyUI() { - return ( -
- { - if (value?.length) { - this.layoutDoc._pivotField = value; - return true; - } - return false; - }} - background={'#f1efeb'} // this._props.headingObject ? this._props.headingObject.color : "#f1efeb"; - contents={':' + StrCast(this.layoutDoc._pivotField)} - showMenuOnLoad={true} - display={'inline'} - menuCallback={this.menuCallback} - /> -
- ); - } render() { let nonNumbers = 0; @@ -263,7 +216,6 @@ export class CollectionTimeView extends CollectionSubView() { return (
- {this.pivotKeyUI} {this.contents} {!this._props.isSelected() || !doTimeline ? null : ( <> @@ -272,6 +224,9 @@ export class CollectionTimeView extends CollectionSubView() {
)} +
+ (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} /> +
); } diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 51672513b..e73ab25b5 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -78,7 +78,7 @@ ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boole } if (SelectionManager.Views.length) { SelectionManager.Docs.forEach(doc => { - Doc.GetProto(doc).layout_headingColor = color; + doc[DocData].layout_headingColor = color === 'transparent' ? undefined : color; doc.layout_showTitle = color === 'transparent' ? undefined : StrCast(doc.layout_showTitle, 'title'); }); } else { diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index c4dab16fb..5421c1b50 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -180,7 +180,6 @@ .documentView-titleWrapper, .documentView-titleWrapper-hover { - overflow: hidden; color: $black; transform-origin: top left; top: 0; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index abb1aa59f..b0e8cf6ff 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -48,6 +48,7 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; +import { FieldsDropdown } from '../FieldsDropdown'; interface Window { MediaRecorder: MediaRecorder; } @@ -784,28 +785,25 @@ export class DocumentViewInternal extends DocComponent, props: Opt, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); - fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => { - const filteredFields = Object.entries(DocOptions).reduce((set, [field, opts]) => (opts.filterable ? set.add(field) : set), new Set(reqdFields)); + fieldsDropdown = (placeholder: string) => { return ( -
-
r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))} - onPointerDown={action(e => (this._changingTitleField = true))} - style={{ width: 'max-content', transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> - !isOpen && (this._changingTitleField = false))} - selectedVal={placeholder} - setSelectedVal={onChange} - color={SettingsManager.userColor} - background={SettingsManager.userVariantColor} - type={Type.TERT} - closeOnSelect={true} - dropdownType={DropdownType.SELECT} - items={Array.from(filteredFields).map(facet => ({ val: facet, text: facet }))} - width={100} - fillWidth - /> -
+
r && (this._titleDropDownInnerWidth = Number(getComputedStyle(r).width.replace('px', ''))))} + onPointerDown={action(e => (this._changingTitleField = true))} + style={{ width: 'max-content', background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, transformOrigin: 'left', transform: `scale(${this.titleHeight / 30 /* height of Dropdown */})` }}> + { + if (this.layoutDoc.layout_showTitle) { + this.layoutDoc._layout_showTitle = field; + } else if (!this._props.layout_showTitle) { + Doc.UserDoc().layout_showTitle = field; + } + this._changingTitleField = false; + })} + menuClose={action(() => (this._changingTitleField = false))} + />
); }; @@ -839,22 +837,7 @@ export class DocumentViewInternal extends DocComponent - {!dropdownWidth - ? null - : this.fieldsDropdown( - [StrCast(this.layoutDoc.layout_showTitle)], - dropdownWidth, - StrCast(this.layoutDoc.layout_showTitle).split(':')[0], - action((field: string | number) => { - if (this.layoutDoc.layout_showTitle) { - this.layoutDoc._layout_showTitle = field; - } else if (!this._props.layout_showTitle) { - Doc.UserDoc().layout_showTitle = field; - } - this._changingTitleField = false; - }), - action(() => (this._changingTitleField = false)) - )} + {!dropdownWidth ? null :
{this.fieldsDropdown(showTitle)}
}
targetDoc[field.trim()]?.toString()) - .join(' \\ ')} + contents={ + showTitle + .split(';') + .map(field => targetDoc[field.trim()]?.toString()) + .join(' \\ ') || '-unset-' + } display="block" oneLine={true} fontSize={(this.titleHeight / 15) * 10} -- cgit v1.2.3-70-g09d2 From 4cce99ec8d0bb2a45e19dd967dbbb8348c68abe8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 28 Feb 2024 22:46:23 -0500 Subject: fixed double click of some icons to make them persist (circle, rectangle, fitall). --- src/client/util/CurrentUserUtils.ts | 4 +--- src/client/views/global/globalScripts.ts | 12 ++++++------ src/client/views/nodes/FontIconBox/FontIconBox.tsx | 10 ++++++++-- 3 files changed, 15 insertions(+), 11 deletions(-) (limited to 'src/client/views/global') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 289384be0..84bed08fa 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -611,9 +611,7 @@ export class CurrentUserUtils { return [ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - { title: "View All", icon: "object-group", toolTip: "Keep all Docs in View",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform - // want the same style as toggle button, but don't want it to act as an actual toggle, so set disableToggle to true, - { title: "Fit All", icon: "arrows-left-right", toolTip: "Fit Docs to View (once)",btnType: ButtonType.ClickButton,ignoreClick:false,expertMode: false, toolType:"fitOnce", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Fit All", icon: "object-group", toolTip: "Fit Docs to View (double click to make sticky)",btnType: ButtonType.ToggleButton, ignoreClick:true, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}', onDoubleClick: '{ return showFreeform(this.toolType, _readOnly_, true);}'}}, // Only when floating document is selected in freeform { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform { title: "Arrange", icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(this.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index e73ab25b5..33704e8fe 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -98,7 +98,7 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { selected ? selected.CollectionFreeFormDocumentView?.float() : console.log('[FontIconBox.tsx] toggleOverlay failed'); }); -ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean) { +ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll' | 'fitOnce', checkResult?: boolean, persist?: boolean) { const selected = SelectionManager.Docs.lastElement(); // prettier-ignore const map: Map<'flashcards' | 'center' |'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll' | 'fitOnce', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc, dv:DocumentView) => void;}> = new Map([ @@ -112,16 +112,16 @@ ScriptingGlobals.add(function showFreeform(attr: 'flashcards' | 'center' | 'grid }], ['viewAll', { checkResult: (doc:Doc) => BoolCast(doc?._freeform_fitContentsToBox, false), - setDoc: (doc:Doc,dv:DocumentView) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox, + setDoc: (doc:Doc,dv:DocumentView) => { + if (persist) doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox; + else if (doc._freeform_fitContentsToBox) doc._freeform_fitContentsToBox = undefined; + else (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce(); + }, }], ['center', { checkResult: (doc:Doc) => BoolCast(doc?._stacking_alignCenter, false), setDoc: (doc:Doc,dv:DocumentView) => doc._stacking_alignCenter = !doc._stacking_alignCenter, }], - ['fitOnce', { - checkResult: (doc:Doc) => false, - setDoc: (doc:Doc, dv:DocumentView) => (dv.ComponentView as CollectionFreeFormView)?.fitContentOnce() - }], ['clusters', { waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire checkResult: (doc:Doc) => BoolCast(doc?._freeform_useClusters, false), diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index 3577cc8d9..91b6de80b 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; import { ScriptField } from '../../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; @@ -314,6 +314,7 @@ export class FontIconBox extends ViewBoxBaseComponent() { const tooltip = StrCast(this.Document.toolTip); const script = ScriptCast(this.Document.onClick); + const double = ScriptCast(this.Document.onDoubleClick); const toggleStatus = script ? script.script.run({ this: this.Document, self: this.Document, value: undefined, _readOnly_: true }).result : false; // Colors const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); @@ -330,7 +331,12 @@ export class FontIconBox extends ViewBoxBaseComponent() { //background={SettingsManager.userBackgroundColor} icon={this.Icon(color)!} label={this.label} - onPointerDown={() => script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false })} + onPointerDown={e => + setupMoveUpEvents(this, e, returnTrue, emptyFunction, (e, doubleTap) => { + !doubleTap && script.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false }); + doubleTap && double.script.run({ this: this.Document, self: this.Document, value: !toggleStatus, _readOnly_: false }); + }) + } /> ); } -- cgit v1.2.3-70-g09d2