From 7f4f5d5d4779a3ff8efd5a64e2cef9f6758e29ae Mon Sep 17 00:00:00 2001 From: "A.J. Shulman" Date: Wed, 21 Feb 2024 17:45:49 -0500 Subject: changed logo for Notes and PPT Slide in tool menu --- src/client/util/CurrentUserUtils.ts | 4 ++-- src/client/views/MainView.tsx | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 714e33d25..b0890b674 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -273,7 +273,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)}, { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)}, { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}}, - { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "folder", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, + { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, @@ -284,7 +284,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, - { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "file", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, + { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "person-chalkboard", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide),openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} }, { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index b6cb845a6..fe0e1d1ab 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -288,6 +288,7 @@ export class MainView extends ObservableReactComponent<{}> { fa.faFolderOpen, fa.faFolderPlus, fa.faFolderClosed, + fa.faBook, fa.faMapPin, fa.faMapMarker, fa.faFingerprint, @@ -488,6 +489,7 @@ export class MainView extends ObservableReactComponent<{}> { fa.faUpload, fa.faBorderAll, fa.faBraille, + fa.faPersonChalkboard, fa.faChalkboard, fa.faPencilAlt, fa.faEyeSlash, -- cgit v1.2.3-70-g09d2 From 4e837a73f5fae06368416f99c047d78f6b94565b Mon Sep 17 00:00:00 2001 From: newalina <157907056+newalina@users.noreply.github.com> Date: Thu, 29 Feb 2024 23:16:33 -0500 Subject: add state message gifs --- .../CollectionFreeFormInfoState.tsx | 20 ++-- .../CollectionFreeFormInfoUI.tsx | 117 +++++++++++++-------- src/server/public/assets/dash-colon-menu.gif | Bin 0 -> 348794 bytes .../public/assets/dash-create-link-board.gif | Bin 0 -> 167854 bytes src/server/public/assets/dash-following-link.gif | Bin 0 -> 181851 bytes 5 files changed, 83 insertions(+), 54 deletions(-) create mode 100644 src/server/public/assets/dash-colon-menu.gif create mode 100644 src/server/public/assets/dash-create-link-board.gif create mode 100644 src/server/public/assets/dash-following-link.gif (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 58f6b1593..688f84cd5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -5,7 +5,6 @@ import * as React from 'react'; import { SettingsManager } from '../../../util/SettingsManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormView.scss'; -// import assets from './assets/link.png'; /** * An Fsa Arc. The first array element is a test condition function that will be observed. @@ -15,18 +14,18 @@ import './CollectionFreeFormView.scss'; export type infoArc = [() => any, (res?: any) => infoState]; export const StateMessage = Symbol('StateMessage'); -export const StateEntryFunc = Symbol('StateEntryFunc'); export const StateMessageGIF = Symbol('StateMessageGIF'); +export const StateEntryFunc = Symbol('StateEntryFunc'); export class infoState { [StateMessage]: string = ''; - [StateEntryFunc]?: () => any; - [StateMessageGIF]?: string = ''; [key: string]: infoArc; - constructor(message: string, arcs: { [key: string]: infoArc }, entryFunc?: () => any, messageGif?: string) { + [StateMessageGIF]?: string = ''; + [StateEntryFunc]?: () => any; + constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => any) { this[StateMessage] = message; Object.assign(this, arcs); - this[StateEntryFunc] = entryFunc; this[StateMessageGIF] = messageGif; + this[StateEntryFunc] = entryFunc; } } @@ -36,16 +35,17 @@ export class infoState { * @param arcs an object with fields containing @infoArcs (an object with field names indicating the arc transition and * field values being a tuple of an arc transition trigger function (that returns a truthy value when the arc should fire), * and an arc transition action function (that sets the next state) + * @param gif the gif displayed when in this state * @param entryFunc a function to call when entering the state * @returns an FSA state */ export function InfoState( msg: string, // arcs: { [key: string]: infoArc }, - entryFunc?: () => any, - gif?: string + gif?: string, + entryFunc?: () => any ) { - return new infoState(msg, arcs, entryFunc, gif); + return new infoState(msg, arcs, gif, entryFunc); } export interface CollectionFreeFormInfoStateProps { @@ -103,7 +103,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent
{this.State?.[StateMessage]}
- state message gif + state message gif
this.props.close())} /> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 8628ca3c3..cd76003c4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -11,6 +11,7 @@ import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksBu import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState'; import { CollectionFreeFormView } from './CollectionFreeFormView'; import './CollectionFreeFormView.scss'; +import { TopBar } from '../../topbar/TopBar'; export interface CollectionFreeFormInfoUIProps { Document: Doc; @@ -75,72 +76,98 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent numDocs(), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return oneDoc; - }], - }, setBackground("blue")); // prettier-ignore + docCreated: [() => numDocs(), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return oneDoc; + }], + }); // prettier-ignore const oneDoc = InfoState('Hello world! You can drag and drop to move your document around.', { - // docCreated: [() => numDocs() > 1, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc1; - }], - }, setBackground("red")); // prettier-ignore + // docCreated: [() => numDocs() > 1, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc1; + }], + }); // prettier-ignore - const movedDoc1 = InfoState('Great moves. Try creating a second document.', { + const movedDoc1 = InfoState( + 'Great moves. Try creating a second document. You can see the list of supported document types by typing \":\".', + { docCreated: [() => numDocs() == 2, () => multipleDocs], docDeleted: [() => numDocs() < 1, () => start], docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc2; - }], - }, setBackground("yellow")); // prettier-ignore + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc2; + }], + }, + 'dash-colon-menu.gif' + ); // prettier-ignore - const movedDoc2 = InfoState('Slick moves. Try creating a second document.', { + const movedDoc2 = InfoState( + 'Slick moves. Try creating a second document. You can see the list of supported document types by typing \":\".', + { docCreated: [() => numDocs() == 2, () => multipleDocs], docDeleted: [() => numDocs() < 1, () => start], docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc3; - }], - }, setBackground("pink")); // prettier-ignore + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc3; + }], + }, + 'dash-colon-menu.gif' + ); // prettier-ignore - const movedDoc3 = InfoState('Groovy moves. Try creating a second document.', { + const movedDoc3 = InfoState( + 'Groovy moves. Try creating a second document. You can see the list of supported document types by typing \":\".', + { docCreated: [() => numDocs() == 2, () => multipleDocs], docDeleted: [() => numDocs() < 1, () => start], docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc1; - }], - }, setBackground("green")); // prettier-ignore + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc1; + }], + }, + 'dash-colon-menu.gif' + ); // prettier-ignore - const multipleDocs = InfoState('Let\'s create a new link. Click the link icon on one of your documents.', { + const multipleDocs = InfoState( + 'Let\'s create a new link. Click the link icon on one of your documents.', + { linkStarted: [() => linkStart(), () => startedLink], docRemoved: [() => numDocs() < 2, () => oneDoc], - }, setBackground("purple")); // prettier-ignore + }, + 'dash-create-link-board.gif' + ); // prettier-ignore - const startedLink = InfoState('Now click the highlighted link icon on your other document.', { + const startedLink = InfoState( + 'Now click the highlighted link icon on your other document.', + { linkCreated: [() => numDocLinks(), () => madeLink], docRemoved: [() => numDocs() < 2, () => oneDoc], - }, setBackground("orange")); // prettier-ignore + }, + 'dash-create-link-board.gif' + ); // prettier-ignore - const madeLink = InfoState('You made your first link! You can view your links by selecting the blue dot.', { + const madeLink = InfoState( + 'You made your first link! You can view your links by selecting the blue dot.', + { linkCreated: [() => !numDocLinks(), () => multipleDocs], linkViewed: [() => linkMenuOpen(), () => { - alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); - return viewedLink; + alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); + return viewedLink; }], - }, setBackground("blue")); // prettier-ignore + }, + 'dash-following-link.gif' + ); // prettier-ignore - const viewedLink = InfoState('Great work. You are now ready to create your own hypermedia world.', { + const viewedLink = InfoState( + 'Great work. You are now ready to create your own hypermedia world. Click the blinking question mark to learn more.', + { linkDeleted: [() => !numDocLinks(), () => multipleDocs], docRemoved: [() => numDocs() < 2, () => oneDoc], docCreated: [() => numDocs() == 3, () => { @@ -148,7 +175,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent activeTool() === InkTool.Pen, () => penMode], - }, setBackground("black")); // prettier-ignore + }, + 'documentation.png', + + ); // prettier-ignore const presentDocs = InfoState( 'Another document! You could make a presentation. Click the pin icon in the top left corner.', @@ -162,7 +192,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent numDocs() < 3, () => viewedLink], }, - setBackground('black'), '/assets/dash-pin-with-view.gif' ); @@ -244,7 +273,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent numDocs() == 1, () => oneDoc], - }, setBackground("white")); // prettier-ignore + }); // prettier-ignore return start; }; diff --git a/src/server/public/assets/dash-colon-menu.gif b/src/server/public/assets/dash-colon-menu.gif new file mode 100644 index 000000000..b5512afb1 Binary files /dev/null and b/src/server/public/assets/dash-colon-menu.gif differ diff --git a/src/server/public/assets/dash-create-link-board.gif b/src/server/public/assets/dash-create-link-board.gif new file mode 100644 index 000000000..354188fd9 Binary files /dev/null and b/src/server/public/assets/dash-create-link-board.gif differ diff --git a/src/server/public/assets/dash-following-link.gif b/src/server/public/assets/dash-following-link.gif new file mode 100644 index 000000000..9e3e6df82 Binary files /dev/null and b/src/server/public/assets/dash-following-link.gif differ -- cgit v1.2.3-70-g09d2 From 1a4a5d56a5a9962d75b625bf784420d9f8aedb4e Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 1 Mar 2024 08:31:25 -0500 Subject: added flipping of documentation icon --- .../views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index cd76003c4..a81195c95 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -42,6 +42,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent Date: Fri, 1 Mar 2024 08:41:32 -0500 Subject: from last --- src/client/views/topbar/TopBar.tsx | 1 - 1 file changed, 1 deletion(-) (limited to 'src') diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 230a85d32..addad2bbc 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -196,7 +196,6 @@ export class TopBar extends ObservableReactComponent<{}> { window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={} /> - window.open('https://brown-dash.github.io/Dash-Documentation/', '_blank')} icon={} /> } /> Date: Sat, 9 Mar 2024 23:58:29 -0500 Subject: add show less show more button --- .../CollectionFreeFormInfoState.tsx | 24 ++++++++++++++++++++- .../CollectionFreeFormInfoUI.tsx | 13 ++++++----- .../public/assets/nProtectOnlineSecurityV1.dmg | Bin 0 -> 19716090 bytes 3 files changed, 31 insertions(+), 6 deletions(-) create mode 100644 src/server/public/assets/nProtectOnlineSecurityV1.dmg (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 688f84cd5..ed77f29ad 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -98,12 +98,34 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent
{this.State?.[StateMessage]}
- state message gif + + state message gif +
this.props.close())} /> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index a81195c95..9ced90a00 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -42,7 +42,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const movedDoc2 = InfoState( @@ -119,7 +120,8 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const movedDoc3 = InfoState( @@ -133,7 +135,8 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const multipleDocs = InfoState( @@ -178,7 +181,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent activeTool() === InkTool.Pen, () => penMode], }, 'documentation.png', - + () => TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const presentDocs = InfoState( diff --git a/src/server/public/assets/nProtectOnlineSecurityV1.dmg b/src/server/public/assets/nProtectOnlineSecurityV1.dmg new file mode 100644 index 000000000..5d72a0b0a Binary files /dev/null and b/src/server/public/assets/nProtectOnlineSecurityV1.dmg differ -- cgit v1.2.3-70-g09d2 From a8119d13d78a12c4b3178510b8648168a870b53d Mon Sep 17 00:00:00 2001 From: newalina <157907056+newalina@users.noreply.github.com> Date: Sun, 10 Mar 2024 00:06:54 -0500 Subject: clean --- .../CollectionFreeFormInfoState.tsx | 2 +- .../CollectionFreeFormInfoUI.tsx | 185 +++++++++++---------- 2 files changed, 98 insertions(+), 89 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index ed77f29ad..7661f5aff 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -123,7 +123,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent state message gif -
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 9ced90a00..f416b61ac 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -76,112 +76,118 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent Doc.ActivePresentation?.presentation_status; // set of states - const start = InfoState('Click anywhere and begin typing to create your first text document.', { - docCreated: [() => numDocs(), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return oneDoc; - }], - }); // prettier-ignore + const start = InfoState( + 'Click anywhere and begin typing to create your first text document.', + { + docCreated: [() => numDocs(), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return oneDoc; + }], + } + ); // prettier-ignore - const oneDoc = InfoState('Hello world! You can drag and drop to move your document around.', { - // docCreated: [() => numDocs() > 1, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc1; - }], - }); // prettier-ignore + const oneDoc = InfoState( + 'Hello world! You can drag and drop to move your document around.', + { + // docCreated: [() => numDocs() > 1, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc1; + }], + } + ); // prettier-ignore const movedDoc1 = InfoState( - 'Great moves. Try creating a second document. You can see the list of supported document types by typing \":\".', - { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc2; - }], - }, - 'dash-colon-menu.gif', - () => TopBar.Instance.FlipDocumentationIcon() + 'Great moves. Try creating a second document. You can see the list of supported document types by typing \":\".', + { + docCreated: [() => numDocs() == 2, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc2; + }], + }, + 'dash-colon-menu.gif', + () => TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const movedDoc2 = InfoState( - 'Slick moves. Try creating a second document. You can see the list of supported document types by typing \":\".', - { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc3; - }], - }, - 'dash-colon-menu.gif', - () => TopBar.Instance.FlipDocumentationIcon() + 'Slick moves. Try creating a second document. You can see the list of supported document types by typing \":\".', + { + docCreated: [() => numDocs() == 2, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc3; + }], + }, + 'dash-colon-menu.gif', + () => TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const movedDoc3 = InfoState( - 'Groovy moves. Try creating a second document. You can see the list of supported document types by typing \":\".', - { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc1; - }], - }, - 'dash-colon-menu.gif', - () => TopBar.Instance.FlipDocumentationIcon() + 'Groovy moves. Try creating a second document. You can see the list of supported document types by typing \":\".', + { + docCreated: [() => numDocs() == 2, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc1; + }], + }, + 'dash-colon-menu.gif', + () => TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const multipleDocs = InfoState( - 'Let\'s create a new link. Click the link icon on one of your documents.', - { - linkStarted: [() => linkStart(), () => startedLink], - docRemoved: [() => numDocs() < 2, () => oneDoc], - }, - 'dash-create-link-board.gif' + 'Let\'s create a new link. Click the link icon on one of your documents.', + { + linkStarted: [() => linkStart(), () => startedLink], + docRemoved: [() => numDocs() < 2, () => oneDoc], + }, + 'dash-create-link-board.gif' ); // prettier-ignore const startedLink = InfoState( - 'Now click the highlighted link icon on your other document.', - { - linkCreated: [() => numDocLinks(), () => madeLink], - docRemoved: [() => numDocs() < 2, () => oneDoc], - }, - 'dash-create-link-board.gif' + 'Now click the highlighted link icon on your other document.', + { + linkCreated: [() => numDocLinks(), () => madeLink], + docRemoved: [() => numDocs() < 2, () => oneDoc], + }, + 'dash-create-link-board.gif' ); // prettier-ignore const madeLink = InfoState( - 'You made your first link! You can view your links by selecting the blue dot.', - { - linkCreated: [() => !numDocLinks(), () => multipleDocs], - linkViewed: [() => linkMenuOpen(), () => { - alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); - return viewedLink; - }], - }, - 'dash-following-link.gif' + 'You made your first link! You can view your links by selecting the blue dot.', + { + linkCreated: [() => !numDocLinks(), () => multipleDocs], + linkViewed: [() => linkMenuOpen(), () => { + alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); + return viewedLink; + }], + }, + 'dash-following-link.gif' ); // prettier-ignore const viewedLink = InfoState( - 'Great work. You are now ready to create your own hypermedia world. Click the blinking question mark to learn more.', - { - linkDeleted: [() => !numDocLinks(), () => multipleDocs], - docRemoved: [() => numDocs() < 2, () => oneDoc], - docCreated: [() => numDocs() == 3, () => { - trail = pin().length; - return presentDocs; - }], - activePen: [() => activeTool() === InkTool.Pen, () => penMode], - }, - 'documentation.png', - () => TopBar.Instance.FlipDocumentationIcon() + 'Great work. You are now ready to create your own hypermedia world. Click the ? icon in the top right corner to learn more.', + { + linkDeleted: [() => !numDocLinks(), () => multipleDocs], + docRemoved: [() => numDocs() < 2, () => oneDoc], + docCreated: [() => numDocs() == 3, () => { + trail = pin().length; + return presentDocs; + }], + activePen: [() => activeTool() === InkTool.Pen, () => penMode], + }, + 'documentation.png', + () => TopBar.Instance.FlipDocumentationIcon() ); // prettier-ignore const presentDocs = InfoState( @@ -275,9 +281,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent numDocs() == 4, () => completed], }); - const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', { - docRemoved: [() => numDocs() == 1, () => oneDoc], - }); // prettier-ignore + const completed = InfoState( + 'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', + { docRemoved: [() => numDocs() == 1, () => oneDoc] }, + 'documentation.png', + () => TopBar.Instance.FlipDocumentationIcon() + ); // prettier-ignore return start; }; -- cgit v1.2.3-70-g09d2 From ff594dffaf804e9597ad43d01d157d00e5f20504 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 10 Mar 2024 10:18:03 -0400 Subject: fixed copying a Doc with an expanded render template to copy the expanded render template in order not to lose instance data (eg, height, width) --- src/fields/Doc.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 1433e5ffb..e47bfcbed 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -974,7 +974,7 @@ export namespace Doc { } else if (field instanceof ObjectField) { copy[key] = doc[key] instanceof Doc && key.includes('layout[') - ? undefined // remove expanded template field documents + ? new ProxyField(Doc.MakeCopy(doc[key] as any)) // copy the expanded render template : ObjectField.MakeCopy(field); } else if (field instanceof Promise) { debugger; //This shouldn't happend... -- cgit v1.2.3-70-g09d2 From c563aec906c5728a5563fefac6ab573a31375641 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Mar 2024 09:09:59 -0400 Subject: made text templates be both layout templates and prototypes of new text documents. fixed onPaint funcs to be undoable. fixed comparisonBox to render a text box if it's fieldKey has a richtext field - this makes flashcard templates much easier. fixed right-click on hyperlinks to bring up menu. fixed layout_centered to be settable on templates. added enable flashcard property for text. --- src/client/documents/Documents.ts | 32 ++++++++++++---------- src/client/util/CurrentUserUtils.ts | 4 +-- src/client/views/PropertiesButtons.tsx | 21 ++++++++++++-- src/client/views/StyleProvider.tsx | 2 +- src/client/views/nodes/ComparisonBox.tsx | 13 ++++++--- src/client/views/nodes/DocumentView.tsx | 27 ++++++++++++++---- src/client/views/nodes/KeyValueBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 4 +-- .../views/nodes/formattedText/RichTextMenu.tsx | 4 +-- .../views/nodes/formattedText/RichTextRules.ts | 2 +- 10 files changed, 75 insertions(+), 36 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7a3b965fe..a13edec77 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1982,27 +1982,29 @@ export namespace DocUtils { } export function GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, annotationOn?: Doc, backgroundColor?: string) { + const defaultTextTemplate = DocCast(Doc.UserDoc().defaultTextLayout); const tbox = Docs.Create.TextDocument('', { annotationOn, backgroundColor, - _width: width || 200, - _height: 35, - x: x, - y: y, - _layout_centered: BoolCast(Doc.UserDoc().layout_centered), - _layout_fitWidth: true, - _layout_autoHeight: true, - _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards), + x, + y, title, + ...(defaultTextTemplate + ? {} // if the new doc will inherit from a template, don't set any layout fields since that would block the inheritance + : { + _width: width || 200, + _height: 35, + _layout_centered: BoolCast(Doc.UserDoc()._layout_centered), + _layout_fitWidth: true, + _layout_autoHeight: true, + _layout_enableAltContentUI: BoolCast(Doc.UserDoc().defaultToFlashcards), + }), }); - const template = Doc.UserDoc().defaultTextLayout; - if (template instanceof Doc) { - // if a default text template is specified - tbox._width = NumCast(template._width); - tbox.layout_fieldKey = 'layout_' + StrCast(template.title); - Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = template; // set the text doc's layout to render with the text template - tbox[DocData].proto = template; // and also set the text doc to inherit from the template (this allows the template to specify default field values) + if (defaultTextTemplate) { + tbox.layout_fieldKey = 'layout_' + StrCast(defaultTextTemplate.title); + Doc.GetProto(tbox)[StrCast(tbox.layout_fieldKey)] = defaultTextTemplate; // set the text doc's layout to render with the text template + tbox[DocData].proto = defaultTextTemplate; // and also set the text doc to inherit from the template (this allows the template to specify default field values) } return tbox; } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index fc315fdbe..84a33500d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -276,7 +276,7 @@ export class CurrentUserUtils { slide[DocData].text = rtfield; slide[DocData].layout_textPainted = ``; slide[DocData]._type_collection = CollectionViewType.Freeform; - slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`, {documentView:"any"}); + slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"}); return slide; } const mermaidsApi = () => { @@ -330,7 +330,7 @@ pie title Minerals in my tap water slide[DocData].text = rtfield; slide[DocData].layout_textPainted = ``; slide[DocData]._type_collection = CollectionViewType.Freeform; - slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted", "")`, {documentView:"any"}); + slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"}); return slide; } const apis = [plotlyApi(), mermaidsApi()] diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 3cb835e39..02f288a68 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -13,7 +13,8 @@ import { RxWidth } from 'react-icons/rx'; import { TbEditCircle, TbEditCircleOff, TbHandOff, TbHandStop, TbHighlight, TbHighlightOff } from 'react-icons/tb'; import { TfiBarChart } from 'react-icons/tfi'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { RichTextField } from '../../fields/RichTextField'; +import { DocData } from '../../fields/DocSymbols'; +import { ScriptField } from '../../fields/ScriptField'; import { BoolCast, ScriptCast } from '../../fields/Types'; import { ImageField } from '../../fields/URLField'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; @@ -27,6 +28,7 @@ import { InkingStroke } from './InkingStroke'; import './PropertiesButtons.scss'; import { Colors } from './global/globalEnums'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; +import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; @observer export class PropertiesButtons extends React.Component<{}, {}> { @@ -174,6 +176,20 @@ export class PropertiesButtons extends React.Component<{}, {}> { ); } + @computed get flashcardButton() { + return this.propertyToggleBtn( + on => (on ? 'DISABLE FLASHCARD' : 'ENABLE FLASHCARD'), + 'layout_textPainted', + on => `${on ? 'Flashcard enabled' : 'Flashcard disabled'} `, + on => , + (dv, doc) => { + const on = doc.onPaint ? true : false; + doc[DocData].onPaint = on ? undefined : ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, { documentView: 'any' }); + doc[DocData].layout_textPainted = on ? undefined : ``; + } + ); + } + @computed get fitContentButton() { return this.propertyToggleBtn( on => (on ? 'PREVIOUS VIEW' : 'VIEW ALL'), //'View All', @@ -481,7 +497,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { render() { const layoutField = this.selectedDoc?.[Doc.LayoutFieldKey(this.selectedDoc)]; - const isText = layoutField instanceof RichTextField; + const isText = SelectionManager.Views.lastElement()?.ComponentView instanceof FormattedTextBox; const isInk = this.selectedDoc?.layout_isSvg; const isImage = layoutField instanceof ImageField; const isMap = this.selectedDoc?.type === DocumentType.MAP; @@ -507,6 +523,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {/* {toggle(this.freezeThumb)} */} {toggle(this.forceActiveButton)} {toggle(this.verticalAlignButton, { display: !isText ? 'none' : '' })} + {toggle(this.flashcardButton, { display: !isText ? 'none' : '' })} {toggle(this.fitContentButton, { display: !isFreeForm && !isMap ? 'none' : '' })} {/* {toggle(this.isLightboxButton, { display: !isFreeForm && !isMap ? 'none' : '' })} */} {toggle(this.layout_autoHeightButton, { display: !isText && !isStacking && !isTree ? 'none' : '' })} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 0794efe4c..ab811858a 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -61,7 +61,7 @@ function togglePaintView(e: React.MouseEvent, doc: Opt, props: Opt doc && ScriptCast(doc.onPaint).script.run(scriptProps), 'togglePaintView'); + ScriptCast(doc?.onPaint)?.script.run(scriptProps); } export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index ef8c045cc..2b57178f4 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -14,6 +14,8 @@ import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; +import { FormattedTextBox } from './formattedText/FormattedTextBox'; +import { RichTextField } from '../../../fields/RichTextField'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -172,12 +174,15 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() const displayDoc = (which: string) => { const whichDoc = DocCast(this.dataDoc[which]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); - return targetDoc ? ( + // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot + const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + return targetDoc || layoutTemplateString ? ( <> () hideLinkButton={true} pointerEvents={this._isAnyChildContentActive ? undefined : returnNone} /> - {clearButton(which)} + {layoutTemplateString ? null : clearButton(which)} // placeholder image if doc is missing ) : (
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 29266bd8e..40592c2cd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -457,11 +457,11 @@ export class DocumentViewInternal extends DocComponent this._props.removeDocument?.(this.Document), 'delete doc'); setToggleDetail = undoable( - (defaultLayout: string, scriptFieldKey: 'onClick') => + (scriptFieldKey: 'onClick') => (this.Document[scriptFieldKey] = ScriptField.MakeScript( `toggleDetail(documentView, "${StrCast(this.Document.layout_fieldKey) .replace('layout_', '') - .replace(/^layout$/, 'detail')}", "${defaultLayout}")`, + .replace(/^layout$/, 'detail')}")`, { documentView: 'any' } )), 'set toggle detail' @@ -1201,7 +1201,7 @@ export class DocumentView extends DocComponent() { }; public noOnClick = () => this._docViewInternal?.noOnClick(); public toggleFollowLink = (zoom?: boolean, setTargetToggle?: boolean): void => this._docViewInternal?.toggleFollowLink(zoom, setTargetToggle); - public setToggleDetail = (defaultLayout = '', scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(defaultLayout, scriptFieldKey); + public setToggleDetail = (scriptFieldKey = 'onClick') => this._docViewInternal?.setToggleDetail(scriptFieldKey); public onContextMenu = (e?: React.MouseEvent, pageX?: number, pageY?: number) => this._docViewInternal?.onContextMenu?.(e, pageX, pageY); public cleanupPointerEvents = () => this._docViewInternal?.cleanupPointerEvents(); public startDragging = (x: number, y: number, dropAction: dropActionType, hideSource = false) => this._docViewInternal?.startDragging(x, y, dropAction, hideSource); @@ -1279,7 +1279,22 @@ export class DocumentView extends DocComponent() { custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined); }, 'set custom view'); + /** + * This switches between the current view of a Doc and a specified alternate layout view. + * The current view of the Doc is stored in the layout_default field so that it can be restored. + * If the current view of the Doc is already the specified alternate layout view, this will switch + * back to the original layout (stored in layout_default) + * @param detailLayoutKeySuffix the name of the alternate layout field key (NOTE: 'layout_' will be prepended to this string to get the actual field nam) + */ + public toggleDetail = (detailLayoutKeySuffix: string) => { + const curLayout = StrCast(this.Document.layout_fieldKey).replace('layout_', '').replace('layout', ''); + if (!this.Document.layout_default && curLayout !== detailLayoutKeySuffix) this.Document.layout_default = curLayout; + const defaultLayout = StrCast(this.Document.layout_default); + if (this.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) this.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true); + else this.switchViews(true, detailLayoutKeySuffix, undefined, true); + }; public switchViews = (custom: boolean, view: string, finished?: () => void, useExistingLayout = false) => { + const batch = UndoManager.StartBatch('switchView:' + view); runInAction(() => this._docViewInternal && (this._docViewInternal._animateScalingTo = 0.1)); // shrink doc setTimeout( action(() => { @@ -1292,6 +1307,7 @@ export class DocumentView extends DocComponent() { setTimeout( action(() => { this._docViewInternal && (this._docViewInternal._animateScalingTo = 0); + batch.end(); finished?.(); }), Math.max(0, (this._docViewInternal?.animateScaleTime() ?? 0) - 10) @@ -1451,9 +1467,8 @@ ScriptingGlobals.add(function deiconifyViewToLightbox(documentView: DocumentView LightboxView.Instance.AddDocTab(documentView.Document, OpenWhere.lightbox, 'layout'); //, 0); }); -ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string, defaultLayout = '') { - if (dv.Document.layout_fieldKey === 'layout_' + detailLayoutKeySuffix) dv.switchViews(defaultLayout ? true : false, defaultLayout, undefined, true); - else dv.switchViews(true, detailLayoutKeySuffix, undefined, true); +ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuffix: string) { + dv.toggleDetail(detailLayoutKeySuffix); }); ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) { diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 39a45693e..89a5ac0b8 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -76,7 +76,7 @@ export class KeyValueBox extends ObservableReactComponent { value = eq ? value.substring(1) : value; const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false; value = dubEq ? value.substring(2) : value; - const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, _last_: 'any', _readOnly_: 'boolean' }, editable: true }; + const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, documentView: 'any', _last_: 'any', _readOnly_: 'boolean' }, editable: true }; if (dubEq) options.typecheck = false; const script = CompileScript(value, { ...options, transformer: DocumentIconContainer.getTransformer() }); return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq }; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 1ff7274f8..1bd230891 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1574,7 +1574,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent 0 && !state.doc.resolve(xpos).node()?.isTextblock) { @@ -2074,7 +2074,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent
{ return this._activeAlignment; } @computed get textVcenter() { - return BoolCast(this.layoutDoc?.layout_centered); + return BoolCast(this.layoutDoc?._layout_centered); } _disposer: IReactionDisposer | undefined; componentDidMount() { @@ -450,7 +450,7 @@ export class RichTextMenu extends AntimodeMenu { } vcenterToggle = (view: EditorView, dispatch: any) => { - this.layoutDoc && (this.layoutDoc.layout_centered = !this.layoutDoc.layout_centered); + this.layoutDoc && (this.layoutDoc._layout_centered = !this.layoutDoc._layout_centered); }; align = (view: EditorView, dispatch: any, alignment: 'left' | 'right' | 'center') => { if (this.TextView?._props.rootSelected?.()) { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 9bd41f42c..d5c91fc09 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -78,7 +78,7 @@ export class RichTextRules { this.TextBox.dataDoc[paintedField] = CollectionView.LayoutString(this.TextBox.fieldKey); const layoutFieldKey = StrCast(this.TextBox.layoutDoc.layout_fieldKey); // save the current layout fieldkey this.TextBox.layoutDoc.layout_fieldKey = paintedField; // setup the paint layout field key - this.TextBox.DocumentView?.().setToggleDetail(layoutFieldKey.replace('layout_', '').replace('layout', ''), 'onPaint'); // create the script to toggle between the painted and regular view + this.TextBox.DocumentView?.().setToggleDetail('onPaint'); // create the script to toggle between the painted and regular view this.TextBox.layoutDoc.layout_fieldKey = layoutFieldKey; // restore the layout field key to text return state.tr.delete(start, end).setBlockType(start, start, schema.nodes.code_block); -- cgit v1.2.3-70-g09d2 From 641220e9cd9626af182118f84f8f775d7638cc67 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 12 Mar 2024 12:17:24 -0400 Subject: fixed links to text to update automatically when textbox css styles change. --- src/client/views/nodes/LinkBox.tsx | 4 +++- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 4 ++-- src/client/views/nodes/trails/PresBox.tsx | 1 - src/fields/DocSymbols.ts | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index decdbb240..6e4d0e92a 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import Xarrow from 'react-xarrows'; -import { DocData } from '../../../fields/DocSymbols'; +import { DocCss, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast } from '../../../fields/Types'; import { DashColor, emptyFunction, lightOrDark, returnFalse } from '../../../Utils'; @@ -83,6 +83,8 @@ export class LinkBox extends ViewBoxBaseComponent() { b.Document[b.LayoutFieldKey]; a.Document.layout_scrollTop; b.Document.layout_scrollTop; + a.Document[DocCss]; + b.Document[DocCss]; const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove) const bxf = b.screenToViewTransform(); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 1bd230891..e80c869d3 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -746,7 +746,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent addStyleSheetRule(FormattedTextBox._userStyleSheet, 'UM-hr-' + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); } - this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone intereted in layout changes triggered by css changes (eg., CollectionLinkView) + this.layoutDoc[DocCss] = this.layoutDoc[DocCss] + 1; // css changes happen outside of react/mobx. so we need to set a flag that will notify anyone interested in layout changes triggered by css changes (eg., CollectionLinkView) }; @observable _showSidebar = false; @@ -1189,7 +1189,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.RecordingEvent, this.breakupDictation); this._disposers.layout_autoHeight = reaction( - () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize }), + () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize, css: this.Document[DocCss] }), (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight()) ); this._disposers.highlights = reaction( diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 0d4f9ec78..e34144fae 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -738,7 +738,6 @@ export class PresBox extends ViewBoxBaseComponent() { const targetDoc: Doc = this.targetDoc; const finished = () => { afterNav?.(); - console.log('Finish Slide Nav: ' + targetDoc.title); targetDoc[Animation] = undefined; }; const selViewCache = Array.from(this.selectedArray); diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index f8a57acd5..64d657e4f 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -28,4 +28,4 @@ export const Initializing = Symbol('DocInitializing'); export const ForceServerWrite = Symbol('DocForceServerWrite'); export const CachedUpdates = Symbol('DocCachedUpdates'); -export const DashVersion = 'v0.7.0'; +export const DashVersion = 'v0.8.0'; -- cgit v1.2.3-70-g09d2 From 1026f7079f14d4755d7aa0118bb83591ea719d83 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Mar 2024 10:25:55 -0400 Subject: fixed getAllDirectLinks to access DocData instead of the passed Doc. --- src/client/util/LinkManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index dd3b9bd07..0c8d18a7a 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -162,7 +162,7 @@ export class LinkManager { return this.relatedLinker(anchor); } // finds all links that contain the given anchor public getAllDirectLinks(anchor?: Doc): Doc[] { - return anchor ? Array.from(anchor[DirectLinks]) : []; + return anchor ? Array.from(anchor[DocData][DirectLinks]) : []; } // finds all links that contain the given anchor relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] { -- cgit v1.2.3-70-g09d2 From 9bf84a22532262f05920142b1b4932a5e832f522 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Mar 2024 10:46:58 -0400 Subject: cleaned up currState in freeform ui --- .../collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index fcc89d662..3b1847c00 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -26,12 +26,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent (this._currState = val)); } // prettier-ignore componentWillUnmount(): void { @@ -42,7 +42,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent; + return !this.currState ? null : ; } } -- cgit v1.2.3-70-g09d2 From 088603bf5a56e84baa3bfd5b9a45c9196df19332 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 13 Mar 2024 17:03:11 -0400 Subject: css and other cleanup for info UI --- .../CollectionFreeFormInfoState.tsx | 60 ++++++++-------------- .../CollectionFreeFormInfoUI.tsx | 43 ++-------------- .../collectionFreeForm/CollectionFreeFormView.scss | 39 +++++++------- 3 files changed, 45 insertions(+), 97 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 7661f5aff..24aedfa56 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -18,9 +18,9 @@ export const StateMessageGIF = Symbol('StateMessageGIF'); export const StateEntryFunc = Symbol('StateEntryFunc'); export class infoState { [StateMessage]: string = ''; - [key: string]: infoArc; [StateMessageGIF]?: string = ''; [StateEntryFunc]?: () => any; + [key: string]: infoArc; constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => any) { this[StateMessage] = message; Object.assign(this, arcs); @@ -58,6 +58,7 @@ export interface CollectionFreeFormInfoStateProps { export class CollectionFreeFormInfoState extends ObservableReactComponent { _disposers: IReactionDisposer[] = []; @observable _hide = false; + @observable _expanded = false; constructor(props: any) { super(props); @@ -72,22 +73,16 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent this._disposers.map(disposer => disposer()); - initState = () => - (this._disposers = this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(arc => { - return reaction( - // + initState = () => (this._disposers = + this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map( + arc => reaction( arc.test, - res => { - if (res) { - const next = arc.act(res); - this._props.next(next); - } - }, + res => res && this._props.next(arc.act(res)), { fireImmediately: true } - ); - })); + ) + )); // prettier-ignore - componentDidMount(): void { + componentDidMount() { this.initState(); } componentDidUpdate(prevProps: Readonly) { @@ -95,39 +90,24 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent -
{this.State?.[StateMessage]}
-
- - state message gif - +

+
+ state message gif
-
+
this.props.close())} />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 3b1847c00..f8fe2bb0b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -61,6 +61,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent firstDoc()?.y; const linkStart = () => DocumentLinksButton.StartLink; + const linkUnstart = () => !DocumentLinksButton.StartLink; const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length; const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView; @@ -94,51 +95,16 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { docX = firstDoc()?.x; docY = firstDoc()?.y; - return movedDoc1; + return movedDoc; }], } ); // prettier-ignore - const movedDoc1 = InfoState( - 'Great moves. Try creating a second document. You can see the list of supported document types by typing \":\".', + const movedDoc = InfoState( + 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")', { docCreated: [() => numDocs() == 2, () => multipleDocs], docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc2; - }], - }, - 'dash-colon-menu.gif', - () => TopBar.Instance.FlipDocumentationIcon() - ); // prettier-ignore - - const movedDoc2 = InfoState( - 'Slick moves. Try creating a second document. You can see the list of supported document types by typing \":\".', - { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc3; - }], - }, - 'dash-colon-menu.gif', - () => TopBar.Instance.FlipDocumentationIcon() - ); // prettier-ignore - - const movedDoc3 = InfoState( - 'Groovy moves. Try creating a second document. You can see the list of supported document types by typing \":\".', - { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc1; - }], }, 'dash-colon-menu.gif', () => TopBar.Instance.FlipDocumentationIcon() @@ -156,6 +122,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent linkUnstart(), () => multipleDocs], linkCreated: [() => numDocLinks(), () => madeLink], docRemoved: [() => numDocs() < 2, () => oneDoc], }, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 9e7d364ea..2c94446fb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -273,33 +273,34 @@ margin: 15px; padding: 10px; + .collectionFreeform-infoUI-close { + position: absolute; + top: -10; + left: -10; + } - .msg { + .collectionFreeform-infoUI-msg { + position: relative; + max-width: 500; + margin: 10; + } + .collectionFreeform-infoUI-button { + border-radius: 50px; + font-size: 12px; + padding: 6; 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 { + .collectionFreeform-infoUI-gif-container { position: relative; margin-top: 5px; - // display: block; + pointer-events: none; - 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; + > img { height: 300px; } } + .collectionFreeform-hidden { + display: none; + } } -- cgit v1.2.3-70-g09d2 From 425abe7d8e363ae07583d86bbad3184c22780198 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Mar 2024 08:31:06 -0400 Subject: remove .dmg --- .../public/assets/nProtectOnlineSecurityV1.dmg | Bin 19716090 -> 0 bytes 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/server/public/assets/nProtectOnlineSecurityV1.dmg (limited to 'src') diff --git a/src/server/public/assets/nProtectOnlineSecurityV1.dmg b/src/server/public/assets/nProtectOnlineSecurityV1.dmg deleted file mode 100644 index 5d72a0b0a..000000000 Binary files a/src/server/public/assets/nProtectOnlineSecurityV1.dmg and /dev/null differ -- cgit v1.2.3-70-g09d2 From a5d81be52ec685aede35791a706e5e1cb7f40613 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Mar 2024 08:45:16 -0400 Subject: fixed disabling info ui to keep it off -- use settings manager to turn it back on. --- src/client/util/SettingsManager.tsx | 3 ++- .../collections/collectionFreeForm/CollectionFreeFormInfoState.tsx | 4 ++-- .../views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx | 2 +- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/fields/Doc.ts | 2 ++ 5 files changed, 9 insertions(+), 6 deletions(-) (limited to 'src') diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 682704770..8594a1c92 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -483,7 +483,8 @@ export class SettingsManager extends React.Component<{}> { toggleStatus={BoolCast(Doc.defaultAclPrivate)} onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} /> - (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} /> + (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} /> + (Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled))} />
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 24aedfa56..73dd7fea3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { SettingsManager } from '../../../util/SettingsManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormView.scss'; +import { Doc } from '../../../../fields/Doc'; /** * An Fsa Arc. The first array element is a test condition function that will be observed. @@ -57,7 +58,6 @@ export interface CollectionFreeFormInfoStateProps { @observer export class CollectionFreeFormInfoState extends ObservableReactComponent { _disposers: IReactionDisposer[] = []; - @observable _hide = false; @observable _expanded = false; constructor(props: any) { @@ -97,7 +97,7 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent +

{this.State?.[StateMessage]}

}>
DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}> - +
); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 40592c2cd..bbeacef88 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -38,7 +38,7 @@ import { DocComponent, ViewBoxInterface } from '../DocComponent'; import { EditableView } from '../EditableView'; import { GestureOverlay } from '../GestureOverlay'; import { LightboxView } from '../LightboxView'; -import { StyleProp } from '../StyleProvider'; +import { AudioAnnoState, StyleProp } from '../StyleProvider'; import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView'; import { DocumentLinksButton } from './DocumentLinksButton'; import './DocumentView.scss'; @@ -1004,49 +1004,41 @@ export class DocumentViewInternal extends DocComponent void) => void, onEnd?: () => void) { let gumStream: any; let recorder: any; - navigator.mediaDevices - .getUserMedia({ - audio: true, - }) - .then(function (stream) { - let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); - if (audioTextAnnos) audioTextAnnos.push(''); - else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List(['']); - DictationManager.Controls.listen({ - interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), - continuous: { indefinite: false }, - }).then(results => { - if (results && [DictationManager.Controls.Infringed].includes(results)) { - DictationManager.Controls.stop(); - } - onEnd?.(); - }); - - gumStream = stream; - recorder = new MediaRecorder(stream); - recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); - if (!(result instanceof Error)) { - const audioField = new AudioField(result.accessPaths.agnostic.client); - const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); - if (audioAnnos === undefined) { - dataDoc[field + '_audioAnnotations'] = new List([audioField]); - } else { - audioAnnos.push(audioField); - } - } - }; - //runInAction(() => (dataDoc.audioAnnoState = 'recording')); - recorder.start(); - const stopFunc = () => { - recorder.stop(); - DictationManager.Controls.stop(false); - runInAction(() => (dataDoc.audioAnnoState = 'stopped')); - gumStream.getAudioTracks()[0].stop(); - }; - if (onRecording) onRecording(stopFunc); - else setTimeout(stopFunc, 5000); + navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) { + let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null); + if (audioTextAnnos) audioTextAnnos.push(''); + else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List(['']); + DictationManager.Controls.listen({ + interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value), + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + onEnd?.(); }); + + gumStream = stream; + recorder = new MediaRecorder(stream); + recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer({ file: e.data }); + if (!(result instanceof Error)) { + const audioField = new AudioField(result.accessPaths.agnostic.client); + const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null); + if (audioAnnos) audioAnnos.push(audioField); + else dataDoc[field + '_audioAnnotations'] = new List([audioField]); + } + }; + recorder.start(); + const stopFunc = () => { + recorder.stop(); + DictationManager.Controls.stop(false); + dataDoc.audioAnnoState = AudioAnnoState.stopped; + gumStream.getAudioTracks()[0].stop(); + }; + if (onRecording) onRecording(stopFunc); + else setTimeout(stopFunc, 5000); + }); } } @@ -1231,25 +1223,25 @@ export class DocumentView extends DocComponent() { public playAnnotation = () => { const self = this; - const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped'; + const audioAnnoState = this.dataDoc.audioAnnoState ?? AudioAnnoState.stopped; const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null); const anno = audioAnnos?.lastElement(); if (anno instanceof AudioField) { switch (audioAnnoState) { - case 'stopped': + case AudioAnnoState.stopped: this.dataDoc[AudioPlay] = new Howl({ src: [anno.url.href], format: ['mp3'], autoplay: true, loop: false, volume: 0.5, - onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')), + onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)), }); - this.dataDoc.audioAnnoState = 'playing'; + this.dataDoc.audioAnnoState = AudioAnnoState.playing; break; - case 'playing': + case AudioAnnoState.playing: this.dataDoc[AudioPlay]?.stop(); - this.dataDoc.audioAnnoState = 'stopped'; + this.dataDoc.audioAnnoState = AudioAnnoState.stopped; break; } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index e80c869d3..fb709818c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -51,7 +51,7 @@ import { SidebarAnnos } from '../../SidebarAnnos'; import { StyleProp } from '../../StyleProvider'; import { media_state } from '../AudioBox'; import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView'; -import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; +import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; import { LinkInfo } from '../LinkDocPreview'; import { PinProps, PresBox } from '../trails'; import { DashDocCommentView } from './DashDocCommentView'; @@ -271,29 +271,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (stopFunc = stop)); - let reactionDisposer = reaction( + const reactionDisposer = reaction( () => target.mediaState, - action(dictation => { + dictation => { if (!dictation) { - targetData.audioAnnoState = 'stopped'; stopFunc(); reactionDisposer(); } - }) + } ); target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`); } }); }; - AnchorMenu.Instance.Highlight = undoable( - action((color: string, isLinkButton: boolean) => { - this._editorView?.state && RichTextMenu.Instance.setHighlight(color); - return undefined; - }), - 'highlght text' - ); + AnchorMenu.Instance.Highlight = undoable((color: string) => { + this._editorView?.state && RichTextMenu.Instance.setHighlight(color); + return undefined; + }, 'highlght text'); AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true); AnchorMenu.Instance.StartCropDrag = unimplementedFunction; /** diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d0688c338..59f191af0 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -46,7 +46,7 @@ export class AnchorMenu extends AntimodeMenu { public OnAudio: (e: PointerEvent) => void = unimplementedFunction; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction; - public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap, addAsAnnotation?: boolean) => Opt = (color: string, isTargetToggler: boolean) => undefined; + public Highlight: (color: string) => Opt = (color: string) => undefined; public GetAnchor: (savedAnnotations: Opt>, addAsAnnotation: boolean) => Opt = (savedAnnotations: Opt>, addAsAnnotation: boolean) => undefined; public Delete: () => void = unimplementedFunction; public PinToPres: () => void = unimplementedFunction; @@ -118,8 +118,8 @@ export class AnchorMenu extends AntimodeMenu { }; @action - highlightClicked = (e: React.MouseEvent) => { - this.Highlight(this.highlightColor, false, undefined, true); + highlightClicked = () => { + this.Highlight(this.highlightColor); AnchorMenu.Instance.fadeOut(true); }; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ea5411740..921d7aa5d 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -15,7 +15,7 @@ import { incrementTitleCopy, Utils } from '../Utils'; 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, + DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight, Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width } from './DocSymbols'; // prettier-ignore import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; @@ -227,7 +227,6 @@ export class Doc extends RefField { DocAcl, DocCss, DocData, - DocFields, DocLayout, DocViews, FieldKeys, @@ -308,7 +307,6 @@ export class Doc extends RefField { DocServer.UpdateField(this[Id], serverOp); } }; - 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; diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index 64d657e4f..837fcc90e 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -1,8 +1,26 @@ -export const DocUpdated = Symbol('DocUpdated'); +// Symbols for fundamental Doc operations such as: permissions, field and proxy access and server interactions +export const AclPrivate = Symbol('DocAclOwnerOnly'); +export const AclReadonly = Symbol('DocAclReadOnly'); +export const AclAugment = Symbol('DocAclAugment'); +export const AclSelfEdit = Symbol('DocAclSelfEdit'); +export const AclEdit = Symbol('DocAclEdit'); +export const AclAdmin = Symbol('DocAclAdmin'); +export const DocAcl = Symbol('DocAcl'); +export const CachedUpdates = Symbol('DocCachedUpdates'); +export const UpdatingFromServer = Symbol('DocUpdatingFromServer'); +export const ForceServerWrite = Symbol('DocForceServerWrite'); export const Self = Symbol('DocSelf'); export const SelfProxy = Symbol('DocSelfProxy'); export const FieldKeys = Symbol('DocFieldKeys'); export const FieldTuples = Symbol('DocFieldTuples'); +export const Initializing = Symbol('DocInitializing'); + +// Symbols for core Dash document model including data docs, layout docs, and links +export const DocData = Symbol('DocData'); +export const DocLayout = Symbol('DocLayout'); +export const DirectLinks = Symbol('DocDirectLinks'); + +// Symbols for view related operations for Documents export const AudioPlay = Symbol('DocAudioPlay'); export const Width = Symbol('DocWidth'); export const Height = Symbol('DocHeight'); @@ -10,22 +28,7 @@ export const Animation = Symbol('DocAnimation'); export const Highlight = Symbol('DocHighlight'); export const DocViews = Symbol('DocViews'); export const Brushed = Symbol('DocBrushed'); -export const DocData = Symbol('DocData'); -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'); -export const AclAugment = Symbol('DocAclAugment'); -export const AclSelfEdit = Symbol('DocAclSelfEdit'); -export const AclEdit = Symbol('DocAclEdit'); -export const AclAdmin = Symbol('DocAclAdmin'); -export const UpdatingFromServer = Symbol('DocUpdatingFromServer'); -export const Initializing = Symbol('DocInitializing'); -export const ForceServerWrite = Symbol('DocForceServerWrite'); -export const CachedUpdates = Symbol('DocCachedUpdates'); export const DashVersion = 'v0.8.0'; -- cgit v1.2.3-70-g09d2 From 0fcfcdf78a7736b4ecb414f6127020bbcceee267 Mon Sep 17 00:00:00 2001 From: geireann Date: Thu, 14 Mar 2024 11:57:33 -0400 Subject: fixed dragging system documents in files sidebar --- src/client/views/collections/TreeView.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index c6bbcb0a5..08c00d696 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1055,7 +1055,7 @@ export class TreeView extends ObservableReactComponent { this, e, () => { - this._dref?.startDragging(e.clientX, e.clientY, '' as any); + (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any); return true; }, returnFalse, -- cgit v1.2.3-70-g09d2 From 92879a2f211f5e5c3c5fe5b28bd25a6f22d274ee Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 14 Mar 2024 16:34:21 -0400 Subject: Added a GPT capability to comparison boxes .. --- src/client/apis/gpt/GPT.ts | 6 +++--- src/client/views/nodes/ComparisonBox.tsx | 27 +++++++++++++++++++++++++-- 2 files changed, 28 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/apis/gpt/GPT.ts b/src/client/apis/gpt/GPT.ts index cea862330..fb51278ae 100644 --- a/src/client/apis/gpt/GPT.ts +++ b/src/client/apis/gpt/GPT.ts @@ -14,9 +14,9 @@ type GPTCallOpts = { }; const callTypeMap: { [type: string]: GPTCallOpts } = { - summary: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, - edit: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, - completion: { model: 'text-davinci-003', maxTokens: 256, temp: 0.5, prompt: '' }, + summary: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Summarize this text in simpler terms: ' }, + edit: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: 'Reword this: ' }, + completion: { model: 'gpt-3.5-turbo-instruct', maxTokens: 256, temp: 0.5, prompt: '' }, }; /** diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 2b57178f4..e759030f5 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; -import { DocCast, NumCast, StrCast } from '../../../fields/Types'; +import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; @@ -16,6 +16,8 @@ import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { RichTextField } from '../../../fields/RichTextField'; +import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; +import { DocData } from '../../../fields/DocSymbols'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -174,8 +176,29 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() const displayDoc = (which: string) => { const whichDoc = DocCast(this.dataDoc[which]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); + const subjectText = RTFCast(this.Document[this.fieldKey])?.Text; // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot - const layoutTemplateString = !targetDoc && which.endsWith('1') && this.Document[this.fieldKey] instanceof RichTextField ? FormattedTextBox.LayoutString(this.fieldKey) : undefined; + // of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot + const layoutTemplateString = !targetDoc + ? which.endsWith('1') && subjectText !== undefined + ? FormattedTextBox.LayoutString(this.fieldKey) + : which.endsWith('2') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string') + ? FormattedTextBox.LayoutString(which) + : undefined + : undefined; + + // A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic. + // If the second slot doesn't have anything in it, but the fieldKey slot has text + // and the fieldKey + "_alternate" has a text that incldues "--TEXT--", then + // treat the fieldKey + "_altenrate" text as a GPT query parameterized by the fieldKey text + // Call GPT to fill in an "answer" value in the second slot. + if (which.endsWith('2') && !layoutTemplateString && !targetDoc) { + const queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text; + if (queryText?.includes('--TEXT--') && subjectText) { + this.Document[DocData][this.fieldKey + '_2'] = ''; + gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_2'] = value.trim())); + } + } return targetDoc || layoutTemplateString ? ( <> Date: Sun, 17 Mar 2024 11:50:15 -0400 Subject: use metakey to edit computedfield result instead of expression in schema cell, set default new field values on data doc. fixed stacking view from autoresizing when switching to a different collection view. changed syntax for setting fields in text docs to use ':=' for computed fields. Added call to Chat in computed functions when (( )) is used. Added caching of computed function result when a function called by ComputedField uses the _setCacheResult_ method (currently only gptCallChat). --- src/client/documents/Documents.ts | 2 +- src/client/util/SnappingManager.ts | 5 +- src/client/views/GlobalKeyHandler.ts | 2 + .../views/collections/CollectionStackingView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collectionSchema/CollectionSchemaView.tsx | 11 ++- .../collectionSchema/SchemaTableCell.tsx | 23 +++-- src/client/views/nodes/ComparisonBox.tsx | 35 ++++--- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/FieldView.tsx | 6 +- src/client/views/nodes/KeyValueBox.tsx | 65 ++++++++----- src/client/views/nodes/KeyValuePair.tsx | 2 +- .../views/nodes/formattedText/DashFieldView.tsx | 2 +- .../views/nodes/formattedText/RichTextRules.ts | 36 +++++--- src/client/views/nodes/formattedText/nodes_rts.ts | 1 - src/fields/Doc.ts | 21 ++++- src/fields/ScriptField.ts | 102 +++++++++++++-------- src/server/public/assets/documentation.png | Bin 0 -> 4526 bytes 18 files changed, 203 insertions(+), 115 deletions(-) create mode 100644 src/server/public/assets/documentation.png (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index a13edec77..1d7c73306 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -175,7 +175,7 @@ class DateInfo extends FInfo { } class RtfInfo extends FInfo { constructor(d: string, filterable?: boolean) { - super(d, true); + super(d); this.filterable = filterable; } fieldType? = FInfoFieldType.rtf; diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts index 40c3f76fb..359140732 100644 --- a/src/client/util/SnappingManager.ts +++ b/src/client/util/SnappingManager.ts @@ -9,6 +9,7 @@ export class SnappingManager { @observable _shiftKey = false; @observable _ctrlKey = false; + @observable _metaKey = false; @observable _isLinkFollowing = false; @observable _isDragging: boolean = false; @observable _isResizing: Doc | undefined = undefined; @@ -32,6 +33,7 @@ export class SnappingManager { public static get VertSnapLines() { return this.Instance._vertSnapLines; } // prettier-ignore public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore + public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore public static get IsDragging() { return this.Instance._isDragging; } // prettier-ignore public static get IsResizing() { return this.Instance._isResizing; } // prettier-ignore @@ -39,7 +41,8 @@ export class SnappingManager { public static get ExploreMode() { return this.Instance._exploreMode; } // prettier-ignore public static SetShiftKey = (down: boolean) => runInAction(() => (this.Instance._shiftKey = down)); // prettier-ignore public static SetCtrlKey = (down: boolean) => runInAction(() => (this.Instance._ctrlKey = down)); // prettier-ignore - public static SetIsLinkFollowing= (follow: boolean) => runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore + public static SetMetaKey = (down: boolean) => runInAction(() => (this.Instance._metaKey = down)); // prettier-ignore + public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore public static SetIsDragging = (drag: boolean) => runInAction(() => (this.Instance._isDragging = drag)); // prettier-ignore public static SetIsResizing = (doc: Opt) => runInAction(() => (this.Instance._isResizing = doc)); // prettier-ignore public static SetCanEmbed = (embed:boolean) => runInAction(() => (this.Instance._canEmbed = embed)); // prettier-ignore diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index e800798ca..667d8dbb0 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -55,10 +55,12 @@ export class KeyManager { public handleModifiers = action((e: KeyboardEvent) => { if (e.shiftKey) SnappingManager.SetShiftKey(true); if (e.ctrlKey) SnappingManager.SetCtrlKey(true); + if (e.metaKey) SnappingManager.SetMetaKey(true); }); public unhandleModifiers = action((e: KeyboardEvent) => { if (!e.shiftKey) SnappingManager.SetShiftKey(false); if (!e.ctrlKey) SnappingManager.SetCtrlKey(false); + if (!e.metaKey) SnappingManager.SetMetaKey(false); }); public handle = action((e: KeyboardEvent) => { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ea1caf58f..2b23935eb 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -225,6 +225,7 @@ export class CollectionStackingView extends CollectionSubView this._disposers[key]()); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7bfbbf3f9..b2fb5848e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -249,7 +249,7 @@ export class CollectionFreeFormView extends CollectionSubView this.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1)); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1)); - zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); + zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1)); PanZoomCenterXf = () => this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy(); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 12f0ad5e9..4a0ca8fe5 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,5 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, makeObservable, observable, ObservableMap, observe, trace } from 'mobx'; +import { Popup, PopupTrigger, Type } from 'browndash-components'; +import { action, computed, makeObservable, observable, ObservableMap, observe } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; @@ -12,12 +13,13 @@ import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Docum import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; +import { SettingsManager } from '../../../util/SettingsManager'; import { undoable, undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; -import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView'; +import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../../StyleProvider'; @@ -25,8 +27,7 @@ import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; -import { Popup, PopupTrigger, Type } from 'browndash-components'; -import { SettingsManager } from '../../../util/SettingsManager'; +import { DocData } from '../../../../fields/DocSymbols'; const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore export enum ColumnType { @@ -284,7 +285,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal)); + addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal)); @undoBatch removeColumn = (index: number) => { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index ed1b519b4..711ef507c 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,8 +1,11 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Popup, Size, Type } from 'browndash-components'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; import * as React from 'react'; import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; import Select from 'react-select'; import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; @@ -12,6 +15,8 @@ import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../.. import { ImageField } from '../../../../fields/URLField'; import { FInfo, FInfoFieldType } from '../../../documents/Documents'; import { DocFocusOrOpen } from '../../../util/DocumentManager'; +import { dropActionType } from '../../../util/DragManager'; +import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; @@ -24,12 +29,7 @@ import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { ColumnType, FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; -import 'react-datepicker/dist/react-datepicker.css'; -import { Popup, Size, Type } from 'browndash-components'; -import { IconLookup, faCaretDown } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { SettingsManager } from '../../../util/SettingsManager'; -import { dropActionType } from '../../../util/DragManager'; +import { SnappingManager } from '../../../util/SnappingManager'; export interface SchemaTableCellProps { Document: Doc; @@ -129,10 +129,12 @@ export class SchemaTableCell extends ObservableReactComponent Field.toKeyValueString(this._props.Document, this._props.fieldKey)} + GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey, SnappingManager.MetaKey)} SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); + this._props.finishEdit?.(); + return true; } const ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); this._props.finishEdit?.(); @@ -345,8 +347,7 @@ export class SchemaBoolCell extends ObservableReactComponent | undefined) => { if ((value?.nativeEvent as any).shiftKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); - } - KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); })} /> { if (shiftDown && enterKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); + this._props.finishEdit?.(); + return true; } - const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value); + const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); this._props.finishEdit?.(); return set; })} diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index e759030f5..715b23fb6 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -4,6 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { emptyFunction, returnFalse, returnNone, returnZero, setupMoveUpEvents } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; +import { RichTextField } from '../../../fields/RichTextField'; import { DocCast, NumCast, RTFCast, StrCast } from '../../../fields/Types'; import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -13,11 +14,9 @@ import { StyleProp } from '../StyleProvider'; import './ComparisonBox.scss'; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; -import { PinProps, PresBox } from './trails'; +import { KeyValueBox } from './KeyValueBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; -import { RichTextField } from '../../../fields/RichTextField'; -import { GPTCallType, gptAPICall } from '../../apis/gpt/GPT'; -import { DocData } from '../../../fields/DocSymbols'; +import { PinProps, PresBox } from './trails'; @observer export class ComparisonBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -173,10 +172,17 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent()
); }; + + /** + * Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case + * where if there are no Docs in the slots, but the main fieldKey contains text, then + * @param which + * @returns + */ const displayDoc = (which: string) => { const whichDoc = DocCast(this.dataDoc[which]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); - const subjectText = RTFCast(this.Document[this.fieldKey])?.Text; + const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim(); // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot // of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot const layoutTemplateString = !targetDoc @@ -188,15 +194,18 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() : undefined; // A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic. - // If the second slot doesn't have anything in it, but the fieldKey slot has text - // and the fieldKey + "_alternate" has a text that incldues "--TEXT--", then - // treat the fieldKey + "_altenrate" text as a GPT query parameterized by the fieldKey text - // Call GPT to fill in an "answer" value in the second slot. + // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string) + // and the fieldKey + "_alternate" has text, then treat the _alternate's text as a GPT query (indicated by (( && )) ) that is parameterized (optionally) + // by the field references in the text (eg., this.text_alternate is + // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))" + // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field + // A GPT call will put the "answer" in the second slot of the comparison (eg., text_2) if (which.endsWith('2') && !layoutTemplateString && !targetDoc) { - const queryText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text; - if (queryText?.includes('--TEXT--') && subjectText) { - this.Document[DocData][this.fieldKey + '_2'] = ''; - gptAPICall(queryText?.replace('--TEXT--', subjectText), GPTCallType.COMPLETION).then(value => (this.Document[DocData][this.fieldKey + '_2'] = value.trim())); + var queryText = RTFCast(this.Document[this.fieldKey + '_alternate']) + ?.Text.replace('(this)', subjectText) // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... + .trim(); + if (subjectText && queryText.match(/\(\(.*\)\)/)) { + KeyValueBox.SetField(this.Document, which, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt } } return targetDoc || layoutTemplateString ? ( diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index bbeacef88..9848f18e0 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1215,7 +1215,7 @@ export class DocumentView extends DocComponent() { if (layout_fieldKey && layout_fieldKey !== 'layout' && layout_fieldKey !== 'layout_icon') this.Document.deiconifyLayout = layout_fieldKey.replace('layout_', ''); } else { const deiconifyLayout = Cast(this.Document.deiconifyLayout, 'string', null); - this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished); + this.switchViews(deiconifyLayout ? true : false, deiconifyLayout, finalFinished, true); this.Document.deiconifyLayout = undefined; this._props.bringToFront?.(this.Document); } diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 8a49b4757..4ecaaa283 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -11,6 +11,7 @@ import { ViewBoxInterface } from '../DocComponent'; import { CollectionFreeFormDocumentView } from './CollectionFreeFormDocumentView'; import { DocumentView, OpenWhere } from './DocumentView'; import { PinProps } from './trails'; +import { computed } from 'mobx'; export interface FocusViewOptions { willPan?: boolean; // determines whether to pan to target document @@ -121,9 +122,12 @@ export class FieldView extends React.Component { public static LayoutString(fieldType: { name: string }, fieldStr: string) { return `<${fieldType.name} {...props} fieldKey={'${fieldStr}'}/>`; //e.g., "" } + @computed get fieldval() { + return this.props.Document[this.props.fieldKey]; + } render() { - const field = this.props.Document[this.props.fieldKey]; + const field = this.fieldval; // prettier-ignore if (field instanceof Doc) return

{field.title?.toString()}

; if (field === undefined) return

{''}

; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 89a5ac0b8..2257e6455 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -6,7 +6,7 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { DocCast } from '../../../fields/Types'; +import { DocCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; @@ -71,34 +71,51 @@ export class KeyValueBox extends ObservableReactComponent { } } }; - public static CompileKVPScript(value: string): KVPScript | undefined { - const eq = value.startsWith('='); - value = eq ? value.substring(1) : value; - const dubEq = value.startsWith(':=') ? 'computed' : value.startsWith('$=') ? 'script' : false; - value = dubEq ? value.substring(2) : value; - const options: ScriptOptions = { addReturn: true, typecheck: false, params: { this: Doc.name, self: Doc.name, documentView: 'any', _last_: 'any', _readOnly_: 'boolean' }, editable: true }; - if (dubEq) options.typecheck = false; - const script = CompileScript(value, { ...options, transformer: DocumentIconContainer.getTransformer() }); - return !script.compiled ? undefined : { script, type: dubEq, onDelegate: eq }; + /** + * this compiles a string as a script after parsing off initial characters that determine script parameters + * if the script starts with '=', then it will be stored on the delegate of the Doc, otherise on the data doc + * if the script then starts with a ':=', then it will be treated as ComputedField, + * '$=', then it will just be a Script + * @param value + * @returns + */ + public static CompileKVPScript(rawvalue: string): KVPScript | undefined { + const onDelegate = rawvalue.startsWith('='); + rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue; + const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false; + rawvalue = type ? rawvalue.substring(2) : rawvalue; + rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, "$1")'); + const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`'; + + var script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer()); + if (!script.compiled) { + script = ScriptField.CompileScript(value, {}, true, undefined, DocumentIconContainer.getTransformer()); + } + return !script.compiled ? undefined : { script, type, onDelegate }; } - public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean): boolean { + public static ApplyKVPScript(doc: Doc, key: string, kvpScript: KVPScript, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) { const { script, type, onDelegate } = kvpScript; //const target = onDelegate ? Doc.Layout(doc.layout) : Doc.GetProto(doc); // bcz: TODO need to be able to set fields on layout templates const target = forceOnDelegate || onDelegate || key.startsWith('_') ? doc : DocCast(doc.proto, doc); - let field: Field; - if (type === 'computed') { - field = new ComputedField(script); - } else if (type === 'script') { - field = new ScriptField(script); - } else { - const res = script.run({ this: Doc.Layout(doc), self: doc }, console.log); - if (!res.success) { - target[key] = script.originalScript; - return true; + let field: Field | undefined; + switch (type) { + case 'computed': field = new ComputedField(script); break; // prettier-ignore + case 'script': field = new ScriptField(script); break; // prettier-ignore + default: { + const _setCacheResult_ = (value: FieldResult) => { + field = value as Field; + setResult?.(value); + }; + const res = script.run({ this: Doc.Layout(doc), self: doc, _setCacheResult_ }, console.log); + if (!res.success) { + if (key) target[key] = script.originalScript; + return false; + } + field === undefined && (field = res.result); } - field = res.result; } + if (!key) return field; if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) { target[key] = field; return true; @@ -107,10 +124,10 @@ export class KeyValueBox extends ObservableReactComponent { } @undoBatch - public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean) { + public static SetField(doc: Doc, key: string, value: string, forceOnDelegate?: boolean, setResult?: (value: FieldResult) => void) { const script = this.CompileKVPScript(value); if (!script) return false; - return this.ApplyKVPScript(doc, key, script, forceOnDelegate); + return this.ApplyKVPScript(doc, key, script, forceOnDelegate, setResult); } onPointerDown = (e: React.PointerEvent): void => { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index f9e8ce4f3..d59489a78 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -125,7 +125,7 @@ export class KeyValuePair extends ObservableReactComponent { pinToPres: returnZero, }} GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)} - SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)} + SetValue={(value: string) => (KeyValueBox.SetField(this._props.doc, this._props.keyName, value) ? true : false)} /> diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 5c4d850ad..62cb460c2 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -137,7 +137,7 @@ export class DashFieldViewInternal extends ObservableReactComponent this._props.tbox._props.PanelWidth() - 20 : returnZero} selectedCell={() => [this._dashDoc!, 0]} fieldKey={this._fieldKey} diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index d5c91fc09..c798ae4b3 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,6 +1,6 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; import { NodeSelection, TextSelection } from 'prosemirror-state'; -import { Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, FieldResult, StrListCast } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; @@ -8,13 +8,14 @@ import { NumCast, StrCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; +import { CollectionViewType } from '../../../documents/DocumentTypes'; +import { CollectionView } from '../../collections/CollectionView'; +import { ContextMenu } from '../../ContextMenu'; +import { KeyValueBox } from '../KeyValueBox'; import { FormattedTextBox } from './FormattedTextBox'; import { wrappingInputRule } from './prosemirrorPatches'; import { RichTextMenu } from './RichTextMenu'; import { schema } from './schema_rts'; -import { CollectionView } from '../../collections/CollectionView'; -import { CollectionViewType } from '../../../documents/DocumentTypes'; -import { ContextMenu } from '../../ContextMenu'; export class RichTextRules { public Document: Doc; @@ -282,18 +283,28 @@ export class RichTextRules { : tr; }), + new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => { + var count = 0; // ignore first return value which will be the notation that chat is pending a result + KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { + count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string))); + count++; + }); + return null; + }), + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // [[ : ]] // [[:docTitle]] => hyperlink // [[fieldKey]] => show field - // [[fieldKey=value]] => show field and also set its value + // [[fieldKey{:,=:}=value]] => show field and also set its value // [[fieldKey:docTitle]] => show field of doc new InputRule( - new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-z,A-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), + new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)((=:|:)?=)([a-z,A-Z_@\?+\-*/\ 0-9\(\)]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), (state, match, start, end) => { const fieldKey = match[1]; - const docTitle = match[3]?.replace(':', ''); - const value = match[2]?.substring(1); + const assign = match[2] === '=' ? '' : match[2]; + const value = match[4]; + const docTitle = match[5]?.replace(':', ''); const linkToDoc = (target: Doc) => { const rstate = this.TextBox.EditorView?.state; const selection = rstate?.selection.$from.pos; @@ -325,13 +336,14 @@ export class RichTextRules { } return state.tr; } - if (value?.includes(',')) { + // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' ) + if (value?.includes(',') && !value.startsWith('((')) { const values = value.split(','); const strs = values.some(v => !v.match(/^[-]?[0-9.]$/)); this.Document[DocData][fieldKey] = strs ? new List(values) : new List(values.map(v => Number(v))); - } else if (value !== '' && value !== undefined) { - const num = value.match(/^[0-9.]$/); - this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + } else if (value) { + KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign ? undefined: + (gptval: FieldResult) => this.Document[DocData][fieldKey] = gptval as string ); // prettier-ignore } const target = getTitledDoc(docTitle); const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false }); diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 4706a97fa..c9115be90 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -1,4 +1,3 @@ -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'; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 921d7aa5d..30f5f716c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -34,14 +34,29 @@ import * as JSZip from 'jszip'; import { FieldViewProps } from '../client/views/nodes/FieldView'; export const LinkedTo = '-linkedTo'; export namespace Field { - export function toKeyValueString(doc: Doc, key: string): string { - const onDelegate = Object.keys(doc).includes(key.replace(/^_/, '')); + /** + * Converts a field to its equivalent input string in the key value box such that if the string + * is entered into a keyValueBox it will create an equivalent field (except if showComputedValue is set). + * @param doc doc containing key + * @param key field key to display + * @param showComputedValue whether copmuted function should display its value instead of its function + * @returns string representation of the field + */ + export function toKeyValueString(doc: Doc, key: string, showComputedValue?: boolean): string { + const onDelegate = !Doc.IsDataProto(doc) && Object.keys(doc).includes(key.replace(/^_/, '')); const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); return !Field.IsField(field) ? key.startsWith('_') ? '=' : '' - : (onDelegate ? '=' : '') + (field instanceof ComputedField ? `:=${field.script.originalScript}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field)); + : (onDelegate ? '=' : '') + + (field instanceof ComputedField && showComputedValue + ? field._lastComputedResult + : field instanceof ComputedField + ? `:=${field.script.originalScript}` + : field instanceof ScriptField + ? `$=${field.script.originalScript}` + : Field.toScriptString(field)); } export function toScriptString(field: Field) { switch (typeof field) { diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index c7fe72ca6..9021c8896 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -1,15 +1,17 @@ +import { action, makeObservable, observable } from 'mobx'; import { computedFn } from 'mobx-utils'; import { createSimpleSchema, custom, map, object, primitive, PropSchema, serializable, SKIP } from 'serializr'; import { DocServer } from '../client/DocServer'; -import { CompiledScript, CompileScript, ScriptOptions } from '../client/util/Scripting'; +import { CompiledScript, CompileScript, ScriptOptions, Transformer } from '../client/util/Scripting'; import { scriptingGlobal, ScriptingGlobals } from '../client/util/ScriptingGlobals'; import { autoObject, Deserializable } from '../client/util/SerializationHelper'; import { numberRange } from '../Utils'; -import { Doc, Field, Opt } from './Doc'; -import { Copy, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols'; +import { Doc, Field, FieldResult, Opt } from './Doc'; +import { Copy, FieldChanged, Id, ToJavascriptString, ToScriptString, ToString, ToValue } from './FieldSymbols'; import { List } from './List'; import { ObjectField } from './ObjectField'; import { Cast, StrCast } from './Types'; +import { GPTCallType, gptAPICall } from '../client/apis/gpt/GPT'; function optional(propSchema: PropSchema) { return custom( @@ -85,6 +87,13 @@ export class ScriptField extends ObjectField { readonly script: CompiledScript; @serializable(object(scriptSchema)) readonly setterscript: CompiledScript | undefined; + @serializable + @observable + _cachedResult: FieldResult = undefined; + setCacheResult = action((value: FieldResult) => { + this._cachedResult = value; + this[FieldChanged]?.(); + }); @serializable(autoObject()) captures?: List; @@ -122,21 +131,25 @@ export class ScriptField extends ObjectField { [ToString]() { return this.script.originalScript; } - public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { + public static CompileScript(script: string, params: object = {}, addReturn = false, capturedVariables?: { [name: string]: Doc | string | number | boolean }, transformer?: Transformer) { return CompileScript(script, { params: { this: Doc?.name || 'Doc', // this is the doc that executes the script self: Doc?.name || 'Doc', // self is the root doc of the doc that executes the script + documentView: 'any', _last_: 'any', // _last_ is the previous value of a computed field when it is being triggered to re-run. + _setCacheResult_: 'any', // set the cached value of the function _readOnly_: 'boolean', // _readOnly_ is set when a computed field is executed to indicate that it should not have mobx side-effects. used for checking the value of a set function (see FontIconBox) ...params, }, + transformer, typecheck: false, editable: true, addReturn: addReturn, capturedVariables, }); } + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }) { const compiled = ScriptField.CompileScript(script, params, true, capturedVariables); return compiled.compiled ? new ScriptField(compiled) : undefined; @@ -146,29 +159,62 @@ export class ScriptField extends ObjectField { const compiled = ScriptField.CompileScript(script, params, false, capturedVariables); return compiled.compiled ? new ScriptField(compiled) : undefined; } + public static CallGpt(queryText: string, setVal: (val: FieldResult) => void, target: Doc) { + if (typeof queryText === 'string' && setVal) { + while (queryText.match(/\(this\.[a-zA-Z_]*\)/)?.length) { + const fieldRef = queryText.split('(this.')[1].replace(/\).*/, ''); + queryText = queryText.replace(/\(this\.[a-zA-Z_]*\)/, Field.toString(target[fieldRef] as Field)); + } + setVal(`Chat Pending: ${queryText}`); + gptAPICall(queryText, GPTCallType.COMPLETION).then(result => { + if (queryText.includes('#')) { + const matches = result.match(/-?[0-9][0-9,]+[.]?[0-9]*/); + if (matches?.length) setVal(Number(matches[0].replace(/,/g, ''))); + } else setVal(result.trim()); + }); + } + } } @scriptingGlobal @Deserializable('computed', deserializeScript) export class ComputedField extends ScriptField { - _lastComputedResult: any; - //TODO maybe add an observable cache based on what is passed in for doc, considering there shouldn't really be that many possible values for doc - value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => (this._lastComputedResult = this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); - - [ToValue](doc: Doc) { - return ComputedField.toValue(doc, this); + static undefined = '__undefined'; + static useComputed = true; + static DisableComputedFields() { this.useComputed = false; } // prettier-ignore + static EnableComputedFields() { this.useComputed = true; } // prettier-ignore + static WithoutComputed(fn: () => T) { + this.DisableComputedFields(); + try { + return fn(); + } finally { + this.EnableComputedFields(); + } } - [Copy](): ObjectField { - return new ComputedField(this.script, this.setterscript, this.rawscript); + + constructor(script: CompiledScript | undefined, setterscript?: CompiledScript, rawscript?: string) { + super(script, setterscript, rawscript); + makeObservable(this); } + _lastComputedResult: FieldResult; + value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); + _valueOutsideReaction = (doc: Doc) => { + this._lastComputedResult = + this._cachedResult ?? (this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _setCacheResult_: this.setCacheResult, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); + return this._lastComputedResult; + }; + + [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this._valueOutsideReaction(doc) }; } // prettier-ignore + [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore + public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { const compiled = ScriptField.CompileScript(script, params, true, { value: '', ...capturedVariables }); const compiledsetter = setterscript ? ScriptField.CompileScript(setterscript, { ...params, value: 'any' }, false, capturedVariables) : undefined; const compiledsetscript = compiledsetter?.compiled ? compiledsetter : undefined; return compiled.compiled ? new ComputedField(compiled, compiledsetscript) : undefined; } + public static MakeInterpolatedNumber(fieldKey: string, interpolatorKey: string, doc: Doc, curTimecode: number, defaultVal: Opt) { if (!doc[`${fieldKey}_indexed`]) { const flist = new List(numberRange(curTimecode + 1).map(i => undefined) as any as number[]); @@ -206,33 +252,6 @@ export class ComputedField extends ScriptField { return (doc[`${fieldKey}`] = getField.compiled ? new ComputedField(getField, setField?.compiled ? setField : undefined) : undefined); } } -export namespace ComputedField { - let useComputed = true; - export function DisableComputedFields() { - useComputed = false; - } - - export function EnableComputedFields() { - useComputed = true; - } - - export const undefined = '__undefined'; - - export function WithoutComputed(fn: () => T) { - DisableComputedFields(); - try { - return fn(); - } finally { - EnableComputedFields(); - } - } - - export function toValue(doc: any, value: any) { - if (useComputed) { - return { value: value._valueOutsideReaction(doc) }; - } - } -} ScriptingGlobals.add( function setIndexVal(list: any[], index: number, value: any) { @@ -258,3 +277,6 @@ ScriptingGlobals.add( 'returns the value at a given index of a list', '(list: any[], index: number)' ); +ScriptingGlobals.add(function dashCallChat(setVal: (val: FieldResult) => void, target: Doc, queryText: string) { + ScriptField.CallGpt(queryText, setVal, target); +}, 'calls chat gpt for the query string and then calls setVal with the result'); diff --git a/src/server/public/assets/documentation.png b/src/server/public/assets/documentation.png new file mode 100644 index 000000000..95c76b198 Binary files /dev/null and b/src/server/public/assets/documentation.png differ -- cgit v1.2.3-70-g09d2 From 625c3a67fbcbfc93f1f1b35aed10b9fe7f33dfa9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 17 Mar 2024 13:50:45 -0400 Subject: fixed displaying of chat gpt query to use (( )) notation. allow '"'s in chat gpt calls by escaping with "`" intead. --- src/client/views/nodes/KeyValueBox.tsx | 6 +++--- src/fields/Doc.ts | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 2257e6455..2bcad806f 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -6,11 +6,11 @@ import { Doc, Field, FieldResult } from '../../../fields/Doc'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { DocCast, StrCast } from '../../../fields/Types'; +import { DocCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { Docs } from '../../documents/Documents'; import { SetupDrag } from '../../util/DragManager'; -import { CompileScript, CompiledScript, ScriptOptions } from '../../util/Scripting'; +import { CompiledScript } from '../../util/Scripting'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; @@ -84,7 +84,7 @@ export class KeyValueBox extends ObservableReactComponent { rawvalue = onDelegate ? rawvalue.substring(1) : rawvalue; const type: 'computed' | 'script' | false = rawvalue.startsWith(':=') ? 'computed' : rawvalue.startsWith('$=') ? 'script' : false; rawvalue = type ? rawvalue.substring(2) : rawvalue; - rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, "$1")'); + rawvalue = rawvalue.replace(/.*\(\((.*)\)\)/, 'dashCallChat(_setCacheResult_, this, `$1`)'); const value = ["'", '"', '`'].includes(rawvalue.length ? rawvalue[0] : '') || !isNaN(rawvalue as any) ? rawvalue : '`' + rawvalue + '`'; var script = ScriptField.CompileScript(rawvalue, {}, true, undefined, DocumentIconContainer.getTransformer()); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 30f5f716c..daae32e9f 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -53,7 +53,7 @@ export namespace Field { (field instanceof ComputedField && showComputedValue ? field._lastComputedResult : field instanceof ComputedField - ? `:=${field.script.originalScript}` + ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}` : field instanceof ScriptField ? `$=${field.script.originalScript}` : Field.toScriptString(field)); -- cgit v1.2.3-70-g09d2 From fa0f4d5b4ce9ad3a891b719ca099f4bbc12f1096 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 18 Mar 2024 10:28:43 -0400 Subject: comments --- src/client/views/DocComponent.tsx | 61 ++++++++++++++++++++++++++++++++------- 1 file changed, 50 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index a25a8f77a..18e7ab12a 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -75,15 +75,25 @@ export function DocComponent

() { makeObservable(this); } - //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then + /** + * This is the document being rendered. In the case of a compound template, it + * may not be the actual document rendered and it also may not be the 'real' root document. + * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,) + */ get Document() { return this._props.Document; } - // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info + + /** + * This is the document being rendered. It may be a template so it may or may no inherit from the data doc. + */ @computed get layoutDoc() { return this._props.LayoutTemplateString ? this.Document : Doc.Layout(this.Document, this._props.LayoutTemplate?.()); } - // This is the data part of a document -- ie, the data that is constant across all views of the document + + /** + * This is the unique data repository for a dcoument that stores the intrinsic document data + */ @computed get dataDoc() { return this.Document[DocData]; } @@ -109,19 +119,32 @@ export function ViewBoxBaseComponent

() { get DocumentView() { return this._props.DocumentView; } - //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then + + /** + * This is the document being rendered. In the case of a compound template, it + * may not be the actual document rendered and it also may not be the 'real' root document. + * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,) + */ get Document() { return this._props.Document; } - // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info + /** + * This is the document being rendered. It may be a template so it may or may no inherit from the data doc. + */ @computed get layoutDoc() { return Doc.Layout(this.Document); } - // This is the data part of a document -- ie, the data that is constant across all views of the document + + /** + * This is the unique data repository for a dcoument that stores the intrinsic document data + */ @computed get dataDoc() { return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData]; } - // key where data is stored + + /** + * this is the field key where the primary rendering data is stored for the layout doc (e.g., it's often the 'data' field for a collection, or the 'text' field for rich text) + */ get fieldKey() { return this._props.fieldKey; } @@ -154,23 +177,39 @@ export function ViewBoxAnnotatableComponent

() { get DocumentView() { return this._props.DocumentView; } - //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then + + /** + * This is the document being rendered. In the case of a compound template, it + * may not be the actual document rendered and it also may not be the 'real' root document. + * Rather, it specifies the shared properties of all layouts of the document (eg, x,y,) + */ @computed get Document() { return this._props.Document; } - // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info + /** + * This is the document being rendered. It may be a template so it may or may no inherit from the data doc. + */ @computed get layoutDoc() { return Doc.Layout(this.Document); } - // This is the data part of a document -- ie, the data that is constant across all views of the document + + /** + * This is the unique data repository for a dcoument that stores the intrinsic document data + */ @computed get dataDoc() { return this.Document.isTemplateForField || this.Document.isTemplateDoc ? this._props.TemplateDataDocument ?? this.Document[DocData] : this.Document[DocData]; } - // key where data is stored + /** + * this is the field key where the primary rendering data is stored for the layout doc (e.g., it's often the 'data' field for a collection, or the 'text' field for rich text) + */ @computed get fieldKey() { return this._props.fieldKey; } + + /** + * this is field key where the list of annotations is stored + */ @computed public get annotationKey() { return this.fieldKey + (this._annotationKeySuffix() ? '_' + this._annotationKeySuffix() : ''); } -- cgit v1.2.3-70-g09d2 From 38be41b6ae73e0b295245d96636db74cb6848d6c Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 18 Mar 2024 10:29:17 -0400 Subject: changed pdf paste ref title --- src/client/views/MarqueeAnnotator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index 3c35b441b..bd6be2519 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -93,7 +93,7 @@ export class MarqueeAnnotator extends ObservableReactComponent' + this.props.Document.title, }); let minX = Number.MAX_VALUE; let maxX = -Number.MAX_VALUE; -- cgit v1.2.3-70-g09d2 From e576f88501da55a1e1abbcbc3260733da94bff8a Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 18 Mar 2024 10:29:47 -0400 Subject: link to pdf's data doc on paste --- src/client/views/pdf/PDFViewer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0d4cfda88..a582f8004 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -5,7 +5,7 @@ import 'pdfjs-dist/web/pdf_viewer.css'; import * as PDFJSViewer from 'pdfjs-dist/web/pdf_viewer.mjs'; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; -import { Height } from '../../../fields/DocSymbols'; +import { DocData, Height } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; @@ -135,7 +135,7 @@ export class PDFViewer extends ObservableReactComponent { const anchor = this._getAnchor(undefined, false); if (anchor) { anchor.textCopied = true; - e.clipboardData.setData('dash/pdfAnchor', anchor[Id]); + e.clipboardData.setData('dash/pdfAnchor', anchor[DocData][Id]); } e.preventDefault(); } -- cgit v1.2.3-70-g09d2 From 7d31c773ab85697b8a5eeecc752b567c8235efec Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 18 Mar 2024 10:30:23 -0400 Subject: cleanup --- src/client/views/nodes/DocumentContentsView.tsx | 2 -- 1 file changed, 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 07e179246..5a326ecb0 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -135,8 +135,6 @@ export class DocumentContentsView extends ObservableReactComponent Date: Tue, 19 Mar 2024 17:08:46 -0400 Subject: cleaned up user templates to not get changed on reload. made setting a template add it to the template tools list and as a tools button. fixed linking to parts of a template. fixed disappearing templates caused by stacking view set a field with an empty key. updated field assignment syntax in trees, dash field views, and key value box to all use :,:=,=,=:= syntax. added text elide button. added @(title) syntax for hyperlinking. made using a template both inherit from the template to get default values and use the template to render. fixed submenu placement of context menu. updated RTF markdown doc. --- src/client/util/CurrentUserUtils.ts | 19 +++-- src/client/util/DropConverter.ts | 45 +++++----- src/client/util/LinkManager.ts | 13 +-- src/client/util/RTFMarkup.tsx | 30 +++---- src/client/views/ContextMenuItem.tsx | 2 +- src/client/views/TemplateMenu.scss | 1 + src/client/views/TemplateMenu.tsx | 7 +- .../collections/CollectionMasonryViewFieldRow.tsx | 10 +-- .../CollectionStackingViewFieldColumn.tsx | 6 +- src/client/views/collections/TreeView.tsx | 14 +++- src/client/views/global/globalScripts.ts | 33 +++----- src/client/views/nodes/ComparisonBox.tsx | 71 +++++++++------- src/client/views/nodes/DocumentView.tsx | 32 ++++++- src/client/views/nodes/FieldView.tsx | 1 + src/client/views/nodes/FontIconBox/FontIconBox.tsx | 22 ++--- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/client/views/nodes/KeyValuePair.tsx | 2 +- .../views/nodes/formattedText/DashFieldView.tsx | 57 +++++++++---- .../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++---- .../views/nodes/formattedText/RichTextMenu.tsx | 13 +++ .../views/nodes/formattedText/RichTextRules.ts | 97 ++++++++++++---------- src/client/views/nodes/formattedText/marks_rts.ts | 5 +- src/client/views/nodes/formattedText/nodes_rts.ts | 2 + src/fields/Doc.ts | 42 +++++----- src/fields/util.ts | 4 + 25 files changed, 328 insertions(+), 236 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 84a33500d..bbee18707 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -73,7 +73,7 @@ export class CurrentUserUtils { }; const reqdScripts = { dropConverter : "convertToButtons(dragData)" }; const reqdFuncs = { /* hidden: "IsNoviceMode()" */ }; - return DocUtils.AssignScripts(DocUtils.AssignOpts(userDocTemplates, reqdOpts, userTemplates) ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs); + return DocUtils.AssignScripts(userDocTemplates ?? Docs.Create.MasonryDocument(userTemplates, reqdOpts), reqdScripts, reqdFuncs); } /// Initializes templates for editing click funcs of a document @@ -133,11 +133,19 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true }; return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts)); } + static setupUserTemplates(doc: Doc, field="template_user") { + const tempUsers = DocCast(doc[field]); + const reqdUserList = DocListCast(tempUsers?.data); + + const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true }; + return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts)); + } /// Initializes collection of templates for notes and click functions static setupDocTemplates(doc: Doc, field="myTemplates") { const templates = [ CurrentUserUtils.setupNoteTemplates(doc), + CurrentUserUtils.setupUserTemplates(doc), CurrentUserUtils.setupClickEditorTemplates(doc) ]; CurrentUserUtils.setupChildClickEditors(doc) @@ -375,9 +383,9 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a flashcard", title: "Flashcard", icon: "id-card", dragFactory: doc.emptyFlashcard as Doc, clickFactory: DocCast(doc.emptyFlashcard)}, { toolTip: "Tap or drag to create an equation", title: "Math", icon: "calculator", dragFactory: doc.emptyEquation as Doc, clickFactory: DocCast(doc.emptyEquation)}, { toolTip: "Tap or drag to create a mermaid node", title: "Mermaids", icon: "rocket", dragFactory: doc.emptyMermaids as Doc, clickFactory: DocCast(doc.emptyMermaids)}, - { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)}, + { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)}, { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}}, - { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, + { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, @@ -385,10 +393,10 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, - { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, + { toolTip: "Tap or drag to create a button", title: "Button", icon: "circle", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a data viz node", title: "DataViz", icon: "chart-bar", dragFactory: doc.emptyDataViz as Doc, clickFactory: DocCast(doc.emptyDataViz)}, - { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "person-chalkboard", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, + { toolTip: "Tap or drag to create a bullet slide", title: "PPT Slide", icon: "person-chalkboard", dragFactory: doc.emptySlide as Doc, clickFactory: DocCast(doc.emptySlide), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a view slide", title: "View Slide", icon: "address-card", dragFactory: doc.emptyViewSlide as Doc,clickFactory: DocCast(doc.emptyViewSlide),openFactoryLocation: OpenWhere.overlay,funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a data note", title: "DataNote", icon: "window-maximize",dragFactory: doc.emptyHeader as Doc,clickFactory: DocCast(doc.emptyHeader), openFactoryAsDelegate: true, funcs: { hidden: "IsNoviceMode()"} }, { toolTip: "Toggle a Calculator REPL", title: "replviewer", icon: "calculator", clickFactory: '' as any, openFactoryLocation: OpenWhere.overlay}, // hack: clickFactory is not a Doc but will get interpreted as a custom UI by the openDoc() onClick script @@ -738,6 +746,7 @@ pie title Minerals in my tap water { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center",ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'} }, ]}, + { title: "Elide", toolTip: "Elide selection", btnType: ButtonType.ToggleButton, icon: "eye", toolType:"elide", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}}, { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", ignoreClick: true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}}, { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(this.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 3df3e36c6..ed5749d06 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -28,6 +28,7 @@ export function MakeTemplate(doc: Doc) { } /** + * * Recursively converts 'doc' into a template that can be used to render other documents. * * For recurive Docs in the template, their target fieldKey is defined by their title, @@ -75,32 +76,36 @@ export function convertDropDataToButtons(data: DragManager.DocumentDragData) { remProps.map(prop => (dbox[prop] = undefined)); } } else if (!doc.onDragStart && !doc.isButtonBar) { - const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; - if (layoutDoc.type !== DocumentType.FONTICON) { - !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); - } - layoutDoc.isTemplateDoc = true; - dbox = Docs.Create.FontIconDocument({ - _nativeWidth: 100, - _nativeHeight: 100, - _width: 100, - _height: 100, - backgroundColor: StrCast(doc.backgroundColor), - title: StrCast(layoutDoc.title), - btnType: ButtonType.ClickButton, - icon: 'bolt', - isSystem: false, - }); - dbox.title = ComputedField.MakeFunction('this.dragFactory.title'); - dbox.dragFactory = layoutDoc; - dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; - dbox.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory)'); + dbox = makeUserTemplateButton(doc); } else if (doc.isButtonBar) { dbox.ignoreClick = true; } data.droppedDocuments[i] = dbox; }); } +export function makeUserTemplateButton(doc: Doc) { + const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc; + if (layoutDoc.type !== DocumentType.FONTICON) { + !layoutDoc.isTemplateDoc && makeTemplate(layoutDoc); + } + layoutDoc.isTemplateDoc = true; + const dbox = Docs.Create.FontIconDocument({ + _nativeWidth: 100, + _nativeHeight: 100, + _width: 100, + _height: 100, + backgroundColor: StrCast(doc.backgroundColor), + title: StrCast(layoutDoc.title), + btnType: ButtonType.ClickButton, + icon: 'bolt', + isSystem: false, + }); + dbox.title = ComputedField.MakeFunction('this.dragFactory.title'); + dbox.dragFactory = layoutDoc; + dbox.dropPropertiesToRemove = doc.dropPropertiesToRemove instanceof ObjectField ? ObjectField.MakeCopy(doc.dropPropertiesToRemove) : undefined; + dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)'); + return dbox; +} ScriptingGlobals.add( function convertToButtons(dragData: any) { convertDropDataToButtons(dragData as DragManager.DocumentDragData); diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 0c8d18a7a..cf16c4d6d 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -58,8 +58,8 @@ export class LinkManager { link && action(lAnchProtoProtos => { Doc.AddDocToList(Doc.UserDoc(), 'links', link); - lAnchs[0] && lAnchs[0][DocData][DirectLinks].add(link); - lAnchs[1] && lAnchs[1][DocData][DirectLinks].add(link); + lAnchs[0]?.[DocData][DirectLinks].add(link); + lAnchs[1]?.[DocData][DirectLinks].add(link); }) ) ) @@ -170,10 +170,11 @@ export class LinkManager { console.log('WAITING FOR DOC/PROTO IN LINKMANAGER'); return []; } - const dirLinks = Doc.GetProto(anchor)[DirectLinks]; - const annos = DocListCast(anchor[Doc.LayoutFieldKey(anchor) + '_annotations']); - if (!annos) debugger; - return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks).slice()); + + const dirLinks = Array.from(anchor[DocData][DirectLinks]).filter(l => Doc.GetProto(anchor) === anchor[DocData] || ['1', '2'].includes(LinkManager.anchorIndex(l, anchor) as any)); + const anchorRoot = DocCast(anchor.rootDocument, anchor); // template Doc fields store annotations on the topmost root of a template (not on themselves since the template layout items are only for layout) + const annos = DocListCast(anchorRoot[Doc.LayoutFieldKey(anchor) + '_annotations']); + return annos.reduce((list, anno) => [...list, ...LinkManager.Instance.relatedLinker(anno)], Array.from(dirLinks)); }, true); // returns map of group type to anchor's links in that group type diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index f96d8a5df..315daad42 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -3,18 +3,12 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { MainViewModal } from '../views/MainViewModal'; import { SettingsManager } from './SettingsManager'; -import { Doc } from '../../fields/Doc'; -import { StrCast } from '../../fields/Types'; @observer export class RTFMarkup extends React.Component<{}> { static Instance: RTFMarkup; @observable private isOpen = false; // whether the SharingManager modal is open or not - // private get linkVisible() { - // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false; - // } - @action public open = () => (this.isOpen = true); @@ -39,6 +33,10 @@ export class RTFMarkup extends React.Component<{}> { {`wiki:phrase`} {` display wikipedia page for entered text (terminate with carriage return)`}

+

+ {`(( any text ))`} + {` submit text to Chat GPT to have results appended afterward`} +

{`#tag `} {` add hashtag metadata to document. e.g, #idea`} @@ -47,10 +45,6 @@ export class RTFMarkup extends React.Component<{}> { {`#, ## ... ###### `} {` set heading style based on number of '#'s between 1 and 6`}

-

- {`#tag `} - {` add hashtag metadata to document. e.g, #idea`} -

{`>> `} {` add a sidebar text document inline`} @@ -61,7 +55,7 @@ export class RTFMarkup extends React.Component<{}> {

{`cmd-f `} - {` collapse to an inline footnote)`} + {` collapse to an inline footnote`}

{`cmd-e `} @@ -116,20 +110,20 @@ export class RTFMarkup extends React.Component<{}> { {` start a block of text that begins with a hanging indent`}

- {`[:doctitle]] `} + {`@(doctitle) `} {` hyperlink to document specified by it’s title`}

- {`[[fieldname]] `} - {` display value of fieldname`} + {`[@(doctitle.)fieldname] `} + {` display value of fieldname of text document (unless (doctitle.) is used to indicate another document by it's title)`}

- {`[[fieldname=value]] `} - {` assign value to fieldname of document and display it`} + {`[@fieldname:value] `} + {` assign value to fieldname to data document and display it (if '=' is used instead of ':' the value is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the response will replace the value)`}

- {`[[fieldname:doctitle]] `} - {` show value of fieldname from doc specified by it’s title`} + {`[@fieldname:=expression] `} + {` assign a computed expression to fieldname to data document and display it (if '=:=' is used instead of ':=' the expression is set on the layout Doc. if value is wrapped in (()) then it will be sent to ChatGPT and the prompt/response will replace the value)`}

); diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index b2076e1a5..d15ab749c 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -113,7 +113,7 @@ export class ContextMenuItem extends ObservableReactComponent 0 ? '90%' : '20%', + marginLeft: window.innerWidth - this._overPosX - 50 > 0 ? '90%' : '20%', marginTop, background: SettingsManager.userBackgroundColor, }}> diff --git a/src/client/views/TemplateMenu.scss b/src/client/views/TemplateMenu.scss index 4d0f1bf00..36a9ce6d0 100644 --- a/src/client/views/TemplateMenu.scss +++ b/src/client/views/TemplateMenu.scss @@ -39,6 +39,7 @@ display: inline-block; height: 100%; width: 100%; + max-height: 250px; .templateToggle, .chromeToggle { diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index eed197b0b..e7269df98 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -1,7 +1,8 @@ -import { action, computed, observable, ObservableSet, runInAction } from 'mobx'; +import { computed, observable, ObservableSet, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, DocCast, StrCast } from '../../fields/Types'; import { TraceMobx } from '../../fields/util'; @@ -9,12 +10,10 @@ import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, retu import { Docs, DocUtils } from '../documents/Documents'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; import { Transform } from '../util/Transform'; -import { undoBatch } from '../util/UndoManager'; import { CollectionTreeView } from './collections/CollectionTreeView'; import { DocumentView, returnEmptyDocViewList } from './nodes/DocumentView'; import { DefaultStyleProvider } from './StyleProvider'; import './TemplateMenu.scss'; -import { DocData } from '../../fields/DocSymbols'; @observer class TemplateToggle extends React.Component<{ template: string; checked: boolean; toggle: (event: React.ChangeEvent, template: string) => void }> { @@ -99,7 +98,7 @@ export class TemplateMenu extends React.Component { .filter(key => !noteTypes.some(nt => nt.title === key)) .forEach(template => templateMenu.push( this.toggleLayout(e, template)} />)); return ( -
    +
      {Doc.noviceMode ? null : } {templateMenu} Doc.SetInPlace(d, key, castedValue, !onLayoutDoc)); + key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key))); } return true; } @@ -128,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent Doc.SetInPlace(d, key, castedValue, true)); + key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); this._heading = castedValue.toString(); return true; } @@ -155,10 +154,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent { this._createEmbeddingSelected = false; const key = this._props.pivotField; - this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); + key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); if (this._props.parent.colHeaderData && this._props.headingObject) { const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject); this._props.parent.colHeaderData.splice(index, 1); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 6a3cb759e..641e01b81 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -112,7 +112,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { return false; } - this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue)); + this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue)); if (this._props.headingObject) { this._props.headingObject.setHeading(castedValue.toString()); this._heading = this._props.headingObject.heading; @@ -137,7 +137,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< if (!value && !forceEmptyNote) return false; const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true }); - newDoc[key] = this.getValue(this._props.heading); + key && (newDoc[key] = this.getValue(this._props.heading)); const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0); const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; newDoc.heading = heading; @@ -148,7 +148,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< @action deleteColumn = () => { - this._props.docList.forEach(d => (d[this._props.pivotField] = undefined)); + this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined)); if (this._props.colHeaderData && this._props.headingObject) { const index = this._props.colHeaderData.indexOf(this._props.headingObject); this._props.colHeaderData.splice(index, 1); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 08c00d696..1b4349f44 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -574,11 +574,21 @@ export class TreeView extends ObservableReactComponent {
      value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)} + SetValue={input => { + const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/); + if (match) { + const key = match[1]; + const assign = match[2]; + const val = match[3]; + KeyValueBox.SetField(doc, key, assign + val, false); + return true; + } + return false; + }} />
      ); diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 0579b07c7..3fdc9a488 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -1,29 +1,26 @@ import { Colors } from 'browndash-components'; import { action, runInAction } from 'mobx'; +import { aggregateBounds } from '../../../Utils'; import { Doc, Opt } from '../../../fields/Doc'; import { InkTool } from '../../../fields/InkField'; import { BoolCast, Cast, NumCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; -import { aggregateBounds } from '../../../Utils'; import { DocumentType } from '../../documents/DocumentTypes'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; -import { undoable, UndoManager } from '../../util/UndoManager'; -import { CollectionFreeFormView } from '../collections/collectionFreeForm'; +import { UndoManager, undoable } from '../../util/UndoManager'; import { GestureOverlay } from '../GestureOverlay'; import { ActiveFillColor, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth, SetActiveIsInkMask } from '../InkingStroke'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm'; // import { InkTranscription } from '../InkTranscription'; +import { DocData } from '../../../fields/DocSymbols'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; 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'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { PrefetchProxy } from '../../../fields/Proxy'; -import { MakeTemplate } from '../../util/DropConverter'; +import { WebBox } from '../nodes/WebBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; @@ -76,19 +73,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b // toggle: Set overlay status of selected document ScriptingGlobals.add(function setDefaultTemplate(checkResult?: boolean) { - if (checkResult) { - return Doc.UserDoc().defaultTextLayout; - } - const view = SelectionManager.Views.length === 1 && SelectionManager.Views[0].ComponentView instanceof FormattedTextBox ? SelectionManager.Views[0] : undefined; - - if (view) { - const tempDoc = view.Document; - if (!view.layoutDoc.isTemplateDoc) { - MakeTemplate(tempDoc); - } - Doc.UserDoc().defaultTextLayout = new PrefetchProxy(tempDoc); - tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_notes, Doc, null), 'data', tempDoc); - } else Doc.UserDoc().defaultTextLayout = undefined; + return DocumentView.setDefaultTemplate(checkResult); }); // toggle: Set overlay status of selected document ScriptingGlobals.add(function setHeaderColor(color?: string, checkResult?: boolean) { @@ -197,7 +182,7 @@ ScriptingGlobals.add(function setFontAttr(attr: 'font' | 'fontColor' | 'highligh map.get(attr)?.setDoc?.(); }); -type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; +type attrname = 'noAutoLink' | 'dictation' | 'bold' | 'italics' | 'elide' | 'underline' | 'left' | 'center' | 'right' | 'vcent' | 'bullet' | 'decimal'; type attrfuncs = [attrname, { checkResult: () => boolean; toggle: () => any }]; ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: boolean) { @@ -221,6 +206,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?: const attrs:attrfuncs[] = [ ['dictation', { checkResult: () => textView?._recordingDictation ? true:false, toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }], + ['elide', { checkResult: () => false, + toggle: () => editorView ? RichTextMenu.Instance?.elideSelection(): 0}], ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false), toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}], ['bold', { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false), diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 715b23fb6..62f630c6c 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -159,6 +159,37 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() moveDoc2 = (doc: Doc | Doc[], targetCol: Doc | undefined, addDoc: any) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc: Doc) => res && this.moveDoc(doc, addDoc, this.fieldKey + '_2'), true); remDoc1 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_1'), true); remDoc2 = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((res, doc) => res && this.remDoc(doc, this.fieldKey + '_2'), true); + + /** + * Tests for whether a comparison box slot (ie, before or after) has renderable text content + * @param whichSlot field key for start or end slot + * @returns a JSX layout string if a text field is found, othwerise undefined + */ + testForTextFields = (whichSlot: string) => { + const slotHasText = Doc.Get(this.dataDoc, whichSlot, true) instanceof RichTextField || typeof Doc.Get(this.dataDoc, whichSlot, true) === 'string'; + const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim(); + const altText = RTFCast(this.Document[this.fieldKey + '_alternate'])?.Text.trim(); + const layoutTemplateString = + slotHasText ? FormattedTextBox.LayoutString(whichSlot): + whichSlot.endsWith('1') ? (subjectText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey) : undefined) : + altText !== undefined ? FormattedTextBox.LayoutString(this.fieldKey + '_alternate'): undefined; // prettier-ignore + + // A bit hacky to try out the concept of using GPT to fill in flashcards + // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string) + // and the fieldKey + "_alternate" has text that includes a GPT query (indicated by (( && )) ) that is parameterized (optionally) by the fieldKey text (this) or other metadata (this.). + // eg., this.text_alternate is + // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))" + // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field + // The GPT call will put the "answer" in the second slot of the comparison (eg., text_2) + if (whichSlot.endsWith('2') && !layoutTemplateString?.includes(whichSlot)) { + var queryText = altText.replace('(this)', subjectText); // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... + if (queryText && queryText.match(/\(\(.*\)\)/)) { + KeyValueBox.SetField(this.Document, whichSlot, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt + } + } + return layoutTemplateString; + }; + _closeRef = React.createRef(); render() { const clearButton = (which: string) => { @@ -176,48 +207,24 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() /** * Display the Docs in the before/after fields of the comparison. This also supports a GPT flash card use case * where if there are no Docs in the slots, but the main fieldKey contains text, then - * @param which + * @param whichSlot * @returns */ - const displayDoc = (which: string) => { - const whichDoc = DocCast(this.dataDoc[which]); + const displayDoc = (whichSlot: string) => { + const whichDoc = DocCast(this.dataDoc[whichSlot]); const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc); - const subjectText = RTFCast(this.Document[this.fieldKey])?.Text.trim(); - // if there is no Doc in the first comparison slot, but the comparison box's fieldKey slot has a RichTextField, then render a text box to show the contents of the document's field key slot - // of if there is no Doc in the second comparison slot, but the second slot has a RichTextField, then render a text box to show the contents of the document's field key slot - const layoutTemplateString = !targetDoc - ? which.endsWith('1') && subjectText !== undefined - ? FormattedTextBox.LayoutString(this.fieldKey) - : which.endsWith('2') && (this.Document[which] instanceof RichTextField || typeof this.Document[which] === 'string') - ? FormattedTextBox.LayoutString(which) - : undefined - : undefined; - - // A bit hacky to try out the concept of using GPT to fill in flashcards -- this whole process should probably be packaged into a script to be more generic. - // If the second slot doesn't have anything in it, but the fieldKey slot has text (e.g., this.text is a string) - // and the fieldKey + "_alternate" has text, then treat the _alternate's text as a GPT query (indicated by (( && )) ) that is parameterized (optionally) - // by the field references in the text (eg., this.text_alternate is - // "((Provide a one sentence definition for (this) that doesn't use any word in (this.excludeWords) ))" - // where (this) is replaced by the text in the fieldKey slot abd this.excludeWords is repalced by the conetnts of the excludeWords field - // A GPT call will put the "answer" in the second slot of the comparison (eg., text_2) - if (which.endsWith('2') && !layoutTemplateString && !targetDoc) { - var queryText = RTFCast(this.Document[this.fieldKey + '_alternate']) - ?.Text.replace('(this)', subjectText) // TODO: this should be done in KeyValueBox.setField but it doesn't know about the fieldKey ... - .trim(); - if (subjectText && queryText.match(/\(\(.*\)\)/)) { - KeyValueBox.SetField(this.Document, which, ':=' + queryText, false); // make the second slot be a computed field on the data doc that calls ChatGpt - } - } + const layoutTemplateString = targetDoc ? '' : this.testForTextFields(whichSlot); return targetDoc || layoutTemplateString ? ( <> () hideLinkButton={true} pointerEvents={this._isAnyChildContentActive ? undefined : returnNone} /> - {layoutTemplateString ? null : clearButton(which)} + {layoutTemplateString ? null : clearButton(whichSlot)} // placeholder image if doc is missing ) : (
      diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9848f18e0..e9ce98583 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -10,6 +10,7 @@ import { AclPrivate, Animation, AudioPlay, DocData, DocViews } from '../../../fi import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; +import { PrefetchProxy } from '../../../fields/Proxy'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; @@ -19,10 +20,11 @@ import { DocServer } from '../../DocServer'; import { Networking } from '../../Network'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; -import { DocOptions, DocUtils, Docs } from '../../documents/Documents'; +import { DocUtils, Docs } from '../../documents/Documents'; import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; +import { MakeTemplate, makeUserTemplateButton } from '../../util/DropConverter'; import { FollowLinkScript } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; @@ -36,6 +38,7 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent, ViewBoxInterface } from '../DocComponent'; import { EditableView } from '../EditableView'; +import { FieldsDropdown } from '../FieldsDropdown'; import { GestureOverlay } from '../GestureOverlay'; import { LightboxView } from '../LightboxView'; import { AudioAnnoState, StyleProp } from '../StyleProvider'; @@ -47,7 +50,6 @@ 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; } @@ -1271,6 +1273,32 @@ export class DocumentView extends DocComponent() { custom && DocUtils.makeCustomViewClicked(this.Document, Docs.Create.StackingDocument, layout, undefined); }, 'set custom view'); + public static setDefaultTemplate(checkResult?: boolean) { + if (checkResult) { + return Doc.UserDoc().defaultTextLayout; + } + const view = SelectionManager.Views[0]?._props.renderDepth > 0 ? SelectionManager.Views[0] : undefined; + undoable(() => { + var tempDoc: Opt; + if (view) { + if (!view.layoutDoc.isTemplateDoc) { + tempDoc = view.Document; + MakeTemplate(tempDoc); + Doc.AddDocToList(Doc.UserDoc(), 'template_user', tempDoc); + Doc.AddDocToList(DocListCast(Doc.MyTools.data)[1], 'data', makeUserTemplateButton(tempDoc)); + tempDoc && Doc.AddDocToList(Cast(Doc.UserDoc().template_user, Doc, null), 'data', tempDoc); + } else { + tempDoc = DocCast(view.Document[StrCast(view.Document.layout_fieldKey)]); + if (!tempDoc) { + tempDoc = view.Document; + while (tempDoc && !Doc.isTemplateDoc(tempDoc)) tempDoc = DocCast(tempDoc.proto); + } + } + } + Doc.UserDoc().defaultTextLayout = tempDoc ? new PrefetchProxy(tempDoc) : undefined; + }, 'set default template')(); + } + /** * This switches between the current view of a Doc and a specified alternate layout view. * The current view of the Doc is stored in the layout_default field so that it can be restored. diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 4ecaaa283..5b47dd91d 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -55,6 +55,7 @@ export interface FieldViewSharedProps { ignoreAutoHeight?: boolean; disableBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. hideClickBehaviors?: boolean; // whether to suppress menu item options for changing click behaviors + ignoreUsePath?: boolean; // ignore the usePath field for selecting the fieldKey (eg., on text docs) CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView; containerViewPath?: () => DocumentView[]; fitContentsToBox?: () => boolean; // used by freeformview to fit its contents to its panel. corresponds to _freeform_fitContentsToBox property on a Document diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx index f02ad7300..57ae92359 100644 --- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx @@ -5,8 +5,7 @@ import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc'; -import { ScriptField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnTrue, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { SelectionManager } from '../../../util/SelectionManager'; @@ -61,23 +60,12 @@ export class FontIconBox extends ViewBoxBaseComponent() { } @observable noTooltip = false; - showTemplate = (): void => { - const dragFactory = Cast(this.layoutDoc.dragFactory, Doc, null); - dragFactory && this._props.addDocTab(dragFactory, OpenWhere.addRight); - }; - dragAsTemplate = (): void => { - this.layoutDoc.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); - }; - useAsPrototype = (): void => { - this.layoutDoc.onDragStart = ScriptField.MakeFunction('makeDelegate(this.dragFactory, true)'); - }; + showTemplate = (dragFactory: Doc) => this._props.addDocTab(dragFactory, OpenWhere.addRight); specificContextMenu = (): void => { - 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' }); - cm.addItem({ description: 'Use as Prototype', event: this.useAsPrototype, icon: 'tag' }); + const dragFactory = DocCast(this.layoutDoc.dragFactory); + if (!Doc.noviceMode && dragFactory) { + ContextMenu.Instance.addItem({ description: 'Show Template', event: () => this.showTemplate(dragFactory), icon: 'tag' }); } }; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 2bcad806f..d85432631 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -115,7 +115,7 @@ export class KeyValueBox extends ObservableReactComponent { field === undefined && (field = res.result); } } - if (!key) return field; + if (!key) return false; if (Field.IsField(field, true) && (key !== 'proto' || field !== target)) { target[key] = field; return true; diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index d59489a78..f9e8ce4f3 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -125,7 +125,7 @@ export class KeyValuePair extends ObservableReactComponent { pinToPres: returnZero, }} GetValue={() => Field.toKeyValueString(this._props.doc, this._props.keyName)} - SetValue={(value: string) => (KeyValueBox.SetField(this._props.doc, this._props.keyName, value) ? true : false)} + SetValue={(value: string) => KeyValueBox.SetField(this._props.doc, this._props.keyName, value)} />
      diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 62cb460c2..6b66d829c 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; @@ -8,12 +8,12 @@ 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, DocCast } 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 { undoBatch } from '../../../util/UndoManager'; +import { undoable, undoBatch } from '../../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell'; import { FilterPanel } from '../../FilterPanel'; @@ -21,6 +21,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; +import { DocData } from '../../../../fields/DocSymbols'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -62,6 +63,8 @@ export class DashFieldView { height={node.attrs.height} hideKey={node.attrs.hideKey} editable={node.attrs.editable} + expanded={node.attrs.expanded} + dataDoc={node.attrs.dataDoc} tbox={tbox} /> ); @@ -89,6 +92,8 @@ interface IDashFieldViewInternal { width: number; height: number; editable: boolean; + expanded: boolean; + dataDoc: boolean; node: any; getPos: any; unclickable: () => boolean; @@ -101,18 +106,19 @@ export class DashFieldViewInternal extends ObservableReactComponent(); @observable _dashDoc: Doc | undefined = undefined; - @observable _expanded = false; + @observable _expanded = this._props.expanded; constructor(props: IDashFieldViewInternal) { super(props); makeObservable(this); this._fieldKey = this._props.fieldKey; - this._textBoxDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc; + this._textBoxDoc = this._props.tbox.Document; + const setDoc = (doc: Doc) => (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc); if (this._props.docId) { - DocServer.GetRefField(this._props.docId).then(action(dashDoc => dashDoc instanceof Doc && (this._dashDoc = dashDoc))); + DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc)); } else { - this._dashDoc = this._fieldKey.startsWith('_') ? this._props.tbox.Document : this._props.tbox.dataDoc; + setDoc(this._props.tbox.Document); } } @@ -126,7 +132,9 @@ export class DashFieldViewInternal extends ObservableReactComponent 100; + isRowActive = () => this._expanded && this._props.editable; + finishEdit = action(() => (this._expanded = false)); + selectedCell = (): [Doc, number] => [this._dashDoc!, 0]; // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { @@ -137,18 +145,18 @@ export class DashFieldViewInternal extends ObservableReactComponent this._props.tbox._props.PanelWidth() - 20 : returnZero} - selectedCell={() => [this._dashDoc!, 0]} + maxWidth={this._props.hideKey || this._hideKey ? undefined : this._props.tbox._props.PanelWidth} + columnWidth={returnZero} + selectedCell={this.selectedCell} fieldKey={this._fieldKey} rowHeight={returnZero} - isRowActive={() => this._expanded && this._props.editable} + isRowActive={this.isRowActive} padding={0} getFinfo={emptyFunction} setColumnValues={returnFalse} allowCRs={true} oneLine={!this._expanded} - finishEdit={action(() => (this._expanded = false))} + finishEdit={this.finishEdit} transform={Transform.Identity} menuTarget={null} /> @@ -173,11 +181,21 @@ export class DashFieldViewInternal extends ObservableReactComponent this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])), + 'hideKey' + ); + + @computed get _hideKey() { + return this._dashDoc && this._dashDoc[this._fieldKey + '_hideKey']; + } + // clicking on the label creates a pivot view collection of all documents // in the same collection. The pivot field is the fieldKey of this label onPointerDownLabelSpan = (e: any) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { DashFieldViewMenu.createFieldView = this.createPivotForField; + DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide; DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey); }); }; @@ -188,6 +206,7 @@ export class DashFieldViewInternal extends ObservableReactComponent ({ value: facet, label: facet })); @@ -203,9 +222,9 @@ export class DashFieldViewInternal extends ObservableReactComponent - {this._props.hideKey ? null : ( + {this._props.hideKey || this._hideKey ? null : ( - {(this._textBoxDoc === this._dashDoc ? '' : this._dashDoc?.title + ':') + this._fieldKey} + {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey} )} {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} @@ -224,6 +243,7 @@ export class DashFieldViewInternal extends ObservableReactComponent { static Instance: DashFieldViewMenu; static createFieldView: (e: React.MouseEvent) => void = emptyFunction; + static toggleFieldHide: () => void = emptyFunction; constructor(props: any) { super(props); DashFieldViewMenu.Instance = this; @@ -233,6 +253,10 @@ export class DashFieldViewMenu extends AntimodeMenu { DashFieldViewMenu.createFieldView(e); DashFieldViewMenu.Instance.fadeOut(true); }; + toggleFieldHide = (e: React.MouseEvent) => { + DashFieldViewMenu.toggleFieldHide(); + DashFieldViewMenu.Instance.fadeOut(true); + }; @observable _fieldKey = ''; @@ -252,6 +276,9 @@ export class DashFieldViewMenu extends AntimodeMenu { + ); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index fb709818c..2b48494f2 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -451,7 +451,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); + Doc.MyPublishedDocs.filter(term => term.title).forEach(term => (tr = this.hyperlinkTerm(tr, term, newAutoLinks))); tr = tr.setSelection(isNodeSel && false ? new NodeSelection(tr.doc.resolve(f)) : new TextSelection(tr.doc.resolve(f), tr.doc.resolve(t))); this._editorView?.dispatch(tr); } @@ -958,8 +958,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.layoutDoc._layout_autoHeight = !this.layoutDoc._layout_autoHeight), icon: this.Document._layout_autoHeight ? 'lock' : 'unlock', }); - optionItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: }); !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'eye' }); + const help = cm.findByDescription('Help...'); + const helpItems = help && 'subitems' in help ? help.subitems : []; + helpItems.push({ description: `show markdown options`, event: RTFMarkup.Instance.open, icon: }); + !help && cm.addItem({ description: 'Help...', subitems: helpItems, icon: 'eye' }); this._downX = this._downY = Number.NaN; }; @@ -1239,8 +1242,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.dispatch(tx.insertText(incomingValue.str))); + } else { + selectAll(this._editorView.state, tx => this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); } } } @@ -1339,15 +1342,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { if (pdfAnchor instanceof Doc) { const dashField = view.state.schema.nodes.paragraph.create({}, [ - view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false }, undefined, [ + view.state.schema.nodes.dashField.create({ fieldKey: 'text', docId: pdfAnchor[Id], hideKey: true, editable: false, expanded: true }, undefined, [ view.state.schema.marks.linkAnchor.create({ allAnchors: [{ href: `/doc/${this.Document[Id]}`, title: this.Document.title, anchorId: `${this.Document[Id]}` }], - title: `from: ${DocCast(pdfAnchor.embedContainer).title}`, + title: StrCast(pdfAnchor.title), noPreview: true, - docref: false, + docref: true, + fontSize: '8px', }), - view.state.schema.marks.pFontSize.create({ fontSize: '8px' }), - view.state.schema.marks.em.create({}), ]), ]); @@ -1926,11 +1928,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent ); } - cycleAlternateText = () => { - if (this.layoutDoc._layout_enableAltContentUI) { - const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; - this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' ? 'alternate:hover' : undefined; - } + cycleAlternateText = (skipHover?: boolean) => { + this.layoutDoc._layout_enableAltContentUI = true; + const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; + this.layoutDoc[`_${this._props.fieldKey}_usePath`] = usePath === undefined ? 'alternate' : usePath === 'alternate' && !skipHover ? 'alternate:hover' : undefined; }; @computed get overlayAlternateIcon() { const usePath = this.layoutDoc[`_${this._props.fieldKey}_usePath`]; @@ -1965,7 +1966,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { }); } + elideSelection = () => { + const state = this.view?.state; + if (!state) return; + if (state.selection.empty) return false; + const mark = state.schema.marks.summarize.create(); + const tr = state.tr; + tr.addMark(state.selection.from, state.selection.to, mark); + const content = tr.selection.content(); + const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); + this.view?.dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + return true; + }; + toggleNoAutoLinkAnchor = () => { if (this.view) { const mark = this.view.state.schema.mark(this.view.state.schema.marks.noAutoLinkAnchor); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index c798ae4b3..b97141e92 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -267,7 +267,7 @@ export class RichTextRules { // toggle alternate text UI %/ new InputRule(new RegExp(/%\//), (state, match, start, end) => { - setTimeout(this.TextBox.cycleAlternateText); + setTimeout(() => this.TextBox.cycleAlternateText(true)); return state.tr.deleteRange(start, end); }), @@ -283,40 +283,26 @@ export class RichTextRules { : tr; }), - new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => { - var count = 0; // ignore first return value which will be the notation that chat is pending a result - KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { - count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string))); - count++; - }); - return null; - }), - - // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document - // [[ : ]] - // [[:docTitle]] => hyperlink - // [[fieldKey]] => show field - // [[fieldKey{:,=:}=value]] => show field and also set its value - // [[fieldKey:docTitle]] => show field of doc - new InputRule( - new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)((=:|:)?=)([a-z,A-Z_@\?+\-*/\ 0-9\(\)]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), - (state, match, start, end) => { - const fieldKey = match[1]; - const assign = match[2] === '=' ? '' : match[2]; - const value = match[4]; - const docTitle = match[5]?.replace(':', ''); + // create a hyperlink to a titled document + // @() + new InputRule(new RegExp(/(^|\s)@\(([a-zA-Z_@:\.\? \-0-9]+)\)/), (state, match, start, end) => { + const docTitle = match[2]; + const prefixLength = '@('.length; + if (docTitle) { const linkToDoc = (target: Doc) => { - const rstate = this.TextBox.EditorView?.state; - const selection = rstate?.selection.$from.pos; - if (rstate) { - this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); + const editor = this.TextBox.EditorView; + const selection = editor?.state?.selection.$from.pos; + if (editor) { + const estate = editor.state; + editor.dispatch(estate.tr.setSelection(new TextSelection(estate.doc.resolve(start), estate.doc.resolve(end - prefixLength)))); } DocUtils.MakeLink(this.TextBox.getAnchor(true), target, { link_relationship: 'portal to:portal from' }); - const fstate = this.TextBox.EditorView?.state; - if (fstate && selection) { - this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); + const teditor = this.TextBox.EditorView; + if (teditor && selection) { + const tstate = teditor.state; + teditor.dispatch(tstate.tr.setSelection(new TextSelection(tstate.doc.resolve(selection)))); } }; const getTitledDoc = (docTitle: string) => { @@ -326,32 +312,57 @@ export class RichTextRules { const titledDoc = DocServer.FindDocByTitle(docTitle); return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; }; - if (!fieldKey) { - if (docTitle) { - const target = getTitledDoc(docTitle); - if (target) { - setTimeout(() => linkToDoc(target)); - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 3); - } - } - return state.tr; + const target = getTitledDoc(docTitle); + if (target) { + setTimeout(() => linkToDoc(target)); + return state.tr.insertText(' ').deleteRange(start, start + prefixLength); } + } + return state.tr; + }), + + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document + // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value] + // [@{this,doctitle,}.fieldKey] + new InputRule( + new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]*))?\]/), + (state, match, start, end) => { + const docTitle = match[1].substring(1).replace(/\.$/, ''); + const fieldKey = match[2]; + const assign = match[4] === ':' ? (match[4] = '') : match[4]; + const value = match[5]; + const dataDoc = value === undefined ? !fieldKey.startsWith('_') : !assign?.startsWith('='); + const getTitledDoc = (docTitle: string) => { + if (!DocServer.FindDocByTitle(docTitle)) { + Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true })); + } + return DocServer.FindDocByTitle(docTitle); + }; // if the value has commas assume its an array (unless it's part of a chat gpt call indicated by '((' ) if (value?.includes(',') && !value.startsWith('((')) { const values = value.split(','); const strs = values.some(v => !v.match(/^[-]?[0-9.]$/)); this.Document[DocData][fieldKey] = strs ? new List(values) : new List(values.map(v => Number(v))); } else if (value) { - KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign ? undefined: - (gptval: FieldResult) => this.Document[DocData][fieldKey] = gptval as string ); // prettier-ignore + KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined: + (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore } - const target = getTitledDoc(docTitle); - const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false }); + const target = docTitle ? getTitledDoc(docTitle) : undefined; + const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, dataDoc }); return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); }, { inCode: true } ), + new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => { + var count = 0; // ignore first return value which will be the notation that chat is pending a result + KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { + count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string))); + count++; + }); + return null; + }), + // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document // wiki:title new InputRule(new RegExp(/wiki:([a-zA-Z_@:\.\?\-0-9]+ )$/), (state, match, start, end) => { diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index a141ef041..b68acc8f8 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -74,6 +74,7 @@ export const marks: { [index: string]: MarkSpec } = { allAnchors: { default: [] as { href: string; title: string; anchorId: string }[] }, title: { default: null }, noPreview: { default: false }, + fontSize: { default: null }, docref: { default: false }, // flags whether the linked text comes from a document within Dash. If so, an attribution label is appended after the text }, inclusive: false, @@ -93,14 +94,16 @@ export const marks: { [index: string]: MarkSpec } = { const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); return node.attrs.docref && node.attrs.title ? [ - 'div', + 'a', ['span', 0], [ 'span', { ...node.attrs, class: 'prosemirror-attribution', + 'data-targethrefs': targethrefs, href: node.attrs.allAnchors[0].href, + style: `font-size: ${node.attrs.fontSize}`, }, node.attrs.title, ], diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index c9115be90..905146ee2 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -265,6 +265,8 @@ export const nodes: { [index: string]: NodeSpec } = { docId: { default: '' }, hideKey: { default: false }, editable: { default: true }, + expanded: { default: null }, + dataDoc: { default: false }, }, leafText: node => Field.toString((DocServer.GetCachedRefField(node.attrs.docId as string) as Doc)?.[node.attrs.fieldKey as string] as Field), group: 'inline', diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index daae32e9f..4b40d11b9 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -445,6 +445,12 @@ export namespace Doc { export function GetT(doc: Doc, key: string, ctor: ToConstructor, ignoreProto: boolean = false): FieldResult { return Cast(Get(doc, key, ignoreProto), ctor) as FieldResult; } + export function isTemplateDoc(doc: Doc) { + return GetT(doc, 'isTemplateDoc', 'boolean', true); + } + export function isTemplateForField(doc: Doc) { + return GetT(doc, 'isTemplateForField', 'string', true); + } export function IsDataProto(doc: Doc) { return GetT(doc, 'isDataDoc', 'boolean', true); } @@ -642,7 +648,7 @@ export namespace Doc { cloneLinks: boolean, cloneTemplates: boolean ): Promise { - if (Doc.IsBaseProto(doc) || ((Doc.Get(doc, 'isTemplateDoc', true) || Doc.Get(doc, 'isTemplateForField', true)) && !cloneTemplates)) { + if (Doc.IsBaseProto(doc) || ((Doc.isTemplateDoc(doc) || Doc.isTemplateForField(doc)) && !cloneTemplates)) { return doc; } if (cloneMap.get(doc[Id])) return cloneMap.get(doc[Id])!; @@ -735,7 +741,7 @@ export namespace Doc { const docAtKey = DocCast(clone[key]); if (docAtKey && !Doc.IsSystem(docAtKey)) { if (!Array.from(cloneMap.values()).includes(docAtKey)) { - clone[key] = !cloneTemplates && (Doc.Get(docAtKey, 'isTemplateDoc', true) || Doc.Get(docAtKey, 'isTemplateForField', true)) ? docAtKey : cloneMap.get(docAtKey[Id]); + clone[key] = !cloneTemplates && (Doc.isTemplateDoc(docAtKey) || Doc.isTemplateForField(docAtKey)) ? docAtKey : cloneMap.get(docAtKey[Id]); } else { repairClone(docAtKey, cloneMap, cloneTemplates, visited); } @@ -857,7 +863,7 @@ export namespace Doc { // of the original layout while allowing for individual layout properties to be overridden in the expanded layout. export function expandTemplateLayout(templateLayoutDoc: Doc, targetDoc?: Doc) { // nothing to do if the layout isn't a template or we don't have a target that's different than the template - if (!targetDoc || templateLayoutDoc === targetDoc || (!templateLayoutDoc.isTemplateForField && !templateLayoutDoc.isTemplateDoc)) { + if (!targetDoc || templateLayoutDoc === targetDoc || (!Doc.isTemplateForField(templateLayoutDoc) && !Doc.isTemplateDoc(templateLayoutDoc))) { return templateLayoutDoc; } @@ -874,7 +880,7 @@ export namespace Doc { expandedTemplateLayout = undefined; _pendingMap.add(targetDoc[Id] + expandedLayoutFieldKey); } else if (expandedTemplateLayout === undefined && !_pendingMap.has(targetDoc[Id] + expandedLayoutFieldKey)) { - if (templateLayoutDoc.resolvedDataDoc === (targetDoc.rootDocument ?? Doc.GetProto(targetDoc))) { + if (templateLayoutDoc.resolvedDataDoc === targetDoc[DocData]) { expandedTemplateLayout = templateLayoutDoc; // reuse an existing template layout if its for the same document with the same params } else { templateLayoutDoc.resolvedDataDoc && (templateLayoutDoc = DocCast(templateLayoutDoc.proto, templateLayoutDoc)); // if the template has already been applied (ie, a nested template), then use the template's prototype @@ -910,8 +916,9 @@ export namespace Doc { console.log('Warning: GetLayoutDataDocPair childDoc not defined'); return { layout: childDoc, data: childDoc }; } - const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!childDoc.isTemplateDoc && !childDoc.isTemplateForField) ? undefined : containerDataDoc; - return { layout: Doc.expandTemplateLayout(childDoc, resolvedDataDoc), data: resolvedDataDoc }; + const resolvedDataDoc = Doc.AreProtosEqual(containerDataDoc, containerDoc) || (!Doc.isTemplateDoc(childDoc) && !Doc.isTemplateForField(childDoc)) ? undefined : containerDataDoc; + const templateRoot = DocCast(containerDoc?.rootDocument); + return { layout: Doc.expandTemplateLayout(childDoc, templateRoot), data: resolvedDataDoc }; } export function FindReferences(infield: Doc | List, references: Set, system: boolean | undefined) { @@ -1035,20 +1042,13 @@ export namespace Doc { // (ie, the 'data' doc), and then creates another delegate of that (ie, the 'layout' doc). // This is appropriate if you're trying to create a document that behaves like all // regularly created documents (e.g, text docs, pdfs, etc which all have data/layout docs) - export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string): Doc { - const delegateProto = new Doc(); - delegateProto[Initializing] = true; - delegateProto.proto = doc; - delegateProto.author = Doc.CurrentUserEmail; - delegateProto.isDataDoc = true; - title && (delegateProto.title = title); - const delegate = new Doc(id, true); - delegate[Initializing] = true; - delegate.proto = delegateProto; - delegate.author = Doc.CurrentUserEmail; - delegate[Initializing] = false; - delegateProto[Initializing] = false; - return delegate; + export function MakeDelegateWithProto(doc: Doc, id?: string, title?: string) { + const ndoc = Doc.ApplyTemplate(doc); + if (ndoc) { + Doc.GetProto(ndoc).isDataDoc = true; + ndoc && (Doc.GetProto(ndoc).proto = doc); + } + return ndoc; } let _applyCount: number = 0; @@ -1671,7 +1671,7 @@ ScriptingGlobals.add(function getEmbedding(doc: any) { return Doc.MakeEmbedding(doc); }); ScriptingGlobals.add(function getCopy(doc: any, copyProto: any) { - return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); + return doc.isTemplateDoc ? Doc.MakeDelegateWithProto(doc) : Doc.MakeCopy(doc, copyProto); }); ScriptingGlobals.add(function copyField(field: any) { return Field.Copy(field); diff --git a/src/fields/util.ts b/src/fields/util.ts index b73520999..c2ec3f13a 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -286,6 +286,10 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc // target should be either a Doc or ListImpl. receiver should be a Proxy Or List. // export function setter(target: any, in_prop: string | symbol | number, value: any, receiver: any): boolean { + if (!in_prop) { + console.log('WARNING: trying to set an empty property. This should be fixed. '); + return false; + } let prop = in_prop; const effectiveAcl = in_prop === 'constructor' || typeof in_prop === 'symbol' ? AclAdmin : GetPropAcl(target, prop); if (effectiveAcl !== AclEdit && effectiveAcl !== AclAugment && effectiveAcl !== AclAdmin) return true; -- cgit v1.2.3-70-g09d2 From 2b9c89e54c19a53f0e1978850e8d05ac31186e21 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 21 Mar 2024 17:34:23 -0400 Subject: changed gpt calls from text box to package result into an elision node. --- .../views/nodes/formattedText/RichTextMenu.tsx | 22 +++++++++++++--------- .../views/nodes/formattedText/RichTextRules.ts | 9 +++++++-- 2 files changed, 20 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/RichTextMenu.tsx b/src/client/views/nodes/formattedText/RichTextMenu.tsx index bee0d72e3..b5d0f28d8 100644 --- a/src/client/views/nodes/formattedText/RichTextMenu.tsx +++ b/src/client/views/nodes/formattedText/RichTextMenu.tsx @@ -318,16 +318,20 @@ export class RichTextMenu extends AntimodeMenu { }); } - elideSelection = () => { - const state = this.view?.state; - if (!state) return; - if (state.selection.empty) return false; + elideSelection = (txstate: EditorState | undefined = undefined, visibility = false) => { + const state = txstate ?? this.view?.state; + if (!state || state.selection.empty) return false; const mark = state.schema.marks.summarize.create(); - const tr = state.tr; - tr.addMark(state.selection.from, state.selection.to, mark); - const content = tr.selection.content(); - const newNode = state.schema.nodes.summary.create({ visibility: false, text: content, textslice: content.toJSON() }); - this.view?.dispatch?.(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); + const tr = state.tr.addMark(state.tr.selection.from, state.selection.to, mark); + const text = tr.selection.content(); + const elideNode = state.schema.nodes.summary.create({ visibility, text, textslice: text.toJSON() }); + const summary = tr.replaceSelectionWith(elideNode).removeMark(tr.selection.from - 1, tr.selection.from, mark); + const expanded = () => { + const endOfElidableText = summary.selection.to + text.content.size; + const res = summary.insert(summary.selection.to, text.content).insert(endOfElidableText, state.schema.nodes.paragraph.create({})); + return res.setSelection(new TextSelection(res.doc.resolve(endOfElidableText + 1))); + }; + this.view?.dispatch?.(visibility ? expanded() : summary); return true; }; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index b97141e92..adc031636 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -354,10 +354,15 @@ export class RichTextRules { { inCode: true } ), - new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))/), (state, match, start, end) => { + // pass the contents between '((' and '))' to chatGPT and append the result + new InputRule(new RegExp(/(^|[^=])(\(\(.*\)\))$/), (state, match, start, end) => { var count = 0; // ignore first return value which will be the notation that chat is pending a result KeyValueBox.SetField(this.Document, '', match[2], false, (gptval: FieldResult) => { - count && this.TextBox.EditorView?.dispatch(this.TextBox.EditorView!.state.tr.insertText(' ' + (gptval as string))); + if (count) { + const tr = this.TextBox.EditorView?.state.tr.insertText(' ' + (gptval as string)); + tr && this.TextBox.EditorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(end + 2), tr.doc.resolve(end + 2 + (gptval as string).length)))); + RichTextMenu.Instance.elideSelection(this.TextBox.EditorView?.state, true); + } count++; }); return null; -- cgit v1.2.3-70-g09d2 From 462580bf70ff563dbfe33a84b8cd015e637cdf7e Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 22 Mar 2024 11:41:22 -0400 Subject: make ink stroke labels visible always for closed gesture strokes. --- src/client/documents/Documents.ts | 1 + src/client/views/InkingStroke.tsx | 2 +- .../views/collections/collectionFreeForm/CollectionFreeFormView.tsx | 3 ++- 3 files changed, 4 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1d7c73306..37db23bdc 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -317,6 +317,7 @@ export class DocumentOptions { _label_minFontSize?: NUMt = new NumInfo('minimum font size for labelBoxes', false); _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false); stroke_width?: NUMt = new NumInfo('width of an ink stroke', false); + stroke_showLabel?: BOOLt = new BoolInfo('show label inside of stroke'); mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false); recording?: BOOLt = new BoolInfo('whether WebCam is recording or not'); slides?: DOCt = new DocInfo('presentation slide associated with video recording (bcz: should be renamed!!)'); diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 122e5c4c3..ce70b5052 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -446,7 +446,7 @@ export class InkingStroke extends ViewBoxBaseComponent() impleme {clickableLine(this.onPointerDown, isInkMask)} {isInkMask ? null : inkLine} - {!closed || (!RTFCast(this.dataDoc.text)?.Text && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : ( + {!closed || (!RTFCast(this.dataDoc.text)?.Text && !this.dataDoc[this.fieldKey + '_showLabel'] && (!this._props.isSelected() || Doc.UserDoc().activeInkHideTextLabels)) ? null : (
      Date: Sat, 23 Mar 2024 14:55:20 -0400 Subject: added empty image tool and fixed createCustomView api --- src/client/documents/Documents.ts | 6 ++---- src/client/util/CurrentUserUtils.ts | 2 ++ 2 files changed, 4 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 37db23bdc..d68021062 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1780,10 +1780,7 @@ export namespace DocUtils { } // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { const batch = UndoManager.StartBatch('makeCustomViewClicked'); - runInAction(() => { - doc.layout_fieldKey = 'layout_' + templateSignature; - createCustomView(doc, creator, templateSignature, docLayoutTemplate); - }); + createCustomView(doc, creator, templateSignature, docLayoutTemplate); batch.end(); return doc; } @@ -1807,6 +1804,7 @@ export namespace DocUtils { } export function createCustomView(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { const templateName = templateSignature.replace(/\(.*\)/, ''); + doc.layout_fieldKey = 'layout_' + (templateSignature || docLayoutTemplate?.title); docLayoutTemplate = docLayoutTemplate || findTemplate(templateName, StrCast(doc.isGroup && doc.transcription ? 'transcription' : doc.type), templateSignature); const customName = 'layout_' + templateSignature; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index bbee18707..11f6c82ec 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -349,6 +349,7 @@ pie title Minerals in my tap water creator:(opts:DocumentOptions)=> any // how to create the empty thing if it doesn't exist }[] = [ {key: "Note", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true }}, + {key: "Image", creator: opts => Docs.Create.ImageDocument("", opts), opts: { _width: 400, _height:400 }}, {key: "Flashcard", creator: opts => Docs.Create.TextDocument("", opts), opts: { _width: 200, _layout_autoHeight: true, _layout_enableAltContentUI: true}}, {key: "Equation", creator: opts => Docs.Create.EquationDocument("",opts), opts: { _width: 300, _height: 35, }}, {key: "Noteboard", creator: opts => Docs.Create.NoteTakingDocument([], opts), opts: { _width: 250, _height: 200, _layout_fitWidth: true}}, @@ -386,6 +387,7 @@ pie title Minerals in my tap water { toolTip: "Tap or drag to create a plotly node", title: "Plotly", icon: "rocket", dragFactory: doc.emptyPlotly as Doc, clickFactory: DocCast(doc.emptyMermaids)}, { toolTip: "Tap or drag to create a physics simulation",title: "Simulation", icon: "rocket",dragFactory: doc.emptySimulation as Doc, clickFactory: DocCast(doc.emptySimulation), funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a note board", title: "Notes", icon: "book", dragFactory: doc.emptyNoteboard as Doc, clickFactory: DocCast(doc.emptyNoteboard)}, + { toolTip: "Tap or drag to create an iamge", title: "Image", icon: "image", dragFactory: doc.emptyImage as Doc, clickFactory: DocCast(doc.emptyImage)}, { toolTip: "Tap or drag to create a collection", title: "Col", icon: "folder", dragFactory: doc.emptyCollection as Doc, clickFactory: DocCast(doc.emptyTab)}, { toolTip: "Tap or drag to create a webpage", title: "Web", icon: "globe-asia", dragFactory: doc.emptyWebpage as Doc, clickFactory: DocCast(doc.emptyWebpage)}, { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, -- cgit v1.2.3-70-g09d2 From 1606b9cfd1bf48890d2cc0b39595982cafeb990e Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 23 Mar 2024 15:03:29 -0400 Subject: from last --- src/client/documents/Documents.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d68021062..17cb6fef8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1777,10 +1777,23 @@ export namespace DocUtils { subitems: userDocList, icon: 'file', }); - } // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) - export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', docLayoutTemplate?: Doc) { + } + + // applies a custom template to a document. the template is identified by it's short name (e.g, slideView not layout_slideView) + + /** + * Applies a template to a Doc and logs the action with the UndoManager + * If the template already exists and has been registered, it can be specified by it's signature name (e.g., 'icon' not 'layout_icon'). + * Alternatively, the signature can be omitted and the template can be provided. + * @param doc the Doc to apply the template to. + * @param creator a function that will create the template if it doesn't exist + * @param templateSignature the signature name for a template that has already been created and registered on the userDoc. (can be "" if template is provide) + * @param template the template to use (optional if templateSignature is provided) + * @returns doc + */ + export function makeCustomViewClicked(doc: Doc, creator: Opt<(documents: Array, options: DocumentOptions, id?: string) => Doc>, templateSignature: string = 'custom', template?: Doc) { const batch = UndoManager.StartBatch('makeCustomViewClicked'); - createCustomView(doc, creator, templateSignature, docLayoutTemplate); + createCustomView(doc, creator, templateSignature || StrCast(template?.title), template); batch.end(); return doc; } -- cgit v1.2.3-70-g09d2 From 4ea0ea97359cf2f3e97367dafbfaf9157ecc987b Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 23 Mar 2024 15:30:49 -0400 Subject: made backspace deleting a schema row undoable. fixed calling chat on string fields --- src/client/views/collections/collectionSchema/CollectionSchemaView.tsx | 2 +- src/client/views/nodes/KeyValueBox.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 4a0ca8fe5..5f4948808 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -238,7 +238,7 @@ export class CollectionSchemaView extends CollectionSubView() { } break; case 'Backspace': { - this.removeDocument(this._selectedDocs); + undoable(() => this.removeDocument(this._selectedDocs), 'delete schema row'); break; } case 'Escape': { diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index d85432631..5394c7dbb 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -105,7 +105,8 @@ export class KeyValueBox extends ObservableReactComponent { default: { const _setCacheResult_ = (value: FieldResult) => { field = value as Field; - setResult?.(value); + if (setResult) setResult?.(value); + else target[key] = field; }; const res = script.run({ this: Doc.Layout(doc), self: doc, _setCacheResult_ }, console.log); if (!res.success) { -- cgit v1.2.3-70-g09d2 From b949608ff69fb66c30bbed439b1c37f8fffd2333 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 23 Mar 2024 21:13:38 -0400 Subject: fixed linking to parts of a template. fixed highlting part of a template when following a link to it. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 12 +++++++++--- src/fields/Doc.ts | 3 ++- 2 files changed, 11 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 01d379b96..1fd453e96 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -314,7 +314,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout).includes(anchor)) return; const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); @@ -335,6 +335,7 @@ export class CollectionFreeFormView extends CollectionSubView> => new Promise>(res => { if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false); + if (doc === this.Document) return res(this.DocumentView?.()); const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -1262,7 +1263,7 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout)?.includes(doc)) { if (doc.hidden) doc.hidden = false; return true; } @@ -1405,7 +1406,12 @@ export class CollectionFreeFormView extends CollectionSubView { // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) - const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document }); + const anchor = Docs.Create.ConfigDocument({ + title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), + layout_unrendered: true, + presentation_transition: 500, + annotationOn: this.Document, + }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document); if (addAsAnnotation) { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 4b40d11b9..246828709 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1279,7 +1279,8 @@ export namespace Doc { highlightedDocs.add(doc); doc[Highlight] = true; doc[Animation] = presentation_effect; - if (dataAndDisplayDocs) { + if (dataAndDisplayDocs && !doc.resolvedDataDoc) { + // if doc is a layout template then we don't want to highlight the proto since that will be the entire template, not just the specific layout field highlightedDocs.add(doc[DocData]); doc[DocData][Highlight] = true; } -- cgit v1.2.3-70-g09d2 From 2e0cb3e0a470994eecbb7f6b2ec87296baf517b9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 24 Mar 2024 19:04:42 -0400 Subject: fixed linkdocpreviews to sequence through multiple links. fixed text boxes to update text when dashfieldView text changes (but the fieldview doesn't), fixed dashFieldViews to be editable cleanly, and to allow sub-dashFieldViews to be editbale. allowed toggle on/off of dashFieldView fieldKey. got rid of sidebars in scemaCells. fied editing dashFieldViews in captions and as childrend of dashFieldViews by passing rootSelected --- .../collectionSchema/SchemaTableCell.tsx | 9 ++++- src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/LinkDocPreview.tsx | 1 + .../views/nodes/formattedText/DashFieldView.scss | 32 ++++++++++----- .../views/nodes/formattedText/DashFieldView.tsx | 45 ++++++++++++++-------- .../views/nodes/formattedText/FormattedTextBox.tsx | 26 +++++++++---- 6 files changed, 80 insertions(+), 34 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 711ef507c..ce73ff8a4 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -51,6 +51,8 @@ export interface SchemaTableCellProps { options?: string[]; menuTarget: HTMLDivElement | null; transform: () => Transform; + autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click + rootSelected?: () => boolean; } @observer @@ -89,6 +91,7 @@ export class SchemaTableCell extends ObservableReactComponent this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} contents={undefined} @@ -314,13 +319,15 @@ export class SchemaRTFCell extends ObservableReactComponent this.selected; render() { const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); fieldProps.isContentActive = this.selectedFunc; return (
      - {this.selected ? : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + {this.selected ? this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
      ); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e9ce98583..1044d6609 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -892,6 +892,7 @@ export class DocumentViewInternal extends DocComponent this._expanded && this._props.editable; - finishEdit = action(() => (this._expanded = false)); + + finishEdit = action(() => { + if (this._expanded) { + this._expanded = false; + // if the edit finishes, then we want to lose focus on the textBox unless something else in the textBox got focus + // the timeout allows switching focus from one dashFieldView to another in the same text box + setTimeout(() => !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.()); + } + }); selectedCell = (): [Doc, number] => [this._dashDoc!, 0]; + columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { return !this._dashDoc ? null : ( -
      (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: this._props.hideKey ? this._props.tbox._props.PanelWidth() - 20 : undefined }}> +
      (this._expanded = !this._props.editable ? !this._expanded : true))} style={{ fontSize: 'smaller', width: !this._hideKey && this._expanded ? this.columnWidth() : undefined }}>
      ); @@ -187,12 +198,12 @@ export class DashFieldViewInternal extends ObservableReactComponent { + onPointerDownLabelSpan = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, e => { DashFieldViewMenu.createFieldView = this.createPivotForField; DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide; @@ -215,7 +226,7 @@ export class DashFieldViewInternal extends ObservableReactComponent + { + this._editingTitle = false; + !hideTitle && this.titleBlur(); + })} + onChange={action(e => !hideTitle && (this._accumulatedTitle = e.target.value))} + onKeyDown={hideTitle ? emptyFunction : this.titleEntered} + onPointerDown={e => e.stopPropagation()} + /> + ) : (
      {hideTitle ? null : ( diff --git a/src/client/views/FieldsDropdown.tsx b/src/client/views/FieldsDropdown.tsx index 5638d34c6..6a5c2cb4c 100644 --- a/src/client/views/FieldsDropdown.tsx +++ b/src/client/views/FieldsDropdown.tsx @@ -61,7 +61,6 @@ export class FieldsDropdown extends ObservableReactComponent filteredOptions.push(pair[0])); const options = filteredOptions.sort().map(facet => ({ value: facet, label: facet })); - console.log(options); return ( } + {Doc.noviceMode ? null : } {templateMenu} { + onKeyDown = (e: KeyboardEvent) => { //make textbox and add it to this collection // tslint:disable-next-line:prefer-const const cm = ContextMenu.Instance; @@ -311,7 +311,7 @@ export class MarqueeView extends ObservableReactComponent { ? DocCast(linkDoc.link_anchor_2) : DocCast(linkDoc.link_anchor_1) : LinkManager.getOppositeAnchor(linkDoc, sourceDoc) || - LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null).annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null)); + LinkManager.getOppositeAnchor(linkDoc, Cast(linkDoc.link_anchor_2, Doc, null)?.annotationOn === sourceDoc ? Cast(linkDoc.link_anchor_2, Doc, null) : Cast(linkDoc.link_anchor_1, Doc, null)); return !destDoc || !sourceDoc ? null : ( () { _height: 300, backgroundColor: 'white', }); - this._props.addDocument?.(graph); + const link = DocUtils.MakeLink(this.Document, graph, { link_relationship: 'function', link_description: 'input' }); + //this._props.addDocument?.(graph); + link && this._props.addDocument?.(link); e.stopPropagation(); } if (e.key === 'Backspace' && !this.dataDoc.text) this._props.removeDocument?.(this.Document); diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 2e7a2120e..67445e552 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -7,12 +7,13 @@ import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { Cast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { Docs } from '../../documents/Documents'; +import { DocUtils, Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; import { FieldView, FieldViewProps } from './FieldView'; import { PinProps, PresBox } from './trails'; +import { LinkManager } from '../../util/LinkManager'; @observer export class FunctionPlotBox extends ViewBoxAnnotatableComponent() { @@ -33,7 +34,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent componentDidMount() { this._props.setContentViewBox?.(this); reaction( - () => [DocListCast(this.dataDoc[this.fieldKey]).map(doc => doc?.text), this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange], + () => [this.graphFuncs, this.layoutDoc.width, this.layoutDoc.height, this.layoutDoc.xRange, this.layoutDoc.yRange], () => this.createGraph() ); } @@ -45,11 +46,17 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent if (addAsAnnotation) this.addDocument(anchor); return anchor; }; + @computed get graphFuncs() { + const links = LinkManager.Instance.getAllRelatedLinks(this.Document) + .map(d => LinkManager.getOppositeAnchor(d, this.Document)) + .filter(d => d) + .map(d => d!); + return links.concat(DocListCast(this.dataDoc[this.fieldKey])).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)')); + } createGraph = (ele?: HTMLDivElement) => { this._plotEle = ele || this._plotEle; const width = this._props.PanelWidth(); const height = this._props.PanelHeight(); - const fns = DocListCast(this.dataDoc.data).map(doc => StrCast(doc.text, 'x^2').replace(/\\frac\{(.*)\}\{(.*)\}/, '($1/$2)')); try { this._plotEle.children.length && this._plotEle.removeChild(this._plotEle.children[0]); this._plot = functionPlot({ @@ -59,7 +66,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent xAxis: { domain: Cast(this.layoutDoc.xRange, listSpec('number'), [-10, 10]) }, yAxis: { domain: Cast(this.layoutDoc.yRange, listSpec('number'), [-1, 9]) }, grid: true, - data: fns.map(fn => ({ + data: this.graphFuncs.map(fn => ({ fn, // derivative: { fn: "2 * x", updateOnMouseMove: true } })), @@ -72,7 +79,14 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent @undoBatch drop = (e: Event, de: DragManager.DropEvent) => { if (de.complete.docDragData?.droppedDocuments.length) { - const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc), true); + const added = de.complete.docDragData.droppedDocuments.reduce((res, doc) => { + ///const ret = res && Doc.AddDocToList(this.dataDoc, this._props.fieldKey, doc); + if (res) { + const link = DocUtils.MakeLink(doc, this.Document, { link_relationship: 'function', link_description: 'input' }); + link && this._props.addDocument?.(link); + } + return res; + }, true); !added && e.preventDefault(); e.stopPropagation(); // prevent parent Doc from registering new position so that it snaps back into place return added; @@ -104,7 +118,7 @@ export class FunctionPlotBox extends ViewBoxAnnotatableComponent {this.theGraph}
      () { public static LayoutString(fieldKey: string = 'link') { return FieldView.LayoutString(LinkBox, fieldKey); } - _disposer: IReactionDisposer | undefined; + _disposers: { [name: string]: IReactionDisposer } = {}; @observable _forceAnimate: number = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor @@ -40,11 +40,15 @@ export class LinkBox extends ViewBoxBaseComponent() { return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement()); }; componentWillUnmount() { - this._disposer?.(); + Object.keys(this._disposers).forEach(key => this._disposers[key]()); } componentDidMount() { this._props.setContentViewBox?.(this); - this._disposer = reaction( + this._disposers.deleting = reaction( + () => !this.anchor1 || !this.anchor2, + empty => empty && this._props.removeDocument?.(this.Document) + ); + this._disposers.dragging = reaction( () => ({ drag: SnappingManager.IsDragging }), ({ drag }) => { !LightboxView.Contains(this.DocumentView?.()) && diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 1645d0813..2a96ce458 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -69,8 +69,10 @@ export class LinkDescriptionPopup extends React.Component<{}> { }}> e.stopPropagation()} - onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)} + onKeyDown={e => { + e.key === 'Enter' && this.onDismiss(true); + e.stopPropagation(); + }} value={this.description} placeholder={this.description || '(Optional) Enter link description...'} onChange={e => this.descriptionChanged(e)} diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index d9d0dbe3e..8c65fd34e 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -670,7 +670,7 @@ export class ScriptingBox extends ViewBoxAnnotatableComponent() const definedParameters = !this.compileParams.length ? null : (
      {this.compileParams.map((parameter, i) => ( -
      e.key === 'Enter' && this._overlayDisposer?.()}> +
      e.key === 'Enter' && this._overlayDisposer?.()}> () {!this.compileParams.length || !this.paramsNames ? null : (
      {this.paramsNames.map((parameter: string, i: number) => ( -
      e.key === 'Enter' && this._overlayDisposer?.()}> +
      e.key === 'Enter' && this._overlayDisposer?.()}>
      {`${parameter}:${this.paramsTypes[i]} = `}
      {this.paramsTypes[i] === 'boolean' ? this.renderEnum(parameter, [true, false]) : null} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 5b03e2236..8802a032f 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction, trace } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; @@ -22,15 +22,20 @@ import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; import { DocData } from '../../../../fields/DocSymbols'; +import { NodeSelection, TextSelection } from 'prosemirror-state'; export class DashFieldView { dom: HTMLDivElement; // container for label and value root: any; node: any; tbox: FormattedTextBox; + @observable expanded = false; + Expanded = () => this.expanded; unclickable = () => !this.tbox._props.rootSelected?.() && this.node.marks.some((m: any) => m.type === this.tbox.EditorView?.state.schema.marks.linkAnchor && m.attrs.noPreview); constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + makeObservable(this); + const self = this; this.node = node; this.tbox = tbox; this.dom = document.createElement('div'); @@ -38,11 +43,26 @@ export class DashFieldView { this.dom.style.height = node.attrs.height; this.dom.style.position = 'relative'; this.dom.style.display = 'inline-block'; - this.dom.onkeypress = function (e: any) { + const tBox = this.tbox; + this.dom.onkeypress = function (e: KeyboardEvent) { e.stopPropagation(); }; - this.dom.onkeydown = function (e: any) { + this.dom.onkeydown = function (e: KeyboardEvent) { e.stopPropagation(); + if (e.key === 'Tab') { + e.preventDefault(); + const editor = tbox.EditorView; + if (editor) { + const state = editor.state; + for (var i = state.selection.to; i < state.doc.content.size; i++) { + if (state.doc.nodeAt(i)?.type.name === state.schema.nodes.dashField.name) { + editor.dispatch(state.tr.setSelection(new NodeSelection(state.doc.resolve(i)))); + return; + } + } + tBox.setFocus(state.selection.to + 1); + } + } }; this.dom.onkeyup = function (e: any) { e.stopPropagation(); @@ -51,6 +71,8 @@ export class DashFieldView { e.stopPropagation(); }; + this.expanded = node.attrs.expanded; + this.root = ReactDOM.createRoot(this.dom); this.root.render( @@ -77,9 +99,11 @@ export class DashFieldView { }); } deselectNode() { + runInAction(() => (this.expanded = false)); this.dom.classList.remove('ProseMirror-selectednode'); } selectNode() { + setTimeout(() => runInAction(() => (this.expanded = true)), 100); this.dom.classList.add('ProseMirror-selectednode'); } } @@ -92,7 +116,7 @@ interface IDashFieldViewInternal { width: number; height: number; editable: boolean; - expanded: boolean; + expanded: () => boolean; dataDoc: boolean; node: any; getPos: any; @@ -106,7 +130,7 @@ export class DashFieldViewInternal extends ObservableReactComponent(); @observable _dashDoc: Doc | undefined = undefined; - @observable _expanded = this._props.expanded; + @observable _expanded = this._props.expanded(); constructor(props: IDashFieldViewInternal) { super(props); @@ -132,7 +156,7 @@ export class DashFieldViewInternal extends ObservableReactComponent this._expanded && this._props.editable; + isRowActive = () => (this._props.expanded() || this._expanded) && this._props.editable; finishEdit = action(() => { if (this._expanded) { @@ -155,7 +179,7 @@ export class DashFieldViewInternal extends ObservableReactComponent ({ value: facet, label: facet })); @@ -239,13 +263,13 @@ export class DashFieldViewInternal extends ObservableReactComponent )} {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} - {!this.values.length ? null : ( - {this.values.map(val => ( ))} - )} + )} */}
      ); } diff --git a/src/client/views/nodes/formattedText/EquationView.tsx b/src/client/views/nodes/formattedText/EquationView.tsx index b786c5ffb..b90653acc 100644 --- a/src/client/views/nodes/formattedText/EquationView.tsx +++ b/src/client/views/nodes/formattedText/EquationView.tsx @@ -8,6 +8,7 @@ import { StrCast } from '../../../../fields/Types'; import './DashFieldView.scss'; import EquationEditor from './EquationEditor'; import { FormattedTextBox } from './FormattedTextBox'; +import { DocData } from '../../../../fields/DocSymbols'; export class EquationView { dom: HTMLDivElement; // container for label and value @@ -88,7 +89,6 @@ export class EquationViewInternal extends React.Component } e.stopPropagation(); }} - onKeyPress={e => e.stopPropagation()} style={{ position: 'relative', display: 'inline-block', @@ -96,12 +96,11 @@ export class EquationViewInternal extends React.Component height: this.props.height, background: 'white', borderRadius: '10%', - bottom: 3, }}> ) => { + onKeyDown = (e: React.KeyboardEvent) => { if (e.key === 'Enter') { DocServer.GetRefField(this.idToAdd).then( action((field: any) => { @@ -168,7 +168,7 @@ class Viewer extends React.Component { render() { return ( <> - +
      {this.fields.map((field, index) => ( false}> -- cgit v1.2.3-70-g09d2 From 397e152d6450b4904645ebb271691fb01b267c4e Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Mar 2024 13:29:55 -0400 Subject: fixed collapsible text box outlines. fixed ending lists with enter. --- .../views/nodes/formattedText/FormattedTextBox.tsx | 18 +++++++----- .../formattedText/ProsemirrorExampleTransfer.ts | 34 ++++++++++------------ src/client/views/nodes/formattedText/marks_rts.ts | 16 ---------- 3 files changed, 26 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 70b3b52fd..1a5e1febf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1671,10 +1671,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent>(schema: S, props: any, mapKey const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); if ( - !liftListItem(schema.nodes.list_item)(state.tr, (tx2: Transaction) => { + !liftListItem(schema.nodes.list_item)(state, (tx2: Transaction) => { const tx3 = updateBullets(tx2, schema); marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); @@ -164,12 +165,6 @@ export function buildKeymap>(schema: S, props: any, mapKey SelectionManager.DeselectAll(); }); - const splitMetadata = (marks: any, tx: Transaction) => { - marks && tx.ensureMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); - marks && tx.setStoredMarks(marks.filter((val: any) => val.type !== schema.marks.metadata && val.type !== schema.marks.metadataKey && val.type !== schema.marks.metadataVal)); - return tx; - }; - bind('Alt-Enter', () => (props.onKey?.(event, props) ? true : true)); bind('Ctrl-Enter', () => (props.onKey?.(event, props) ? true : true)); bind('Cmd-a', (state: EditorState, dispatch: (tx: Transaction) => void) => { @@ -288,7 +283,8 @@ export function buildKeymap>(schema: S, props: any, mapKey //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock //command to break line - bind('Enter', (state: EditorState, dispatch: (tx: Transaction) => void) => { + + const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; @@ -304,7 +300,11 @@ export function buildKeymap>(schema: S, props: any, mapKey const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); const cr = state.selection.$from.node().textContent.endsWith('\n'); if (/*cr ||*/ !newlineInCode(state, dispatch as any)) { - if ( + if (!view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) { + do { + liftListItem(schema.nodes.list_item)(view.state, view.dispatch); + } while (view.state.selection.$from.depth > 1); + } else if ( !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { const tx3 = updateBullets(tx2, schema); marks && tx3.ensureMarks([...marks]); @@ -318,10 +318,9 @@ export function buildKeymap>(schema: S, props: any, mapKey const tonode = tx3.selection.$to.node(); if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); - splitMetadata(marks, tx4); - if (!liftListItem(schema.nodes.list_item)(tx4, dispatch as (tx: Transaction) => void)) { - dispatch(tx4); - } + dispatch(tx4); + if (!view.state.selection.$from.node().content.size) liftListItem(schema.nodes.list_item)(view.state, view.dispatch); + else enter(view.state, view.dispatch, view); // view.dispatch(view.state.tr.insertText('\r\n')); } else dispatch(tx3.insertText('\r\n')); }) ) { @@ -330,13 +329,12 @@ export function buildKeymap>(schema: S, props: any, mapKey } } return true; - }); + }; + bind('Enter', enter); //Command to create a blank space bind('Space', (state: EditorState, dispatch: (tx: Transaction) => void) => { if (props.TemplateDataDocument && GetEffectiveAcl(props.TemplateDataDocument) != AclEdit && GetEffectiveAcl(props.TemplateDataDocument) != AclAugment && GetEffectiveAcl(props.TemplateDataDocument) != AclAdmin) return true; - const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - dispatch(splitMetadata(marks, state.tr)); return false; }); diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index b68acc8f8..ccf7de4a1 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -235,22 +235,6 @@ export const marks: { [index: string]: MarkSpec } = { }, }, - metadata: { - toDOM() { - return ['span', { style: 'font-size:75%; background:rgba(100, 100, 100, 0.2); ' }]; - }, - }, - metadataKey: { - toDOM() { - return ['span', { style: 'font-style:italic; ' }]; - }, - }, - metadataVal: { - toDOM() { - return ['span']; - }, - }, - summarizeInclusive: { parseDOM: [ { -- cgit v1.2.3-70-g09d2 From 3bf4c1e7e9e34b2f4730e3df504ef06c36d05a9e Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Mar 2024 13:39:36 -0400 Subject: fixed backspacing to delete list items. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 1a5e1febf..729c4d534 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1671,7 +1671,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent>(schema: S, props: any, mapKey }); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); - bind('Backspace', (state: EditorState, dispatch: (tx: Transaction) => void) => { + const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; @@ -267,6 +267,7 @@ export function buildKeymap>(schema: S, props: any, mapKey if ( !joinBackward(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); + if (!view.state.selection.$from.node().content.size) backspace(view.state, view.dispatch, view); }) ) { if ( @@ -279,7 +280,8 @@ export function buildKeymap>(schema: S, props: any, mapKey } } return true; - }); + }; + bind('Backspace', backspace); //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock //command to break line -- cgit v1.2.3-70-g09d2 From 1d47f6cc8be84ab368ad91f287909ee162d1f2e2 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Mar 2024 15:42:12 -0400 Subject: fixed toggling footnotes. fixed error in bullet hit test. fixed problems with backspace and enter in prosemirror transfer. fixed display of markdown options to start at top. --- src/client/util/RTFMarkup.tsx | 2 +- .../views/nodes/formattedText/FootnoteView.tsx | 4 ++- .../views/nodes/formattedText/FormattedTextBox.tsx | 3 +- .../formattedText/ProsemirrorExampleTransfer.ts | 41 ++++++++++++++-------- 4 files changed, 32 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index 315daad42..57485d893 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -132,7 +132,7 @@ export class RTFMarkup extends React.Component<{}> { render() { return ( e.stopPropagation(), true); // These are used when the footnote is selected this.innerView = null; } @@ -82,9 +83,10 @@ export class FootnoteView { document.removeEventListener('pointerup', this.ignore, true); }; - toggle = () => { + toggle = (e: PointerEvent) => { if (this.innerView) this.close(); else this.open(); + e.stopPropagation(); }; close() { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 729c4d534..9882a9be9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1685,7 +1685,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent>(schema: S, props: any, mapKey }); // backspace = chainCommands(deleteSelection, joinBackward, selectNodeBackward); - const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => { + const backspace = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; @@ -267,7 +267,7 @@ export function buildKeymap>(schema: S, props: any, mapKey if ( !joinBackward(state, (tx: Transaction) => { dispatch(updateBullets(tx, schema)); - if (!view.state.selection.$from.node().content.size) backspace(view.state, view.dispatch, view); + if (once && view.state.selection.$from.depth > 1 && view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === view.state.schema.nodes.list_item) backspace(view.state, view.dispatch, view, false); }) ) { if ( @@ -286,26 +286,26 @@ export function buildKeymap>(schema: S, props: any, mapKey //newlineInCode, createParagraphNear, liftEmptyBlock, splitBlock //command to break line - const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView) => { + const enter = (state: EditorState, dispatch: (tx: Transaction) => void, view: EditorView, once = true) => { if (props.onKey?.(event, props)) return true; if (!canEdit(state)) return true; const trange = state.selection.$from.blockRange(state.selection.$to); - const path = (state.selection.$from as any).path; - const depth = trange ? liftTarget(trange) : undefined; - const split = path.length > 5 && !path[path.length - 3].textContent && path[path.length - 6].type !== schema.nodes.list_item; - if (split && trange && depth !== undefined && depth !== null) { + const depth = trange ? liftTarget(trange) : null; + if ( + depth !== null && + state.selection.$from.node(state.selection.$from.depth - 1)?.type === state.schema.nodes.blockquote && // + !state.selection.$from.node().content.size && + trange + ) { dispatch(state.tr.lift(trange, depth) as any); return true; } const marks = state.storedMarks || (state.selection.$to.parentOffset && state.selection.$from.marks()); - const cr = state.selection.$from.node().textContent.endsWith('\n'); - if (/*cr ||*/ !newlineInCode(state, dispatch as any)) { - if (!view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) { - do { - liftListItem(schema.nodes.list_item)(view.state, view.dispatch); - } while (view.state.selection.$from.depth > 1); + if (!newlineInCode(state, dispatch as any)) { + if (once && view.state.selection.$from.depth > 1 && !view.state.selection.$from.nodeBefore && !view.state.selection.$from.nodeBefore) { + for (let i = 0; i < 10 && view.state.selection.$from.depth > 1 && liftListItem(schema.nodes.list_item)(view.state, view.dispatch); i++); } else if ( !splitListItem(schema.nodes.list_item)(state as any, (tx2: Transaction) => { const tx3 = updateBullets(tx2, schema); @@ -321,8 +321,19 @@ export function buildKeymap>(schema: S, props: any, mapKey if (tx3.selection.to && tx3.doc.nodeAt(tx3.selection.to - 1)) { const tx4 = tx3.setNodeMarkup(tx3.selection.to - 1, tonode.type, fromattrs, tonode.marks); dispatch(tx4); - if (!view.state.selection.$from.node().content.size) liftListItem(schema.nodes.list_item)(view.state, view.dispatch); - else enter(view.state, view.dispatch, view); // view.dispatch(view.state.tr.insertText('\r\n')); + if ( + view.state.selection.$from.parentOffset && // + !view.state.selection.$from.node().content.size + ) + liftListItem(schema.nodes.list_item)(view.state, view.dispatch); + else if ( + once && + view.state.selection.$from.parentOffset && + view.state.selection.$from.depth > 1 && // + view.state.selection.$from.node(view.state.selection.$from.depth - 1).type === schema.nodes.list_item + ) + enter(view.state, view.dispatch, view, false); + else if (once && depth && !view.state.selection.$from.parentOffset) backspace(view.state, view.dispatch, view, false); } else dispatch(tx3.insertText('\r\n')); }) ) { -- cgit v1.2.3-70-g09d2 From 6eff9b8943a74c8f1c80741efac20fbe8c50cece Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Mar 2024 15:49:09 -0400 Subject: fixed forward hyperlink reference to not publish. --- src/client/views/nodes/formattedText/RichTextRules.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index c3eaf4dd2..c59a94394 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -307,7 +307,7 @@ export class RichTextRules { }; const getTitledDoc = (docTitle: string) => { if (!DocServer.FindDocByTitle(docTitle)) { - Doc.AddToMyPublished(Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_autoHeight: true })); + Docs.Create.TextDocument('', { title: docTitle, _width: 400, _layout_fitWidth: true, _layout_autoHeight: true }); } const titledDoc = DocServer.FindDocByTitle(docTitle); return titledDoc ? Doc.BestEmbedding(titledDoc) : titledDoc; -- cgit v1.2.3-70-g09d2 From 00367534e4a61eea788beffedaa0bc266ea3aa16 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Mar 2024 17:23:55 -0400 Subject: fixed lightbox views to suppressheight for docs with layout_fitWidth set --- src/client/views/LightboxView.tsx | 1 + src/client/views/nodes/DocumentView.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index b9d805145..1f0b8e387 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -259,6 +259,7 @@ export class LightboxView extends ObservableReactComponent { styleProvider={DefaultStyleProvider} ScreenToLocalTransform={this.lightboxScreenToLocal} renderDepth={0} + suppressSetHeight={this._doc._layout_fitWidth ? true : false} containerViewPath={returnEmptyDoclist} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3154ee434..6d8656888 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -222,7 +222,7 @@ export class DocumentViewInternal extends DocComponent !link.link_matchEmbeddings || link.link_anchor_1 === this.Document || link.link_anchor_2 === this.Document); } -- cgit v1.2.3-70-g09d2 From 9b2cfea0c13fef048ba6ae3d5281d1eea836e9b3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Mar 2024 19:22:26 -0400 Subject: preserve nodeSelections after onBlur by not calling autoLink. fix promoting text to list cursor locatoin --- .../views/nodes/formattedText/FormattedTextBox.tsx | 22 +++++++++++++--------- .../formattedText/ProsemirrorExampleTransfer.ts | 3 ++- 2 files changed, 15 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9882a9be9..d25101844 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1694,8 +1694,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - tr.addStoredMark(m); - return tr; - }, this._editorView.state.tr); - tr && this._editorView.dispatch(tr); + if (!(this.EditorView?.state.selection instanceof NodeSelection)) { + this.autoLink(); + if (this._editorView?.state.tr) { + const tr = stordMarks?.reduce((tr, m) => { + tr.addStoredMark(m); + return tr; + }, this._editorView.state.tr); + tr && this._editorView.dispatch(tr); + } } } if (RichTextMenu.Instance?.view === this._editorView && !this._props.rootSelected?.()) { diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 87eee1b2f..ab49a53ea 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -107,7 +107,8 @@ export function buildKeymap>(schema: S, props: any, mapKey // when promoting to a list, assume list will format things so don't copy the stored marks. marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); - dispatch(tx3); + const tx4 = tx3.setSelection(TextSelection.near(tx3.doc.resolve(state.selection.to + 2))); + dispatch(tx4); }) ) { console.log('bullet promote fail'); -- cgit v1.2.3-70-g09d2 From 52bd492747cf169df7bcdecdbdbb353d45b19734 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Mar 2024 00:05:05 -0400 Subject: fixed tabbing between items in nested dashFieldViews --- src/client/views/EditableView.tsx | 1 + .../views/collections/collectionSchema/CollectionSchemaView.scss | 9 +++++++++ .../views/collections/collectionSchema/SchemaTableCell.tsx | 8 ++++---- src/fields/Doc.ts | 3 +++ src/fields/RichTextField.ts | 7 +++++++ 5 files changed, 24 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 44c8e4e1f..85e893e19 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -121,6 +121,7 @@ export class EditableView extends ObservableReactComponent { @action onKeyDown = (e: React.KeyboardEvent) => { + if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here switch (e.key) { case 'Tab': e.stopPropagation(); diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index ac0bd2378..9768877ff 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -226,6 +226,15 @@ align-items: center; } +.schemaRTFCell { + display: flex; + width: 100%; + height: 100%; + position: relative; + min-width: 10px; + min-height: 10px; +} + .schema-row { cursor: grab; justify-content: flex-end; diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index bda8afa41..ab05a62d3 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -17,6 +17,7 @@ import { FInfo, FInfoFieldType } from '../../../documents/Documents'; import { DocFocusOrOpen } from '../../../util/DocumentManager'; import { dropActionType } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; @@ -29,7 +30,6 @@ import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { ColumnType, FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; -import { SnappingManager } from '../../../util/SnappingManager'; export interface SchemaTableCellProps { Document: Doc; @@ -128,7 +128,7 @@ export class SchemaTableCell extends ObservableReactComponent this._props.autoFocus && r?.setIsFocused(true)} + ref={r => this.selected && this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} contents={undefined} @@ -141,7 +141,7 @@ export class SchemaTableCell extends ObservableReactComponent +
      {this.selected ? this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))}
      ); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 1bd49cf3f..bdf600475 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1713,3 +1713,6 @@ ScriptingGlobals.add(function setDocRangeFilter(container: Doc, key: string, ran ScriptingGlobals.add(function toJavascriptString(str: string) { return Field.toJavascriptString(str as Field); }); +ScriptingGlobals.add(function RtfField() { + return RichTextField.RTFfield(); +}); diff --git a/src/fields/RichTextField.ts b/src/fields/RichTextField.ts index d0a149506..3f13f7e6d 100644 --- a/src/fields/RichTextField.ts +++ b/src/fields/RichTextField.ts @@ -36,4 +36,11 @@ export class RichTextField extends ObjectField { [ToString]() { return this.Text; } + + public static RTFfield() { + return new RichTextField( + `{"doc":{"type":"doc","content":[{"type":"paragraph","attrs":{"align":null,"color":null,"id":null,"indent":null,"inset":null,"lineSpacing":null,"paddingBottom":null,"paddingTop":null},"content":[]}]},"selection":{"type":"text","anchor":2,"head":2},"storedMarks":[]}`, + '' + ); + } } -- cgit v1.2.3-70-g09d2 From 1b592f74a7df8f6dd7b2881032725f26aedff403 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Mar 2024 11:38:56 -0400 Subject: fixed keyvaluebox to show props document, never the doc in the fieldKey slot. changed computedFIelds to do mobx caching. changed text boxes to do updating from templates based on a fieldKey_autoUpdate flag combined with modification timestamps. enabled comparison box to work with text fields in addition to docs. --- src/client/util/Scripting.ts | 9 +-- src/client/views/InkControlPtHandles.tsx | 85 ++++++++++++---------- src/client/views/PropertiesButtons.tsx | 4 +- src/client/views/nodes/ComparisonBox.tsx | 6 +- src/client/views/nodes/KeyValueBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 31 +++++--- src/fields/Doc.ts | 2 +- src/fields/ScriptField.ts | 22 ++++-- 8 files changed, 90 insertions(+), 71 deletions(-) (limited to 'src') diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index f5e162d16..31222aa50 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -88,15 +88,10 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an } const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray); - if (batch) { - batch.end(); - } - + batch?.end(); return { success: true, result }; } catch (error) { - if (batch) { - batch.end(); - } + batch?.end(); onError?.(script + ' ' + error); return { success: false, error, result: errorVal }; } diff --git a/src/client/views/InkControlPtHandles.tsx b/src/client/views/InkControlPtHandles.tsx index 31b13d2c8..01d52135a 100644 --- a/src/client/views/InkControlPtHandles.tsx +++ b/src/client/views/InkControlPtHandles.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { action, observable } from 'mobx'; +import { action, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import { Doc } from '../../fields/Doc'; import { ControlPoint, InkData, PointData } from '../../fields/InkField'; @@ -13,6 +13,7 @@ import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { SnappingManager } from '../util/SnappingManager'; +import { ObservableReactComponent } from './ObservableReactComponent'; export interface InkControlProps { inkDoc: Doc; @@ -24,10 +25,15 @@ export interface InkControlProps { } @observer -export class InkControlPtHandles extends React.Component { +export class InkControlPtHandles extends ObservableReactComponent { @observable private _overControl = -1; get docView() { - return this.props.inkView.DocumentView?.(); + return this._props.inkView.DocumentView?.(); + } + + constructor(props: InkControlProps) { + super(props); + makeObservable(this); } componentDidMount() { @@ -42,51 +48,51 @@ export class InkControlPtHandles extends React.Component { */ @action onControlDown = (e: React.PointerEvent, controlIndex: number): void => { - const ptFromScreen = this.props.inkView.ptFromScreen; + const ptFromScreen = this._props.inkView.ptFromScreen; if (ptFromScreen) { const order = controlIndex % 4; - const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this.props.inkCtrlPoints.length) % this.props.inkCtrlPoints.length; - const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this.props.inkCtrlPoints.length; - const brokenIndices = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number')); + const handleIndexA = ((order === 3 ? controlIndex - 1 : controlIndex - 2) + this._props.inkCtrlPoints.length) % this._props.inkCtrlPoints.length; + const handleIndexB = (order === 3 ? controlIndex + 2 : controlIndex + 1) % this._props.inkCtrlPoints.length; + const brokenIndices = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number')); const wasSelected = InkStrokeProperties.Instance._currentPoint === controlIndex; if (!wasSelected) InkStrokeProperties.Instance._currentPoint = -1; - const origInk = this.props.inkCtrlPoints.slice(); + const origInk = this._props.inkCtrlPoints.slice(); setupMoveUpEvents( this, e, action((e: PointerEvent, down: number[], delta: number[]) => { - if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt'); + if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('drag ink ctrl pt'); const inkMoveEnd = ptFromScreen({ X: delta[0], Y: delta[1] }); const inkMoveStart = ptFromScreen({ X: 0, Y: 0 }); this.docView && InkStrokeProperties.Instance.moveControlPtHandle(this.docView, inkMoveEnd.X - inkMoveStart.X, inkMoveEnd.Y - inkMoveStart.Y, controlIndex, origInk); return false; }), action(() => { - if (this.props.inkView.controlUndo && this.docView) { + if (this._props.inkView.controlUndo && this.docView) { InkStrokeProperties.Instance.snapControl(this.docView, controlIndex); } - this.props.inkView.controlUndo?.end(); - this.props.inkView.controlUndo = undefined; + this._props.inkView.controlUndo?.end(); + this._props.inkView.controlUndo = undefined; UndoManager.FilterBatches(['data', 'x', 'y', 'width', 'height']); }), action((e: PointerEvent, doubleTap: boolean | undefined) => { - const equivIndex = controlIndex === 0 ? this.props.inkCtrlPoints.length - 1 : controlIndex === this.props.inkCtrlPoints.length - 1 ? 0 : controlIndex; + const equivIndex = controlIndex === 0 ? this._props.inkCtrlPoints.length - 1 : controlIndex === this._props.inkCtrlPoints.length - 1 ? 0 : controlIndex; if (doubleTap || e.button === 2) { if (!brokenIndices?.includes(equivIndex) && !brokenIndices?.includes(controlIndex)) { if (brokenIndices) brokenIndices.push(controlIndex); - else this.props.inkDoc.brokenInkIndices = new List([controlIndex]); + else this._props.inkDoc.brokenInkIndices = new List([controlIndex]); } else { if (brokenIndices?.includes(equivIndex)) { - if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth'); + if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth'); this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, equivIndex, handleIndexA, handleIndexB); } if (equivIndex !== controlIndex && brokenIndices?.includes(controlIndex)) { - if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('make smooth'); + if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('make smooth'); this.docView && InkStrokeProperties.Instance.snapHandleTangent(this.docView, controlIndex, handleIndexA, handleIndexB); } } - this.props.inkView.controlUndo?.end(); - this.props.inkView.controlUndo = undefined; + this._props.inkView.controlUndo?.end(); + this._props.inkView.controlUndo = undefined; } this.changeCurrPoint(controlIndex); }), @@ -126,14 +132,14 @@ export class InkControlPtHandles extends React.Component { render() { // Accessing the current ink's data and extracting all control points. - const scrData = this.props.screenCtrlPoints; + const scrData = this._props.screenCtrlPoints; const sreenCtrlPoints: ControlPoint[] = []; for (let i = 0; i <= scrData.length - 4; i += 4) { sreenCtrlPoints.push({ ...scrData[i], I: i }); sreenCtrlPoints.push({ ...scrData[i + 3], I: i + 3 }); } - const inkData = this.props.inkCtrlPoints; + const inkData = this._props.inkCtrlPoints; const inkCtrlPts: ControlPoint[] = []; for (let i = 0; i <= inkData.length - 4; i += 4) { inkCtrlPts.push({ ...inkData[i], I: i }); @@ -141,23 +147,23 @@ export class InkControlPtHandles extends React.Component { } const closed = InkingStroke.IsClosed(inkData); - const nearestScreenPt = this.props.nearestScreenPt(); + const nearestScreenPt = this._props.nearestScreenPt(); const TagType = (broken?: boolean) => (broken ? 'rect' : 'circle'); const hdl = (control: { X: number; Y: number; I: number }, scale: number, color: string) => { - const broken = Cast(this.props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I); + const broken = Cast(this._props.inkDoc.brokenInkIndices, listSpec('number'))?.includes(control.I); const Tag = TagType((control.I === 0 || control.I === inkData.length - 1) && !closed) as keyof JSX.IntrinsicElements; return ( this.onControlDown(e, control.I)} @@ -170,7 +176,7 @@ export class InkControlPtHandles extends React.Component { }; return ( - {!nearestScreenPt ? null : } + {!nearestScreenPt ? null : } {sreenCtrlPoints.map(control => hdl(control, this._overControl !== control.I ? 1 : 3 / 2, Colors.WHITE))} ); @@ -185,10 +191,15 @@ export interface InkEndProps { endPt: () => PointData; } @observer -export class InkEndPtHandles extends React.Component { +export class InkEndPtHandles extends ObservableReactComponent { @observable _overStart: boolean = false; @observable _overEnd: boolean = false; + constructor(props: InkEndProps) { + super(props); + makeObservable(this); + } + _throttle = 0; // need to throttle dragging since the position may change when the control points change. this allows the stroke to settle so that we don't get increasingly bad jitter @action dragRotate = (e: React.PointerEvent, pt1: () => { X: number; Y: number }, pt2: () => { X: number; Y: number }) => { @@ -198,7 +209,7 @@ export class InkEndPtHandles extends React.Component { e, action(e => { if (this._throttle++ % 2 !== 0) return false; - if (!this.props.inkView.controlUndo) this.props.inkView.controlUndo = UndoManager.StartBatch('stretch ink'); + if (!this._props.inkView.controlUndo) this._props.inkView.controlUndo = UndoManager.StartBatch('stretch ink'); // compute stretch factor by finding scaling along axis between start and end points const p1 = pt1(); const p2 = pt2(); @@ -216,8 +227,8 @@ export class InkEndPtHandles extends React.Component { }), action(() => { SnappingManager.SetIsDragging(false); - this.props.inkView.controlUndo?.end(); - this.props.inkView.controlUndo = undefined; + this._props.inkView.controlUndo?.end(); + this._props.inkView.controlUndo = undefined; UndoManager.FilterBatches(['stroke', 'x', 'y', 'width', 'height']); }), returnFalse @@ -230,7 +241,7 @@ export class InkEndPtHandles extends React.Component { key={key} cx={pt.X} cy={pt.Y} - r={this.props.screenSpaceLineWidth * 2} + r={this._props.screenSpaceLineWidth * 2} fill={this._overStart ? '#aaaaaa' : '#99999977'} stroke={'#00007777'} strokeWidth={0} @@ -242,8 +253,8 @@ export class InkEndPtHandles extends React.Component { ); return ( - {hdl('start', this.props.startPt(), e => this.dragRotate(e, this.props.startPt, this.props.endPt))} - {hdl('end', this.props.endPt(), e => this.dragRotate(e, this.props.endPt, this.props.startPt))} + {hdl('start', this._props.startPt(), e => this.dragRotate(e, this._props.startPt, this._props.endPt))} + {hdl('end', this._props.endPt(), e => this.dragRotate(e, this._props.endPt, this._props.startPt))} ); } diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 02f288a68..517a80d63 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -87,7 +87,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { containerDoc._isLightbox = !containerDoc._isLightbox; //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; const containerContents = DocListCast(dv.dataDoc[Doc.LayoutFieldKey(containerDoc)]); - //dv.Docuemnt.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); + //dv.Docuemnt.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' }); containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.link_displayLine = false))); }); } @@ -219,7 +219,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { // containerDoc._isLightbox = !containerDoc._isLightbox; // //containerDoc._xPadding = containerDoc._yPadding = containerDoc._isLightbox ? 10 : undefined; // const containerContents = DocListCast(dv.dataDoc[dv.props.fieldKey ?? Doc.LayoutFieldKey(containerDoc)]); - // //dv.Document.onClick = ScriptField.MakeScript('{self.data = undefined; documentView.select(false)}', { documentView: 'any' }); + // //dv.Document.onClick = ScriptField.MakeScript('{this.data = undefined; documentView.select(false)}', { documentView: 'any' }); // containerContents.forEach(doc => LinkManager.Links(doc).forEach(link => (link.layout_linkDisplay = false))); // }); // } diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index 59d99b371..9ffdc350d 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -69,8 +69,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() action((e, doubleTap) => { if (doubleTap) { this._isAnyChildContentActive = true; - if (!this.dataDoc[this.fieldKey + '_1']) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); - if (!this.dataDoc[this.fieldKey + '_2']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + if (!this.dataDoc[this.fieldKey + '_1'] && !this.dataDoc[this.fieldKey]) this.dataDoc[this.fieldKey + '_1'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); + if (!this.dataDoc[this.fieldKey + '_2'] && !this.dataDoc[this.fieldKey + '_alternate']) this.dataDoc[this.fieldKey + '_2'] = DocUtils.copyDragFactory(Doc.UserDoc().emptyNote as Doc); } }), false, @@ -131,8 +131,6 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent() return false; }; - whenChildContentsActiveChanged = action((isActive: boolean) => (this._isAnyChildContentActive = isActive)); - closeDown = (e: React.PointerEvent, which: string) => { setupMoveUpEvents( this, diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 78e4435ce..31a2367fc 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -55,7 +55,7 @@ export class KeyValueBox extends ObservableReactComponent { @observable _splitPercentage = 50; get fieldDocToLayout() { - return this._props.fieldKey ? DocCast(this._props.Document[this._props.fieldKey], DocCast(this._props.Document)) : this._props.Document; + return DocCast(this._props.Document); } @action diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d25101844..f06e5fad0 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -21,7 +21,7 @@ import { List } from '../../../../fields/List'; import { PrefetchProxy } from '../../../../fields/Proxy'; import { RichTextField } from '../../../../fields/RichTextField'; import { ComputedField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types'; +import { BoolCast, Cast, DateCast, DocCast, FieldValue, NumCast, RTFCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivWidth, emptyFunction, numberRange, returnFalse, returnZero, setupMoveUpEvents, smoothScroll, unimplementedFunction, Utils } from '../../../../Utils'; import { gptAPICall, GPTCallType } from '../../../apis/gpt/GPT'; @@ -349,6 +349,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent json?.replace(/"selection":.*/, ''); @@ -369,22 +370,20 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this.dataDoc[this.fieldKey + '_autoUpdate'] = !this.dataDoc[this.fieldKey + '_autoUpdate']), icon: 'star' }); optionItems.push({ description: `Generate Dall-E Image`, event: () => this.generateImage(), icon: 'star' }); optionItems.push({ description: `Ask GPT-3`, event: () => this.askGPT(), icon: 'lightbulb' }); this._props.renderDepth && @@ -1244,9 +1244,15 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const dataDoc = Doc.IsDelegateField(DocCast(this.layoutDoc?.proto), this.fieldKey) ? DocCast(this.layoutDoc?.proto) : this?.dataDoc; - const whichDoc = !this.dataDoc || !this.layoutDoc ? undefined : dataDoc?.[this.fieldKey + '_noTemplate'] || !this.layoutDoc[this.fieldKey] ? dataDoc : this.layoutDoc; - return !whichDoc ? undefined : { data: Cast(whichDoc[this.fieldKey], RichTextField, null), str: Field.toString(DocCast(whichDoc[this.fieldKey]) ?? StrCast(whichDoc[this.fieldKey])) }; + const protoData = DocCast(this.dataDoc.proto)?.[this.fieldKey]; + const dataData = this.dataDoc[this.fieldKey]; + const layoutData = Doc.AreProtosEqual(this.layoutDoc, this.dataDoc) ? undefined : this.layoutDoc[this.fieldKey]; + const dataTime = dataData ? DateCast(this.dataDoc[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0; + const layoutTime = layoutData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.layoutDoc)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0; + const protoTime = protoData && this.dataDoc[this.fieldKey + '_autoUpdate'] ? DateCast(DocCast(this.dataDoc.proto)[this.fieldKey + '_modificationDate'])?.date.getTime() ?? 0 : 0; + const recentData = dataTime >= layoutTime ? (protoTime >= dataTime ? protoData : dataData) : layoutTime >= protoTime ? layoutData : protoData; + const whichData = recentData ?? (this.layoutDoc.isTemplateDoc ? layoutData : protoData) ?? protoData; + return !whichData ? undefined : { data: RTFCast(whichData), str: Field.toString(DocCast(whichData) ?? StrCast(whichData)) }; }, incomingValue => { if (this._editorView && this._applyingChange !== this.fieldKey) { @@ -1256,11 +1262,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.dispatch(tx.insertText(incomingValue?.str ?? ''))); } } - } + }, + { fireImmediately: true } ); this._disposers.search = reaction( diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index bdf600475..200896e25 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -48,7 +48,7 @@ export namespace Field { const valFunc = (field: Field): string => { const res = field instanceof ComputedField && showComputedValue - ? field._lastComputedResult + ? field.value(doc) : field instanceof ComputedField ? `:=${field.script.originalScript.replace(/dashCallChat\(_setCacheResult_, this, `(.*)`\)/, '(($1))')}` : field instanceof ScriptField diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 9021c8896..8b51088b2 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -198,14 +198,22 @@ export class ComputedField extends ScriptField { } _lastComputedResult: FieldResult; - value = computedFn((doc: Doc) => this._valueOutsideReaction(doc)); - _valueOutsideReaction = (doc: Doc) => { - this._lastComputedResult = - this._cachedResult ?? (this.script.compiled && this.script.run({ this: doc, self: doc, value: '', _setCacheResult_: this.setCacheResult, _last_: this._lastComputedResult, _readOnly_: true }, console.log).result); - return this._lastComputedResult; - }; + value = (doc:Doc) => (this._lastComputedResult = this._cachedResult ?? + computedFn((doc: Doc) => + this.script.compiled && + this.script.run( { + this: doc, + //value: '', + _setCacheResult_: this.setCacheResult, + _last_: this._lastComputedResult, + _readOnly_: true, + }, + console.log + ).result + )(doc) + ); // prettier-ignore - [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this._valueOutsideReaction(doc) }; } // prettier-ignore + [ToValue](doc: Doc) { if (ComputedField.useComputed) return { value: this.value(doc) }; } // prettier-ignore [Copy](): ObjectField { return new ComputedField(this.script, this.setterscript, this.rawscript); } // prettier-ignore public static MakeFunction(script: string, params: object = {}, capturedVariables?: { [name: string]: Doc | string | number | boolean }, setterscript?: string) { -- cgit v1.2.3-70-g09d2 From 75a520e2cbdd611fcd1846540c42c25df9f70241 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Mar 2024 15:22:15 -0400 Subject: enabled functions to be entered in dashFieldViews on creation. --- src/client/views/nodes/formattedText/RichTextRules.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index c59a94394..7afe91e94 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -325,7 +325,7 @@ export class RichTextRules { // [@{this,doctitle,}.fieldKey{:,=,:=,=:=}value] // [@{this,doctitle,}.fieldKey] new InputRule( - new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]*))?\]/), + new RegExp(/\[(@|@this\.|@[a-zA-Z_\? \-0-9]+\.)([a-zA-Z_\?\-0-9]+)((:|=|:=|=:=)([a-zA-Z,_\(\)\.@\?\+\-\*\/\ 0-9\(\)]*))?\]/), (state, match, start, end) => { const docTitle = match[1].substring(1).replace(/\.$/, ''); const fieldKey = match[2]; @@ -346,6 +346,7 @@ export class RichTextRules { } else if (value) { KeyValueBox.SetField(this.Document, fieldKey, assign + value, Doc.IsDataProto(this.Document) ? true : undefined, assign.includes(":=") ? undefined: (gptval: FieldResult) => (dataDoc ? this.Document[DocData]:this.Document)[fieldKey] = gptval as string ); // prettier-ignore + if (fieldKey === this.TextBox.fieldKey) return this.TextBox.EditorView!.state.tr; } const target = docTitle ? getTitledDoc(docTitle) : undefined; const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId: target?.[Id], hideKey: false, dataDoc }); -- cgit v1.2.3-70-g09d2 From 82a74549a6b1c942fabaf8396c450b70d65e81dd Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Mar 2024 23:28:11 -0400 Subject: fixed following link to pdf text selection with highlights. fixed hide key/value in dashfieldView. fixed selecting dashFieldView nodes. --- src/client/documents/Documents.ts | 25 +- src/client/util/CurrentUserUtils.ts | 4 +- src/client/views/MarqueeAnnotator.tsx | 1 + src/client/views/SidebarAnnos.tsx | 4 +- .../CollectionFreeFormLayoutEngines.tsx | 2 +- src/client/views/nodes/MapBox/MapBox.tsx | 791 +++++---------------- src/client/views/nodes/MapBox/MapPushpinBox.tsx | 4 +- .../views/nodes/RecordingBox/RecordingView.scss | 322 ++++----- .../views/nodes/formattedText/DashFieldView.tsx | 70 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 32 +- .../views/nodes/formattedText/RichTextRules.ts | 3 +- src/client/views/nodes/formattedText/nodes_rts.ts | 1 + 12 files changed, 445 insertions(+), 814 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index b63c5e429..b3d14bc22 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,6 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { action, reaction, runInAction } from 'mobx'; import { basename } from 'path'; +import { OmitKeys, Utils } from '../../Utils'; import { DateField } from '../../fields/DateField'; import { Doc, DocListCast, Field, LinkedTo, Opt, StrListCast, updateCachedAcls } from '../../fields/Doc'; import { DocData, Initializing } from '../../fields/DocSymbols'; @@ -13,30 +14,29 @@ import { SchemaHeaderField } from '../../fields/SchemaHeaderField'; import { ComputedField, ScriptField } from '../../fields/ScriptField'; import { BoolCast, Cast, DocCast, FieldValue, NumCast, ScriptCast, StrCast } from '../../fields/Types'; import { AudioField, CsvField, ImageField, PdfField, VideoField, WebField, YoutubeField } from '../../fields/URLField'; -import { inheritParentAcls, SharingPermissions } from '../../fields/util'; +import { SharingPermissions, inheritParentAcls } from '../../fields/util'; import { Upload } from '../../server/SharedMediaTypes'; -import { OmitKeys, Utils } from '../../Utils'; -import { YoutubeBox } from '../apis/youtube/YoutubeBox'; import { DocServer } from '../DocServer'; import { Networking } from '../Network'; +import { YoutubeBox } from '../apis/youtube/YoutubeBox'; import { DragManager, dropActionType } from '../util/DragManager'; import { FollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { undoable, UndoManager } from '../util/UndoManager'; -import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; -import { CollectionView } from '../views/collections/CollectionView'; +import { UndoManager, undoable } from '../util/UndoManager'; import { ContextMenu } from '../views/ContextMenu'; import { ContextMenuProps } from '../views/ContextMenuItem'; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke'; +import { CollectionDockingView } from '../views/collections/CollectionDockingView'; +import { CollectionView } from '../views/collections/CollectionView'; +import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; import { AudioBox, media_state } from '../views/nodes/AudioBox'; import { ComparisonBox } from '../views/nodes/ComparisonBox'; import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox'; +import { OpenWhere } from '../views/nodes/DocumentView'; import { EquationBox } from '../views/nodes/EquationBox'; import { FieldViewProps } from '../views/nodes/FieldView'; import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; -import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; import { FunctionPlotBox } from '../views/nodes/FunctionPlotBox'; import { ImageBox } from '../views/nodes/ImageBox'; import { KeyValueBox } from '../views/nodes/KeyValueBox'; @@ -52,14 +52,14 @@ import { RecordingBox } from '../views/nodes/RecordingBox/RecordingBox'; import { ScreenshotBox } from '../views/nodes/ScreenshotBox'; import { ScriptingBox } from '../views/nodes/ScriptingBox'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; -import { PresBox } from '../views/nodes/trails/PresBox'; -import { PresElementBox } from '../views/nodes/trails/PresElementBox'; import { VideoBox } from '../views/nodes/VideoBox'; import { WebBox } from '../views/nodes/WebBox'; +import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox'; +import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; +import { PresBox } from '../views/nodes/trails/PresBox'; +import { PresElementBox } from '../views/nodes/trails/PresElementBox'; import { SearchBox } from '../views/search/SearchBox'; import { CollectionViewType, DocumentType } from './DocumentTypes'; -import { CalendarBox } from '../views/nodes/calendarBox/CalendarBox'; -import { OpenWhere } from '../views/nodes/DocumentView'; const { default: { DFLT_IMAGE_NATIVE_DIM } } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace('px', '')); @@ -204,6 +204,7 @@ export class DocumentOptions { overlayX?: NUMt = new NumInfo('horizontal coordinate in overlay view', false); overlayY?: NUMt = new NumInfo('vertical coordinate in overlay view', false); text?: RTFt = new RtfInfo('plain or rich text', true); + text_html?: STRt = new StrInfo('plain text or html', 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', false); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 11f6c82ec..f4599f04a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -321,9 +321,9 @@ export class CurrentUserUtils { {type:"text",text:" "}, {type:"text",text:"Minerals in my tap water"}, {type:"text",text:"\n \"Calcium\" : "}, - {type:"dashField",attrs:{fieldKey:"calcium",docId:"","hideKey":false,editable:true}}, + {type:"dashField",attrs:{fieldKey:"calcium",docId:"",hideKey:false,hideValue:false,editable:true}}, {type:"text",text:"\n \"Potassium\" : "}, - {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,editable:true}}, + {type:"dashField",attrs:{fieldKey:"pot",docId:"",hideKey:false,hideValue:false,editable:true}}, {type:"text",text:"\n \"Magnesium\" : 10.01"} ]} ]}, diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c29474fcd..0b68fd59e 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -89,6 +89,7 @@ export class MarqueeAnnotator extends ObservableReactComponent, pivotDoc: Do y: -y + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt, + backgroundColor: StrCast(layoutDoc.backgroundColor), pair: { layout: doc }, replica: val.replicas[i], }); diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 927e6fad4..36dad2747 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -6,7 +6,7 @@ import { IconButton, Size, Type } from 'browndash-components'; import * as d3 from 'd3'; import { Feature, FeatureCollection, GeoJsonProperties, Geometry, LineString, Position } from 'geojson'; import mapboxgl, { LngLat, LngLatBoundsLike, MapLayerMouseEvent } from 'mapbox-gl'; -import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { IReactionDisposer, ObservableMap, action, autorun, computed, makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { CirclePicker, ColorResult } from 'react-color'; @@ -14,7 +14,6 @@ import { Layer, MapProvider, MapRef, Map as MapboxMap, Marker, Source, ViewState import { MarkerEvent } from 'react-map-gl/dist/esm/types'; import { Utils, emptyFunction, setupMoveUpEvents } from '../../../../Utils'; import { Doc, DocListCast, Field, LinkedTo, Opt } from '../../../../fields/Doc'; -import { DocCss, Highlight } from '../../../../fields/DocSymbols'; import { DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { DocUtils, Docs } from '../../../documents/Documents'; @@ -28,7 +27,7 @@ import { SidebarAnnos } from '../../SidebarAnnos'; import { MarqueeOptionsMenu } from '../../collections/collectionFreeForm'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../DocumentView'; -import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView'; +import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; import { FormattedTextBox } from '../formattedText/FormattedTextBox'; import { PinProps, PresBox } from '../trails'; import { fastSpeedIcon, mediumSpeedIcon, slowSpeedIcon } from './AnimationSpeedIcons'; @@ -53,7 +52,6 @@ import { MarkerIcons } from './MarkerIcons'; * A map marker is considered a document that contains a collection with stacking view of documents, it has a lat, lng location, which is passed to Maps API's custom marker (red pin) to be rendered on the google maps */ -const bingApiKey = process.env.BING_MAPS; // if you're running local, get a Bing Maps api key here: https://www.bingmapsportal.com/ and then add it to the .env file in the Dash-Web root directory as: _CLIENT_BING_MAPS= const MAPBOX_ACCESS_TOKEN = 'pk.eyJ1IjoiemF1bHRhdmFuZ2FyIiwiYSI6ImNscHgwNDd1MDA3MXIydm92ODdianp6cGYifQ.WFAqbhwxtMHOWSPtu0l2uQ'; const MAPBOX_FORWARD_GEOCODE_BASE_URL = 'https://api.mapbox.com/geocoding/v5/mapbox.places/'; @@ -66,72 +64,69 @@ type PopupInfo = { description: string; }; -// export type GeocoderControlProps = Omit & { -// mapboxAccessToken: string; -// marker?: Omit; -// position: ControlPosition; - -// onResult: (...args: any[]) => void; -// }; - -type MapMarker = { - longitude: number; - latitude: number; -}; - -/** - * Consider integrating later: allows for drawing, circling, making shapes on map - */ -// const drawingManager = new window.google.maps.drawing.DrawingManager({ -// drawingControl: true, -// drawingControlOptions: { -// position: google.maps.ControlPosition.TOP_RIGHT, -// drawingModes: [ -// google.maps.drawing.OverlayType.MARKER, -// // currently we are not supporting the following drawing mode on map, a thought for future development -// google.maps.drawing.OverlayType.CIRCLE, -// google.maps.drawing.OverlayType.POLYLINE, -// ], -// }, -// }); - @observer export class MapBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(MapBox, fieldKey); } - private _dragRef = React.createRef(); + private _unmounting = false; private _sidebarRef = React.createRef(); private _ref: React.RefObject = React.createRef(); private _mapRef: React.RefObject = React.createRef(); private _disposers: { [key: string]: IReactionDisposer } = {}; - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean, hide: boolean, doc: Opt) => void); constructor(props: FieldViewProps) { super(props); makeObservable(this); } - @observable private _savedAnnotations = new ObservableMap(); - @computed get allSidebarDocs() { - return DocListCast(this.dataDoc[this.SidebarKey]); - } + @observable _featuresFromGeocodeResults: any[] = []; + @observable _savedAnnotations = new ObservableMap(); + @observable _selectedPinOrRoute: Doc | undefined = undefined; // The pin that is selected + @observable _mapReady = false; + @observable _isAnimating: boolean = false; + @observable _routeToAnimate: Doc | undefined = undefined; + @observable _animationPhase: number = 0; + @observable _finishedFlyTo: boolean = false; + @observable _frameId: number | null = null; + @observable _animationUtility: AnimationUtility | null = null; + @observable _settingsOpen: boolean = false; + @observable _mapStyle: string = 'mapbox://styles/mapbox/standard'; + @observable _showTerrain: boolean = true; + @observable _currentPopup: PopupInfo | undefined = undefined; + @observable _isStreetViewAnimation: boolean = false; + @observable _animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM; + @observable _animationLineColor: string = '#ffff00'; + @observable _temporaryRouteSource: FeatureCollection = { type: 'FeatureCollection', features: [] }; + @observable _dynamicRouteFeature: Feature = { + type: 'Feature', + properties: {}, + geometry: { type: 'LineString', coordinates: [] }, + }; + + @observable path: turf.helpers.Feature = { + type: 'Feature', + geometry: { type: 'LineString', coordinates: [] }, + properties: {}, + }; + // this list contains pushpins and configs - @computed get allAnnotations() { - return DocListCast(this.dataDoc[this.annotationKey]); - } - @computed get allPushpins() { - return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); - } - @computed get allRoutes() { - return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); + @computed get allAnnotations() { return DocListCast(this.dataDoc[this.annotationKey]); } //prettier-ignore + @computed get allSidebarDocs() { return DocListCast(this.dataDoc[this.SidebarKey]); } //prettier-ignore + @computed get allPushpins() { return this.allAnnotations.filter(anno => anno.type === DocumentType.PUSHPIN); } //prettier-ignore + @computed get allRoutes() { return this.allAnnotations.filter(anno => anno.type === DocumentType.MAPROUTE); } //prettier-ignore + @computed get SidebarShown() { return this.layoutDoc._layout_showSidebar ? true : false; } //prettier-ignore + @computed get sidebarWidthPercent() { return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); } //prettier-ignore + @computed get SidebarKey() { return this.fieldKey + '_sidebar'; } //prettier-ignore + @computed get sidebarColor() { + return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4')); } @computed get updatedRouteCoordinates(): Feature { - if (this.routeToAnimate?.routeCoordinates) { - const originalCoordinates: Position[] = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates)); + if (this._routeToAnimate?.routeCoordinates) { + const originalCoordinates: Position[] = JSON.parse(StrCast(this._routeToAnimate.routeCoordinates)); // const index = Math.floor(this.animationPhase * originalCoordinates.length); - const index = this.animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index - console.log('Animation phase', this.animationPhase); + const index = this._animationPhase * (originalCoordinates.length - 1); // Calculate the fractional index + console.log('Animation phase', this._animationPhase); const startIndex = Math.floor(index); const endIndex = Math.ceil(index); let feature: Feature; @@ -147,7 +142,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem feature = { type: 'Feature', properties: { - routeTitle: StrCast(this.routeToAnimate.title), + routeTitle: StrCast(this._routeToAnimate.title), }, geometry: geometry, }; @@ -158,9 +153,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const fraction = index - startIndex; const interpolator = d3.interpolateArray(startCoord, endCoord); - const interpolatedCoord = interpolator(fraction); - const coordinates = originalCoordinates.slice(0, startIndex + 1).concat([interpolatedCoord]); geometry = { @@ -170,14 +163,14 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem feature = { type: 'Feature', properties: { - routeTitle: StrCast(this.routeToAnimate.title), + routeTitle: StrCast(this._routeToAnimate.title), }, geometry: geometry, }; } autorun(() => { - const animationUtil = this.animationUtility; + const animationUtil = this._animationUtility; const concattedCoordinates = geometry.coordinates.concat(originalCoordinates.slice(endIndex)); const newFeature: Feature = { type: 'Feature', @@ -204,11 +197,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem }; } @computed get selectedRouteCoordinates(): Position[] { - let coordinates: Position[] = []; - if (this.routeToAnimate?.routeCoordinates) { - coordinates = JSON.parse(StrCast(this.routeToAnimate.routeCoordinates)); - } - return coordinates; + return !this._routeToAnimate?.routeCoordinates ? [] : JSON.parse(StrCast(this._routeToAnimate.routeCoordinates)); } @computed get allRoutesGeoJson(): FeatureCollection { @@ -233,29 +222,14 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem }; } - @computed get SidebarShown() { - return this.layoutDoc._layout_showSidebar ? true : false; - } - @computed get sidebarWidthPercent() { - return StrCast(this.layoutDoc._layout_sidebarWidthPercent, '0%'); - } - @computed get sidebarColor() { - return StrCast(this.layoutDoc.sidebar_color, StrCast(this.layoutDoc[this._props.fieldKey + '_backgroundColor'], '#e4e4e4')); - } - @computed get SidebarKey() { - return this.fieldKey + '_sidebar'; - } - componentDidMount() { this._unmounting = false; this._props.setContentViewBox?.(this); } - _unmounting = false; - componentWillUnmount(): void { + componentWillUnmount() { this._unmounting = true; this.deselectPinOrRoute(); - this._rerenderTimeout && clearTimeout(this._rerenderTimeout); Object.keys(this._disposers).forEach(key => this._disposers[key]?.()); } @@ -269,7 +243,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem if (!this.layoutDoc._layout_showSidebar) this.toggleSidebar(); const docs = doc instanceof Doc ? [doc] : doc; docs.forEach(doc => { - let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this.selectedPinOrRoute; + let existingPin = this.allPushpins.find(pin => pin.latitude === doc.latitude && pin.longitude === doc.longitude) ?? this._selectedPinOrRoute; if (doc.latitude !== undefined && doc.longitude !== undefined && !existingPin) { existingPin = this.createPushpin(NumCast(doc.latitude), NumCast(doc.longitude), StrCast(doc.map)); } @@ -370,10 +344,10 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const sourceAnchorCreator = action(() => { const note = this.getAnchor(true); - if (note && this.selectedPinOrRoute) { - note.latitude = this.selectedPinOrRoute.latitude; - note.longitude = this.selectedPinOrRoute.longitude; - note.map = this.selectedPinOrRoute.map; + if (note && this._selectedPinOrRoute) { + note.latitude = this._selectedPinOrRoute.latitude; + note.longitude = this._selectedPinOrRoute.longitude; + note.map = this._selectedPinOrRoute.map; } return note as Doc; }); @@ -399,10 +373,10 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const createFunc = undoable( action(() => { const note = this._sidebarRef.current?.anchorMenuClick(this.getAnchor(true), ['latitude', 'longitude', LinkedTo]); - if (note && this.selectedPinOrRoute) { - note.latitude = this.selectedPinOrRoute.latitude; - note.longitude = this.selectedPinOrRoute.longitude; - note.map = this.selectedPinOrRoute.map; + if (note && this._selectedPinOrRoute) { + note.latitude = this._selectedPinOrRoute.latitude; + note.longitude = this._selectedPinOrRoute.longitude; + note.map = this._selectedPinOrRoute.map; } }), 'create note annotation' @@ -423,12 +397,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem return false; }; - setPreviewCursor = (func?: (x: number, y: number, drag: boolean, hide: boolean, doc: Opt) => void) => (this._setPreviewCursor = func); - - addDocumentWrapper = (doc: Doc | Doc[], annotationKey?: string) => this.addDocument(doc, annotationKey); - pointerEvents = () => (this._props.isContentActive() && !MarqueeOptionsMenu.Instance.isShown() ? 'all' : 'none'); - panelWidth = () => this._props.PanelWidth() / (this._props.NativeDimScaling?.() || 1) - this.sidebarWidth(); panelHeight = () => this._props.PanelHeight() / (this._props.NativeDimScaling?.() || 1); scrollXf = () => this.ScreenToLocalBoxXf().translate(0, NumCast(this.layoutDoc._layout_scrollTop)); @@ -439,52 +408,9 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem anchorMenuClick = () => this._sidebarRef.current?.anchorMenuClick; savedAnnotations = () => this._savedAnnotations; - _bingSearchManager: any; - _bingMap: any; - get MicrosoftMaps() { - return (window as any).Microsoft.Maps; - } - // uses Bing Search to retrieve lat/lng for a location. eg., - // const results = this.geocodeQuery(map.map, 'Philadelphia, PA'); - // to move the map to that location: - // const location = await this.geocodeQuery(this._bingMap, 'Philadelphia, PA'); - // this._bingMap.current.setView({ - // mapTypeId: this.MicrosoftMaps.MapTypeId.aerial, - // center: new this.MicrosoftMaps.Location(loc.latitude, loc.longitude), - // }); - // - bingGeocode = (map: any, query: string) => { - return new Promise<{ latitude: number; longitude: number }>((res, reject) => { - //If search manager is not defined, load the search module. - if (!this._bingSearchManager) { - //Create an instance of the search manager and call the geocodeQuery function again. - this.MicrosoftMaps.loadModule('Microsoft.Maps.Search', () => { - this._bingSearchManager = new this.MicrosoftMaps.Search.SearchManager(map.current); - res(this.bingGeocode(map, query)); - }); - } else { - this._bingSearchManager.geocode({ - where: query, - callback: action((r: any) => res(r.results[0].location)), - errorCallback: (e: any) => reject(), - }); - } - }); - }; - - @observable - bingSearchBarContents: any = this.Document.map; // For Bing Maps: The contents of the Bing search bar (string) - - geoDataRequestOptions = { - entityType: 'PopulatedPlace', - }; - - // The pin that is selected - @observable selectedPinOrRoute: Doc | undefined = undefined; - @action deselectPinOrRoute = () => { - if (this.selectedPinOrRoute) { + if (this._selectedPinOrRoute) { // // Removes filter // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'remove'); // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'remove'); @@ -511,79 +437,6 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem } return new Promise>(res => DocumentManager.Instance.AddViewRenderedCb(doc, dv => res(dv))); }; - /* - * Pushpin onclick - */ - @action - pushpinClicked = (pinDoc: Doc) => { - this.deselectPinOrRoute(); - this.selectedPinOrRoute = pinDoc; - this.bingSearchBarContents = pinDoc.map; - - // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match'); - // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match'); - Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); - - this.recolorPin(this.selectedPinOrRoute, 'green'); - - MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; - MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; - MapAnchorMenu.Instance.OnClick = this.createNoteAnnotation; - MapAnchorMenu.Instance.StartDrag = this.startAnchorDrag; - - const point = this._bingMap.current.tryLocationToPixel(new this.MicrosoftMaps.Location(this.selectedPinOrRoute.latitude, this.selectedPinOrRoute.longitude)); - const x = point.x + (this._props.PanelWidth() - this.sidebarWidth()) / 2; - const y = point.y + this._props.PanelHeight() / 2 + 32; - const cpt = this.ScreenToLocalBoxXf().inverse().transformPoint(x, y); - MapAnchorMenu.Instance.jumpTo(cpt[0], cpt[1], true); - - document.addEventListener('pointerdown', this.tryHideMapAnchorMenu, true); - }; - - /** - * Map OnClick - */ - @action - mapOnClick = (e: { location: { latitude: any; longitude: any } }) => { - this._props.select(false); - this.deselectPinOrRoute(); - }; - /* - * Updates values of layout doc to match the current map - */ - @action - mapRecentered = () => { - if ( - Math.abs(NumCast(this.dataDoc.latitude) - this._bingMap.current.getCenter().latitude) > 1e-7 || // - Math.abs(NumCast(this.dataDoc.longitude) - this._bingMap.current.getCenter().longitude) > 1e-7 - ) { - this.dataDoc.latitude = this._bingMap.current.getCenter().latitude; - this.dataDoc.longitude = this._bingMap.current.getCenter().longitude; - this.dataDoc.map = ''; - this.bingSearchBarContents = ''; - } - this.dataDoc.map_zoom = this._bingMap.current.getZoom(); - }; - /* - * Updates maptype - */ - @action - updateMapType = () => (this.dataDoc.map_type = this._bingMap.current.getMapTypeId()); - - /* - * For Bing Maps - * Called by search button's onClick - * Finds the geocode of the searched contents and sets location to that location - **/ - @action - bingSearch = () => { - return this.bingGeocode(this._bingMap, this.bingSearchBarContents).then(location => { - this.dataDoc.latitude = location.latitude; - this.dataDoc.longitude = location.longitude; - this.dataDoc.map_zoom = this._bingMap.current.getZoom(); - this.dataDoc.map = this.bingSearchBarContents; - }); - }; /* * Returns doc w/ relevant info @@ -592,14 +445,14 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem /// this should use SELECTED pushpin for lat/long if there is a selection, otherwise CENTER const anchor = Docs.Create.ConfigDocument({ title: 'MapAnchor:' + this.Document.title, - text: (StrCast(this.selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any, - config_latitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude), - config_longitude: NumCast((existingPin ?? this.selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude), + text: (StrCast(this._selectedPinOrRoute?.map) || StrCast(this.Document.map) || 'map location') as any, + config_latitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.latitude ?? this.dataDoc.latitude), + config_longitude: NumCast((existingPin ?? this._selectedPinOrRoute)?.longitude ?? this.dataDoc.longitude), config_map_zoom: NumCast(this.dataDoc.map_zoom), // config_map_type: StrCast(this.dataDoc.map_type), - config_map: StrCast((existingPin ?? this.selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map), + config_map: StrCast((existingPin ?? this._selectedPinOrRoute)?.map) || StrCast(this.dataDoc.map), layout_unrendered: true, - mapPin: existingPin ?? this.selectedPinOrRoute, + mapPin: existingPin ?? this._selectedPinOrRoute, annotationOn: this.Document, }); if (anchor) { @@ -613,25 +466,6 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem map_docToPinMap = new Map(); map_pinHighlighted = new Map(); - /* - * Input: pin doc - * Adds MicrosoftMaps Pushpin to the map (render) - */ - @action - addPushpin = (pin: Doc) => { - const pushPin = pin.infoWindowOpen - ? new this.MicrosoftMaps.Pushpin(new this.MicrosoftMaps.Location(pin.latitude, pin.longitude), {}) - : new this.MicrosoftMaps.Pushpin( - new this.MicrosoftMaps.Location(pin.latitude, pin.longitude) - // {icon: 'http://icons.iconarchive.com/icons/icons-land/vista-map-markers/24/Map-Marker-Marker-Outside-Chartreuse-icon.png'} - ); - - this._bingMap.current.entities.push(pushPin); - - this.MicrosoftMaps.Events.addHandler(pushPin, 'click', (e: any) => this.pushpinClicked(pin)); - // this.MicrosoftMaps.Events.addHandler(pushPin, 'dblclick', (e: any) => this.pushpinDblClicked(pushPin, pin)); - this.map_docToPinMap.set(pin, pushPin); - }; /* * Input: pin doc @@ -640,27 +474,16 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action removePushpinOrRoute = (pinOrRouteDoc: Doc) => this.removeMapDocument(pinOrRouteDoc, this.annotationKey); - /* - * Removes pushpin from map render - */ - deletePushpin = (pinDoc: Doc) => { - if (!this._unmounting) { - this._bingMap.current.entities.remove(this.map_docToPinMap.get(pinDoc)); - } - this.map_docToPinMap.delete(pinDoc); - this.selectedPinOrRoute = undefined; - }; - @action deleteSelectedPinOrRoute = undoable(() => { console.log('deleting'); - if (this.selectedPinOrRoute) { + if (this._selectedPinOrRoute) { // Removes filter - Doc.setDocFilter(this.Document, 'latitude', this.selectedPinOrRoute.latitude, 'remove'); - Doc.setDocFilter(this.Document, 'longitude', this.selectedPinOrRoute.longitude, 'remove'); - Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this.selectedPinOrRoute))}`, 'remove'); + Doc.setDocFilter(this.Document, 'latitude', this._selectedPinOrRoute.latitude, 'remove'); + Doc.setDocFilter(this.Document, 'longitude', this._selectedPinOrRoute.longitude, 'remove'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(DocCast(this._selectedPinOrRoute))}`, 'remove'); - this.removePushpinOrRoute(this.selectedPinOrRoute); + this.removePushpinOrRoute(this._selectedPinOrRoute); } MapAnchorMenu.Instance.fadeOut(true); document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); @@ -677,7 +500,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem e.preventDefault(); MapAnchorMenu.Instance.fadeOut(true); runInAction(() => { - this.temporaryRouteSource = { + this._temporaryRouteSource = { type: 'FeatureCollection', features: [], }; @@ -688,9 +511,9 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action centerOnSelectedPin = () => { - if (this.selectedPinOrRoute) { + if (this._selectedPinOrRoute) { this._mapRef.current?.flyTo({ - center: [NumCast(this.selectedPinOrRoute.longitude), NumCast(this.selectedPinOrRoute.latitude)], + center: [NumCast(this._selectedPinOrRoute.longitude), NumCast(this._selectedPinOrRoute.latitude)], }); } // if (this.selectedPin) { @@ -703,33 +526,6 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu); }; - /** - * View options for bing maps - */ - bingViewOptions = { - // center: { latitude: this.dataDoc.latitude ?? defaultCenter.lat, longitude: this.dataDoc.longitude ?? defaultCenter.lng }, - zoom: this.dataDoc.latitude ?? 10, - mapTypeId: 'grayscale', - }; - - /** - * Map options - */ - bingMapOptions = { - navigationBarMode: 'square', - backgroundColor: '#f1f3f4', - enableInertia: true, - supportedMapTypes: ['grayscale', 'canvasLight'], - disableMapTypeSelectorMouseOver: true, - // showScalebar:true - // disableRoadView:true, - // disableBirdseye:true - streetsideOptions: { - showProblemReporting: false, - showCurrentAddress: false, - }, - }; - recolorPin = (pin: Doc, color?: string) => { // this._bingMap.current.entities.remove(this.map_docToPinMap.get(pin)); // this.map_docToPinMap.delete(pin); @@ -739,109 +535,6 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem // this.map_docToPinMap.set(pin, newpin); }; - /* - * Called when BingMap is first rendered - * Initializes starting values - */ - @observable _mapReady = false; - @action - bingMapReady = (map: any) => { - this._mapReady = true; - this._bingMap = map.map; - if (!this._bingMap.current) { - alert('NO Map!?'); - } - this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'click', this.mapOnClick); - this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'viewchangeend', undoable(this.mapRecentered, 'Map Layout Change')); - this.MicrosoftMaps.Events.addHandler(this._bingMap.current, 'maptypechanged', undoable(this.updateMapType, 'Map ViewType Change')); - - this._disposers.mapLocation = reaction( - () => this.Document.map, - mapLoc => (this.bingSearchBarContents = mapLoc), - { fireImmediately: true } - ); - this._disposers.highlight = reaction( - () => this.allAnnotations.map(doc => doc[Highlight]), - () => { - const allConfigPins = this.allAnnotations.map(doc => ({ doc, pushpin: DocCast(doc.mapPin) })).filter(pair => pair.pushpin); - allConfigPins.forEach(({ doc, pushpin }) => { - if (!pushpin[Highlight] && this.map_pinHighlighted.get(pushpin)) { - this.recolorPin(pushpin); - this.map_pinHighlighted.delete(pushpin); - } - }); - allConfigPins.forEach(({ doc, pushpin }) => { - if (doc[Highlight] && !this.map_pinHighlighted.get(pushpin)) { - this.recolorPin(pushpin, 'orange'); - this.map_pinHighlighted.set(pushpin, true); - } - }); - }, - { fireImmediately: true } - ); - - this._disposers.location = reaction( - () => ({ lat: this.Document.latitude, lng: this.Document.longitude, zoom: this.Document.map_zoom, mapType: this.Document.map_type }), - locationObject => { - // if (this._bingMap.current) - try { - locationObject?.zoom && - this._bingMap.current?.setView({ - mapTypeId: locationObject.mapType, - zoom: locationObject.zoom, - center: new this.MicrosoftMaps.Location(locationObject.lat, locationObject.lng), - }); - } catch (e) { - console.log(e); - } - }, - { fireImmediately: true } - ); - }; - - dragToggle = (e: React.PointerEvent) => { - let dragClone: HTMLDivElement | undefined; - - setupMoveUpEvents( - e, - e, - e => { - // move event - if (!dragClone) { - dragClone = this._dragRef.current?.cloneNode(true) as HTMLDivElement; // copy draggable pin - dragClone.style.position = 'absolute'; - dragClone.style.zIndex = '10000'; - DragManager.Root().appendChild(dragClone); // add clone to root - } - dragClone.style.transform = `translate(${e.clientX - 15}px, ${e.clientY - 15}px)`; - return false; - }, - e => { - // up event - if (!dragClone) return; - DragManager.Root().removeChild(dragClone); - let target = document.elementFromPoint(e.x, e.y); // element for specified x and y coordinates - while (target) { - if (target === this._ref.current) { - const cpt = this.ScreenToLocalBoxXf().transformPoint(e.clientX, e.clientY); - const x = cpt[0] - (this._props.PanelWidth() - this.sidebarWidth()) / 2; - const y = cpt[1] - 20 /* height of search bar */ - this._props.PanelHeight() / 2; - const location = this._bingMap.current.tryPixelToLocation(new this.MicrosoftMaps.Point(x, y)); - this.createPushpin(location.latitude, location.longitude); - break; - } - target = target.parentElement; - } - }, - e => { - const createPin = () => this.createPushpin(this.Document.latitude, this.Document.longitude, this.Document.map); - if (this.bingSearchBarContents) { - this.bingSearch().then(createPin); - } else createPin(); - } - ); - }; - // incrementer: number = 0; /* * Creates Pushpin doc and adds it to the list of annotations @@ -880,7 +573,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem if (createPinForDestination) { this.createPushpin(destination.center[1], destination.center[0], destination.place_name); } - this.temporaryRouteSource = { + this._temporaryRouteSource = { type: 'FeatureCollection', features: [], }; @@ -890,10 +583,14 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem // TODO: Display error that can't create route to same location }, 'createmaproute'); - searchbarKeyDown = (e: any) => e.key === 'Enter' && this.bingSearch(); - - @observable - featuresFromGeocodeResults: any[] = []; + @action + searchbarKeyDown = (e: any) => { + if (e.key === 'Enter' && this._featuresFromGeocodeResults) { + const center = this._featuresFromGeocodeResults[0]?.center; + this._featuresFromGeocodeResults = []; + setTimeout(() => center && this._mapRef.current?.flyTo({ center })); + } + }; @action addMarkerForFeature = (feature: any) => { @@ -910,7 +607,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem center: feature.center, }); } - this.featuresFromGeocodeResults = []; + this._featuresFromGeocodeResults = []; } else { // TODO: handle error } @@ -922,11 +619,11 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem */ handleSearchChange = async (searchText: string) => { const features = await MapboxApiUtility.forwardGeocodeForFeatures(searchText); - if (features && !this.isAnimating) { + if (features && !this._isAnimating) { runInAction(() => { - this.settingsOpen = false; - this.featuresFromGeocodeResults = features; - this.routeToAnimate = undefined; + this._settingsOpen = false; + this._featuresFromGeocodeResults = features; + this._routeToAnimate = undefined; }); } // try { @@ -946,8 +643,8 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action handleMapClick = (e: MapLayerMouseEvent) => { - this.featuresFromGeocodeResults = []; - this.settingsOpen = false; + this._featuresFromGeocodeResults = []; + this._settingsOpen = false; if (this._mapRef.current) { const features = this._mapRef.current.queryRenderedFeatures(e.point, { layers: ['map-routes-layer'], @@ -960,8 +657,8 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const routeDoc: Doc | undefined = this.allRoutes.find(routeDoc => routeDoc.title === routeTitle); this.deselectPinOrRoute(); // TODO: Also deselect route if selected if (routeDoc) { - this.selectedPinOrRoute = routeDoc; - Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); + this._selectedPinOrRoute = routeDoc; + Doc.setDocFilter(this.Document, LinkedTo, `mapRoute=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check'); // TODO: Recolor route @@ -1008,7 +705,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem const features = await MapboxApiUtility.reverseGeocodeForFeatures(longitude, latitude); if (features) { runInAction(() => { - this.featuresFromGeocodeResults = features; + this._featuresFromGeocodeResults = features; }); } @@ -1028,21 +725,18 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem // } }; - @observable - currentPopup: PopupInfo | undefined = undefined; - @action handleMarkerClick = (e: MarkerEvent, pinDoc: Doc) => { - this.featuresFromGeocodeResults = []; + this._featuresFromGeocodeResults = []; this.deselectPinOrRoute(); // TODO: check this method - this.selectedPinOrRoute = pinDoc; + this._selectedPinOrRoute = pinDoc; // this.bingSearchBarContents = pinDoc.map; // Doc.setDocFilter(this.Document, 'latitude', this.selectedPin.latitude, 'match'); // Doc.setDocFilter(this.Document, 'longitude', this.selectedPin.longitude, 'match'); - Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this.selectedPinOrRoute)}`, 'check'); + Doc.setDocFilter(this.Document, LinkedTo, `mapPin=${Field.toScriptString(this._selectedPinOrRoute)}`, 'check'); - this.recolorPin(this.selectedPinOrRoute, 'green'); // TODO: check this method + this.recolorPin(this._selectedPinOrRoute, 'green'); // TODO: check this method MapAnchorMenu.Instance.Delete = this.deleteSelectedPinOrRoute; MapAnchorMenu.Instance.Center = this.centerOnSelectedPin; @@ -1072,12 +766,6 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem // }) }; - @observable - temporaryRouteSource: FeatureCollection = { - type: 'FeatureCollection', - features: [], - }; - @action displayRoute = (routeInfoMap: Record | undefined, type: TransportationType) => { if (routeInfoMap) { @@ -1096,41 +784,23 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem }; // TODO: Create pin for destination // TODO: Fly to point where full route will be shown - this.temporaryRouteSource = newTempRouteSource; + this._temporaryRouteSource = newTempRouteSource; } }; - @observable - isAnimating: boolean = false; - - @observable - routeToAnimate: Doc | undefined = undefined; - - @observable - animationPhase: number = 0; - - @observable - finishedFlyTo: boolean = false; - @action setAnimationPhase = (newValue: number) => { - this.animationPhase = newValue; + this._animationPhase = newValue; }; - @observable - frameId: number | null = null; - @action setFrameId = (frameId: number) => { - this.frameId = frameId; + this._frameId = frameId; }; - @observable - animationUtility: AnimationUtility | null = null; - @action setAnimationUtility = (util: AnimationUtility) => { - this.animationUtility = util; + this._animationUtility = util; }; @action @@ -1138,8 +808,8 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem if (routeDoc) { MapAnchorMenu.Instance.fadeOut(true); document.removeEventListener('pointerdown', this.tryHideMapAnchorMenu, true); - this.featuresFromGeocodeResults = []; - this.routeToAnimate = routeDoc; + this._featuresFromGeocodeResults = []; + this._routeToAnimate = routeDoc; } }; @@ -1161,29 +831,20 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @computed get preAnimationViewState() { - if (!this.isAnimating) { + if (!this._isAnimating) { return this.mapboxMapViewState; } } - @observable - isStreetViewAnimation: boolean = false; - - @observable - animationSpeed: AnimationSpeed = AnimationSpeed.MEDIUM; - - @observable - animationLineColor: string = '#ffff00'; - @action setAnimationLineColor = (color: ColorResult) => { - this.animationLineColor = color.hex; + this._animationLineColor = color.hex; }; @action updateAnimationSpeed = () => { let newAnimationSpeed: AnimationSpeed; - switch (this.animationSpeed) { + switch (this._animationSpeed) { case AnimationSpeed.SLOW: newAnimationSpeed = AnimationSpeed.MEDIUM; break; @@ -1197,63 +858,33 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem newAnimationSpeed = AnimationSpeed.MEDIUM; break; } - this.animationSpeed = newAnimationSpeed; - if (this.animationUtility) { - this.animationUtility.updateAnimationSpeed(newAnimationSpeed); + this._animationSpeed = newAnimationSpeed; + if (this._animationUtility) { + this._animationUtility.updateAnimationSpeed(newAnimationSpeed); } }; @computed get animationSpeedTooltipText(): string { - switch (this.animationSpeed) { - case AnimationSpeed.SLOW: - return '1x speed'; - case AnimationSpeed.MEDIUM: - return '2x speed'; - case AnimationSpeed.FAST: - return '3x speed'; - default: - return '2x speed'; - } + switch (this._animationSpeed) { + case AnimationSpeed.SLOW: return '1x speed'; + case AnimationSpeed.MEDIUM: return '2x speed'; + case AnimationSpeed.FAST: return '3x speed'; + default: return '2x speed'; + } // prettier-ignore } @computed get animationSpeedIcon(): JSX.Element { - switch (this.animationSpeed) { - case AnimationSpeed.SLOW: - return slowSpeedIcon; - case AnimationSpeed.MEDIUM: - return mediumSpeedIcon; - case AnimationSpeed.FAST: - return fastSpeedIcon; - default: - return mediumSpeedIcon; - } + switch (this._animationSpeed) { + case AnimationSpeed.SLOW: return slowSpeedIcon; + case AnimationSpeed.MEDIUM: return mediumSpeedIcon; + case AnimationSpeed.FAST: return fastSpeedIcon; + default: return mediumSpeedIcon; + } // prettier-ignore } @action toggleIsStreetViewAnimation = () => { - const newVal = !this.isStreetViewAnimation; - this.isStreetViewAnimation = newVal; - if (this.animationUtility) { - this.animationUtility.updateIsStreetViewAnimation(newVal); - } - }; - - @observable - dynamicRouteFeature: Feature = { - type: 'Feature', - properties: {}, - geometry: { - type: 'LineString', - coordinates: [], - }, - }; - - @observable - path: turf.helpers.Feature = { - type: 'Feature', - geometry: { - type: 'LineString', - coordinates: [], - }, - properties: {}, + const newVal = !this._isStreetViewAnimation; + this._isStreetViewAnimation = newVal; + this._animationUtility?.updateIsStreetViewAnimation(newVal); }; getFeatureFromRouteDoc = (routeDoc: Doc): Feature => { @@ -1272,19 +903,19 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action playAnimation = (status: AnimationStatus) => { - if (!this._mapRef.current || !this.routeToAnimate) { + if (!this._mapRef.current || !this._routeToAnimate) { return; } - this.animationPhase = status === AnimationStatus.RESUME ? this.animationPhase : 0; - this.frameId = AnimationStatus.RESUME ? this.frameId : null; - this.finishedFlyTo = AnimationStatus.RESUME ? this.finishedFlyTo : false; + this._animationPhase = status === AnimationStatus.RESUME ? this._animationPhase : 0; + this._frameId = AnimationStatus.RESUME ? this._frameId : null; + this._finishedFlyTo = AnimationStatus.RESUME ? this._finishedFlyTo : false; const path = turf.lineString(this.selectedRouteCoordinates); - this.settingsOpen = false; + this._settingsOpen = false; this.path = path; - this.isAnimating = true; + this._isAnimating = true; runInAction(() => { return new Promise(async resolve => { @@ -1293,18 +924,11 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem lat: this.selectedRouteCoordinates[0][1], }; - const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this.isStreetViewAnimation, this.animationSpeed, this.showTerrain, this._mapRef.current); - runInAction(() => { - this.setAnimationUtility(animationUtil); - }); - - const updateFrameId = (newFrameId: number) => { - this.setFrameId(newFrameId); - }; + const animationUtil = new AnimationUtility(targetLngLat, this.selectedRouteCoordinates, this._isStreetViewAnimation, this._animationSpeed, this._showTerrain, this._mapRef.current); + runInAction(() => this.setAnimationUtility(animationUtil)); - const updateAnimationPhase = (newAnimationPhase: number) => { - this.setAnimationPhase(newAnimationPhase); - }; + const updateFrameId = (newFrameId: number) => this.setFrameId(newFrameId); + const updateAnimationPhase = (newAnimationPhase: number) => this.setAnimationPhase(newAnimationPhase); if (status !== AnimationStatus.RESUME) { const result = await animationUtil.flyInAndRotate({ @@ -1324,9 +948,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem console.log('Altitude: ', result.altitude); } - runInAction(() => { - this.finishedFlyTo = true; - }); + runInAction(() => (this._finishedFlyTo = true)); // follow the path while slowly rotating the camera, passing in the camera bearing and altitude from the previous animation await animationUtil.animatePath({ @@ -1335,7 +957,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem // startBearing: -20, // startAltitude: this.isStreetViewAnimation ? 80 : 12000, // pitch: this.isStreetViewAnimation ? 80: 50, - currentAnimationPhase: this.animationPhase, + currentAnimationPhase: this._animationPhase, updateAnimationPhase, updateFrameId, }); @@ -1353,7 +975,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem }); setTimeout(() => { - this.isStreetViewAnimation = false; + this._isStreetViewAnimation = false; resolve(); }, 10000); }); @@ -1362,27 +984,27 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action pauseAnimation = () => { - if (this.frameId && this.animationPhase > 0) { - window.cancelAnimationFrame(this.frameId); - this.frameId = null; - this.isAnimating = false; + if (this._frameId && this._animationPhase > 0) { + window.cancelAnimationFrame(this._frameId); + this._frameId = null; + this._isAnimating = false; } }; @action stopAnimation = (close: boolean) => { - if (this.frameId) { - window.cancelAnimationFrame(this.frameId); + if (this._frameId) { + window.cancelAnimationFrame(this._frameId); } - this.animationPhase = 0; - this.frameId = null; - this.finishedFlyTo = false; - this.isAnimating = false; + this._animationPhase = 0; + this._frameId = null; + this._finishedFlyTo = false; + this._isAnimating = false; if (close) { - this.animationSpeed = AnimationSpeed.MEDIUM; - this.isStreetViewAnimation = false; - this.routeToAnimate = undefined; - this.animationUtility = null; + this._animationSpeed = AnimationSpeed.MEDIUM; + this._isStreetViewAnimation = false; + this._routeToAnimate = undefined; + this._animationUtility = null; } }; @@ -1390,21 +1012,21 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem return ( <> { - if (this.isAnimating && this.finishedFlyTo) { + if (this._isAnimating && this._finishedFlyTo) { this.pauseAnimation(); - } else if (this.animationPhase > 0) { + } else if (this._animationPhase > 0) { this.playAnimation(AnimationStatus.RESUME); // Resume from the current phase } else { this.playAnimation(AnimationStatus.START); // Play from the beginning } }} - icon={this.isAnimating && this.finishedFlyTo ? : } + icon={this._isAnimating && this._finishedFlyTo ? : } color="black" size={Size.MEDIUM} /> - {this.isAnimating && this.finishedFlyTo && ( + {this._isAnimating && this._finishedFlyTo && ( { @@ -1420,7 +1042,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem <>
      |
      - } /> + } />
      |
      |
      @@ -1436,26 +1058,17 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action hideRoute = () => { - this.temporaryRouteSource = { + this._temporaryRouteSource = { type: 'FeatureCollection', features: [], }; }; - @observable - settingsOpen: boolean = false; - - @observable - mapStyle: string = 'mapbox://styles/mapbox/standard'; - - @observable - showTerrain: boolean = true; - @action toggleSettings = () => { - if (!this.isAnimating && this.animationPhase == 0) { - this.featuresFromGeocodeResults = []; - this.settingsOpen = !this.settingsOpen; + if (!this._isAnimating && this._animationPhase == 0) { + this._featuresFromGeocodeResults = []; + this._settingsOpen = !this._settingsOpen; } }; @@ -1500,14 +1113,10 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem @action onStepZoomChange = (increment: boolean) => { if (this._mapRef.current) { - let newZoom: number; - if (increment) { - console.log('inc'); - newZoom = Math.min(16, this.mapboxMapViewState.zoom + 1); - } else { - console.log('dec'); - newZoom = Math.max(0, this.mapboxMapViewState.zoom - 1); - } + const newZoom = increment // + ? Math.min(16, this.mapboxMapViewState.zoom + 1) + : Math.max(0, this.mapboxMapViewState.zoom - 1); + this._mapRef.current.setZoom(newZoom); this.dataDoc.map_zoom = newZoom; } @@ -1523,7 +1132,7 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem }; @action - toggleShowTerrain = () => (this.showTerrain = !this.showTerrain); + toggleShowTerrain = () => (this._showTerrain = !this._showTerrain); getMarkerIcon = (pinDoc: Doc): JSX.Element | null => { const markerType = StrCast(pinDoc.markerType); @@ -1532,25 +1141,8 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem return MarkerIcons.getFontAwesomeIcon(markerType, '2x', markerColor) ?? null; }; - static _firstRender = true; - static _rerenderDelay = 500; - _rerenderTimeout: any; + _textRef = React.createRef(); render() { - // bcz: no idea what's going on here, but bings maps have some kind of bug - // such that we need to delay rendering a second map on startup until the first map is rendered. - this.Document[DocCss]; - if (MapBox._rerenderDelay) { - // prettier-ignore - this._rerenderTimeout = this._rerenderTimeout ?? - setTimeout(action(() => { - if ((window as any).Microsoft?.Maps?.Internal._WorkDispatcher) { - MapBox._rerenderDelay = 0; - } - this._rerenderTimeout = undefined; - this.Document[DocCss] = this.Document[DocCss] + 1; - }), MapBox._rerenderDelay); - return null; - } const scale = this._props.NativeDimScaling?.() || 1; const parscale = scale === 1 ? 1 : this.ScreenToLocalBoxXf().Scale ?? 1; @@ -1560,20 +1152,18 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem
      e.stopPropagation()} - onPointerDown={async e => { - e.button === 0 && !e.ctrlKey && e.stopPropagation(); - }} + onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} style={{ transformOrigin: 'top left', transform: `scale(${scale})`, width: `calc(100% - ${this.sidebarWidthPercent})`, pointerEvents: this.pointerEvents() }}>
      {renderAnnotations(this.transparentFilter)}
      {renderAnnotations(this.opaqueFilter)} {SnappingManager.IsDragging ? null : renderAnnotations()} - {!this.routeToAnimate && ( + {!this._routeToAnimate && (
      - this.handleSearchChange(e.target.value)} /> + this.handleSearchChange(e.target.value)} /> } type={Type.TERT} onClick={e => this.toggleSettings()} />
      )} - {this.settingsOpen && !this.routeToAnimate && ( + {this._settingsOpen && !this._routeToAnimate && (
      Map Style:
      @@ -1605,21 +1195,21 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem
      Show terrain:
      - +
      )} - {this.routeToAnimate && ( + {this._routeToAnimate && (
      -
      {StrCast(this.routeToAnimate.title)}
      +
      {StrCast(this._routeToAnimate.title)}
      {this.getRouteAnimationOptions()}
      )} - {this.featuresFromGeocodeResults.length > 0 && ( + {this._featuresFromGeocodeResults.length > 0 && (
      - + <>

      Choose a location for your pin:

      - {this.featuresFromGeocodeResults + {this._featuresFromGeocodeResults .filter(feature => feature.place_name) .map((feature, idx) => (
      () implem
      {feature.place_name}
      ))} -
      +
      )} () implem width: NumCast(this.layoutDoc._width) * parscale, height: NumCast(this.layoutDoc._height) * parscale, }} - initialViewState={this.isAnimating ? undefined : this.mapboxMapViewState} + initialViewState={this._isAnimating ? undefined : this.mapboxMapViewState} onZoom={this.onMapZoom} onMove={this.onMapMove} onClick={this.handleMapClick} onDblClick={this.handleMapDblClick} - terrain={this.showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}> + terrain={this._showTerrain ? { source: 'mapbox-dem', exaggeration: 2.0 } : undefined}> - + - {!this.isAnimating && this.animationPhase == 0 && } - {this.routeToAnimate && (this.isAnimating || this.animationPhase > 0) && ( + {!this._isAnimating && this._animationPhase == 0 && ( + + )} + {this._routeToAnimate && (this._isAnimating || this._animationPhase > 0) && ( <> - {!this.isStreetViewAnimation && ( + {!this._isStreetViewAnimation && ( <> () implem type="line" source="animated-route" paint={{ - 'line-color': this.animationLineColor, + 'line-color': this._animationLineColor, 'line-width': 5, }} /> @@ -1722,10 +1314,9 @@ export class MapBox extends ViewBoxAnnotatableComponent() implem )} <> - {!this.isAnimating && - this.animationPhase == 0 && - this.allPushpins - // .filter(anno => !anno.layout_unrendered) + {!this._isAnimating && + this._animationPhase == 0 && + this.allPushpins // .filter(anno => !anno.layout_unrendered) .map((pushpin, idx) => ( ) => this.handleMarkerClick(e, pushpin)}> {this.getMarkerIcon(pushpin)} diff --git a/src/client/views/nodes/MapBox/MapPushpinBox.tsx b/src/client/views/nodes/MapBox/MapPushpinBox.tsx index fc5b4dd18..8ebc90157 100644 --- a/src/client/views/nodes/MapBox/MapPushpinBox.tsx +++ b/src/client/views/nodes/MapBox/MapPushpinBox.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { FieldView, FieldViewProps } from '../FieldView'; -import { MapBox } from './MapBox'; +import { MapBoxContainer } from '../MapboxMapBox/MapboxContainer'; /** * Map Pushpin doc class @@ -18,7 +18,7 @@ export class MapPushpinBox extends ViewBoxBaseComponent() { } get mapBoxView() { - return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBox; + return this.DocumentView?.()?.containerViewPath?.().lastElement()?.ComponentView as MapBoxContainer; } get mapBox() { return this.DocumentView?.().containerViewPath?.().lastElement()?.Document; diff --git a/src/client/views/nodes/RecordingBox/RecordingView.scss b/src/client/views/nodes/RecordingBox/RecordingView.scss index 287cccd8f..f2d5a980d 100644 --- a/src/client/views/nodes/RecordingBox/RecordingView.scss +++ b/src/client/views/nodes/RecordingBox/RecordingView.scss @@ -1,208 +1,200 @@ video { - // flex: 100%; - width: 100%; - // min-height: 400px; - //height: auto; - height: 100%; - //display: block; - object-fit: cover; - background-color: black; -} - -button { - margin: 0 .5rem + // flex: 100%; + width: 100%; + // min-height: 400px; + //height: auto; + height: 100%; + //display: block; + object-fit: cover; + background-color: black; } .recording-container { - height: 100%; - width: 100%; - // display: flex; - pointer-events: all; - background-color: black; + height: 100%; + width: 100%; + // display: flex; + pointer-events: all; + background-color: black; + button { + margin: 0 0.5rem; + } } .video-wrapper { - // max-width: 600px; - // max-width: 700px; - // position: relative; - display: flex; - justify-content: center; - // overflow: hidden; - border-radius: 10px; - margin: 0; + // max-width: 600px; + // max-width: 700px; + // position: relative; + display: flex; + justify-content: center; + // overflow: hidden; + border-radius: 10px; + margin: 0; } .video-wrapper:hover .controls { - bottom: 34.5px; - transform: translateY(0%); - opacity: 100%; + bottom: 34.5px; + transform: translateY(0%); + opacity: 100%; } .controls { - display: flex; - align-items: center; - justify-content: center; - position: absolute; - width: 100%; - flex-wrap: wrap; - background: rgba(255, 255, 255, 0.25); - box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1); - // backdrop-filter: blur(4px); - border-radius: 10px; - border: 1px solid rgba(255, 255, 255, 0.18); - transition: all 0.3s ease-in-out; - bottom: 34.5px; - height: 60px; + display: flex; + align-items: center; + justify-content: center; + position: absolute; + width: 100%; + flex-wrap: wrap; + background: rgba(255, 255, 255, 0.25); + box-shadow: 0 8px 32px 0 rgba(255, 255, 255, 0.1); + // backdrop-filter: blur(4px); + border-radius: 10px; + border: 1px solid rgba(255, 255, 255, 0.18); + transition: all 0.3s ease-in-out; + bottom: 34.5px; + height: 60px; } .controls:active { - bottom: 40px; + bottom: 40px; } .actions button { - background: none; - border: none; - outline: none; - cursor: pointer; + background: none; + border: none; + outline: none; + cursor: pointer; } .actions button i { - background-color: none; - color: white; - font-size: 30px; + background-color: none; + color: white; + font-size: 30px; } - .velocity { - appearance: none; - background: none; - color: white; - outline: none; - border: none; - text-align: center; - font-size: 16px; + appearance: none; + background: none; + color: white; + outline: none; + border: none; + text-align: center; + font-size: 16px; } .mute-btn { - background: none; - border: none; - outline: none; - cursor: pointer; + background: none; + border: none; + outline: none; + cursor: pointer; } .mute-btn i { - background-color: none; - color: white; - font-size: 20px; + background-color: none; + color: white; + font-size: 20px; } .recording-sign { - height: 20px; - width: auto; - display: flex; - flex-direction: row; - position: absolute; - top: 10px; - right: 15px; - align-items: center; - justify-content: center; - - .timer { - font-size: 15px; - color: white; - margin: 0; - } - - .dot { - height: 15px; - width: 15px; - margin: 5px; - background-color: red; - border-radius: 50%; - display: inline-block; - } + height: 20px; + width: auto; + display: flex; + flex-direction: row; + position: absolute; + top: 10px; + right: 15px; + align-items: center; + justify-content: center; + + .timer { + font-size: 15px; + color: white; + margin: 0; + } + + .dot { + height: 15px; + width: 15px; + margin: 5px; + background-color: red; + border-radius: 50%; + display: inline-block; + } } .controls-inner-container { - display: flex; - flex-direction: row; - position: relative; - width: 100%; - align-items: center; - justify-content: center; + display: flex; + flex-direction: row; + position: relative; + width: 100%; + align-items: center; + justify-content: center; } .record-button-wrapper { - width: 35px; - height: 35px; - font-size: 0; - background-color: grey; - border: 0px; - border-radius: 35px; - margin: 10px; - display: flex; - justify-content: center; - - .record-button { - background-color: red; - border: 0px; - border-radius: 50%; - height: 80%; - width: 80%; - align-self: center; - margin: 0; - - &:hover { - height: 85%; - width: 85%; - } - } - - .stop-button { - background-color: red; - border: 0px; - border-radius: 10%; - height: 70%; - width: 70%; - align-self: center; - margin: 0; - - - // &:hover { - // width: 40px; - // height: 40px - // } - } - + width: 35px; + height: 35px; + font-size: 0; + background-color: grey; + border: 0px; + border-radius: 35px; + margin: 10px; + display: flex; + justify-content: center; + + .record-button { + background-color: red; + border: 0px; + border-radius: 50%; + height: 80%; + width: 80%; + align-self: center; + margin: 0; + + &:hover { + height: 85%; + width: 85%; + } + } + + .stop-button { + background-color: red; + border: 0px; + border-radius: 10%; + height: 70%; + width: 70%; + align-self: center; + margin: 0; + + // &:hover { + // width: 40px; + // height: 40px + // } + } } .options-wrapper { - height: 100%; - display: flex; - flex-direction: row; - align-content: center; - position: relative; - top: 0; - bottom: 0; - - &.video-edit-wrapper { - - // right: 50% - 15; - - .track-screen { - font-weight: 200; - } - - } - - &.track-screen-wrapper { - - // right: 50% - 30; - - .track-screen { - font-weight: 200; - color: aqua; - } - - } -} \ No newline at end of file + height: 100%; + display: flex; + flex-direction: row; + align-content: center; + position: relative; + top: 0; + bottom: 0; + + &.video-edit-wrapper { + // right: 50% - 15; + + .track-screen { + font-weight: 200; + } + } + + &.track-screen-wrapper { + // right: 50% - 30; + + .track-screen { + font-weight: 200; + color: aqua; + } + } +} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 8802a032f..cdfeebe66 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -22,7 +22,7 @@ import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; import { DocData } from '../../../../fields/DocSymbols'; -import { NodeSelection, TextSelection } from 'prosemirror-state'; +import { NodeSelection } from 'prosemirror-state'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -60,7 +60,7 @@ export class DashFieldView { return; } } - tBox.setFocus(state.selection.to + 1); + tBox.setFocus(state.selection.to); } } }; @@ -84,6 +84,7 @@ export class DashFieldView { width={node.attrs.width} height={node.attrs.height} hideKey={node.attrs.hideKey} + hideValue={node.attrs.hideValue} editable={node.attrs.editable} expanded={this.Expanded} dataDoc={node.attrs.dataDoc} @@ -112,6 +113,7 @@ interface IDashFieldViewInternal { fieldKey: string; docId: string; hideKey: boolean; + hideValue: boolean; tbox: FormattedTextBox; width: number; height: number; @@ -217,12 +219,28 @@ export class DashFieldViewInternal extends ObservableReactComponent this._dashDoc && (this._dashDoc[this._fieldKey + '_hideKey'] = !this._dashDoc[this._fieldKey + '_hideKey'])), + action(() => { + const editor = this._props.tbox.EditorView!; + editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideKey: !this._props.node.attrs.hideKey ? true : false })); + }), 'hideKey' ); + toggleValueHide = undoable( + action(() => { + const editor = this._props.tbox.EditorView!; + editor.dispatch(editor.state.tr.setNodeMarkup(this._props.getPos(), this._props.node.type, { ...this._props.node.attrs, hideValue: !this._props.node.attrs.hideValue ? true : false })); + }), + 'hideValue' + ); + + @observable _showValue = false; @computed get _hideKey() { - return this._dashDoc?.[this._fieldKey + '_hideKey'] && !this._expanded; + return this._props.hideKey && !this._expanded; + } + + @computed get _hideValue() { + return !this._showValue && this._props.hideValue && !this._expanded; } // clicking on the label creates a pivot view collection of all documents @@ -231,7 +249,11 @@ export class DashFieldViewInternal extends ObservableReactComponent { DashFieldViewMenu.createFieldView = this.createPivotForField; DashFieldViewMenu.toggleFieldHide = this.toggleFieldHide; + DashFieldViewMenu.toggleValueHide = this.toggleValueHide; DashFieldViewMenu.Instance.show(e.clientX, e.clientY + 16, this._fieldKey); + this._dashDoc?.[this._fieldKey + '_hideValue'] && runInAction(() => (this._showValue = !this._showValue)); + const editor = this._props.tbox.EditorView!; + setTimeout(() => editor.dispatch(editor.state.tr.setSelection(new NodeSelection(editor.state.doc.resolve(this._props.getPos())))), 100); }); }; @@ -257,12 +279,12 @@ export class DashFieldViewInternal extends ObservableReactComponent - {this._props.hideKey || this._hideKey ? null : ( + {this._hideKey ? null : ( {(Doc.AreProtosEqual(DocCast(this._textBoxDoc.rootDocument) ?? this._textBoxDoc, DocCast(this._dashDoc?.rootDocument) ?? this._dashDoc) ? '' : this._dashDoc?.title + ':') + this._fieldKey} )} - {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} + {this._props.fieldKey.startsWith('#') || this._hideValue ? null : this.fieldValueContent} {/* {!this.values.length ? null : ( + {!this.values.length ? null : ( + - )} */} + )}
      ); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 66df1eaf2..3700b08d6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2064,8 +2064,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (this._isHovering = true))} - onPointerLeave={action(() => (this._isHovering = false))} + onPointerEnter={action(() => { + this._isHovering = true; + this.layoutDoc[`_${this._props.fieldKey}_usePath`] && (this.Document.isHovering = true); + })} + onPointerLeave={action(() => (this.Document.isHovering = this._isHovering = false))} ref={r => { this._oldWheel?.removeEventListener('wheel', this.onPassiveWheel); this._oldWheel = r; -- cgit v1.2.3-70-g09d2 From 1e37adacfe5b44a71ed7d7dfd785c6c2d0808eae Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 31 Mar 2024 08:43:38 -0400 Subject: fixed divider column for notetaking view. fixed scrolling of text docs. fixed keeping user docs in system button bars. --- src/client/documents/Documents.ts | 2 +- .../views/collections/CollectionNoteTakingViewDivider.tsx | 12 ++++++++++++ src/client/views/nodes/formattedText/FormattedTextBox.scss | 1 + src/client/views/nodes/formattedText/FormattedTextBox.tsx | 1 + 4 files changed, 15 insertions(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 8dccdeba9..6a0d45543 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1591,7 +1591,7 @@ export namespace DocUtils { } }); items?.forEach(item => !DocListCast(doc.data).includes(item) && Doc.AddDocToList(Doc.GetProto(doc), 'data', item)); - items && DocListCast(doc.data).forEach(item => !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item)); + items && DocListCast(doc.data).forEach(item => Doc.IsSystem(item) && !items.includes(item) && Doc.RemoveDocFromList(Doc.GetProto(doc), 'data', item)); } return doc; } diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx index 5e4bce19d..50a97b978 100644 --- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx @@ -51,6 +51,18 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent +
      this.registerResizing(e)} + style={{ + height: '95%', + width: 12, + borderRight: '4px solid #282828', + borderLeft: '4px solid #282828', + position: 'fixed', + pointerEvents: 'none', + }} + />
      this.registerResizing(e)} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 03ff0436b..00d890860 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -156,6 +156,7 @@ audiotag:hover { .formattedTextBox-inner, .formattedTextBox-inner-minimal { height: 100%; + overflow: auto; white-space: pre-wrap; .ProseMirror:hover { background: rgba(200, 200, 200, 0.2); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 3700b08d6..2deecb6ad 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -2032,6 +2032,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Date: Sun, 31 Mar 2024 23:09:53 -0400 Subject: fixed scrolling to targets in text views. fixed referencing fields on another doc from text box. fixed '@name' technique for publishing documents to also remove documents and work anywhere a title is set. --- src/client/documents/Documents.ts | 5 ++++ src/client/util/Scripting.ts | 2 +- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/DocumentDecorations.tsx | 6 ---- .../views/collections/CollectionDockingView.tsx | 25 ++++++++++++++-- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- .../views/nodes/formattedText/DashFieldView.tsx | 8 ++--- .../views/nodes/formattedText/FormattedTextBox.tsx | 34 ++++++++++------------ .../views/nodes/formattedText/RichTextRules.ts | 13 +++------ src/client/views/nodes/formattedText/nodes_rts.ts | 1 - src/fields/Doc.ts | 13 +++++++-- src/fields/util.ts | 16 +++++++++- 12 files changed, 78 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6a0d45543..eb15c332f 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1516,6 +1516,9 @@ export namespace DocUtils { return linkDoc; }); + const a = source.layout_unrendered ? 'link_anchor_1.annotationOn' : 'link_anchor_1'; + const b = target.layout_unrendered ? 'link_anchor_2.annotationOn' : 'link_anchor_2'; + return makeLink( Docs.Create.LinkDocument( source, @@ -1529,6 +1532,8 @@ export namespace DocUtils { link_displayLine: linkSettings.link_displayLine, link_relationship: linkSettings.link_relationship, link_description: linkSettings.link_description, + x: ComputedField.MakeFunction(`(this.${a}.x+this.${b}.x)/2`) as any, + y: ComputedField.MakeFunction(`(this.${a}.y+this.${b}.y)/2`) as any, link_autoMoveAnchors: true, _lockedPosition: true, _layout_showCaption: '', // removed since they conflict with showing a link with a LinkBox (ie, line, not comparison box) diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 31222aa50..422e708bc 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -250,7 +250,7 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp const outputText = host.readFile('file.js'); const diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics); - + if (script.startsWith('@')) options.typecheck = true; // need the compilation to fail so that the script will return itself as a string (instead of nothing) const result = Run(outputText, paramNames, diagnostics, script, options); if (options.globals) { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index d65e0b406..15ce4c15f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -416,7 +416,7 @@ export class DocumentButtonBar extends ObservableReactComponent<{ views: () => ( )} {DocumentLinksButton.StartLink && DocumentLinksButton.StartLink !== doc ?
      {this.endLinkButton}
      : null} - {Doc.noviceMode ? null :
      {this.templateButton}
      } +
      {this.templateButton}
      {!SelectionManager.Views?.some(v => v.allLinks.length) ? null :
      {this.followLinkButton}
      }
      {this.pinButton}
      {this.recordButton}
      diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 951e0912c..9e469ed1f 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -131,12 +131,6 @@ export class DocumentDecorations extends ObservableReactComponent { if (titleFieldKey === 'title') { d.dataDoc.title_custom = !this._accumulatedTitle.startsWith('-'); - if (StrCast(d.Document.title).startsWith('@') && !this._accumulatedTitle.startsWith('@')) { - Doc.RemFromMyPublished(d.Document); - } - if (!StrCast(d.Document.title).startsWith('@') && this._accumulatedTitle.startsWith('@')) { - Doc.AddToMyPublished(d.Document); - } } KeyValueBox.SetField(d.Document, titleFieldKey, this._accumulatedTitle); }), diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 25bfdb588..b2897a9b7 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -3,14 +3,14 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util'; +import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; @@ -32,6 +32,7 @@ import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import { TabDocView } from './TabDocView'; +import { ComputedField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -313,8 +314,26 @@ export class CollectionDockingView extends CollectionSubView() { } }; + /** + * This publishes Docs having titles starting with '@' to Doc.myPublishedDocs + * Once published, any text that uses the 'title' in its body will automatically + * be linked to this published document. + * @param target + * @param title + */ + titleChanged = (target: any, value: any) => { + const title = Field.toString(value); + if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) { + const embedding = DocListCast(target.proto_embeddings).lastElement(); + embedding && Doc.AddToMyPublished(embedding); + } else if (!title.startsWith('@')) { + DocListCast(target.proto_embeddings).forEach(doc => Doc.RemFromMyPublished(doc)); + } + }; + componentDidMount: () => void = async () => { this._unmounting = false; + SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property if (this._containerRef.current) { this._lightboxReactionDisposer = reaction( () => LightboxView.LightboxDoc, diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 6b3a56b0b..6eca91e9d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -499,7 +499,7 @@ export class MarqueeView extends ObservableReactComponent ); @@ -119,7 +118,6 @@ interface IDashFieldViewInternal { height: number; editable: boolean; nodeSelected: () => boolean; - dataDoc: boolean; node: any; getPos: any; unclickable: () => boolean; @@ -139,7 +137,7 @@ export class DashFieldViewInternal extends ObservableReactComponent (this._dashDoc = this._props.dataDoc ? doc[DocData] : doc)); + const setDoc = action((doc: Doc) => (this._dashDoc = doc)); if (this._props.docId) { DocServer.GetRefField(this._props.docId).then(dashDoc => dashDoc instanceof Doc && setDoc(dashDoc)); @@ -168,7 +166,7 @@ export class DashFieldViewInternal extends ObservableReactComponent !this._props.tbox.ProseRef?.contains(document.activeElement) && this._props.tbox._props.onBlur?.()); } }); - selectedCell = (): [Doc, number] => [this._dashDoc!, 0]; + selectedCell = (): [Doc, number] | undefined => (this._dashDoc ? [this._dashDoc, 0] : undefined); columnWidth = () => Math.min(this._props.tbox._props.PanelWidth(), Math.max(50, this._props.tbox._props.PanelWidth() - 100)); // try to leave room for the fieldKey // set the display of the field's value (checkbox for booleans, span of text for strings) @@ -284,7 +282,7 @@ export class DashFieldViewInternal extends ObservableReactComponent +