From df7257d1b39f51a7e00a495f0d4a2366f0e21f7d Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 14 May 2023 22:04:36 -0400 Subject: fixed webpage link following by adding a presData for the current URL to all embedded docs. fixed getView() in showDocuments to not get called with the proper anchors. changed unrendered MARKERs to CONFIGs. changed anchors to Configs or Markers as appropriate. --- src/client/views/search/SearchBox.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src/client/views/search') diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 536ec7c75..1c1b41f73 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -211,7 +211,7 @@ export class SearchBox extends ViewBoxBaseComponent() { } @action static staticSearchCollection(rootDoc: Opt, query: string) { - const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.MARKER, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; + const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FILTER, DocumentType.SEARCH, DocumentType.SEARCHITEM, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; const blockedKeys = [ 'x', 'y', @@ -240,7 +240,7 @@ export class SearchBox extends ViewBoxBaseComponent() { 'layout', 'layout_keyValue', 'layout_fitWidth', - 'viewType', + 'type_collection', 'title_custom', 'freeform_panX', 'freeform_panY', -- cgit v1.2.3-70-g09d2 From 46cf6c823ca8ab628cd8c5bd7fdfe8945344a014 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 16 May 2023 14:50:29 -0400 Subject: fixed bugs with goldenlayout dragging and undoing. fixed searching for filter values in sidebars. Stopped creating empty list for collections when datafield() is accessed because it messes up undo of a collection. fixed tab title editing. from marquee. Added UndoStack UI and additional naming support in code. --- src/client/documents/Documents.ts | 12 ++-- src/client/goldenLayout.js | 2 +- src/client/util/CurrentUserUtils.ts | 44 +++++++------- src/client/util/DragManager.ts | 6 +- src/client/util/LinkFollower.ts | 2 +- src/client/util/SelectionManager.ts | 2 +- src/client/util/UndoManager.ts | 53 ++++++++++++----- src/client/views/ContextMenuItem.tsx | 5 +- src/client/views/DocumentDecorations.tsx | 46 +++++---------- src/client/views/EditableView.tsx | 2 + src/client/views/FilterPanel.tsx | 9 ++- src/client/views/GlobalKeyHandler.ts | 10 ++-- src/client/views/MainView.tsx | 8 +-- src/client/views/PropertiesButtons.tsx | 6 +- .../views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesDocContextSelector.tsx | 2 +- src/client/views/PropertiesView.tsx | 32 +++++----- src/client/views/SidebarAnnos.scss | 14 +++-- src/client/views/SidebarAnnos.tsx | 15 ++++- src/client/views/UndoStack.tsx | 30 ++++++++++ .../views/collections/CollectionDockingView.tsx | 63 ++++++++++---------- .../collections/CollectionNoteTakingViewColumn.tsx | 5 +- .../collections/CollectionStackedTimeline.tsx | 3 +- .../CollectionStackingViewFieldColumn.tsx | 4 +- src/client/views/collections/CollectionSubView.tsx | 13 ++-- src/client/views/collections/TabDocView.tsx | 21 ++++--- src/client/views/collections/TreeView.tsx | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 - .../collections/collectionFreeForm/MarqueeView.tsx | 26 +++----- .../collectionSchema/CollectionSchemaView.tsx | 32 +++++----- .../collectionSchema/SchemaColumnHeader.tsx | 2 - .../collections/collectionSchema/SchemaRowBox.tsx | 10 ++-- .../collectionSchema/SchemaTableCell.tsx | 2 +- src/client/views/linking/LinkMenu.tsx | 2 +- src/client/views/nodes/AudioBox.tsx | 2 +- src/client/views/nodes/DocumentLinksButton.tsx | 20 +++---- src/client/views/nodes/DocumentView.tsx | 57 ++++++++++-------- src/client/views/nodes/LinkDocPreview.tsx | 3 +- src/client/views/nodes/button/FontIconBox.tsx | 60 ++++++++----------- .../views/nodes/formattedText/FormattedTextBox.tsx | 69 +++++++++++----------- src/client/views/nodes/trails/PresBox.tsx | 9 ++- src/client/views/nodes/trails/PresElementBox.tsx | 13 ++-- src/client/views/pdf/AnchorMenu.tsx | 11 ++-- src/client/views/search/SearchBox.tsx | 2 + src/fields/Doc.ts | 18 ++++-- src/fields/util.ts | 38 +++++++----- 46 files changed, 421 insertions(+), 372 deletions(-) create mode 100644 src/client/views/UndoStack.tsx (limited to 'src/client/views/search') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2151d0ec7..795d60b48 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -24,7 +24,7 @@ import { DirectoryImportBox } from '../util/Import & Export/DirectoryImportBox'; import { FollowLinkScript } from '../util/LinkFollower'; import { LinkManager } from '../util/LinkManager'; import { ScriptingGlobals } from '../util/ScriptingGlobals'; -import { undoBatch, UndoManager } from '../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; import { DimUnit } from '../views/collections/collectionMulticolumn/CollectionMulticolumnView'; import { CollectionView } from '../views/collections/CollectionView'; @@ -249,7 +249,7 @@ export class DocumentOptions { contextMenuIcons?: List; defaultDoubleClick?: 'ignore' | 'default'; // ignore double clicks, or deafult (undefined) means open document full screen waitForDoubleClickToClick?: 'always' | 'never' | 'default'; // whether a click function wait for double click to expire. 'default' undefined = wait only if there's a click handler, "never" = never wait, "always" = alway wait - dontUndo?: boolean; // whether button clicks should be undoable (this is set to true for Undo/Redo/and sidebar buttons that open the siebar panel) + dontUndo?: boolean; // whether button clicks should be undoable ( true for Undo/Redo/and sidebar) AND whether modifications to document are undoable (true for linearview menu buttons to prevent open/close from entering undo stack) layout?: string | Doc; // default layout string for a document contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents childLimitHeight?: number; // whether to limit the height of collection children. 0 - means height can be no bigger than width @@ -1532,7 +1532,7 @@ export namespace DocUtils { description: 'Quick Notes', subitems: DocListCast((Doc.UserDoc()['template-notes'] as Doc).data).map((note, i) => ({ description: ':' + StrCast(note.title), - event: undoBatch((args: { x: number; y: number }) => { + event: undoable((args: { x: number; y: number }) => { const textDoc = Docs.Create.TextDocument('', { _width: 200, x, @@ -1546,7 +1546,7 @@ export namespace DocUtils { textDoc[pivotField] = pivotValue; } docTextAdder(textDoc); - }), + }, 'create quick note'), icon: StrCast(note.icon) as IconProp, })) as ContextMenuProps[], icon: 'sticky-note', @@ -1557,7 +1557,7 @@ export namespace DocUtils { .filter(doc => doc && doc !== Doc.UserDoc().emptyTrail && doc !== Doc.UserDoc().emptyDataViz) .map((dragDoc, i) => ({ description: ':' + StrCast(dragDoc.title).replace('Untitled ', ''), - event: undoBatch((args: { x: number; y: number }) => { + event: undoable((args: { x: number; y: number }) => { const newDoc = DocUtils.copyDragFactory(dragDoc); if (newDoc) { newDoc.author = Doc.CurrentUserEmail; @@ -1570,7 +1570,7 @@ export namespace DocUtils { } docAdder?.(newDoc); } - }), + }, StrCast(dragDoc.title)), icon: Doc.toIcon(dragDoc), })) as ContextMenuProps[]; ContextMenu.Instance.addItem({ diff --git a/src/client/goldenLayout.js b/src/client/goldenLayout.js index 9cb20d834..843b8bb5f 100644 --- a/src/client/goldenLayout.js +++ b/src/client/goldenLayout.js @@ -4740,7 +4740,7 @@ newstack._$init(); newstack.addChild(this.contentItems[0]); } - correctRowOrCol.addChild(newstack, insertBefore ? 0 : undefined, true); + correctRowOrCol.addChild(newstack, !insertBefore ? 0 : undefined, true); newstack.config[dimension] = 50; contentItem.config[dimension] = 50; correctRowOrCol.callDownwards('setSize'); diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 26fe8f440..b0a2c7d60 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -363,7 +363,7 @@ export class CurrentUserUtils { const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, scripts, funcs }) => { const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined; const reqdBtnOpts:DocumentOptions = { - title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, dontUndo: true, dontRegisterView: true, + title, icon, target, btnType: ButtonType.MenuButton, isSystem: true, dontUndo: true, dontRegisterView: true, _width: 60, _height: 60, _stayInCollection: true, _hideContextMenu: true, _removeDropProperties: new List(["_stayInCollection"]), }; @@ -598,12 +598,12 @@ export class CurrentUserUtils { CurrentUserUtils.createToolButton(opts), scripts, funcs); const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet - { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, - { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }}, - { scripts: { }, opts: { title: "linker", icon: "linkui", toolTip: "link started"}}, - { scripts: { }, opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing media"}}, + { scripts: { onClick: "undo()"}, opts: { title: "undo", icon: "undo-alt", toolTip: "Click to undo" }}, + { scripts: { onClick: "redo()"}, opts: { title: "redo", icon: "redo-alt", toolTip: "Click to redo" }}, + { scripts: { }, opts: { title: "linker", icon: "linkui", toolTip: "link started"}}, + { scripts: { }, opts: { title: "currently playing", icon: "currentlyplayingui", toolTip: "currently playing media"}}, ]; - const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); + const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', dontUndo: true, _stayInCollection: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts:DocumentOptions = { title: "docked buttons", _height: 40, flexGap: 0, boxShadow: "standard", childDropAction: 'embed', childDontRegisterViews: true, linearViewIsExpanded: true, linearViewExpandable: true, ignoreClick: true @@ -632,21 +632,21 @@ export class CurrentUserUtils { } static textTools():Button[] { return [ - { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, + { title: "Font", toolTip: "Font", width: 100, btnType: ButtonType.DropdownList, toolType:"font", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, btnList: new List(["Roboto", "Roboto Mono", "Nunito", "Times New Roman", "Arial", "Georgia", "Comic Sans MS", "Tahoma", "Impact", "Crimson Text"]) }, - { title: "Size", toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, width: 75, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0 }, - { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}}, + { title: "Font Size",toolTip: "Font size (%size)", btnType: ButtonType.NumberDropdownButton, width: 75, toolType:"fontSize", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, numBtnMax: 200, numBtnMin: 0 }, + { title: "Color", toolTip: "Font color (%color)", btnType: ButtonType.ColorButton, icon: "font", toolType:"fontColor",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}}, { title: "Highlight",toolTip:"Font highlight", btnType: ButtonType.ColorButton, icon: "highlighter", toolType:"highlight",ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'},funcs: {hidden: "IsNoviceMode()"} }, - { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, - { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, - { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, + { title: "Bold", toolTip: "Bold (Ctrl+B)", btnType: ButtonType.ToggleButton, icon: "bold", toolType:"bold", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Italic", toolTip: "Italic (Ctrl+I)", btnType: ButtonType.ToggleButton, icon: "italic", toolType:"italics", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, + { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, + { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, // { title: "Superscript", tooltip: "Superscript", btnType: ButtonType.ToggleButton, icon: "superscript", scripts: {onClick:: 'toggleSuperscript()'}}, // { title: "Subscript", tooltip: "Subscript", btnType: ButtonType.ToggleButton, icon: "subscript", scripts: {onClick:: 'toggleSubscript()'}}, @@ -711,7 +711,7 @@ export class CurrentUserUtils { const reqdOpts:DocumentOptions = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, backgroundColor: params.backgroundColor ??"transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background - color: Colors.WHITE, isSystem: true, dontUndo: true, + color: Colors.WHITE, isSystem: true, //dontUndo: true, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth, toolType: params.toolType, expertMode: params.expertMode, @@ -727,14 +727,14 @@ export class CurrentUserUtils { /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; + const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", dontUndo:true, flexGap: 0, childDropAction: 'embed', childDontRegisterViews: true, linearViewIsExpanded: true, ignoreClick: true, linearViewExpandable: false, _height: 35 }; const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); if (!params.subMenu) { return this.setupContextMenuButton(params, menuBtnDoc); } else { - const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, + const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, dontUndo: true, childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true, linearViewSubMenu: true, linearViewExpandable: true, }; const items = params.subMenu?.map(sub => diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 070e0f918..e3798233e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -263,7 +263,7 @@ export namespace DragManager { // drags a column from a schema view export function StartColumnDrag(ele: HTMLElement[], dragData: ColumnDragData, downX: number, downY: number, options?: DragOptions) { - StartDrag(ele, dragData, downX, downY, options); + StartDrag(ele, dragData, downX, downY, options, undefined, 'Drag Column'); } export function SetSnapLines(horizLines: number[], vertLines: number[]) { @@ -325,10 +325,10 @@ export namespace DragManager { export let docsBeingDragged: Doc[] = observable([] as Doc[]); export let CanEmbed = false; export let DocDragData: DocumentDragData | undefined; - export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void) { + export function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?:string) { if (dragData.dropAction === 'none') return; DocDragData = dragData as DocumentDragData; - const batch = UndoManager.StartBatch('dragging'); + const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag'); eles = eles.filter(e => e); CanEmbed = dragData.canEmbed || false; if (!dragDiv) { diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index 2812d6c88..f74409e42 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -28,7 +28,7 @@ export class LinkFollower { // if the target isn't onscreen, then it will open up the target in the lightbox, or in place // depending on the followLinkLocation property of the source (or the link itself as a fallback); public static FollowLink = (linkDoc: Opt, sourceDoc: Doc, altKey: boolean) => { - const batch = UndoManager.StartBatch('follow link click'); + const batch = UndoManager.StartBatch('Follow Link'); runInAction(() => (LinkFollower.IsFollowing = true)); // turn off decoration bounds while following links since animations may occur, and DocDecorations is based on screenToLocal which is not always an observable value LinkFollower.traverseLink( linkDoc, diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index fba0a4f76..0125331ec 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -118,6 +118,6 @@ ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, exp if (type === 'tab') { return SelectionManager.Views().lastElement()?.props.renderDepth === 0; } - let selected = (sel => (checkContext ? DocCast(sel?.context) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); + let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement()); return selected?.type === type || selected?.type_collection === type || !type; }); diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index d0aec45a6..6fef9d660 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -3,7 +3,7 @@ import { Without } from '../../Utils'; function getBatchName(target: any, key: string | symbol): string { const keyName = key.toString(); - if (target && target.constructor && target.constructor.name) { + if (target?.constructor?.name) { return `${target.constructor.name}.${keyName}`; } return keyName; @@ -34,6 +34,17 @@ function propertyDecorator(target: any, key: string | symbol) { }); } +export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any { + return function () { + const batch = UndoManager.StartBatch(batchName); + try { + return fn.apply(undefined, arguments as any); + } finally { + batch.end(); + } + }; +} + export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor): any; export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any; export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor): any { @@ -73,15 +84,18 @@ export namespace UndoManager { } type UndoBatch = UndoEvent[]; + export let undoStackNames: string[] = observable([]); + export let redoStackNames: string[] = observable([]); export let undoStack: UndoBatch[] = observable([]); export let redoStack: UndoBatch[] = observable([]); let currentBatch: UndoBatch | undefined; - export let batchCounter = 0; + export let batchCounter = observable.box(0); let undoing = false; let tempEvents: UndoEvent[] | undefined = undefined; - export function AddEvent(event: UndoEvent): void { - if (currentBatch && batchCounter && !undoing) { + export function AddEvent(event: UndoEvent, value?: any): void { + if (currentBatch && batchCounter.get() && !undoing) { + console.log(' '.slice(0, batchCounter.get()) + 'UndoEvent : ' + event.prop + ' = ' + value); currentBatch.push(event); tempEvents?.push(event); } @@ -135,11 +149,13 @@ export namespace UndoManager { private dispose = (cancel: boolean) => { if (this.disposed) { - throw new Error('Cannot dispose an already disposed batch'); + console.log('WARNING: undo batch already disposed'); + return false; + } else { + this.disposed = true; + openBatches.splice(openBatches.indexOf(this)); + return EndBatch(this.batchName, cancel); } - this.disposed = true; - openBatches.splice(openBatches.indexOf(this)); - return EndBatch(cancel); }; end = () => this.dispose(false); @@ -147,22 +163,23 @@ export namespace UndoManager { } export function StartBatch(batchName: string): Batch { - // console.log("Start " + batchCounter + " " + batchName); - batchCounter++; - if (batchCounter > 0 && currentBatch === undefined) { + console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName); + runInAction(() => batchCounter.set(batchCounter.get() + 1)); + if (currentBatch === undefined) { currentBatch = []; } return new Batch(batchName); } - const EndBatch = action((cancel: boolean = false) => { - batchCounter--; - // console.log("End " + batchCounter); - if (batchCounter === 0 && currentBatch?.length) { - // console.log("------ended----") + const EndBatch = action((batchName: string, cancel: boolean = false) => { + runInAction(() => batchCounter.set(batchCounter.get() - 1)); + console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')'); + if (batchCounter.get() === 0 && currentBatch?.length) { if (!cancel) { undoStack.push(currentBatch); + undoStackNames.push(batchName ?? '???'); } + redoStackNames.length = 0; redoStack.length = 0; currentBatch = undefined; return true; @@ -204,6 +221,7 @@ export namespace UndoManager { return; } + const names = undoStackNames.pop(); const commands = undoStack.pop(); if (!commands) { return; @@ -215,6 +233,7 @@ export namespace UndoManager { } undoing = false; + redoStackNames.push(names ?? '???'); redoStack.push(commands); }); @@ -223,6 +242,7 @@ export namespace UndoManager { return; } + const names = redoStackNames.pop(); const commands = redoStack.pop(); if (!commands) { return; @@ -234,6 +254,7 @@ export namespace UndoManager { } undoing = false; + undoStackNames.push(names ?? '???'); undoStack.push(commands); }); } diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index e87d2046b..33f250986 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -40,10 +40,7 @@ export class ContextMenuItem extends React.Component) => { if ('event' in this.props) { this.props.closeMenu?.(); - let batch: UndoManager.Batch | undefined; - if (this.props.undoable !== false) { - batch = UndoManager.StartBatch(`Context menu event: ${this.props.description}`); - } + const batch = this.props.undoable !== false ? UndoManager.StartBatch(`Click Menu item: ${this.props.description}`) : undefined; await this.props.event({ x: e.clientX, y: e.clientY }); batch?.end(); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 39073d763..8077b9af1 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -2,23 +2,25 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { IconButton } from 'browndash-components'; -import { action, computed, observable, reaction, runInAction } from 'mobx'; +import { action, computed, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { FaUndo } from 'react-icons/fa'; import { DateField } from '../../fields/DateField'; import { AclAdmin, AclEdit, DataSym, Doc, DocListCast, Field, HeightSym, WidthSym } from '../../fields/Doc'; -import { Document } from '../../fields/documentSchemas'; import { InkField } from '../../fields/InkField'; +import { RichTextField } from '../../fields/RichTextField'; import { ScriptField } from '../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../fields/Types'; import { GetEffectiveAcl } from '../../fields/util'; import { aggregateBounds, emptyFunction, numberValue, returnFalse, setupMoveUpEvents, Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; +import { DocumentManager } from '../util/DocumentManager'; import { DragManager } from '../util/DragManager'; +import { LinkFollower } from '../util/LinkFollower'; import { SelectionManager } from '../util/SelectionManager'; import { SnappingManager } from '../util/SnappingManager'; -import { undoBatch, UndoManager } from '../util/UndoManager'; +import { UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { CollectionFreeFormView } from './collections/collectionFreeForm'; import { DocumentButtonBar } from './DocumentButtonBar'; @@ -27,15 +29,11 @@ import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { InkStrokeProperties } from './InkStrokeProperties'; import { LightboxView } from './LightboxView'; -import { DocumentView, OpenWhere, OpenWhereMod } from './nodes/DocumentView'; +import { DocumentView, OpenWhereMod } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/formattedText/FormattedTextBox'; import { ImageBox } from './nodes/ImageBox'; import React = require('react'); -import { RichTextField } from '../../fields/RichTextField'; -import { LinkFollower } from '../util/LinkFollower'; import _ = require('lodash'); -import { DocumentManager } from '../util/DocumentManager'; -import { isUndefined } from 'lodash'; @observer export class DocumentDecorations extends React.Component<{ PanelWidth: number; PanelHeight: number; boundsLeft: number; boundsTop: number }, { value: string }> { @@ -150,7 +148,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P Doc.SetInPlace(d.rootDoc, titleFieldKey, titleField, true); } }), - 'title blur' + 'edit title' ); } }; @@ -227,11 +225,6 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P .filter(v => v && v.props.renderDepth > 0); if (forceDeleteOrIconify === false && this._iconifyBatch) return; this._deleteAfterIconify = forceDeleteOrIconify || this._iconifyBatch ? true : false; - if (!this._iconifyBatch) { - this._iconifyBatch = UndoManager.StartBatch('iconifying'); - } else { - forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes - } var iconifyingCount = views.length; const finished = action((force?: boolean) => { if ((force || --iconifyingCount === 0) && this._iconifyBatch) { @@ -250,6 +243,12 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P this._iconifyBatch = undefined; } }); + if (!this._iconifyBatch) { + this._iconifyBatch = UndoManager.StartBatch(forceDeleteOrIconify ? 'delete selected docs' : 'iconifying'); + } else { + forceDeleteOrIconify = false; // can't force immediate close in the middle of iconifying -- have to wait until iconifying completes + } + if (forceDeleteOrIconify) finished(forceDeleteOrIconify); else if (!this._deleteAfterIconify) views.forEach(dv => dv.iconify(finished)); }; @@ -397,7 +396,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P onRotateDown = (e: React.PointerEvent): void => { this._isRotating = true; const rcScreen = { X: this.rotCenter[0], Y: this.rotCenter[1] }; - const rotateUndo = UndoManager.StartBatch('rotatedown'); + const rotateUndo = UndoManager.StartBatch('drag rotation'); const selectedInk = SelectionManager.Views().filter(i => i.ComponentView instanceof InkingStroke); const centerPoint = this.rotCenter.slice(); const infos = new Map(); @@ -465,7 +464,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const bounds = e.currentTarget.getBoundingClientRect(); this._offX = this._resizeHdlId.toLowerCase().includes('left') ? bounds.right - e.clientX : bounds.left - e.clientX; this._offY = this._resizeHdlId.toLowerCase().includes('top') ? bounds.bottom - e.clientY : bounds.top - e.clientY; - this._resizeUndo = UndoManager.StartBatch('DocDecs resize'); + this._resizeUndo = UndoManager.StartBatch('drag resizing'); this._snapX = e.pageX; this._snapY = e.pageY; const ffviewSet = new Set(); @@ -770,20 +769,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number; P const topBtn = (key: string, icon: string, pointerDown: undefined | ((e: React.PointerEvent) => void), click: undefined | ((e: any) => void), title: string) => ( {title}} placement="top"> -
e.preventDefault()} - onPointerDown={ - pointerDown ?? - (e => - setupMoveUpEvents( - this, - e, - returnFalse, - emptyFunction, - undoBatch(e => click!(e)) - )) - }> +
e.preventDefault()} onPointerDown={pointerDown ?? (e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, e => click!(e)))}>
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 6b4132814..7043edcee 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -156,12 +156,14 @@ export class EditableView extends React.Component { break; case ':': if (this.props.menuCallback) { + e.stopPropagation(); this.props.menuCallback(e.currentTarget.getBoundingClientRect().x, e.currentTarget.getBoundingClientRect().y); break; } default: if (this.props.textCallback?.(e.key)) { + e.stopPropagation(); this._editing = false; this.props.isEditingCallback?.(false); } diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index 75e0e7c4c..53c1f1018 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -10,6 +10,7 @@ import { UserOptions } from '../util/GroupManager'; import './FilterPanel.scss'; import { FieldView } from './nodes/FieldView'; import { SearchBox } from './search/SearchBox'; +import { undoable } from '../util/UndoManager'; interface filterProps { rootDoc: Doc; @@ -31,7 +32,7 @@ export class FilterPanel extends React.Component { return targetView?.ComponentView?.annotationKey ?? targetView?.ComponentView?.fieldKey ?? 'data'; } @computed get targetDocChildren() { - return DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data); + return [...DocListCast(this.targetDoc?.[this.targetDocChildKey] || Doc.ActiveDashboard?.data), ...DocListCast(this.targetDoc[Doc.LayoutFieldKey(this.targetDoc) + '_sidebar'])]; } @computed get allDocs() { @@ -89,6 +90,7 @@ export class FilterPanel extends React.Component { const fieldKey = Doc.LayoutFieldKey(t); const annos = !Field.toString(Doc.LayoutField(t) as Field).includes('CollectionView'); DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); + annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); }); subDocs = newarray; } @@ -204,7 +206,8 @@ export class FilterPanel extends React.Component { .find(filter => filter.split(':')[0] === facetHeader) ?.split(':')[1] ?? '-empty-' } - onKeyDown={e => e.key === 'Enter' && Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match')} + onBlur={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')} + onKeyDown={e => e.key === 'Enter' && undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, e.currentTarget.value, !e.currentTarget.value ? 'remove' : 'match'), 'set text filter')(e)} /> ); case 'checkbox': @@ -220,7 +223,7 @@ export class FilterPanel extends React.Component { ?.split(':')[2] === 'check' } type={type} - onChange={e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove')} + onChange={undoable(e => Doc.setDocFilter(this.targetDoc, facetHeader, fval, e.target.checked ? 'check' : 'remove'), 'set filter')} /> {facetValue}
diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index b9b92dd2b..625bc760d 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -161,12 +161,10 @@ export class KeyManager { case 'delete': case 'backspace': if (document.activeElement?.tagName !== 'INPUT' && document.activeElement?.tagName !== 'TEXTAREA') { - UndoManager.RunInBatch(() => { - if (LightboxView.LightboxDoc) { - LightboxView.SetLightboxDoc(undefined); - SelectionManager.DeselectAll(); - } else DocumentDecorations.Instance.onCloseClick(true); - }, 'backspace'); + if (LightboxView.LightboxDoc) { + LightboxView.SetLightboxDoc(undefined); + SelectionManager.DeselectAll(); + } else DocumentDecorations.Instance.onCloseClick(true); return { stopPropagation: true, preventDefault: true }; } break; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 853f9cace..50f451c0a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -915,11 +915,11 @@ export class MainView extends React.Component { return !SelectionManager.Views().some(dv => dv.rootDoc.freeform_snapLines) ? null : (
- {SnappingManager.horizSnapLines().map(l => ( - + {SnappingManager.horizSnapLines().map((l, i) => ( + ))} - {SnappingManager.vertSnapLines().map(l => ( - + {SnappingManager.vertSnapLines().map((l, i) => ( + ))}
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index a37447505..a5c58c9d2 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -14,7 +14,7 @@ import { DocUtils } from '../documents/Documents'; import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; -import { undoBatch } from '../util/UndoManager'; +import { undoable, undoBatch } from '../util/UndoManager'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { DocumentView, OpenWhere } from './nodes/DocumentView'; @@ -51,11 +51,11 @@ export class PropertiesButtons extends React.Component<{}, {}> {
e.stopPropagation()} - onClick={undoBatch(() => { + onClick={undoable(() => { if (SelectionManager.Views().length > 1) { SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property)); } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property); - })}> + }, property)}>
{label}
diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index 46e6fd188..7b21629da 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component { if (!this.props.DocView) return; - col = Doc.IsDocDataProto(col) ? Doc.MakeDelegate(col) : col; + col = Doc.IsDataProto(col) ? Doc.MakeDelegate(col) : col; DocFocusOrOpen(Doc.GetProto(this.props.DocView.props.Document), undefined, col); }; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 19c138a21..297820e37 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -21,7 +21,7 @@ import { LinkManager } from '../util/LinkManager'; import { SelectionManager } from '../util/SelectionManager'; import { SharingManager } from '../util/SharingManager'; import { Transform } from '../util/Transform'; -import { undoBatch, UndoManager } from '../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../util/UndoManager'; import { EditableView } from './EditableView'; import { FilterPanel } from './FilterPanel'; import { Colors } from './global/globalEnums'; @@ -538,6 +538,7 @@ export class PropertiesView extends React.Component { className="inputBox-input" type="text" value={value} + readOnly={true} onChange={e => { setter(e.target.value); }} @@ -656,21 +657,21 @@ export class PropertiesView extends React.Component { return this.inputBoxDuo( 'hgt', this.shapeHgt, - (val: string) => { + undoable((val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; - }, + }, 'set height'), 'H:', 'wid', this.shapeWid, - (val: string) => { + undoable((val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; - }, + }, 'set width'), 'W:' ); } @@ -678,21 +679,21 @@ export class PropertiesView extends React.Component { return this.inputBoxDuo( 'Xps', this.shapeXps, - (val: string) => { + undoable((val: string) => { if (val !== '0' && !isNaN(Number(val))) { this.shapeXps = val; } return true; - }, + }, 'set x coord'), 'X:', 'Yps', this.shapeYps, - (val: string) => { + undoable((val: string) => { if (val !== '0' && !isNaN(Number(val))) { this.shapeYps = val; } return true; - }, + }, 'set y coord'), 'Y:' ); } @@ -867,7 +868,7 @@ export class PropertiesView extends React.Component { regInput = (key: string, value: any, setter: (val: string) => {}) => { return (
- setter(e.target.value)} /> + setter(e.target.value)} />
this.upDownButtons('up', key)))}> @@ -1228,8 +1229,7 @@ export class PropertiesView extends React.Component { } }); - @undoBatch - changeFollowBehavior = action((follow: Opt) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = follow)); + changeFollowBehavior = undoable((loc: Opt) => this.sourceAnchor && (this.sourceAnchor.followLinkLocation = loc), 'change follow behavior'); @undoBatch changeAnimationBehavior = action((behavior: string) => this.sourceAnchor && (this.sourceAnchor.followLinkAnimEffect = behavior)); @@ -1315,6 +1315,7 @@ export class PropertiesView extends React.Component { autoComplete={'off'} id="link_relationship_input" value={StrCast(LinkManager.currentLink?.link_relationship)} + readOnly={true} onKeyDown={this.onRelationshipKey} onBlur={this.onSelectOutRelationship} onChange={e => this.handlelinkRelationshipChange(e.currentTarget.value)} @@ -1332,6 +1333,7 @@ export class PropertiesView extends React.Component { style={{ textAlign: 'left' }} id="link_description_input" value={Field.toString(LinkManager.currentLink?.link_description as any as Field)} + readOnly={true} onKeyDown={this.onDescriptionKey} onBlur={this.onSelectOutDesc} onChange={e => this.handleDescriptionChange(e.currentTarget.value)} @@ -1457,7 +1459,9 @@ export class PropertiesView extends React.Component {
@@ -1567,7 +1571,7 @@ export class PropertiesView extends React.Component {

Zoom %

- +
this.setZoom(String(zoom), 0.1))}> diff --git a/src/client/views/SidebarAnnos.scss b/src/client/views/SidebarAnnos.scss index a0506cb3a..d7de2b641 100644 --- a/src/client/views/SidebarAnnos.scss +++ b/src/client/views/SidebarAnnos.scss @@ -4,22 +4,26 @@ overflow: auto; flex-flow: row; flex-wrap: wrap; - .sidebarAnnos-filterTag, .sidebarAnnos-filterTag-active, - .sidebarAnnos-filterUser, .sidebarAnnos-filterUser-active { + .sidebarAnnos-filterTag, + .sidebarAnnos-filterTag-active, + .sidebarAnnos-filterUser, + .sidebarAnnos-filterUser-active { font-weight: bold; font-size: 10px; padding-left: 5; padding-right: 5; box-shadow: black 1px 1px 3px; border-radius: 5; - margin: 2; + margin: 2; height: 15; background-color: lightgrey; } - .sidebarAnnos-filterUser, .sidebarAnnos-filterUser-active { + .sidebarAnnos-filterUser, + .sidebarAnnos-filterUser-active { border-radius: 15px; } + .sidebarAnnos-filterUser-active, .sidebarAnnos-filterTag-active { background-color: white; } -} \ No newline at end of file +} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index e12621f35..c9e52a1db 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -49,13 +49,21 @@ export class SidebarAnnos extends React.Component { ); return keys; } + @computed get allHashtags() { + const keys = new Set(); + DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => StrListCast(doc.tags).forEach(tag => keys.add(tag))); + return Array.from(keys.keys()) + .filter(key => key[0]) + .filter(key => !key.startsWith('_') && (key[0] === '#' || key[0] === key[0].toUpperCase())) + .sort(); + } @computed get allUsers() { const keys = new Set(); DocListCast(this.props.rootDoc[this.sidebarKey]).forEach(doc => keys.add(StrCast(doc.author))); return Array.from(keys.keys()).sort(); } get filtersKey() { - return '_' + this.sidebarKey + '-docFilters'; + return '_' + this.sidebarKey + '_docFilters'; } anchorMenuClick = (anchor: Doc) => { @@ -179,9 +187,9 @@ export class SidebarAnnos extends React.Component { }; render() { const renderTag = (tag: string) => { - const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`${tag}:${tag}:check`); + const active = StrListCast(this.props.rootDoc[this.filtersKey]).includes(`tags:${tag}:check`); return ( -
Doc.setDocFilter(this.props.rootDoc, tag, tag, 'check', true, this.sidebarKey, e.shiftKey)}> +
Doc.setDocFilter(this.props.rootDoc, 'tags', tag, 'check', true, this.sidebarKey, e.shiftKey)}> {tag}
); @@ -216,6 +224,7 @@ export class SidebarAnnos extends React.Component { }}>
e.stopPropagation()}> {this.allUsers.map(renderUsers)} + {this.allHashtags.map(renderTag)} {Array.from(this.allMetadata.keys()) .sort() .map(key => renderMeta(key, this.allMetadata.get(key)))} diff --git a/src/client/views/UndoStack.tsx b/src/client/views/UndoStack.tsx new file mode 100644 index 000000000..01e184d6b --- /dev/null +++ b/src/client/views/UndoStack.tsx @@ -0,0 +1,30 @@ +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { UndoManager } from '../util/UndoManager'; +import './ScriptingRepl.scss'; + +@observer +export class UndoStack extends React.Component { + render() { + return ( +
+
r?.scroll({ behavior: 'auto', top: r?.scrollHeight + 20 })} style={{ background: UndoManager.batchCounter.get() ? 'yellow' : undefined }}> + {UndoManager.undoStackNames.map((name, i) => ( +
+
{name.replace(/[^\.]*\./, '')}
+
+ ))} + {Array.from(UndoManager.redoStackNames) + .reverse() + .map((name, i) => ( +
+
+ {name.replace(/[^\.]*\./, '')} +
+
+ ))} +
+
+ ); + } +} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index cce21a3aa..4ae24af60 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -6,7 +6,7 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { Cast, ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; @@ -20,14 +20,14 @@ import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; import { LightboxView } from '../LightboxView'; +import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; +import { OverlayView } from '../OverlayView'; +import { ScriptingRepl } from '../ScriptingRepl'; import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { TabDocView } from './TabDocView'; import React = require('react'); -import { OpenWhere, OpenWhereMod } from '../nodes/DocumentView'; -import { OverlayView } from '../OverlayView'; -import { ScriptingRepl } from '../ScriptingRepl'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -85,6 +85,7 @@ export class CollectionDockingView extends CollectionSubView() { tabItemDropped = () => DragManager.CompleteWindowDrag?.(false); tabDragStart = (proxy: any, finishDrag?: (aborted: boolean) => void) => { + this._flush = this._flush ?? UndoManager.StartBatch('tab move'); const dashDoc = proxy?._contentItem?.tab?.DashDoc as Doc; dashDoc && (DragManager.DocDragData = new DragManager.DocumentDragData([proxy._contentItem.tab.DashDoc])); DragManager.CompleteWindowDrag = (aborted: boolean) => { @@ -92,12 +93,12 @@ export class CollectionDockingView extends CollectionSubView() { proxy._dragListener.AbortDrag(); if (this._flush) { this._flush.cancel(); // cancel the undo change being logged - this._flush = undefined; this.setupGoldenLayout(); // restore golden layout to where it was before the drag (this is a no-op when using StartOtherDrag because the proxy dragged item was never in the golden layout) } DragManager.CompleteWindowDrag = undefined; } finishDrag?.(aborted); + setTimeout(this.endUndoBatch, 100); }; }; @undoBatch @@ -180,7 +181,6 @@ export class CollectionDockingView extends CollectionSubView() { // // Creates a split on any side of the docking view based on the passed input pullSide and then adds the Document to the requested side // - @undoBatch @action public static AddSplit(document: Doc, pullSide: OpenWhereMod, stack?: any, panelName?: string, keyValue?: boolean) { if (document?._type_collection === CollectionViewType.Docking) return DashboardView.openDashboard(document); @@ -195,6 +195,8 @@ export class CollectionDockingView extends CollectionSubView() { if (!instance) return false; const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName, undefined, keyValue); + CollectionDockingView.Instance._flush = CollectionDockingView.Instance._flush ?? UndoManager.StartBatch('Add Split'); + setTimeout(CollectionDockingView.Instance.endUndoBatch, 100); if (!pullSide && stack) { stack.addChild(docContentConfig, undefined); setTimeout(() => stack.setActiveContentItem(stack.contentItems[stack.contentItems.length - 1])); @@ -370,16 +372,30 @@ export class CollectionDockingView extends CollectionSubView() { !LightboxView.LightboxDoc && cur && this._goldenLayout?.updateSize(cur.getBoundingClientRect().width, cur.getBoundingClientRect().height); }; + endUndoBatch = () => { + const json = JSON.stringify(this._goldenLayout.toConfig()); + const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); + const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')); + const docs = !docids + ? [] + : docids + .map(id => DocServer.GetCachedRefField(id)) + .filter(f => f) + .map(f => f as Doc); + const changesMade = this.props.Document.dockingConfig !== json; + if (changesMade) { + this.props.Document.dockingConfig = json; + this.props.Document.data = new List(docs); + } + this._flush?.end(); + this._flush = undefined; + }; + @action onPointerUp = (e: MouseEvent): void => { window.removeEventListener('pointerup', this.onPointerUp); - const flush = this._flush; - this._flush = undefined; - if (flush) { - DragManager.CompleteWindowDrag = undefined; - if (!this.stateChanged()) flush.cancel(); - else flush.end(); - } + DragManager.CompleteWindowDrag = undefined; + setTimeout(this.endUndoBatch, 100); }; @action @@ -393,10 +409,8 @@ export class CollectionDockingView extends CollectionSubView() { window.addEventListener('mouseup', this.onPointerUp); if (!htmlTarget.closest('*.lm_content') && (htmlTarget.closest('*.lm_tab') || htmlTarget.closest('*.lm_stack'))) { const className = typeof htmlTarget.className === 'string' ? htmlTarget.className : ''; - if (!className.includes('lm_close') && !className.includes('lm_maximise')) { - this._flush = UndoManager.StartBatch('golden layout edit'); - DocServer.UPDATE_SERVER_CACHE(); - } + if (className.includes('lm_maximise')) this._flush = UndoManager.StartBatch('tab maximize'); + else if (!className.includes('lm_close')) DocServer.UPDATE_SERVER_CACHE(); } } if (!InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) && !InteractionUtils.IsType(e, InteractionUtils.PENTYPE) && ![InkTool.Highlighter, InkTool.Pen, InkTool.Write].includes(Doc.ActiveTool)) { @@ -452,25 +466,12 @@ export class CollectionDockingView extends CollectionSubView() { stateChanged = () => { this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig()); const json = JSON.stringify(this._goldenLayout.toConfig()); - const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); - const docids = matches?.map(m => m.replace('"documentId":"', '').replace('"', '')); - const docs = !docids - ? [] - : docids - .map(id => DocServer.GetCachedRefField(id)) - .filter(f => f) - .map(f => f as Doc); const changesMade = this.props.Document.dockingConfig !== json; - if (changesMade && !this._flush) { - UndoManager.RunInBatch(() => { - this.props.Document.dockingConfig = json; - this.props.Document.data = new List(docs); - }, 'state changed'); - } return changesMade; }; tabDestroyed = (tab: any) => { + this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); if (tab.DashDoc && ![DocumentType.KVP, DocumentType.PRES].includes(tab.DashDoc?.type)) { Doc.AddDocToList(Doc.MyHeaderBar, 'data', tab.DashDoc); Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', tab.DashDoc, undefined, true, true); diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 63becac1e..2f28ecd00 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -121,7 +121,8 @@ export class CollectionNoteTakingViewColumn extends React.Component SnappingManager.GetIsDragging() && (this._background = '#b4b4b4'); @action pointerLeave = () => (this._background = 'inherit'); - textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true); + @undoBatch + addTextNote = (char: string) => this.addNewTextDoc('-typed text-', false, true); // addNewTextDoc is called when a user starts typing in a column to create a new node @action @@ -272,7 +273,7 @@ export class CollectionNoteTakingViewColumn extends React.Component
- +
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 6b4c8a3e9..b131d38d8 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -119,7 +119,8 @@ export class CollectionStackedTimeline extends CollectionSubView scriptContext.clickAnchor(this, clientX))`, { + // setTimeout is a hack to run script in its own properly named undo group (instead of being part of the generic onClick) self: Doc.name, scriptContext: 'any', clientX: 'number', diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 243550c0b..6be9cb72d 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -128,7 +128,7 @@ export class CollectionStackingViewFieldColumn extends React.Component SnappingManager.GetIsDragging() && (this._background = '#b4b4b4'); @action pointerLeave = () => (this._background = 'inherit'); - textCallback = (char: string) => this.addNewTextDoc('-typed text-', false, true); + @undoBatch typedNote = (char: string) => this.addNewTextDoc('-typed text-', false, true); @action addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { @@ -363,7 +363,7 @@ export class CollectionStackingViewFieldColumn extends React.Component} menuCallback={this.menuCallback} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index bcda13c8b..cfe78afa1 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -5,7 +5,6 @@ import { AclPrivate, Doc, DocListCast, Field, Opt, StrListCast } from '../../../ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; -import { ScriptField } from '../../../fields/ScriptField'; import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; @@ -65,11 +64,7 @@ export function CollectionSubView(moreProps?: X) { // to its children which may be templates. // If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey' @computed get dataField() { - if (this.layoutDoc[this.props.fieldKey]) return this.layoutDoc[this.props.fieldKey]; - // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly - // setTimeout changes it outside of the @computed section - !this.dataDoc[this.props.fieldKey] && setTimeout(() => !this.dataDoc[this.props.fieldKey] && (this.dataDoc[this.props.fieldKey] = new List())); - return this.dataDoc[this.props.fieldKey]; + return this.layoutDoc[this.props.fieldKey]; } get childLayoutPairs(): { layout: Doc; data: Doc }[] { @@ -130,8 +125,9 @@ export function CollectionSubView(moreProps?: X) { const fieldKey = Doc.LayoutFieldKey(d); const annos = !Field.toString(Doc.LayoutField(d) as Field).includes(CollectionView.name); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; - if (data !== undefined) { - let subDocs = DocListCast(data); + const side = annos && d[fieldKey + '_sidebar']; + if (data !== undefined || side !== undefined) { + let subDocs = [...DocListCast(data), ...DocListCast(side)]; if (subDocs.length > 0) { let newarray: Doc[] = []; notFiltered = notFiltered || (!searchDocs.length && DocUtils.FilterDocs(subDocs, childDocFilters, docRangeFilters, d).length); @@ -142,6 +138,7 @@ export function CollectionSubView(moreProps?: X) { const annos = !Field.toString(Doc.LayoutField(t) as Field).includes(CollectionView.name); notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !docRangeFilters.length) || DocUtils.FilterDocs([t], childDocFilters, docRangeFilters, d).length)); DocListCast(t[annos ? fieldKey + '_annotations' : fieldKey]).forEach(newdoc => newarray.push(newdoc)); + annos && DocListCast(t[fieldKey + '_sidebar']).forEach(newdoc => newarray.push(newdoc)); }); subDocs = newarray; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 7e88959a4..33fa434e1 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -13,14 +13,13 @@ import { listSpec } from '../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, lightOrDark, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick, Utils } from '../../../Utils'; import { DocServer } from '../../DocServer'; -import { DocUtils } from '../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { undoable, UndoManager } from '../../util/UndoManager'; import { DashboardView } from '../DashboardView'; import { Colors, Shadows } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; @@ -116,15 +115,13 @@ export class TabDocView extends React.Component { titleEle.size = StrCast(doc.title).length + 3; titleEle.value = doc.title; - titleEle.onkeydown = (e: KeyboardEvent) => { - e.stopPropagation(); - }; - titleEle.onchange = undoBatch( - action((e: any) => { + titleEle.onkeydown = (e: KeyboardEvent) => e.stopPropagation(); + titleEle.onchange = (e: any) => { + undoable(() => { titleEle.size = e.currentTarget.value.length + 3; Doc.GetProto(doc).title = e.currentTarget.value; - }) - ); + }, 'edit tab title')(); + }; if (tab.element[0].children[1].children.length === 1) { iconWrap.className = 'lm_iconWrap lm_moreInfo'; @@ -198,7 +195,9 @@ export class TabDocView extends React.Component { action(selected => { if (selected) this._activated = true; const toggle = tab.element[0].children[2].children[0] as HTMLInputElement; - selected && tab.contentItem !== tab.header.parent.getActiveContentItem() && UndoManager.RunInBatch(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch'); + if (selected && tab.contentItem !== tab.header.parent.getActiveContentItem()) { + undoable(() => tab.header.parent.setActiveContentItem(tab.contentItem), 'tab switch')(); + } toggle.style.fontWeight = selected ? 'bold' : ''; // toggle.style.textTransform = selected ? "uppercase" : ""; }), @@ -234,7 +233,7 @@ export class TabDocView extends React.Component { public static PinDoc(docs: Doc | Doc[], pinProps: PinProps) { const docList = docs instanceof Doc ? [docs] : docs; - const batch = UndoManager.StartBatch('pinning doc'); + const batch = UndoManager.StartBatch('Pin doc to pres trail'); const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); if (!Doc.ActivePresentation) { diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 037148bb9..f56eaee07 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -102,7 +102,7 @@ export class TreeView extends React.Component { private _treedropDisposer?: DragManager.DragDropDisposer; get treeViewOpenIsTransient() { - return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsDocDataProto(this.doc); + return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsDataProto(this.doc); } set treeViewOpen(c: boolean) { if (this.treeViewOpenIsTransient) this._transientOpenState = c; @@ -221,7 +221,7 @@ export class TreeView extends React.Component { } else { // choose an appropriate embedding or make one. --- choose the first embedding that (1) user owns, (2) has no context field ... otherwise make a new embedding const bestEmbedding = - docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDocDataProto(docView.props.Document) + docView.props.Document.author === Doc.CurrentUserEmail && !Doc.IsDataProto(docView.props.Document) ? docView.props.Document : DocListCast(this.props.document.proto_embeddings).find(doc => !doc.embedContainer && doc.author === Doc.CurrentUserEmail); const nextBestEmbedding = DocListCast(this.props.document.proto_embeddings).find(doc => doc.author === Doc.CurrentUserEmail); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5ac444147..95046661e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -483,7 +483,6 @@ export class CollectionFreeFormView extends CollectionSubView pair.layout); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 47d7801e6..641088675 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -102,7 +102,6 @@ export class MarqueeView extends React.Component { //make textbox and add it to this collection @@ -111,7 +110,6 @@ export class MarqueeView extends React.Component this.props.addDocTab(Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight)); - cm.displayMenu(this._downX, this._downY, undefined, true); e.stopPropagation(); } else if (e.key === 'u' && this.props.ungroup) { @@ -173,7 +171,7 @@ export class MarqueeView extends React.Component { const selected = this.marqueeSelect(false); @@ -508,8 +507,7 @@ export class MarqueeView extends React.Component { + summary = action((e: KeyboardEvent | React.PointerEvent | undefined) => { const selected = this.marqueeSelect(false).map(d => { this.props.removeDocument?.(d); d.x = NumCast(d.x) - this.Bounds.left; @@ -534,19 +532,10 @@ export class MarqueeView extends React.Component { - const newCollection = this.getCollection([], undefined, undefined); - this.props.addDocument?.(newCollection); - MarqueeOptionsMenu.Instance.fadeOut(true); - this.hideMarquee(); - setTimeout(() => this.props.selectDocuments([newCollection])); - }; - - @undoBatch - marqueeCommand = action((e: KeyboardEvent) => { + marqueeCommand = (e: KeyboardEvent) => { if (this._commandExecuted || (e as any).propagationIsStopped) { return; } @@ -557,7 +546,7 @@ export class MarqueeView extends React.Component { + setColumnSort = (field: string | undefined, desc: boolean = false) => { this.layoutDoc.sortField = field; this.layoutDoc.sortDesc = desc; }; @@ -484,24 +486,11 @@ export class CollectionSchemaView extends CollectionSubView() { return false; }; - @action - addNewTextDoc = (value: string, shiftDown?: boolean, forceEmptyNote?: boolean) => { - if (!value && !forceEmptyNote) return false; - const newDoc = Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true }); - FormattedTextBox.SelectOnLoad = newDoc[Id]; - FormattedTextBox.SelectOnLoadChar = forceEmptyNote ? '' : ' '; - return this.addRow(newDoc) || false; - }; - menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); - DocUtils.addDocumentCreatorMenuItems(doc => this.addRow(doc), this.addRow, x, y, true); + DocUtils.addDocumentCreatorMenuItems(this.addRow, this.addRow, x, y, true); - ContextMenu.Instance.setDefaultItem('::', (name: string): void => { - Doc.GetProto(this.props.Document)[name] = ''; - this.addRow(Docs.Create.TextDocument('', { title: name, _layout_autoHeight: true })); - }); ContextMenu.Instance.displayMenu(x, y, undefined, true); }; @@ -866,7 +855,7 @@ export class CollectionSchemaView extends CollectionSubView() { columnWidths={this.displayColumnWidths} sortField={this.sortField} sortDesc={this.sortDesc} - setSort={this.setSort} + setSort={this.setColumnSort} rowHeight={this.rowHeightFunc} removeColumn={this.removeColumn} resizeColumn={this.startResize} @@ -879,7 +868,14 @@ export class CollectionSchemaView extends CollectionSubView() { {this._columnMenuIndex !== undefined && this.renderColumnMenu} {this._filterColumnIndex !== undefined && this.renderFilterMenu} (this._tableContentRef = ref)} /> - + (value ? this.addRow(Docs.Create.TextDocument(value, { title: value, _layout_autoHeight: true })) : false), 'add text doc')} + placeholder={"Type ':' for commands"} + contents={'+ New Node'} + menuCallback={this.menuCallback} + height={CollectionSchemaView._newNodeInputHeight} + />
{this.previewWidth > 0 &&
} {this.previewWidth > 0 && ( diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 7da3c042c..46c2f622b 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -5,8 +5,6 @@ import { observer } from 'mobx-react'; import { emptyFunction, setupMoveUpEvents } from '../../../../Utils'; import { Colors } from '../../global/globalEnums'; import './CollectionSchemaView.scss'; -import { SnappingManager } from '../../../util/SnappingManager'; -import { DragManager } from '../../../util/DragManager'; export interface SchemaColumnHeaderProps { columnKeys: string[]; diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index 45bfe4f77..978b65053 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -7,7 +7,7 @@ import { Doc } from '../../../../fields/Doc'; import { BoolCast } from '../../../../fields/Types'; import { DragManager } from '../../../util/DragManager'; import { SnappingManager } from '../../../util/SnappingManager'; -import { undoBatch } from '../../../util/UndoManager'; +import { undoable, undoBatch } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { OpenWhere } from '../../nodes/DocumentView'; @@ -111,18 +111,18 @@ export class SchemaRowBox extends ViewBoxBaseComponent() { }}>
{ + onPointerDown={undoable(e => { e.stopPropagation(); this.props.removeDocument?.(this.rootDoc); - })}> + }, 'Delete Row')}>
{ + onPointerDown={undoable(e => { e.stopPropagation(); this.props.addDocTab(this.rootDoc, OpenWhere.addRight); - }}> + }, 'Open Doc on Right')}>
diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index 42bf32475..6d5ef9df6 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -122,7 +122,7 @@ export class SchemaTableCell extends React.Component { const ret = KeyValueBox.SetField(this.props.Document, this.props.fieldKey.replace(/^_/, ''), value); this.props.finishEdit?.(); return ret; - })} + }, 'edit schema cell')} />
); diff --git a/src/client/views/linking/LinkMenu.tsx b/src/client/views/linking/LinkMenu.tsx index 3f6369898..65d13a6c3 100644 --- a/src/client/views/linking/LinkMenu.tsx +++ b/src/client/views/linking/LinkMenu.tsx @@ -49,7 +49,7 @@ export class LinkMenu extends React.Component { )); - return linkItems.length ? linkItems : this.props.style ? [<>] : [

No links have been created yet. Drag the linking button onto another document to create a link.

]; + return linkItems.length ? linkItems : this.props.style ? [] : [

No links have been created yet. Drag the linking button onto another document to create a link.

]; }; render() { diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 8e83cf121..410e0bbdc 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -447,7 +447,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.props.ScreenToLocalTransform().translate(0, -AudioBox.topControlsHeight); - setPlayheadTime = (time: number) => (this._ele!.currentTime = this.layoutDoc._layout_currentTimecode = time); + setPlayheadTime = (time: number) => (this._ele!.currentTime /*= this.layoutDoc._layout_currentTimecode*/ = time); playing = () => this.mediaState === media_state.Playing; diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index d5ca30957..bd1952ecb 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -9,7 +9,7 @@ import { DocUtils } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { Hypothesis } from '../../util/HypothesisUtils'; import { LinkManager } from '../../util/LinkManager'; -import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../../util/UndoManager'; import './DocumentLinksButton.scss'; import { DocumentView } from './DocumentView'; import { LinkDescriptionPopup } from './LinkDescriptionPopup'; @@ -78,7 +78,6 @@ export class DocumentLinksButton extends React.Component { setupMoveUpEvents( this, @@ -123,17 +122,14 @@ export class DocumentLinksButton extends React.Component { - DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View); - }) - ) + action(e => DocumentLinksButton.finishLinkClick(e.clientX, e.clientY, DocumentLinksButton.StartLink, this.props.View.props.Document, true, this.props.View)) ); }; - public static finishLinkClick = undoBatch( - action((screenX: number, screenY: number, startLink: Doc, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) => { - if (startLink === endLink) { + @undoBatch + public static finishLinkClick(screenX: number, screenY: number, startLink: Doc | undefined, endLink: Doc, startIsAnnotation: boolean, endLinkView?: DocumentView, pinProps?: PinProps) { + runInAction(() => { + if (startLink === endLink || !startLink) { DocumentLinksButton.StartLink = undefined; DocumentLinksButton.StartLinkView = undefined; DocumentLinksButton.AnnotationId = undefined; @@ -185,8 +181,8 @@ export class DocumentLinksButton extends React.Component (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'on click')); + clickFunc = () => (this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, 'click ' + this.rootDoc.title)); } else { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplateForField implies we're clicking on part of a template instance and we want to select the whole template, not the part if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { @@ -490,7 +491,7 @@ export class DocumentViewInternal extends DocComponent { - this._longPressSelector = setTimeout(() => DocumentView.LongPress && this.props.select(false), 1000); + this._longPressSelector = setTimeout(() => { + if (DocumentView.LongPress) { + if (this.rootDoc.dontUndo) { + OverlayView.Instance.addWindow(, { x: 300, y: 100, width: 200, height: 200, title: 'Undo Stack' }); + } else { + this.props.select(false); + } + } + }, 1000); if (!GestureOverlay.DownDocView) GestureOverlay.DownDocView = this.props.DocumentView(); this._downX = e.clientX; @@ -557,7 +566,7 @@ export class DocumentViewInternal extends DocComponent { this._markerTargetDoc = linkTarget; this._targetDoc = /*linkTarget?.type === DocumentType.MARKER &&*/ linkTarget?.annotationOn ? Cast(linkTarget.annotationOn, Doc, null) ?? linkTarget : linkTarget; } - this._toolTipText = 'link to ' + this._targetDoc?.title; if (LinkDocPreview.LinkInfo?.noPreview || this._linkSrc?.followLinkToggle || this._markerTargetDoc?.type === DocumentType.PRES) this.followLink(); } }) @@ -296,7 +295,7 @@ export class LinkDocPreview extends React.Component { className="linkDocPreview" ref={this._linkDocRef} onPointerDown={this.followLinkPointerDown} - style={{ display: !this._toolTipText ? 'none' : undefined, left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> + style={{ left: this.props.location[0], top: this.props.location[1], width: this.width() + borders, height: this.height() + borders + (this.props.showHeader ? 37 : 0) }}> {this.docPreview}
); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 57aa852ac..b07cf7e00 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -16,7 +16,7 @@ import { CollectionViewType, DocumentType } from '../../../documents/DocumentTyp import { LinkManager } from '../../../util/LinkManager'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; -import { undoBatch, UndoManager } from '../../../util/UndoManager'; +import { undoable, undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; import { ContextMenu } from '../../ContextMenu'; import { DocComponent } from '../../DocComponent'; @@ -129,29 +129,27 @@ export class FontIconBox extends DocComponent() { * Number button */ @computed get numberSliderButton() { - const numScript = ScriptCast(this.rootDoc.script); - const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value'); - + const numScript = (value?: number) => ScriptCast(this.rootDoc.script).script.run({ self: this.rootDoc, value, _readOnly_: value === undefined }); // Script for checking the outcome of the toggle - const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); - + const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); const label = !FontIconBox.GetShowLabels() ? null :
{this.label}
; const dropdown = (
e.stopPropagation()}> (this._batch = UndoManager.StartBatch('presDuration'))} + onPointerDown={() => (this._batch = UndoManager.StartBatch('num slider changing'))} onPointerUp={() => this._batch?.end()} - onChange={e => { + onChange={undoable(e => { e.stopPropagation(); - setValue(Number(e.target.value)); - }} + numScript(Number(e.target.value)); + }, 'set num value')} />
); @@ -174,20 +172,13 @@ export class FontIconBox extends DocComponent() { * Number button */ @computed get numberDropdownButton() { - const numScript = ScriptCast(this.rootDoc.script); - const setValue = (value: number) => UndoManager.RunInBatch(() => numScript?.script.run({ self: this.rootDoc, value, _readOnly_: false }), 'set num value'); - - // Script for checking the outcome of the toggle - const checkResult = Number(numScript?.script.run({ self: this.rootDoc, value: 0, _readOnly_: true }).result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); + const numScript = (value?: number) => ScriptCast(this.rootDoc.script)?.script.run({ self: this.rootDoc, value, _readOnly_: value === undefined }); - const label = !FontIconBox.GetShowLabels() ? null :
{this.label}
; + const checkResult = Number(numScript().result ?? 0).toPrecision(NumCast(this.dataDoc.numPrecision, 3)); const items: number[] = []; - for (let i = 0; i < 100; i++) { - if (i % 2 === 0) { - items.push(i); - } - } + for (let i = 0; i < 100; i += 2) items.push(i); + const list = items.map(value => { return (
() { style={{ backgroundColor: value.toString() === checkResult ? Colors.LIGHT_BLUE : undefined, }} - onClick={() => setValue(value)}> + onClick={undoable(value => numScript(value), `${this.rootDoc.title} button set from list`)}> {value}
); }); return (
-
setValue(Number(checkResult) - 1))}> - +
numScript(Number(checkResult) - 1), `${this.rootDoc.title} decrement value`)}> +
() { this.noTooltip = this.rootDoc.dropDownOpen; Doc.UnBrushAllDocs(); })}> - setValue(Number(e.target.value))))} /> + numScript(Number(e.target.value)), `${this.rootDoc.title} button set value`)} />
-
setValue(Number(checkResult) + 1))}> +
numScript(Number(checkResult) + 1), `${this.rootDoc.title} increment value`)}>
@@ -322,12 +313,12 @@ export class FontIconBox extends DocComponent() { .map(value => (
script.script.run({ self: this.rootDoc, value }))}> + onClick={undoable(() => script.script.run({ self: this.rootDoc, value }), value)}> {value[0].toUpperCase() + value.slice(1)}
)); @@ -640,29 +631,26 @@ ScriptingGlobals.add(function toggleOverlay(checkResult?: boolean) { ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snaplines' | 'clusters' | 'arrange' | 'viewAll', checkResult?: boolean) { const selected = SelectionManager.Docs().lastElement(); // prettier-ignore - const map: Map<'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { undo: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ + const map: Map<'grid' | 'snaplines' | 'clusters' | 'arrange'| 'viewAll', { waitForRender?: boolean, checkResult: (doc:Doc) => any; setDoc: (doc:Doc) => void;}> = new Map([ ['grid', { - undo: false, checkResult: (doc:Doc) => doc._freeform_backgroundGrid, setDoc: (doc:Doc) => doc._freeform_backgroundGrid = !doc._freeform_backgroundGrid, }], ['snaplines', { - undo: false, checkResult: (doc:Doc) => doc._freeform_snapLines, setDoc: (doc:Doc) => doc._freeform_snapLines = !doc._freeform_snapLines, }], ['viewAll', { - undo: false, checkResult: (doc:Doc) => doc._freeform_fitContentsToBox, setDoc: (doc:Doc) => doc._freeform_fitContentsToBox = !doc._freeform_fitContentsToBox, }], ['clusters', { - undo: false, + waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire checkResult: (doc:Doc) => doc._freeform_useClusters, setDoc: (doc:Doc) => doc._freeform_useClusters = !doc._freeform_useClusters, }], ['arrange', { - undo: true, + waitForRender: true, // flags that undo batch should terminate after a re-render giving the script the chance to fire checkResult: (doc:Doc) => doc._autoArrange, setDoc: (doc:Doc) => doc._autoArrange = !doc._autoArrange, }], @@ -671,7 +659,7 @@ ScriptingGlobals.add(function showFreeform(attr: 'grid' | 'snaplines' | 'cluster if (checkResult) { return map.get(attr)?.checkResult(selected) ? Colors.MEDIUM_BLUE : 'transparent'; } - const batch = map.get(attr)?.undo ? UndoManager.StartBatch('set feature') : { end: () => {} }; + const batch = map.get(attr)?.waitForRender ? UndoManager.StartBatch('set freeform attribute') : { end: () => {} }; SelectionManager.Docs().map(dv => map.get(attr)?.setDoc(dv)); setTimeout(() => batch.end(), 100); }); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9c06aa7d8..e85835002 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -301,11 +301,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent (json?.indexOf('"storedMarks"') === -1 ? json?.replace(/"selection":.*/, '') : json?.replace(/"selection":"\"storedMarks\""/, '"storedMarks"')); @@ -320,29 +320,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent(Array.from(new Set(accumTags))) : undefined; let unchanged = true; - if (this._applyingChange !== this.fieldKey && removeSelection(json) !== removeSelection(curProto?.Data)) { + if (this._applyingChange !== this.fieldKey && removeSelection(newJson) !== removeSelection(prevData?.Data)) { this._applyingChange = this.fieldKey; - const textChange = curText !== Cast(dataDoc[this.fieldKey], RichTextField)?.Text; + const textChange = newText !== prevData?.Text; textChange && (dataDoc[this.fieldKey + '_modificationDate'] = new DateField(new Date(Date.now()))); - if ((!curTemp && !curProto) || curText || json.includes('dash')) { + if ((!prevData && !protoData) || newText || (!newText && !protoData)) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) - if (removeSelection(json) !== removeSelection(curLayout?.Data)) { + if (removeSelection(newJson) !== removeSelection(prevLayoutData?.Data)) { const numstring = NumCast(dataDoc[this.fieldKey], null); - if (numstring !== undefined) { - dataDoc[this.fieldKey] = Number(curText); - } else { - dataDoc[this.fieldKey] = new RichTextField(json, curText); - } + dataDoc[this.fieldKey] = numstring !== undefined ? Number(newText) : new RichTextField(newJson, newText); dataDoc[this.fieldKey + '_noTemplate'] = true; //(curTemp?.Text || "") !== curText; // mark the data field as being split from the template if it has been edited - textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); + textChange && ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText }); unchanged = false; } } else { // if we've deleted all the text in a note driven by a template, then restore the template data dataDoc[this.fieldKey] = undefined; - this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((curProto || curTemp).Data))); + this._editorView.updateState(EditorState.fromJSON(this.config, JSON.parse((protoData || prevData).Data))); dataDoc[this.fieldKey + '_noTemplate'] = undefined; // mark the data field as not being split from any template it might have - ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: curText }); + ScriptCast(this.layoutDoc.onTextChanged, null)?.script.run({ this: this.layoutDoc, self: this.rootDoc, text: newText }); unchanged = false; } this._applyingChange = ''; @@ -667,7 +663,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { - const batch = UndoManager.StartBatch('sidebar'); + const batch = UndoManager.StartBatch('toggle sidebar'); setupMoveUpEvents( this, e, @@ -694,13 +690,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { + const batch = UndoManager.StartBatch('delete link'); LinkManager.Instance.deleteLink(LinkManager.Links(anchor)[0]); // const docAnnotations = DocListCast(this.props.dataDoc[this.fieldKey]); // this.props.dataDoc[this.fieldKey] = new List(docAnnotations.filter(a => a !== this.annoTextRegion)); // AnchorMenu.Instance.fadeOut(true); this.props.select(false); + setTimeout(batch.end); // wait for reaction to remove link from document }; @undoBatch @@ -733,17 +730,22 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent h); const anchorDoc = Array.from(hrefs).lastElement().replace(Doc.localServerPath(), '').split('?')[0]; + const deleteMarkups = undoBatch(() => { + const sel = editor.state.selection; + editor.dispatch(editor.state.tr.removeMark(sel.from, sel.to, editor.state.schema.marks.linkAnchor)); + }); e.persist(); anchorDoc && DocServer.GetRefField(anchorDoc).then( action(anchor => { + anchor && SelectionManager.SelectSchemaViewDoc(anchor as Doc); AnchorMenu.Instance.Status = 'annotation'; - AnchorMenu.Instance.Delete = () => this.deleteAnnotation(anchor as Doc); + AnchorMenu.Instance.Delete = !anchor && editor.state.selection.empty ? returnFalse : !anchor ? deleteMarkups : () => this.deleteAnnotation(anchor as Doc); AnchorMenu.Instance.Pinned = false; - AnchorMenu.Instance.PinToPres = () => this.pinToPres(anchor as Doc); - AnchorMenu.Instance.MakeTargetToggle = () => this.makeTargetToggle(anchor as Doc); - AnchorMenu.Instance.ShowTargetTrail = () => this.showTargetTrail(anchor as Doc); - AnchorMenu.Instance.IsTargetToggler = () => this.isTargetToggler(anchor as Doc); + AnchorMenu.Instance.PinToPres = !anchor ? returnFalse : () => this.pinToPres(anchor as Doc); + AnchorMenu.Instance.MakeTargetToggle = !anchor ? returnFalse : () => this.makeTargetToggle(anchor as Doc); + AnchorMenu.Instance.ShowTargetTrail = !anchor ? returnFalse : () => this.showTargetTrail(anchor as Doc); + AnchorMenu.Instance.IsTargetToggler = !anchor ? returnFalse : () => this.isTargetToggler(anchor as Doc); AnchorMenu.Instance.jumpTo(e.clientX, e.clientY, true); }) ); @@ -1002,7 +1004,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent this._editorView?.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(from), tr.doc.resolve(to)))), 250); this._editorView.state.storedMarks = [ ...(this._editorView.state.storedMarks ?? []), @@ -1630,7 +1631,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent { //applyDevTools.applyDevTools(this._editorView); this.ProseRef?.children[0] === e.nativeEvent.target && this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); - this.startUndoTypingBatch(); + e.stopPropagation(); }; onClick = (e: React.MouseEvent): void => { @@ -1705,13 +1706,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent() { min={min} max={max} value={value} + readOnly={true} style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }} className={`toolbar-slider ${active ? '' : 'none'}`} onPointerDown={e => { @@ -1501,7 +1502,7 @@ export class PresBox extends ViewBoxBaseComponent() {
Slide Duration
- e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s + e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
this.updateDurationTime(String(duration), 1000)}> @@ -1654,7 +1655,7 @@ export class PresBox extends ViewBoxBaseComponent() {
Zoom (% screen filled)
- this.updateZoom(e.target.value)} />% + this.updateZoom(e.target.value)} />%
this.updateZoom(String(zoom), 0.1)}> @@ -1669,7 +1670,7 @@ export class PresBox extends ViewBoxBaseComponent() {
Transition Time
- e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s + e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
this.updateTransitionTime(String(transitionSpeed), 1000)}> @@ -1756,6 +1757,7 @@ export class PresBox extends ViewBoxBaseComponent() { className="presBox-input" style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} type="number" + readOnly={true} value={NumCast(activeItem.presStartTime).toFixed(2)} onKeyDown={e => e.stopPropagation()} onChange={action((e: React.ChangeEvent) => { @@ -1782,6 +1784,7 @@ export class PresBox extends ViewBoxBaseComponent() { onKeyDown={e => e.stopPropagation()} style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} type="number" + readOnly={true} value={NumCast(activeItem.presEndTime).toFixed(2)} onChange={action((e: React.ChangeEvent) => { activeItem.presEndTime = Number(e.target.value); diff --git a/src/client/views/nodes/trails/PresElementBox.tsx b/src/client/views/nodes/trails/PresElementBox.tsx index 4eb6aee25..2279ffe84 100644 --- a/src/client/views/nodes/trails/PresElementBox.tsx +++ b/src/client/views/nodes/trails/PresElementBox.tsx @@ -1,6 +1,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; import { Id } from '../../../../fields/FieldSymbols'; @@ -13,7 +13,7 @@ import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from '../../../util/DragManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { Transform } from '../../../util/Transform'; -import { undoBatch } from '../../../util/UndoManager'; +import { undoable, undoBatch } from '../../../util/UndoManager'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from '../../EditableView'; import { Colors } from '../../global/globalEnums'; @@ -263,16 +263,15 @@ export class PresElementBox extends ViewBoxBaseComponent() { } }; - @undoBatch - removeItem = action((e: React.MouseEvent) => { + removePresentationItem = undoable((e: React.MouseEvent) => { e.stopPropagation(); if (this.presBox && this.indexInPres < (this.presBoxView?.itemIndex || 0)) { - this.presBox.itemIndex = (this.presBoxView?.itemIndex || 0) - 1; + runInAction(() => (this.presBox!.itemIndex = (this.presBoxView?.itemIndex || 0) - 1)); } this.props.removeDocument?.(this.rootDoc); this.presBoxView?.removeFromSelectedArray(this.rootDoc); this.removeAllRecordingInOverlay(); - }); + }, 'Remove doc from pres trail'); // set the value/title of the individual pres element @undoBatch @@ -476,7 +475,7 @@ export class PresElementBox extends ViewBoxBaseComponent() { ); items.push( Remove from presentation
}> -
+
e.stopPropagation()} />
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx index d6dddf71a..5480600b0 100644 --- a/src/client/views/pdf/AnchorMenu.tsx +++ b/src/client/views/pdf/AnchorMenu.tsx @@ -395,22 +395,25 @@ export class AnchorMenu extends AntimodeMenu { ) : ( <> Remove Link Anchor
}> - Pin to Presentation
}> - Show Linked Trail
}> - make target visibility toggle on click
}> - diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 1c1b41f73..3479cd20f 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -141,6 +141,8 @@ export class SearchBox extends ViewBoxBaseComponent() { const annos = !Field.toString(Doc.LayoutField(d) as Field).includes('CollectionView'); const data = d[annos ? fieldKey + '_annotations' : fieldKey]; data && newarray.push(...DocListCast(data)); + const sidebar = d[fieldKey + '_sidebar']; + sidebar && newarray.push(...DocListCast(sidebar)); func(depth, d); }); docs = newarray; diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 5312da009..d8447deb6 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -236,7 +236,7 @@ export class Doc extends RefField { if ( doc && Doc.MyFileOrphans instanceof Doc && - Doc.IsDocDataProto(doc) && + Doc.IsDataProto(doc) && !Doc.IsSystem(doc) && ![DocumentType.CONFIG, DocumentType.KVP, DocumentType.LINK, DocumentType.LINKANCHOR].includes(doc.type as any) && !doc.isFolder && @@ -510,7 +510,7 @@ 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 IsDocDataProto(doc: Doc) { + export function IsDataProto(doc: Doc) { return GetT(doc, 'isDataDoc', 'boolean', true); } export function IsBaseProto(doc: Doc) { @@ -981,7 +981,7 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string, retitle = false): Doc { const copy = new Doc(copyProtoId, true); updateCachedAcls(copy); - const exclude = [...Cast(doc.cloneFieldFilter, listSpec('string'), []), 'dragFactory_count', 'cloneFieldFilter']; + const exclude = [...StrListCast(doc.cloneFieldFilter), 'dragFactory_count', 'cloneFieldFilter']; Object.keys(doc).forEach(key => { if (exclude.includes(key)) return; const cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); @@ -1433,7 +1433,7 @@ export namespace Doc { export function setDocFilter(container: Opt, key: string, value: any, modifiers: 'remove' | 'match' | 'check' | 'x' | 'exists' | 'unset', toggle?: boolean, fieldPrefix?: string, append: boolean = true) { if (!container) return; const filterField = '_' + (fieldPrefix ? fieldPrefix + '_' : '') + 'docFilters'; - const docFilters = Cast(container[filterField], listSpec('string'), []); + const docFilters = StrListCast(container[filterField]); runInAction(() => { for (let i = 0; i < docFilters.length; i++) { const fields = docFilters[i].split(':'); // split key:value:modifier @@ -1745,6 +1745,16 @@ ScriptingGlobals.add(function undo() { SelectionManager.DeselectAll(); return UndoManager.Undo(); }); + +export function ShowUndoStack() { + SelectionManager.DeselectAll(); + var buffer = ''; + UndoManager.undoStack.forEach((batch, i) => { + buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n'; + ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n')); + }); + alert(buffer); +} ScriptingGlobals.add(function redo() { SelectionManager.DeselectAll(); return UndoManager.Redo(); diff --git a/src/fields/util.ts b/src/fields/util.ts index f4fd3200c..a2b445d6c 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -65,6 +65,7 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number if (value instanceof RefField) { value = new ProxyField(value); } + if (value instanceof ObjectField) { if (value[Parent] && value[Parent] !== receiver && !(value instanceof PrefetchProxy)) { throw new Error("Can't put the same object in multiple documents at the same time"); @@ -102,20 +103,24 @@ const _setterImpl = action(function (target: any, prop: string | symbol | number DocServer.registerDocWithCachedUpdate(receiver, prop as string, curValue); } !receiver[Initializing] && + !receiver.dontUndo && (!receiver[UpdatingFromServer] || receiver[ForceServerWrite]) && - UndoManager.AddEvent({ - redo: () => (receiver[prop] = value), - undo: () => { - const wasUpdate = receiver[UpdatingFromServer]; - const wasForce = receiver[ForceServerWrite]; - receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet - receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. - receiver[prop] = curValue; - receiver[ForceServerWrite] = wasForce; - receiver[UpdatingFromServer] = wasUpdate; + UndoManager.AddEvent( + { + redo: () => (receiver[prop] = value), + undo: () => { + const wasUpdate = receiver[UpdatingFromServer]; + const wasForce = receiver[ForceServerWrite]; + receiver[ForceServerWrite] = true; // needed since writes aren't propagated to server if UpdatingFromServerIsSet + receiver[UpdatingFromServer] = true; // needed if the event caused ACL's to change such that the doc is otherwise no longer editable. + receiver[prop] = curValue; + receiver[ForceServerWrite] = wasForce; + receiver[UpdatingFromServer] = wasUpdate; + }, + prop: prop?.toString(), }, - prop: prop?.toString(), - }); + value + ); return true; } return false; @@ -390,6 +395,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any diff?.op === '$addToSet' ? { redo: () => { + console.log('redo $add: ' + prop, diff.items); // bcz: uncomment to log undo receiver[prop].push(...diff.items.map((item: any) => item.value ?? item)); lastValue = ObjectField.MakeCopy(receiver[prop]); }, @@ -406,11 +412,12 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any }); lastValue = ObjectField.MakeCopy(receiver[prop]); }), - prop: '', + prop: 'add ' + diff.items.length + ' items to list', } : diff?.op === '$remFromSet' ? { redo: action(() => { + console.log('redo $rem: ' + prop, diff.items); // bcz: uncomment to log undo diff.items.forEach((item: any) => { const ind = item instanceof SchemaHeaderField ? receiver[prop].findIndex((ele: any) => ele instanceof SchemaHeaderField && ele.heading === item.heading) : receiver[prop].indexOf(item.value ?? item); ind !== -1 && receiver[prop].splice(ind, 1); @@ -430,10 +437,11 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any }); lastValue = ObjectField.MakeCopy(receiver[prop]); }, - prop: '', + prop: 'remove ' + diff.items.length + ' items from list', } : { redo: () => { + console.log('redo list: ' + prop, receiver[prop]); // bcz: uncomment to log undo receiver[prop] = ObjectField.MakeCopy(newValue as List); lastValue = ObjectField.MakeCopy(receiver[prop]); }, @@ -442,7 +450,7 @@ export function updateFunction(target: any, prop: any, value: any, receiver: any receiver[prop] = ObjectField.MakeCopy(prevValue as List); lastValue = ObjectField.MakeCopy(receiver[prop]); }, - prop: '', + prop: 'assign list', } ); } -- cgit v1.2.3-70-g09d2 From 9accdb19ea34240ad2c7739ea72e51aeaae2a77b Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 6 Jun 2023 20:50:27 -0400 Subject: more Symbol updating. --- src/client/DocServer.ts | 3 +- src/client/util/DocumentManager.ts | 7 +- src/client/views/DashboardView.tsx | 5 +- src/client/views/MarqueeAnnotator.tsx | 5 +- src/client/views/PropertiesView.tsx | 224 ++++++--------------- src/client/views/StyleProvider.tsx | 117 ++++------- .../collections/CollectionMasonryViewFieldRow.tsx | 5 +- src/client/views/collections/CollectionSubView.tsx | 3 +- .../CollectionFreeFormLinkView.tsx | 7 +- .../views/nodes/DataVizBox/components/TableBox.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 8 +- src/client/views/nodes/MapBox/MapBox.tsx | 5 +- src/client/views/nodes/button/FontIconBadge.tsx | 30 +-- .../views/nodes/formattedText/DashFieldView.tsx | 2 +- .../formattedText/ProsemirrorExampleTransfer.ts | 3 +- .../views/nodes/formattedText/RichTextRules.ts | 16 +- src/client/views/nodes/trails/PresBox.tsx | 13 +- src/client/views/search/SearchBox.tsx | 7 +- src/client/views/topbar/TopBar.tsx | 3 +- src/fields/Schema.ts | 78 +++---- 21 files changed, 214 insertions(+), 332 deletions(-) (limited to 'src/client/views/search') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index ba64f993c..02b047a95 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,7 +1,8 @@ import { runInAction } from 'mobx'; import * as rp from 'request-promise'; import * as io from 'socket.io-client'; -import { Doc, Opt, UpdatingFromServer } from '../fields/Doc'; +import { Doc, Opt } from '../fields/Doc'; +import { UpdatingFromServer } from '../fields/DocSymbols'; import { FieldLoader } from '../fields/FieldLoader'; import { HandleUpdate, Id, Parent } from '../fields/FieldSymbols'; import { ObjectField } from '../fields/ObjectField'; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 1f8f4fd45..7a88ca991 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,5 +1,6 @@ import { action, computed, observable, ObservableSet } from 'mobx'; -import { AnimationSym, Doc, Opt } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; +import { Animation } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; import { Cast, DocCast, StrCast } from '../../fields/Types'; @@ -294,7 +295,7 @@ export class DocumentManager { Doc.linkFollowHighlight(docView.rootDoc, undefined, options.effect); if (options.playAudio) DocumentManager.playAudioAnno(docView.rootDoc); if (options.toggleTarget && (!options.didMove || docView.rootDoc.hidden)) docView.rootDoc.hidden = !docView.rootDoc.hidden; - if (options.effect) docView.rootDoc[AnimationSym] = options.effect; + if (options.effect) docView.rootDoc[Animation] = options.effect; if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && viewSpec.textHtml) { // if the docView is a text anchor, the contextView is the PDF/Web/Text doc @@ -303,7 +304,7 @@ export class DocumentManager { DocumentManager._overlayViews.add(contextView); } Doc.AddUnHighlightWatcher(() => { - docView.rootDoc[AnimationSym] = undefined; + docView.rootDoc[Animation] = undefined; DocumentManager.removeOverlayViews(); contextView && (contextView.htmlOverlayEffect = ''); }); diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index fb41ca8af..94dd010c3 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -3,7 +3,8 @@ import { Button, ColorPicker, FontSize, IconButton, Size } from 'browndash-compo import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { DataSym, Doc, DocListCast, DocListCastAsync } from '../../fields/Doc'; +import { Doc, DocListCast, DocListCastAsync } from '../../fields/Doc'; +import { DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; @@ -368,7 +369,7 @@ export class DashboardView extends React.Component { const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, 'row'); // switching the tabs from the datadoc to the regular doc - const dashboardTabs = DocListCast(dashboardDoc[DataSym].data); + const dashboardTabs = DocListCast(dashboardDoc[DocData].data); dashboardDoc.data = new List(dashboardTabs); dashboardDoc['pane-count'] = 1; diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx index c9555ab91..6c36d39b9 100644 --- a/src/client/views/MarqueeAnnotator.tsx +++ b/src/client/views/MarqueeAnnotator.tsx @@ -1,6 +1,7 @@ import { action, observable, ObservableMap, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DataSym, Doc, Opt } from '../../fields/Doc'; +import { Doc, Opt } from '../../fields/Doc'; +import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocData } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { List } from '../../fields/List'; import { NumCast } from '../../fields/Types'; @@ -207,7 +208,7 @@ export class MarqueeAnnotator extends React.Component { @action highlight = (color: string, isLinkButton: boolean, savedAnnotations?: ObservableMap, addAsAnnotation?: boolean, summarize?: boolean) => { // creates annotation documents for current highlights - const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DataSym]); + const effectiveAcl = GetEffectiveAcl(this.props.rootDoc[DocData]); const annotationDoc = [AclAugment, AclSelfEdit, AclEdit, AclAdmin].includes(effectiveAcl) && this.makeAnnotationDocument(color, isLinkButton, savedAnnotations); addAsAnnotation && !savedAnnotations && annotationDoc && this.props.addDocument(annotationDoc); return (annotationDoc as Doc) ?? undefined; diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 1111886d8..00e077f96 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -127,9 +127,8 @@ export class PropertiesView extends React.Component { const aspect = Doc.NativeAspect(layoutDoc, undefined, !layoutDoc._layout_fitWidth); if (aspect) return Math.min(layoutDoc[Width](), Math.min(this.MAX_EMBED_HEIGHT * aspect, this.props.width - 20)); return Doc.NativeWidth(layoutDoc) ? Math.min(layoutDoc[Width](), this.props.width - 20) : this.props.width - 20; - } else { - return 0; } + return 0; }; @action @@ -435,9 +434,7 @@ export class PropertiesView extends React.Component { } @action - toggleCheckbox = () => { - this.layoutFields = !this.layoutFields; - }; + toggleCheckbox = () => (this.layoutFields = !this.layoutFields); @computed get editableTitle() { const titles = new Set(); @@ -535,18 +532,7 @@ export class PropertiesView extends React.Component { marginLeft: title === '∠:' ? '39px' : '', }}>
{title}
- { - setter(e.target.value); - }} - onKeyPress={e => { - e.stopPropagation(); - }} - /> + setter(e.target.value)} onKeyPress={e => e.stopPropagation()} />
this.upDownButtons('up', key)))}> @@ -570,55 +556,50 @@ export class PropertiesView extends React.Component { @action upDownButtons = (dirs: string, field: string) => { + const selDoc = this.selectedDoc; + if (!selDoc) return; + //prettier-ignore switch (field) { - case 'Xps': - this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10)); - break; - case 'Yps': - this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10)); - break; - case 'stk': - this.selectedDoc && (this.selectedDoc.stroke_width = NumCast(this.selectedDoc?.stroke_width) + (dirs === 'up' ? 0.1 : -0.1)); - break; + case 'Xps': selDoc.x = NumCast(this.selectedDoc?.x) + (dirs === 'up' ? 10 : -10); break; + case 'Yps': selDoc.y = NumCast(this.selectedDoc?.y) + (dirs === 'up' ? 10 : -10); break; + case 'stk': selDoc.stroke_width = NumCast(this.selectedDoc?.stroke_width) + (dirs === 'up' ? 0.1 : -0.1); break; case 'wid': - const oldWidth = NumCast(this.selectedDoc?._width); - const oldHeight = NumCast(this.selectedDoc?._height); - const oldX = NumCast(this.selectedDoc?.x); - const oldY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === 'up' ? 10 : -10)); - const doc = this.selectedDoc; - if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { - const ink = Cast(doc.data, InkField)?.inkData; + const oldWidth = NumCast(selDoc._width); + const oldHeight = NumCast(selDoc._height); + const oldX = NumCast(selDoc.x); + const oldY = NumCast(selDoc.y); + selDoc._width = oldWidth + (dirs === 'up' ? 10 : -10); + if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) { + const ink = Cast(selDoc.data, InkField)?.inkData; if (ink) { const newPoints: { X: number; Y: number }[] = []; for (var j = 0; j < ink.length; j++) { // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = NumCast(doc.x) - oldX + (ink[j].X * NumCast(doc._width)) / oldWidth; - const newY = NumCast(doc.y) - oldY + (ink[j].Y * NumCast(doc._height)) / oldHeight; + const newX = NumCast(selDoc.x) - oldX + (ink[j].X * NumCast(selDoc._width)) / oldWidth; + const newY = NumCast(selDoc.y) - oldY + (ink[j].Y * NumCast(selDoc._height)) / oldHeight; newPoints.push({ X: newX, Y: newY }); } - doc.data = new InkField(newPoints); + selDoc.data = new InkField(newPoints); } } break; case 'hgt': - const oWidth = NumCast(this.selectedDoc?._width); - const oHeight = NumCast(this.selectedDoc?._height); - const oX = NumCast(this.selectedDoc?.x); - const oY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === 'up' ? 10 : -10)); - const docu = this.selectedDoc; - if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) { - const ink = Cast(docu.data, InkField)?.inkData; + const oWidth = NumCast(selDoc._width); + const oHeight = NumCast(selDoc._height); + const oX = NumCast(selDoc.x); + const oY = NumCast(selDoc.y); + selDoc._height = oHeight + (dirs === 'up' ? 10 : -10); + if (selDoc.type === DocumentType.INK && selDoc.x && selDoc.y && selDoc._height && selDoc._width) { + const ink = Cast(selDoc.data, InkField)?.inkData; if (ink) { const newPoints: { X: number; Y: number }[] = []; for (var j = 0; j < ink.length; j++) { // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = NumCast(docu.x) - oX + (ink[j].X * NumCast(docu._width)) / oWidth; - const newY = NumCast(docu.y) - oY + (ink[j].Y * NumCast(docu._height)) / oHeight; + const newX = NumCast(selDoc.x) - oX + (ink[j].X * NumCast(selDoc._width)) / oWidth; + const newY = NumCast(selDoc.y) - oY + (ink[j].Y * NumCast(selDoc._height)) / oHeight; newPoints.push({ X: newX, Y: newY }); } - docu.data = new InkField(newPoints); + selDoc.data = new InkField(newPoints); } } break; @@ -658,21 +639,11 @@ export class PropertiesView extends React.Component { return this.inputBoxDuo( 'hgt', this.shapeHgt, - undoable((val: string) => { - if (!isNaN(Number(val))) { - this.shapeHgt = val; - } - return true; - }, 'set height'), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeHgt = val), 'set height'), 'H:', 'wid', this.shapeWid, - undoable((val: string) => { - if (!isNaN(Number(val))) { - this.shapeWid = val; - } - return true; - }, 'set width'), + undoable((val: string) => !isNaN(Number(val)) && (this.shapeWid = val), 'set width'), 'W:' ); } @@ -680,21 +651,11 @@ export class PropertiesView extends React.Component { return this.inputBoxDuo( 'Xps', this.shapeXps, - undoable((val: string) => { - if (val !== '0' && !isNaN(Number(val))) { - this.shapeXps = val; - } - return true; - }, 'set x coord'), + undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeXps = val), 'set x coord'), 'X:', 'Yps', this.shapeYps, - undoable((val: string) => { - if (val !== '0' && !isNaN(Number(val))) { - this.shapeYps = val; - } - return true; - }, 'set y coord'), + undoable((val: string) => val !== '0' && !isNaN(Number(val)) && (this.shapeYps = val), 'set y coord'), 'Y:' ); } @@ -702,36 +663,24 @@ export class PropertiesView extends React.Component { @observable private _fillBtn = false; @observable private _lineBtn = false; - private _lastFill = '#D0021B'; - private _lastLine = '#D0021B'; private _lastDash: any = '2'; @computed get colorFil() { - const ccol = this.getField('fillColor') || ''; - ccol && (this._lastFill = ccol); - return ccol; + return StrCast(this.selectedDoc?.fillColor); } @computed get colorStk() { - const ccol = this.getField('color') || ''; - ccol && (this._lastLine = ccol); - return ccol; + return StrCast(this.selectedDoc?.color); } set colorFil(value) { - value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } set colorStk(value) { - value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } - colorButton(value: string, type: string, setter: () => {}) { - // return
this.changeScrolling(false)} - // onPointerLeave={e => this.changeScrolling(true)}> - // + colorButton(value: string, type: string, setter: () => void) { return ( -
setter()))}> +
setter())}>
{ {value === '' || value === 'transparent' ?

: ''}
); - // - //
; } - @undoBatch - @action - switchStk = (color: ColorState) => { - const val = String(color.hex); - this.colorStk = val; - return true; - }; - @undoBatch - @action - switchFil = (color: ColorState) => { - const val = String(color.hex); - this.colorFil = val; - return true; - }; - - colorPicker(setter: (color: string) => {}, type: string) { + colorPicker(color: string, setter: (color: string) => void) { return ( setter(color.hex)), + 'set stroke color property' + )} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} - color={type === 'stk' ? this.colorStk : this.colorFil} + color={color} /> ); } @@ -777,22 +712,20 @@ export class PropertiesView extends React.Component { return this.colorButton(this.colorFil, 'fill', () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; - return true; }); } @computed get lineButton() { return this.colorButton(this.colorStk, 'line', () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; - return true; }); } @computed get fillPicker() { - return this.colorPicker((color: string) => (this.colorFil = color), 'fil'); + return this.colorPicker(this.colorFil, (color: string) => (this.colorFil = color)); } @computed get linePicker() { - return this.colorPicker((color: string) => (this.colorStk = color), 'stk'); + return this.colorPicker(this.colorStk, (color: string) => (this.colorStk = color)); } @computed get strokeAndFill() { @@ -814,15 +747,9 @@ export class PropertiesView extends React.Component { ); } - @computed get solidStk() { - return this.selectedDoc?.color && (!this.selectedDoc?.stroke_dash || this.selectedDoc?.stroke_dash === '0') ? true : false; - } @computed get dashdStk() { return this.selectedDoc?.stroke_dash || ''; } - @computed get unStrokd() { - return this.selectedDoc?.color ? true : false; - } @computed get widthStk() { return this.getField('stroke_width') || '1'; } @@ -835,12 +762,8 @@ export class PropertiesView extends React.Component { @computed get markTail() { return this.getField('stroke_endMarker') || ''; } - set solidStk(value) { - this.dashdStk = ''; - this.unStrokd = !value; - } set dashdStk(value) { - value && (this._lastDash = value) && (this.unStrokd = false); + value && (this._lastDash = value); this.selectedDoc && (this.selectedDoc.stroke_dash = value ? this._lastDash : undefined); } set markScal(value) { @@ -849,9 +772,6 @@ export class PropertiesView extends React.Component { set widthStk(value) { this.selectedDoc && (this.selectedDoc.stroke_width = Number(value)); } - set unStrokd(value) { - this.colorStk = value ? '' : this._lastLine; - } set markHead(value) { this.selectedDoc && (this.selectedDoc.stroke_startMarker = value); } @@ -869,7 +789,7 @@ export class PropertiesView extends React.Component { regInput = (key: string, value: any, setter: (val: string) => {}) => { return (
- setter(e.target.value)} /> + setter(e.target.value)} />
this.upDownButtons('up', key)))}> @@ -1164,27 +1084,28 @@ export class PropertiesView extends React.Component { // handleDescriptionChange = (e: React.ChangeEvent) => { this.link_description = e.target.value; } // handleRelationshipChange = (e: React.ChangeEvent) => { this.link_relationship = e.target.value; } - @undoBatch - handleDescriptionChange = action((value: string) => { - if (LinkManager.currentLink && this.selectedDoc) { - this.setDescripValue(value); - return true; - } - }); - - @undoBatch - handlelinkRelationshipChange = action((value: string) => { - if (LinkManager.currentLink && this.selectedDoc) { - this.setlinkRelationshipValue(value); - return true; - } - }); + handleDescriptionChange = undoable( + action((value: string) => { + if (LinkManager.currentLink && this.selectedDoc) { + this.setDescripValue(value); + } + }), + 'change link description' + ); + + handlelinkRelationshipChange = undoable( + action((value: string) => { + if (LinkManager.currentLink && this.selectedDoc) { + this.setlinkRelationshipValue(value); + } + }), + 'change link relationship' + ); @undoBatch setDescripValue = action((value: string) => { if (LinkManager.currentLink) { Doc.GetProto(LinkManager.currentLink).link_description = value; - return true; } }); @@ -1316,7 +1237,6 @@ export class PropertiesView extends React.Component { autoComplete={'off'} id="link_relationship_input" value={StrCast(LinkManager.currentLink?.link_relationship)} - readOnly={true} onKeyDown={this.onRelationshipKey} onBlur={this.onSelectOutRelationship} onChange={e => this.handlelinkRelationshipChange(e.currentTarget.value)} @@ -1333,8 +1253,7 @@ export class PropertiesView extends React.Component { autoComplete="off" style={{ textAlign: 'left' }} id="link_description_input" - value={Field.toString(LinkManager.currentLink?.link_description as any as Field)} - readOnly={true} + value={StrCast(LinkManager.currentLink?.link_description)} onKeyDown={this.onDescriptionKey} onBlur={this.onSelectOutDesc} onChange={e => this.handleDescriptionChange(e.currentTarget.value)} @@ -1688,19 +1607,6 @@ export class PropertiesView extends React.Component { {this.openSlideOptions ?
{PresBox.Instance.mediaOptionsDropdown}
: null}
)} - {/*
-
{ this.openAddSlide = !this.openAddSlide; })} - style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> -     Add new slide -
- -
-
- {this.openAddSlide ?
- {PresBox.Instance.newDocumentDropdown} -
: null} -
*/}
); } diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 5efecaf3a..c77bfd468 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -18,13 +18,12 @@ import { TreeSort } from './collections/TreeView'; import { Colors } from './global/globalEnums'; import { InkingStroke } from './InkingStroke'; import { MainView } from './MainView'; -import { DocumentViewProps, OpenWhere } from './nodes/DocumentView'; +import { DocumentViewProps } from './nodes/DocumentView'; import { FieldViewProps } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { SliderBox } from './nodes/SliderBox'; import './StyleProvider.scss'; import React = require('react'); -import { SchemaRowBox } from './collections/collectionSchema/SchemaRowBox'; export enum StyleProp { TreeViewIcon = 'treeViewIcon', @@ -37,13 +36,13 @@ export enum StyleProp { BackgroundColor = 'backgroundColor', // background color of a document view FillColor = 'fillColor', // fill color of an ink stroke or shape WidgetColor = 'widgetColor', // color to display UI widgets on a document view -- used for the sidebar divider dragger on a text note - HideLinkButton = 'hideLinkButton', // hides the blue-dot link button. used when a document acts like a button + HideLinkBtn = 'hideLinkButton', // hides the blue-dot link button. used when a document acts like a button PointerEvents = 'pointerEvents', // pointer events for DocumentView -- inherits pointer events if not specified Decorations = 'decorations', // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background HeaderMargin = 'headerMargin', // margin at top of documentview, typically for displaying a title -- doc contents will start below that - showCaption = 'layout_showCaption', + ShowCaption = 'layout_showCaption', TitleHeight = 'titleHeight', // Height of Title area - layout_showTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix) + ShowTitle = 'layout_showTitle', // whether to display a title on a Document (optional :hover suffix) JitterRotation = 'jitterRotation', // whether documents should be randomly rotated BorderPath = 'customBorder', // border path for document view FontSize = 'fontSize', // size of text font @@ -96,8 +95,9 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc && BoolCast(doc._lockedPosition); const backgroundCol = () => props?.styleProvider?.(doc, props, StyleProp.BackgroundColor); const opacity = () => props?.styleProvider?.(doc, props, StyleProp.Opacity); - const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.layout_showTitle); + const layout_showTitle = () => props?.styleProvider?.(doc, props, StyleProp.ShowTitle); const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min); + // prettier-ignore switch (property.split(':')[0]) { case StyleProp.TreeViewIcon: const img = ImageCast(doc?.icon, ImageCast(doc?.data)); @@ -131,21 +131,17 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt = StrCast(doc?.[fieldKey + 'color'], StrCast(doc?._color)); @@ -174,8 +168,6 @@ export function DefaultStyleProvider(doc: Opt, props: Opt, props: Opt (props?.PanelHeight() || 0) ? 5 : 10) : 0; - case StyleProp.showCaption: - return doc?._type_collection === CollectionViewType.Carousel || props?.hideCaptions ? undefined : StrCast(doc?._layout_showCaption); case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.NoteTaking, CollectionViewType.Masonry, CollectionViewType.Tree].includes(doc?._type_collection as any) || (doc?.type === DocumentType.RTF && !layout_showTitle()?.includes('noMargin')) || @@ -207,55 +197,29 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + '_backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : '')); + // prettier-ignore switch (doc?.type) { - case DocumentType.PRESELEMENT: - docColor = docColor || (darkScheme() ? '' : ''); - break; - case DocumentType.PRES: - docColor = docColor || (darkScheme() ? 'transparent' : 'transparent'); - break; - case DocumentType.FONTICON: - docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; - break; - case DocumentType.RTF: - docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); - break; - case DocumentType.FILTER: - docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : 'rgba(105, 105, 105, 0.432)'); - break; - case DocumentType.INK: - docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; - break; - case DocumentType.SLIDER: - break; - case DocumentType.EQUATION: - docColor = docColor || 'transparent'; - break; - case DocumentType.LABEL: - docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); - break; - case DocumentType.BUTTON: - docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); - break; - case DocumentType.LINKANCHOR: - docColor = isAnchor ? Colors.LIGHT_BLUE : 'transparent'; - break; - case DocumentType.LINK: - docColor = (isAnchor ? docColor : '') || 'transparent'; - break; + case DocumentType.SLIDER: break; + case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? '' : ''); break; + case DocumentType.PRES: docColor = docColor || (darkScheme() ? 'transparent' : 'transparent'); break; + case DocumentType.FONTICON: docColor = boxBackground ? undefined : docColor || Colors.DARK_GRAY; break; + case DocumentType.RTF: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; + case DocumentType.FILTER: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : 'rgba(105, 105, 105, 0.432)'); break; + case DocumentType.INK: docColor = doc?.stroke_isInkMask ? 'rgba(0,0,0,0.7)' : undefined; break; + case DocumentType.EQUATION: docColor = docColor || 'transparent'; break; + case DocumentType.LABEL: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; + case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; + case DocumentType.LINKANCHOR: docColor = isAnchor ? Colors.LIGHT_BLUE : 'transparent'; break; + case DocumentType.LINK: docColor = (isAnchor ? docColor : '') || 'transparent'; break; case DocumentType.IMG: case DocumentType.WEB: case DocumentType.PDF: case DocumentType.MAP: case DocumentType.SCREENSHOT: - case DocumentType.VID: - docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); - break; + case DocumentType.VID: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY); break; case DocumentType.COL: if (StrCast(Doc.LayoutField(doc)).includes(SliderBox.name)) break; - docColor = - docColor || - (Doc.IsSystem(doc) + docColor = docColor || (Doc.IsSystem(doc) ? darkScheme() ? Colors.DARK_GRAY : Colors.LIGHT_GRAY // system docs (seen in treeView) get a grayish background @@ -266,12 +230,9 @@ export function DefaultStyleProvider(doc: Opt, props: Opt 0 ? Doc.UserDoc().activeCollectionNestedBackground : Doc.UserDoc().activeCollectionBackground, 'string') ?? (darkScheme() ? Colors.BLACK : 'linear-gradient(#065fff, #85c1f9)')); break; //if (doc._type_collection !== CollectionViewType.Freeform && doc._type_collection !== CollectionViewType.Time) return "rgb(62,62,62)"; - default: - docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); - break; + default: docColor = docColor || (darkScheme() ? Colors.DARK_GRAY : Colors.WHITE); } - if (docColor && !doc) docColor = DashColor(docColor).fade(0.5).toString(); - return docColor; + return (docColor && !doc) ? DashColor(docColor).fade(0.5).toString() : docColor; } case StyleProp.BoxShadow: { if (!doc || opacity() === 0 || doc.noShadow) return undefined; // if it's not visible, then no shadow) @@ -378,13 +339,9 @@ export function DashboardToggleButton(doc: Doc, field: string, onIcon: IconProp, */ export function DashboardStyleProvider(doc: Opt, props: Opt, property: string) { if (doc && property.split(':')[0] === StyleProp.Decorations) { - return doc._type_collection === CollectionViewType.Docking ? null : ( - <> - {DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => { - DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.embedContainer ?? doc?.annotationOn)); - })} - - ); + return doc._type_collection === CollectionViewType.Docking + ? null + : DashboardToggleButton(doc, 'hidden', 'eye-slash', 'eye', () => DocFocusOrOpen(doc, { toggleTarget: true, willZoomCentered: true, zoomScale: 0 }, DocCast(doc?.embedContainer ?? doc?.annotationOn))); } return DefaultStyleProvider(doc, props, property); } diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 528781991..64f9c6a87 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -2,7 +2,8 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { DataSym, Doc, DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; @@ -156,7 +157,7 @@ export class CollectionMasonryViewFieldRow extends React.Component [ this.props.A.props.ScreenToLocalTransform(), Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[CssSym], + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_1, Doc, null)?.annotationOn, Doc, null)?.[DocCss], this.props.B.props.ScreenToLocalTransform(), Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.layout_scrollTop, - Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[CssSym], + Cast(Cast(Cast(this.props.A.rootDoc, Doc, null)?.link_anchor_2, Doc, null)?.annotationOn, Doc, null)?.[DocCss], ], action(() => { this._start = Date.now(); diff --git a/src/client/views/nodes/DataVizBox/components/TableBox.tsx b/src/client/views/nodes/DataVizBox/components/TableBox.tsx index 3816bddfa..60480ac33 100644 --- a/src/client/views/nodes/DataVizBox/components/TableBox.tsx +++ b/src/client/views/nodes/DataVizBox/components/TableBox.tsx @@ -1,7 +1,7 @@ import { action, computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { AnimationSym, Doc } from '../../../../../fields/Doc'; +import { Doc } from '../../../../../fields/Doc'; import { Id } from '../../../../../fields/FieldSymbols'; import { List } from '../../../../../fields/List'; import { emptyFunction, returnFalse, setupMoveUpEvents, Utils } from '../../../../../Utils'; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 05a3b56f7..e954d0484 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -1,6 +1,7 @@ import { computed } from 'mobx'; import { observer } from 'mobx-react'; -import { AclPrivate, Doc, Opt } from '../../../fields/Doc'; +import { Doc, Opt } from '../../../fields/Doc'; +import { AclPrivate } from '../../../fields/DocSymbols'; import { ScriptField } from '../../../fields/ScriptField'; import { Cast, StrCast } from '../../../fields/Types'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 1c7ef5217..fd9997ea4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -279,7 +279,7 @@ export class DocumentViewInternal extends DocComponent; + return this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.ShowTitle) as Opt; } @computed get NativeDimScaling() { return this.props.NativeDimScaling?.() || 1; @@ -309,7 +309,7 @@ export class DocumentViewInternal extends DocComponent, props: Opt, property: string): any => { // prettier-ignore switch (property.split(':')[0]) { - case StyleProp.layout_showTitle: return ''; + case StyleProp.ShowTitle: return ''; case StyleProp.PointerEvents: return 'none'; case StyleProp.Highlighting: return undefined; } @@ -1338,7 +1338,7 @@ export class DocumentView extends React.Component { return this.props.LayoutTemplateString?.includes('link_anchor_2') ? DocCast(this.rootDoc['link_anchor_2']) : this.props.LayoutTemplateString?.includes('link_anchor_1') ? DocCast(this.rootDoc['link_anchor_1']) : undefined; } @computed get hideLinkButton() { - return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkButton + (this.isSelected() ? ':selected' : '')); + return this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.HideLinkBtn + (this.isSelected() ? ':selected' : '')); } @computed get linkCountView() { const hideCount = this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (this.isSelected() && this.props.renderDepth) || !this._isHovering || this.hideLinkButton; diff --git a/src/client/views/nodes/MapBox/MapBox.tsx b/src/client/views/nodes/MapBox/MapBox.tsx index 9b0fddce4..f03954789 100644 --- a/src/client/views/nodes/MapBox/MapBox.tsx +++ b/src/client/views/nodes/MapBox/MapBox.tsx @@ -4,7 +4,8 @@ import BingMapsReact from 'bingmaps-react'; import { action, computed, IReactionDisposer, observable, ObservableMap, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Opt, WidthSym } from '../../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; +import { Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; import { NumCast, StrCast } from '../../../../fields/Types'; @@ -370,7 +371,7 @@ export class MapBox extends ViewBoxAnnotatableComponent 0) { this._showSidebar = true; diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx index 3b5aac221..b50588ce2 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -1,10 +1,6 @@ -import { observer } from "mobx-react"; -import * as React from "react"; -import { AclPrivate, Doc, DocListCast } from "../../../../fields/Doc"; -import { GetEffectiveAcl } from "../../../../fields/util"; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils"; -import { DragManager } from "../../../util/DragManager"; -import "./FontIconBadge.scss"; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import './FontIconBadge.scss'; interface FontIconBadgeProps { value: string | undefined; @@ -25,13 +21,17 @@ export class FontIconBadge extends React.Component { // } render() { - if (this.props.value === undefined) return (null); - return
-
- {this.props.value} + if (this.props.value === undefined) return null; + return ( +
+
+ {this.props.value} +
-
; + ); } -} \ No newline at end of file +} diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 2642bc144..b4fb7a44e 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -3,7 +3,7 @@ import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as ReactDOM from 'react-dom/client'; -import { DataSym, Doc, Field } from '../../../../fields/Doc'; +import { Doc } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 4dfe07b24..8d57cc081 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -4,7 +4,8 @@ import { Schema } from 'prosemirror-model'; import { splitListItem, wrapInList } from 'prosemirror-schema-list'; import { EditorState, NodeSelection, TextSelection, Transaction } from 'prosemirror-state'; import { liftTarget } from 'prosemirror-transform'; -import { AclAugment, AclSelfEdit, Doc } from '../../../../fields/Doc'; +import { Doc } from '../../../../fields/Doc'; +import { AclAugment, AclSelfEdit } from '../../../../fields/DocSymbols'; import { GetEffectiveAcl } from '../../../../fields/util'; import { Utils } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 104aed058..b4dd34416 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -1,11 +1,11 @@ import { ellipsis, emDash, InputRule, smartQuotes, textblockTypeInputRule } from 'prosemirror-inputrules'; import { NodeSelection, TextSelection } from 'prosemirror-state'; -import { DataSym, Doc, StrListCast } from '../../../../fields/Doc'; +import { Doc, StrListCast } from '../../../../fields/Doc'; +import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { ComputedField } from '../../../../fields/ScriptField'; -import { NumCast, StrCast } from '../../../../fields/Types'; -import { normalizeEmail } from '../../../../fields/util'; +import { NumCast } from '../../../../fields/Types'; import { Utils } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { Docs, DocUtils } from '../../../documents/Documents'; @@ -76,7 +76,7 @@ export class RichTextRules { //Create annotation to a field on the text document new InputRule(new RegExp(/>>$/), (state, match, start, end) => { - const textDoc = this.Document[DataSym]; + const textDoc = this.Document[DocData]; const numInlines = NumCast(textDoc.inlineTextCount); textDoc.inlineTextCount = numInlines + 1; const inlineFieldKey = 'inline' + numInlines; // which field on the text document this annotation will write to @@ -268,7 +268,7 @@ export class RichTextRules { } if (value !== '' && value !== undefined) { const num = value.match(/^[0-9.]$/); - this.Document[DataSym][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; + this.Document[DocData][fieldKey] = value === 'true' ? true : value === 'false' ? false : num ? Number(value) : value; } const fieldView = state.schema.nodes.dashField.create({ fieldKey, docId, hideKey: false }); return state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).replaceSelectionWith(fieldView, true); @@ -303,11 +303,11 @@ export class RichTextRules { new InputRule(new RegExp(/#([a-zA-Z_\-]+[a-zA-Z_\-0-9]*)\s$/), (state, match, start, end) => { const tag = match[1]; if (!tag) return state.tr; - //this.Document[DataSym]['#' + tag] = '#' + tag; - const tags = StrListCast(this.Document[DataSym].tags); + //this.Document[DocData]['#' + tag] = '#' + tag; + const tags = StrListCast(this.Document[DocData].tags); if (!tags.includes(tag)) { tags.push(tag); - this.Document[DataSym].tags = new List(tags); + this.Document[DocData].tags = new List(tags); } const fieldView = state.schema.nodes.dashField.create({ fieldKey: '#' + tag }); return state.tr diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 913018b69..70bf7c61f 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -3,7 +3,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; -import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { Animation } from '../../../../fields/DocSymbols'; import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -197,7 +198,7 @@ export class PresBox extends ViewBoxBaseComponent() { this._disposers.selection = reaction( () => SelectionManager.Views(), views => (!PresBox.Instance || views.some(view => view.props.Document === this.rootDoc)) && this.updateCurrentPresentation(), - {fireImmediately:true} + { fireImmediately: true } ); this._disposers.editing = reaction( () => this.layoutDoc.presStatus === PresStatus.Edit, @@ -687,7 +688,7 @@ export class PresBox extends ViewBoxBaseComponent() { const finished = () => { afterNav?.(); console.log('Finish Slide Nav: ' + targetDoc.title); - targetDoc[AnimationSym] = undefined; + targetDoc[Animation] = undefined; }; const selViewCache = Array.from(this.selectedArray); const dragViewCache = Array.from(this._dragArray); @@ -733,7 +734,7 @@ export class PresBox extends ViewBoxBaseComponent() { } } if (targetDoc) { - if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[AnimationSym] = undefined; + if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[Animation] = undefined; DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => { // if target or the doc it annotates is not in the lightbox, then close the lightbox @@ -2175,7 +2176,9 @@ export class PresBox extends ViewBoxBaseComponent() { const mode = StrCast(this.rootDoc._type_collection) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; return ( -
+
{isMini ? null : ( this.changePermissions(e, user)}> - {dropdownValues - .filter(permission => !Doc.noviceMode || ![SharingPermissions.View].includes(permission as any)) - .map(permission => ( - - ))} + {dropdownValues.map(permission => ( + + ))} ); } @@ -379,7 +374,7 @@ export class PropertiesView extends React.Component {
{/* {name !== "Me" ? this.notifyIcon : null} */}
- {this.colorACLDropDown(name, admin, permission, showExpansionIcon)} + {this.colorACLDropDown(name, admin, permission, false)} {(permission === 'Owner' && name == 'Me') || showExpansionIcon ? this.expansionIcon : null}
@@ -389,13 +384,13 @@ export class PropertiesView extends React.Component { /** * @returns a colored dropdown bar reflective of the permission */ - colorACLDropDown(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { + colorACLDropDown(name: string, admin: boolean, permission: string, showGuestOptions: boolean) { var shareImage = ReverseHierarchyMap.get(permission)?.image; return (
-
{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission) : concat(shareImage, ' ', permission)}
+
{admin && permission !== 'Owner' ? this.getPermissionsSelect(name, permission, showGuestOptions) : concat(shareImage, ' ', permission)}
@@ -430,15 +425,16 @@ export class PropertiesView extends React.Component { const individualTableEntries = []; const usersAdded: string[] = []; // all shared users being added - organized by denormalized email + const seldoc = this.layoutDocAcls || !this.selectedDoc ? this.selectedDoc : Doc.GetProto(this.selectedDoc); // adds each user to usersAdded SharingManager.Instance.users.forEach(eachUser => { var userOnDoc = true; - if (this.selectedDoc) { - if (this.selectedDoc['acl-' + normalizeEmail(eachUser.user.email)] == '' || this.selectedDoc['acl-' + normalizeEmail(eachUser.user.email)] == undefined) { + if (seldoc) { + if (Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === '' || Doc.GetT(seldoc, 'acl-' + normalizeEmail(eachUser.user.email), 'string', true) === undefined) { userOnDoc = false; } } - if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email != 'Public' && eachUser.user.email != target.author) { + if (userOnDoc && !usersAdded.includes(eachUser.user.email) && eachUser.user.email !== 'guest' && eachUser.user.email != target.author) { usersAdded.push(eachUser.user.email); } }); @@ -447,15 +443,16 @@ export class PropertiesView extends React.Component { usersAdded.sort(this.sortUsers); usersAdded.map(userEmail => { const userKey = `acl-${normalizeEmail(userEmail)}`; - var permission = StrCast(target[userKey]); + var aclField = Doc.GetT(this.layoutDocAcls ? target : Doc.GetProto(target), userKey, 'string', true); + var permission = StrCast(aclField); individualTableEntries.unshift(this.sharingItem(userEmail, showAdmin, permission!, false)); // adds each user }); // adds current user var userEmail = Doc.CurrentUserEmail; + if (userEmail == 'guest') userEmail = 'Guest'; const userKey = `acl-${normalizeEmail(userEmail)}`; - if (userEmail == 'guest') userEmail = 'Public'; - if (!usersAdded.includes(userEmail) && userEmail != 'Public' && userEmail != target.author) { + if (!usersAdded.includes(userEmail) && userEmail !== 'Guest' && userEmail != target.author) { var permission; if (this.layoutDocAcls) { if (target[DocAcl][userKey]) permission = HierarchyMapping.get(target[DocAcl][userKey])?.name; @@ -473,7 +470,7 @@ export class PropertiesView extends React.Component { const groupList = GroupManager.Instance?.allGroups || []; groupList.sort(this.sortGroups); groupList.map(group => { - if (group.title != 'Public' && this.selectedDoc) { + if (group.title != 'Guest' && this.selectedDoc) { const groupKey = 'acl-' + normalizeEmail(StrCast(group.title)); if (this.selectedDoc[groupKey] != '' && this.selectedDoc[groupKey] != undefined) { var permission; @@ -489,17 +486,11 @@ export class PropertiesView extends React.Component { }); // public permission - const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Public']); + const publicPermission = StrCast((this.layoutDocAcls ? target : Doc.GetProto(target))['acl-Guest']); return (

- Public / Guest Users -
{this.colorACLDropDown('Public', showAdmin, publicPermission!, false)}
-
- {' '} -

Individual Users with Access to this Document{' '} -
{
{individualTableEntries}
}
{groupTableEntries.length > 0 ? (
@@ -510,6 +501,12 @@ export class PropertiesView extends React.Component {
{
{groupTableEntries}
}
) : null} + Guest +
{this.colorACLDropDown('Guest', true, publicPermission!, true)}
+
+ {' '} +

Individual Users with Access to this Document{' '} +
); } diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 13d3b2cdc..8d1b46ebb 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -517,7 +517,7 @@ export class CollectionDockingView extends CollectionSubView() { _layout_fitWidth: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - inheritParentAcls(this.rootDoc, docToAdd); + inheritParentAcls(this.rootDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }); @@ -560,7 +560,7 @@ export class CollectionDockingView extends CollectionSubView() { _freeform_backgroundGrid: true, title: `Untitled Tab ${NumCast(dashboard['pane-count'])}`, }); - inheritParentAcls(this.dataDoc, Doc.GetProto(docToAdd)); + inheritParentAcls(this.dataDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } }) diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 764b1e08a..e2718b52d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -9,7 +9,7 @@ import { RichTextField } from '../../../../fields/RichTextField'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; import { Cast, DocCast, FieldValue, NumCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; -import { GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; +import { distributeAcls, GetEffectiveAcl, SharingPermissions } from '../../../../fields/util'; import { intersectRect, returnFalse, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocumentOptions, DocUtils } from '../../../documents/Documents'; @@ -390,11 +390,7 @@ export class MarqueeView extends React.Component { - Doc.SetContainer(d, newCollection); - d['acl-Public'] = Doc.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Augment; - }); + selected.forEach(d => Doc.SetContainer(d, newCollection)); this.hideMarquee(); return newCollection; }); diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx index 5bba51ec8..a0b0caf98 100644 --- a/src/client/views/nodes/button/FontIconBox.tsx +++ b/src/client/views/nodes/button/FontIconBox.tsx @@ -597,7 +597,7 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b if (contentFrameNumber !== undefined) { CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.rootDoc, { fieldKey: color }); } else { - dv.rootDoc['_' + fieldKey] = color; + dv.dataDoc[fieldKey] = color; } }); } else { diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index d13c09443..ab9eebd78 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -222,7 +222,7 @@ export class SearchBox extends ViewBoxBaseComponent() { 'width', 'layout_autoHeight', 'acl-Override', - 'acl-Public', + 'acl-Guest', 'embedContainer', 'zIndex', 'height', diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 698e09915..6121668e3 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -20,7 +20,6 @@ import { AclEdit, AclPrivate, AclReadonly, - AclUnset, Animation, CachedUpdates, DirectLinks, @@ -134,7 +133,6 @@ export const HierarchyMapping: Map = new Map(Array.from(HierarchyMapping.entries()).map(value => [value[1].name, { level: value[1].level, acl: value[0], image: value[1].image }])); @@ -380,7 +378,8 @@ export class Doc extends RefField { return self.resolvedDataDoc && !self.isTemplateForField ? self : Doc.GetProto(Cast(Doc.Layout(self).resolvedDataDoc, Doc, null) || self); } @computed get __LAYOUT__(): Doc | undefined { - const templateLayoutDoc = Cast(Doc.LayoutField(this[SelfProxy]), Doc, null); + const self = this[SelfProxy]; + const templateLayoutDoc = Cast(Doc.LayoutField(self), Doc, null); if (templateLayoutDoc) { let renderFieldKey: any; const layoutField = templateLayoutDoc[StrCast(templateLayoutDoc.layout_fieldKey, 'layout')]; @@ -389,7 +388,7 @@ export class Doc extends RefField { } else { return Cast(layoutField, Doc, null); } - return Cast(this[SelfProxy][renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; + return Cast(self[renderFieldKey + '-layout[' + templateLayoutDoc[Id] + ']'], Doc, null) || templateLayoutDoc; } return undefined; } @@ -480,11 +479,6 @@ export namespace Doc { export function SetContainer(doc: Doc, container: Doc) { doc.embedContainer = container; - if (Doc.GetProto(container).author === doc.author) { - Object.keys(Doc.GetProto(container)) - .filter(key => key.startsWith('acl') && !key.includes(Doc.CurrentUserEmailNormalized)) - .forEach(key => (doc[key] = Doc.GetProto(container)[key])); - } } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; @@ -697,7 +691,7 @@ export namespace Doc { Doc.SetLayout(embedding, Doc.MakeEmbedding(layout)); } embedding.createdFrom = doc; - embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length + 1; + embedding.proto_embeddingId = Doc.GetProto(doc).proto_embeddingId = DocListCast(Doc.GetProto(doc).proto_embeddings).length - 1; embedding.title = ComputedField.MakeFunction(`renameEmbedding(this)`); embedding.author = Doc.CurrentUserEmail; @@ -1027,7 +1021,6 @@ export namespace Doc { Doc.AddDocToList(Doc.GetProto(copy)[DocData], 'proto_embeddings', copy); } copy.embedContainer = undefined; - Doc.defaultAclPrivate && (copy['acl-Public'] = 'Not Shared'); if (retitle) { copy.title = incrementTitleCopy(StrCast(copy.title)); } @@ -1087,7 +1080,6 @@ export namespace Doc { const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + '(...' + _applyCount++ + ')'); target.layout_fieldKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.defaultAclPrivate && (applied['acl-Public'] = 'Not Shared'); return applied; } return undefined; diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index eab26ed10..66d1ab094 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -13,7 +13,6 @@ export const DocFields = Symbol('DocFields'); export const DocCss = Symbol('DocCss'); export const DocAcl = Symbol('DocAcl'); export const DirectLinks = Symbol('DocDirectLinks'); -export const AclUnset = Symbol('DocAclUnset'); export const AclPrivate = Symbol('DocAclOwnerOnly'); export const AclReadonly = Symbol('DocAclReadOnly'); export const AclAugment = Symbol('DocAclAugment'); diff --git a/src/fields/util.ts b/src/fields/util.ts index 034229319..e89cb1fb1 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -131,17 +131,19 @@ export function denormalizeEmail(email: string) { /** * Copies parent's acl fields to the child */ -export function inheritParentAcls(parent: Doc, child: Doc) { +export function inheritParentAcls(parent: Doc, child: Doc, layoutOnly: boolean) { if (GetEffectiveAcl(parent) !== AclAdmin) return; - for (const key of Object.keys(parent)) { - // if the default acl mode is private, then don't inherit the acl-Public permission, but set it to private. - // const permission: string = key === 'acl-Public' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; - const symbol = ReverseHierarchyMap.get(StrCast(parent[key])); - if (symbol) { - const sharePermission = HierarchyMapping.get(symbol.acl!)!.name; - key.startsWith('acl') && distributeAcls(key, sharePermission, child); - } - } + Object.keys(parent) + .filter(key => key.startsWith('acl')) + .forEach(key => { + // if the default acl mode is private, then don't inherit the acl-guest permission, but set it to private. + // const permission: string = key === 'acl-guest' && Doc.defaultAclPrivate ? AclPrivate : parent[key]; + const parAcl = ReverseHierarchyMap.get(StrCast(parent[key]))?.acl; + if (parAcl) { + const sharePermission = HierarchyMapping.get(parAcl)?.name; + sharePermission && distributeAcls(key, sharePermission, child, undefined, false, layoutOnly); + } + }); } /** @@ -160,7 +162,6 @@ export function inheritParentAcls(parent: Doc, child: Doc) { * Unset: Remove a sharing permission (eg., used ) */ export enum SharingPermissions { - Unset = 'None', Admin = 'Admin', Edit = 'Edit', Augment = 'Augment', @@ -233,7 +234,7 @@ function getEffectiveAcl(target: any, user?: string): symbol { * @param allowUpgrade whether permissions can be made less restrictive * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change */ -export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean) { +export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, visited?: Doc[], allowUpgrade?: boolean, layoutOnly = false) { const selfKey = `acl-${Doc.CurrentUserEmailNormalized}`; if (!visited) visited = [] as Doc[]; if (!target || visited.includes(target) || key === selfKey) return; @@ -243,19 +244,19 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc const dataDoc = target[DocData]; const curVal = ReverseHierarchyMap.get(StrCast(dataDoc[key]))?.level ?? 0; const aclVal = ReverseHierarchyMap.get(acl)?.level ?? 0; - if (dataDoc && (allowUpgrade !== false|| !dataDoc[key] || curVal > aclVal)) { + if (!layoutOnly && dataDoc && (allowUpgrade !== false || !dataDoc[key] || curVal > aclVal)) { // propagate ACLs to links, children, and annotations - LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade? true: false)); + LinkManager.Links(dataDoc).forEach(link => distributeAcls(key, acl, link, visited, allowUpgrade ? true : false)); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc)]).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade ? true: false); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true: false); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); DocListCast(dataDoc[Doc.LayoutFieldKey(dataDoc) + '_annotations']).forEach(d => { - distributeAcls(key, acl, d, visited, allowUpgrade? true: false); - d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade? true: false); + distributeAcls(key, acl, d, visited, allowUpgrade ? true : false); + d !== d[DocData] && distributeAcls(key, acl, d[DocData], visited, allowUpgrade ? true : false); }); if (GetEffectiveAcl(dataDoc) === AclAdmin) { @@ -266,7 +267,7 @@ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc let layoutDocChanged = false; // determines whether fetchProto should be called or not (i.e. is there a change that should be reflected in target[AclSym]) // if it is inheriting from a collection, it only inherits if A) allowUpgrade is set B) the key doesn't already exist or c) the right being inherited is more restrictive - if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !target[key] || ReverseHierarchyMap.get(StrCast(target[key]))!.level > ReverseHierarchyMap.get(acl)!.level)) { + if (GetEffectiveAcl(target) === AclAdmin && (allowUpgrade || !Doc.GetT(target, key, 'boolean', true) || ReverseHierarchyMap.get(StrCast(target[key]))!.level > aclVal)) { target[key] = acl; layoutDocChanged = true; } -- cgit v1.2.3-70-g09d2 From 54308259a8cd3ac98aaee550ea01ad97f17172e6 Mon Sep 17 00:00:00 2001 From: Geireann Lindfield Roberts <60007097+geireann@users.noreply.github.com> Date: Mon, 17 Jul 2023 23:11:35 -0400 Subject: so many updates --- deploy/index.html | 7 +- deploy/loader.css | 5 +- package-lock.json | 232 +++---- package.json | 2 +- src/client/documents/Documents.ts | 6 +- src/client/util/CurrentUserUtils.ts | 63 +- src/client/util/DropConverter.ts | 2 +- src/client/util/ReportManager.tsx | 4 +- src/client/util/SettingsManager.scss | 93 +-- src/client/util/SettingsManager.tsx | 186 ++++-- src/client/views/AntimodeMenu.scss | 32 +- src/client/views/AntimodeMenu.tsx | 5 + src/client/views/DashboardView.scss | 22 + src/client/views/DashboardView.tsx | 98 ++- src/client/views/EditableView.tsx | 75 ++- src/client/views/GestureOverlay.tsx | 2 +- src/client/views/MainView.scss | 20 - src/client/views/MainView.tsx | 13 +- src/client/views/MainViewModal.scss | 6 +- src/client/views/MainViewModal.tsx | 7 +- src/client/views/PropertiesButtons.scss | 8 +- src/client/views/PropertiesButtons.tsx | 98 ++- .../views/PropertiesDocBacklinksSelector.scss | 2 +- .../views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesDocContextSelector.tsx | 1 + src/client/views/PropertiesSection.scss | 24 + src/client/views/PropertiesSection.tsx | 51 ++ src/client/views/PropertiesView.scss | 364 +--------- src/client/views/PropertiesView.tsx | 743 ++++++++++----------- src/client/views/StyleProvider.tsx | 10 +- src/client/views/UndoStack.scss | 9 +- src/client/views/UndoStack.tsx | 48 +- src/client/views/collections/CollectionMenu.scss | 651 +----------------- src/client/views/collections/CollectionMenu.tsx | 51 +- .../views/collections/CollectionTreeView.tsx | 2 + src/client/views/collections/TabDocView.scss | 17 - src/client/views/collections/TabDocView.tsx | 103 ++- src/client/views/collections/TreeView.scss | 36 +- src/client/views/collections/TreeView.tsx | 35 +- .../collectionFreeForm/MarqueeOptionsMenu.tsx | 69 +- .../collectionLinear/CollectionLinearView.tsx | 51 +- src/client/views/global/globalScripts.ts | 49 +- src/client/views/linking/LinkPopup.scss | 5 - src/client/views/linking/LinkPopup.tsx | 4 +- src/client/views/nodes/DocumentContentsView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 4 +- .../views/nodes/FontIconBox/ButtonInterface.ts | 12 + .../views/nodes/FontIconBox/FontIconBadge.scss | 11 + .../views/nodes/FontIconBox/FontIconBadge.tsx | 37 + .../views/nodes/FontIconBox/FontIconBox.scss | 438 ++++++++++++ src/client/views/nodes/FontIconBox/FontIconBox.tsx | 412 ++++++++++++ src/client/views/nodes/button/FontIconBox.scss | 438 ------------ src/client/views/nodes/button/FontIconBox.tsx | 385 ----------- src/client/views/pdf/AnchorMenu.tsx | 121 ++-- src/client/views/search/SearchBox.scss | 3 +- src/client/views/search/SearchBox.tsx | 7 +- src/client/views/selectedDoc/SelectedDocView.scss | 3 + src/client/views/selectedDoc/SelectedDocView.tsx | 47 ++ src/client/views/selectedDoc/index.ts | 1 + src/client/views/topbar/TopBar.tsx | 36 +- 60 files changed, 2279 insertions(+), 2991 deletions(-) create mode 100644 src/client/views/PropertiesSection.scss create mode 100644 src/client/views/PropertiesSection.tsx create mode 100644 src/client/views/nodes/FontIconBox/ButtonInterface.ts create mode 100644 src/client/views/nodes/FontIconBox/FontIconBadge.scss create mode 100644 src/client/views/nodes/FontIconBox/FontIconBadge.tsx create mode 100644 src/client/views/nodes/FontIconBox/FontIconBox.scss create mode 100644 src/client/views/nodes/FontIconBox/FontIconBox.tsx delete mode 100644 src/client/views/nodes/button/FontIconBox.scss delete mode 100644 src/client/views/nodes/button/FontIconBox.tsx create mode 100644 src/client/views/selectedDoc/SelectedDocView.scss create mode 100644 src/client/views/selectedDoc/SelectedDocView.tsx create mode 100644 src/client/views/selectedDoc/index.ts (limited to 'src/client/views/search') diff --git a/deploy/index.html b/deploy/index.html index f8003f126..c8ad2a8d4 100644 --- a/deploy/index.html +++ b/deploy/index.html @@ -5,7 +5,7 @@ Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" }); @@ -609,7 +609,7 @@ export class CurrentUserUtils { const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts:DocumentOptions = { title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move', - childDontRegisterViews: true, linearView_IsExpanded: true, linearView_Expandable: true, ignoreClick: true + childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true }; reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); @@ -625,12 +625,12 @@ export class CurrentUserUtils { } static viewTools(): Button[] { return [ - { title: "Snap\xA0Lines", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "View\xA0All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform - { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: 'showFreeform(self.toolType, _readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Grid", icon: "border-all", toolTip: "Show Grid", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"grid", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "View All", icon: "object-group", toolTip: "Fit all Docs to View",btnType: ButtonType.ToggleButton, expertMode: false, toolType:"viewAll", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Clusters", icon: "braille", toolTip: "Show Doc Clusters", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"clusters", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Cards", icon: "brain", toolTip: "Flashcards", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"flashcards", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + { title: "Arrange",icon:"arrow-down-short-wide",toolTip:"Toggle Auto Arrange", btnType: ButtonType.ToggleButton, expertMode: false, toolType:"arrange", funcs: {hidden: 'IsNoviceMode()'}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform ] } static textTools():Button[] { @@ -645,9 +645,13 @@ export class CurrentUserUtils { { title: "Under", toolTip: "Underline (Ctrl+U)", btnType: ButtonType.ToggleButton, icon: "underline", toolType:"underline", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "Bullets", toolTip: "Bullet List", btnType: ButtonType.ToggleButton, icon: "list", toolType:"bullet", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, { title: "#", toolTip: "Number List", btnType: ButtonType.ToggleButton, icon: "list-ol", toolType:"decimal", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, - { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, - { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Alignment",toolTip: "Alignment", btnType: ButtonType.MultiToggleButton, toolType:"alignment", ignoreClick: true, scripts: {script: '{ return setFontAttr(self.toolType, value, _readOnly_);}'}, + subMenu: [ + { title: "Left", toolTip: "Left align (Cmd-[)", btnType: ButtonType.ToggleButton, icon: "align-left", toolType:"left", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}' }}, + { title: "Center", toolTip: "Center align (Cmd-\\)",btnType: ButtonType.ToggleButton, icon: "align-center",toolType:"center", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + { title: "Right", toolTip: "Right align (Cmd-])", btnType: ButtonType.ToggleButton, icon: "align-right", toolType:"right", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'} }, + ] + }, { title: "Dictate", toolTip: "Dictate", btnType: ButtonType.ToggleButton, icon: "microphone", toolType:"dictation", scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}}, { title: "NoLink", toolTip: "Auto Link", btnType: ButtonType.ToggleButton, icon: "link", toolType:"noAutoLink", expertMode:true, scripts: {onClick: '{ return toggleCharStyle(self.toolType, _readOnly_);}'}, funcs: {hidden: 'IsNoviceMode()'}}, // { title: "Strikethrough", tooltip: "Strikethrough", btnType: ButtonType.ToggleButton, icon: "strikethrough", scripts: {onClick:: 'toggleStrikethrough()'}}, @@ -695,17 +699,17 @@ export class CurrentUserUtils { title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, { title: "Pin", icon: "map-pin", toolTip: "Pin View to Trail", btnType: ButtonType.ClickButton, expertMode: false, width: 30, scripts: { onClick: 'pinWithView(altKey)'}}, { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}}, // Only when a document is selected - { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, - { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'toggleOverlay(_readOnly_)'}}, // Only when floating document is selected in freeform + { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}}, + { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'}, scripts: { onClick: 'return { toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform { title: "Back", icon: "chevron-left", toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}}, { title: "Num", icon:"", toolTip: "Frame Number (click to toggle edit mode)", btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}}, { title: "Fwd", icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30, scripts: { onClick: 'nextKeyFrame(_readOnly_)'}}, - { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available - { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available - { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available - { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available - { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected - { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsExpanded: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected + { title: "Text", icon: "Text", toolTip: "Text functions", subMenu: CurrentUserUtils.textTools(), expertMode: false, toolType:DocumentType.RTF, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: { linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available + { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available + { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected + { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected ]; } @@ -713,7 +717,6 @@ export class CurrentUserUtils { static setupContextMenuButton(params:Button, btnDoc?:Doc) { const reqdOpts:DocumentOptions = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, - backgroundColor: params.backgroundColor ??"transparent", /// a bit hacky. if an onClick is specified, then assume a toggle uses onClick to get the backgroundColor (see below). Otherwise, assume a transparent background color: Colors.WHITE, isSystem: true, _nativeWidth: params.width ?? 30, _width: params.width ?? 30, _height: 30, _nativeHeight: 30, linearBtnWidth: params.linearBtnWidth, @@ -722,21 +725,20 @@ export class CurrentUserUtils { }; const reqdFuncs:{[key:string]:any} = { ...params.funcs, - backgroundColor: params.btnType === ButtonType.ToggleButton ? params.scripts?.onClick:undefined /// a bit hacky. if onClick is set, then we assume it returns a color value when queried with '_readOnly_'. This will be true for toggle buttons, but not generally } return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } /// Initializes all the default buttons for the top bar context menu static setupContextMenuButtons(doc: Doc, field="myContextMenuBtns") { - const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List(['width', "linearView_IsExpanded"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsExpanded: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; + const reqdCtxtOpts:DocumentOptions = { title: "context menu buttons", undoIgnoreFields:new List(['width', "linearView_IsOpen"]), flexGap: 0, childDragAction: 'embed', childDontRegisterViews: true, linearView_IsOpen: true, ignoreClick: true, linearView_Expandable: false, _height: 35 }; const ctxtMenuBtnsDoc = DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), reqdCtxtOpts, undefined); const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => { const menuBtnDoc = DocListCast(ctxtMenuBtnsDoc?.data).find(doc => doc.title === params.title); - if (!params.subMenu) { + if (!params.subMenu) { // button does not have a sub menu return this.setupContextMenuButton(params, menuBtnDoc); - } else { - const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List(['width', "linearView_IsExpanded"]), + } else { // linear view + const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List(['width', "linearView_IsOpen"]), childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true, linearView_SubMenu: true, linearView_Expandable: true, }; const items = params.subMenu?.map(sub => @@ -847,6 +849,11 @@ export class CurrentUserUtils { doc.fontHighlight ?? (doc.fontHighlight = ""); doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false); doc.savedFilters ?? (doc.savedFilters = new List()); + doc.userBackgroundColor ?? (doc.userBackgroundColor = Colors.DARK_GRAY); + addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${doc.userBackgroundColor} !important` }); + doc.userVariantColor ?? (doc.userVariantColor = Colors.MEDIUM_BLUE); + doc.userColor ?? (doc.userColor = Colors.LIGHT_GRAY); + doc.userTheme ?? (doc.userTheme = ColorScheme.Dark); doc.filterDocCount = 0; doc.treeViewFreezeChildren = "remove|add"; this.setupLinkDocs(doc, linkDatabaseId); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index 47997cc5c..f235be192 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -9,7 +9,7 @@ import { RichTextField } from '../../fields/RichTextField'; import { ImageField } from '../../fields/URLField'; import { ScriptingGlobals } from './ScriptingGlobals'; import { listSpec } from '../../fields/Schema'; -import { ButtonType } from '../views/nodes/button/FontIconBox'; +import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox'; export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt = undefined, templateField: string = '') { if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated diff --git a/src/client/util/ReportManager.tsx b/src/client/util/ReportManager.tsx index 4c1020455..89c17e42f 100644 --- a/src/client/util/ReportManager.tsx +++ b/src/client/util/ReportManager.tsx @@ -11,7 +11,7 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager import { DocServer } from '../DocServer'; import { Networking } from '../Network'; import { MainViewModal } from '../views/MainViewModal'; -import { FontIconBox } from '../views/nodes/button/FontIconBox'; +import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { DragManager } from './DragManager'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; @@ -290,7 +290,7 @@ export class ReportManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: 'auto', height: '500px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }} + dialogueBoxStyle={{ width: 'auto', height: '500px', background: Cast(Doc.UserDoc().userColor, 'string', null) }} /> ); } diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index c41adfdd7..bca649bc3 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -2,41 +2,20 @@ .settings-interface { //background-color: whitesmoke !important; - color: grey; width: 450px; - - button { - background: #315a96; - outline: none; - border-radius: 5px; - border: 0px; - color: #fcfbf7; - text-transform: uppercase; - letter-spacing: 2px; - font-size: 75%; - padding: 10px; - margin: 10px; - transition: transform 0.2s; - margin: 2px; - } } .settings-title { font-size: 25px; font-weight: bold; padding-right: 10px; - color: black; } .settings-username { font-size: 12px; padding-right: 15px; - color: black; margin-top: 10px; margin-bottom: 10px; - /* right: 135; */ - // position: absolute; - // left: 243; } .settings-section { @@ -49,7 +28,6 @@ font-size: 16; font-weight: bold; text-align: left; - color: black; width: 80; margin-right: 50px; } @@ -61,45 +39,10 @@ .password-content { display: flex; + width: 100%; flex-direction: column; align-items: flex-start; - - .password-content-inputs { - display: flex; - flex-direction: column; - width: 100%; - font-size: 10px; - align-items: flex-start; - - .password-inputs { - border: 1px solid rgb(160, 160, 160); - margin-bottom: 15px; - width: 130; - color: $medium-gray; - border-radius: 5px; - padding: 7px; - } - } - - .password-content-buttons { - display: flex; - gap: 5px; - justify-content: center; - align-items: flex-start; - flex-direction: column; - - .password-submit { - //margin-left: 85px; - margin-top: 5px; - } - - .password-forgot { - //margin-left: 65px; - //margin-top: -20px; - font-size: 12px; - white-space: nowrap; - } - } + gap: 5px; } .accounts-content { @@ -115,7 +58,6 @@ width: 80%; height: 35px; margin-right: 10px; - color: black; border-radius: 5px; &:hover { @@ -139,12 +81,6 @@ } } - .playground-text { - color: black; - margin-right: 10px; - margin-top: 2; - } - .acl-text { color: black; margin-top: 2; @@ -176,7 +112,6 @@ .appearances-content { display: flex; - color: black; font-size: 10px; .preferences-color { @@ -201,7 +136,6 @@ margin-top: 2px; .preferences-font-text { - color: black; margin-top: 4; margin-right: 4; margin-bottom: 2px; @@ -216,7 +150,6 @@ .font-select { height: 35px; - color: black; font-size: 9; margin-right: 6; border-radius: 5px; @@ -229,7 +162,6 @@ .size-select { height: 35px; - color: black; font-size: 9; border-radius: 5px; width: 30%; @@ -241,7 +173,6 @@ } .preferences-check { - color: black; margin-right: 4; margin-bottom: -3; margin-left: 5; @@ -265,7 +196,6 @@ .settings-content { - background: #e4e4e4; padding: 10px; width: 500px; flex: 1 1 auto; @@ -296,7 +226,6 @@ } h1 { - color: #121721; text-transform: uppercase; letter-spacing: 2px; font-size: 19; @@ -325,7 +254,6 @@ .padding { padding: 0 0 0 20px; - color: black; } } } @@ -336,16 +264,11 @@ min-height: 250px; height: 100%; width: 100%; - - .settings-content { - background-color: $off-white; - } } .settings-panel { position: relative; min-width: 150px; - background-color: $light-blue; .settings-user { position: absolute; @@ -370,16 +293,11 @@ .settings-tabs { // font-size: 16px; font-weight: 600; - color: black; .tab-control { padding: 10px; border-bottom: 1px solid #9f9e9e; cursor: pointer; - - &.active { - background-color: #fdfdfd; - } } } @@ -398,16 +316,18 @@ .tab-content { display: flex; + flex-flow: row wrap; + gap: 10px; + overflow: hidden; .tab-column { - flex: 0 0 50%; + flex: 0 0 calc(50% - 10px); flex-direction: column; display: flex; justify-content: flex-start; align-items: flex-start; .tab-column-title { - color: $dark-gray; font-size: 16px; font-weight: bold; margin-bottom: 10px; @@ -420,6 +340,7 @@ align-items: flex-start; flex-direction: column; gap: 10px; + width: 100%; } } } diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 09d6d034f..b6df5f26a 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -11,12 +11,12 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager import { DocServer } from '../DocServer'; import { Networking } from '../Network'; import { MainViewModal } from '../views/MainViewModal'; -import { FontIconBox } from '../views/nodes/button/FontIconBox'; +import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { DragManager } from './DragManager'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; import { undoBatch } from './UndoManager'; -import { Button, ColorPicker, Dropdown, DropdownType, Group, Size, Toggle, ToggleType, Type } from 'browndash-components'; +import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components'; import { BsGoogle } from 'react-icons/bs' import { FaFillDrip, FaPalette } from 'react-icons/fa' const higflyout = require('@hig/flyout'); @@ -24,9 +24,10 @@ export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; export enum ColorScheme { - Dark = '-Dark', - Light = '-Light', - System = '-MatchSystem', + Dark = 'Dark', + Light = 'Light', + System = 'Match System', + Custom = 'Custom' } export enum freeformScrollMode { @@ -53,8 +54,8 @@ export class SettingsManager extends React.Component<{}> { @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; } - @computed get colorScheme() { - return Doc.ActiveDashboard?.colorScheme; + @computed get userTheme() { + return Doc.UserDoc().userTheme; } constructor(props: {}) { @@ -76,14 +77,31 @@ export class SettingsManager extends React.Component<{}> { } }; + @computed get userColor() { + return StrCast(Doc.UserDoc().userColor) + } + + @computed get userVariantColor() { + return StrCast(Doc.UserDoc().userVariantColor) + } + + @computed get userBackgroundColor() { + return StrCast(Doc.UserDoc().userBackgroundColor) + } + @undoBatch selectUserMode = action((mode: string) => (Doc.noviceMode = mode === 'Novice')); @undoBatch changelayout_showTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().layout_showTitle = (e.currentTarget as any).value ? 'title' : undefined)); @undoBatch changeFontFamily = action((font: string) => (Doc.UserDoc().fontFamily = font)); @undoBatch changeFontSize = action((val: number) => (Doc.UserDoc().fontSize = val)); - @undoBatch switchActiveBackgroundColor = action((color: string) => (Doc.UserDoc().activeCollectionBackground = color)); + @undoBatch switchUserBackgroundColor = action((color: string) => { + Doc.UserDoc().userBackgroundColor = color; + addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` }); + }); @undoBatch switchUserColor = action((color: string) => { - Doc.SharingDoc().userColor = undefined; - Doc.GetProto(Doc.SharingDoc()).userColor = color; + Doc.UserDoc().userColor = color; + }); + @undoBatch switchUserVariantColor = action((color: string) => { + Doc.UserDoc().userVariantColor = color; }); @undoBatch playgroundModeToggle = action(() => { this.playgroundMode = !this.playgroundMode; @@ -96,22 +114,24 @@ export class SettingsManager extends React.Component<{}> { @undoBatch @action changeColorScheme = action((scheme: string) => { - const activeDashboard = Doc.ActiveDashboard; - if (!activeDashboard) return; + Doc.UserDoc().userTheme = scheme; switch (scheme) { case ColorScheme.Light: - activeDashboard.colorScheme = ColorScheme.Light - addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: '#d3d3d3 !important' }); + this.switchUserColor("#323232") + this.switchUserBackgroundColor("#DFDFDF") + this.switchUserVariantColor("#BDDDF5") break; case ColorScheme.Dark: - activeDashboard.colorScheme = ColorScheme.Dark; - addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: 'black !important' }); + this.switchUserColor("#DFDFDF") + this.switchUserBackgroundColor("#323232") + this.switchUserVariantColor("#4476F7") + break; + case ColorScheme.Custom: break; case ColorScheme.System: default: - activeDashboard.colorScheme = ColorScheme.System; window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - activeDashboard.colorScheme = e.matches ? ColorScheme.Dark : ColorScheme.Light; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + e.matches ? ColorScheme.Dark : ColorScheme.Light; // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) }); break; } @@ -120,18 +140,16 @@ export class SettingsManager extends React.Component<{}> { @computed get colorsContent() { - const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.System]; - const schemeMap = ['Light', 'Dark', 'Match System']; - + const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.Custom, ColorScheme.System]; + const schemeMap = ['Light', 'Dark', 'Custom', 'Match System']; + const userTheme = StrCast(Doc.UserDoc().userTheme); return ( -
- } selectedColor={StrCast(Doc.UserDoc().activeCollectionBackground)} setSelectedColor={this.switchActiveBackgroundColor}/> - } selectedColor={StrCast(Doc.SharingDoc().userColor)} setSelectedColor={this.switchUserColor}/> +
this.changeColorScheme(scheme as string)} items={colorSchemes.map((scheme, i) => ( { @@ -140,8 +158,14 @@ export class SettingsManager extends React.Component<{}> { } ))} dropdownType={DropdownType.SELECT} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} + fillWidth /> + {userTheme === ColorScheme.Custom && + } selectedColor={this.userColor} setSelectedColor={this.switchUserColor}/> + } selectedColor={this.userBackgroundColor} setSelectedColor={this.switchUserBackgroundColor}/> + } selectedColor={this.userVariantColor} setSelectedColor={this.switchUserVariantColor}/> + }
); } @@ -155,7 +179,7 @@ export class SettingsManager extends React.Component<{}> { toggleType={ToggleType.SWITCH} onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')} toggleStatus={Doc.UserDoc().layout_showTitle !== undefined} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} toggleStatus={FontIconBox.GetShowLabels()} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())} toggleStatus={FontIconBox.GetRecognizeGestures()} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> { onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)} size={Size.XSMALL} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} />
@@ -238,6 +262,15 @@ export class SettingsManager extends React.Component<{}> {
{/* */} + {}} + /> { return { @@ -252,7 +285,8 @@ export class SettingsManager extends React.Component<{}> { type={Type.TERT} selectedVal={StrCast(Doc.UserDoc().fontFamily)} setSelectedVal={(val) => {this.changeFontFamily(val as string)}} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} + fillWidth />
@@ -262,8 +296,7 @@ export class SettingsManager extends React.Component<{}> { } @action - changeVal = (e: React.ChangeEvent, pass: string) => { - const value = (e.target as any).value; + changeVal = (value: string, pass: string) => { switch (pass) { case 'curr': this.curr_password = value; @@ -280,16 +313,33 @@ export class SettingsManager extends React.Component<{}> { @computed get passwordContent() { return (
-
- this.changeVal(e, 'curr')} /> - this.changeVal(e, 'new')} /> - this.changeVal(e, 'conf')} /> -
-
- {!this.passwordResultText ? null :
{this.passwordResultText}
} -
+ this.changeVal(val as string, 'curr')} + fillWidth + password + /> + this.changeVal(val as string, 'new')} + fillWidth + password + /> + this.changeVal(val as string, 'conf')} + fillWidth + password + /> + {!this.passwordResultText ? null :
{this.passwordResultText}
} +
); } @@ -346,14 +396,15 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement='bottom-start' - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} + fillWidth />
@@ -379,7 +430,7 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement='bottom-start' - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} />
@@ -390,12 +441,12 @@ export class SettingsManager extends React.Component<{}> { text={"Manage Groups"} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} - color={StrCast(Doc.SharingDoc().userColor)} + color={this.userColor} /> (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))}/>
@@ -414,23 +465,35 @@ export class SettingsManager extends React.Component<{}> { { title: 'Appearance', ele: this.appearanceContent }, { title: 'Text', ele: this.textContent }, ]; - return (
-
+
- {tabs.map(tab => ( -
(this.activeTab = tab.title))}> - {tab.title} -
- ))} + {tabs.map(tab => { + const isActive = this.activeTab === tab.title + return ( +
(this.activeTab = tab.title)) + }> + {tab.title} +
+ ) + })}
-
{Doc.CurrentUserEmail}
+
{Doc.CurrentUserEmail}
@@ -439,12 +502,13 @@ export class SettingsManager extends React.Component<{}> {
-
+
{tabs.map(tab => (
{tab.ele} @@ -462,7 +526,7 @@ export class SettingsManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.SharingDoc().userColor, 'string', null) }} + dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }} /> ); } diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss index 8a0e5480e..b205a0f1e 100644 --- a/src/client/views/AntimodeMenu.scss +++ b/src/client/views/AntimodeMenu.scss @@ -5,11 +5,15 @@ position: absolute; z-index: 10001; height: $antimodemenu-height; - background: $dark-gray; - border-bottom: $standard-border; + width: fit-content; + border-radius: $standard-border-radius; + overflow: hidden; // box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); // border-radius: 0px 6px 6px 6px; display: flex; + justify-content: center; + align-items: center; + gap: 3px; &.with-rows { flex-direction: column @@ -20,30 +24,6 @@ height: 35px; } - .antimodeMenu-button { - background-color: transparent; - width: 35px; - height: 35px; - padding: 5; - text-align: center; - display: flex; - justify-content: center; - align-items: center; - position: relative; - - .svg { - margin: 0; - } - - &.active { - background-color: #121212; - } - } - - .antimodeMenu-button:hover { - background-color: rgba(0, 0, 0, 0.4); - } - .antimodeMenu-dragger { height: 100%; transition: width .2s; diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index de1207ce4..c41ea7053 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -1,6 +1,8 @@ import React = require('react'); import { observable, action, runInAction } from 'mobx'; import './AntimodeMenu.scss'; +import { StrCast } from '../../fields/Types'; +import { Doc } from '../../fields/Doc'; export interface AntimodeMenuProps {} /** @@ -148,6 +150,7 @@ export abstract class AntimodeMenu extends React.Co left: this._left, top: this._top, opacity: this._opacity, + background: StrCast(Doc.UserDoc().userBackgroundColor), transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, @@ -173,6 +176,7 @@ export abstract class AntimodeMenu extends React.Co height: 'inherit', width: 200, opacity: this._opacity, + background: StrCast(Doc.UserDoc().userBackgroundColor), transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, @@ -195,6 +199,7 @@ export abstract class AntimodeMenu extends React.Co left: this._left, top: this._top, opacity: this._opacity, + background: StrCast(Doc.UserDoc().userBackgroundColor), transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss index b8a6f6c05..b95312ea0 100644 --- a/src/client/views/DashboardView.scss +++ b/src/client/views/DashboardView.scss @@ -14,6 +14,7 @@ flex-direction: column; width: 250px; min-width: 250px; + gap: 5px; } .all-dashboards { @@ -56,15 +57,26 @@ display: flex; justify-content: center; align-items: center; + position: relative; &:hover { color: $light-blue; border: solid 2px $light-blue; } + + .background { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + z-index: -1; + } } .dashboard-container { border-radius: 10px; + position: relative; cursor: pointer; width: 250px; height: 200px; @@ -99,10 +111,20 @@ .more { z-index: 100; } + + .background { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + z-index: -1; + } } .new-dashboard { color: $dark-gray; + padding: 10px; display: flex; width: 100%; height: 100%; diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 3bd6ac61c..dd1079d88 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Button, ColorPicker, FontSize, IconButton, Size, Type } from 'browndash-components'; +import { Button, ColorPicker, EditableText, FontSize, IconButton, Size, Type } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -24,7 +24,7 @@ import { ContextMenu } from './ContextMenu'; import './DashboardView.scss'; import { Colors } from './global/globalEnums'; import { MainViewModal } from './MainViewModal'; -import { ButtonType } from './nodes/button/FontIconBox'; +import { ButtonType } from './nodes/FontIconBox/FontIconBox'; import { FaPlus } from 'react-icons/fa'; enum DashboardGroup { @@ -43,13 +43,16 @@ export class DashboardView extends React.Component { @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; @observable private newDashboardName: string | undefined = undefined; - @observable private newDashboardColor: string | undefined = undefined; + @observable private newDashboardColor: string | undefined = "#AFAFAF"; @action abortCreateNewDashboard = () => { this.newDashboardName = undefined; }; @action setNewDashboardName(name: string) { this.newDashboardName = name; } + @action setNewDashboardColor(color: string) { + this.newDashboardColor = color; + } @action selectDashboardGroup = (group: DashboardGroup) => { @@ -95,23 +98,33 @@ export class DashboardView extends React.Component { const dashboardCount = DocListCast(Doc.MyDashboards.data).length + 1; const placeholder = `Dashboard ${dashboardCount}`; return ( -
+
Create New Dashboard
-
- Title - this.setNewDashboardName((e.target as any).value)} /> -
-
- Background - { - this.newDashboardColor = color; - }} - /> -
+ this.setNewDashboardName(val as string)} + fillWidth + /> + { + this.setNewDashboardColor(color); + }} + />
-
); @@ -150,32 +163,49 @@ export class DashboardView extends React.Component { }; render() { + const color = StrCast(Doc.UserDoc().userColor) + const variant = StrCast(Doc.UserDoc().userVariantColor) return ( <>
-
-
-
this.selectDashboardGroup(DashboardGroup.MyDashboards)}> - My Dashboards -
-
this.selectDashboardGroup(DashboardGroup.SharedDashboards)}> - Shared Dashboards -
+
{this.getDashboards().map(dashboard => { const href = ImageCast(dashboard.thumb)?.url.href; return ( -
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}> +
this.onContextMenu(dashboard, e)} onClick={e => this.clickDashboard(e, dashboard)}>
- e.stopPropagation()} defaultValue={StrCast(dashboard.title)} onChange={e => (Doc.GetProto(dashboard).title = (e.target as any).value)} /> + (Doc.GetProto(dashboard).title = val)} + /> {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ?
unviewed
:
}
-
+
); })} @@ -200,6 +234,10 @@ export class DashboardView extends React.Component { this.setNewDashboardName(''); }}> + +
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 7043edcee..4deed5ed9 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -6,6 +6,7 @@ import { ObjectField } from '../../fields/ObjectField'; import './EditableView.scss'; import { DocumentIconContainer } from './nodes/DocumentIcon'; import { OverlayView } from './OverlayView'; +import { EditableText } from 'browndash-components'; export interface EditableProps { /** @@ -231,39 +232,49 @@ export class EditableView extends React.Component { onChange: this.props.autosuggestProps.onChange, }} /> - ) : this.props.oneLine !== false && this.props.GetValue()?.toString().indexOf('\n') === -1 ? ( - (this._inputref = r)} - style={{ display: this.props.display, overflow: 'auto', fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }} - placeholder={this.props.placeholder} - onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} - defaultValue={this.props.GetValue()} - autoFocus={true} - onChange={this.onChange} - onKeyDown={this.onKeyDown} - onKeyPress={this.stopPropagation} - onPointerDown={this.stopPropagation} - onClick={this.stopPropagation} - onPointerUp={this.stopPropagation} + ) : - ) : ( -