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 ++ 21 files changed, 179 insertions(+), 112 deletions(-) (limited to 'src/client') 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) { -- cgit v1.2.3-70-g09d2