diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/BranchingTrailManager.tsx | 137 | ||||
| -rw-r--r-- | src/client/util/CurrentUserUtils.ts | 97 | ||||
| -rw-r--r-- | src/client/util/DocumentManager.ts | 12 | ||||
| -rw-r--r-- | src/client/util/DragManager.ts | 22 | ||||
| -rw-r--r-- | src/client/util/InteractionUtils.tsx | 5 | ||||
| -rw-r--r-- | src/client/util/LinkFollower.ts | 24 | ||||
| -rw-r--r-- | src/client/util/LinkManager.ts | 11 | ||||
| -rw-r--r-- | src/client/util/RTFMarkup.tsx | 4 | ||||
| -rw-r--r-- | src/client/util/SearchUtil.ts | 4 | ||||
| -rw-r--r-- | src/client/util/SelectionManager.ts | 5 | ||||
| -rw-r--r-- | src/client/util/ServerStats.tsx | 6 | ||||
| -rw-r--r-- | src/client/util/SettingsManager.tsx | 50 | ||||
| -rw-r--r-- | src/client/util/reportManager/ReportManager.tsx | 30 | ||||
| -rw-r--r-- | src/client/util/reportManager/reportManagerUtils.ts | 7 |
14 files changed, 310 insertions, 104 deletions
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx new file mode 100644 index 000000000..a224b84f4 --- /dev/null +++ b/src/client/util/BranchingTrailManager.tsx @@ -0,0 +1,137 @@ +import { action, computed, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Doc } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { PresBox } from '../views/nodes/trails'; +import { OverlayView } from '../views/OverlayView'; +import { DocumentManager } from './DocumentManager'; +import { Docs } from '../documents/Documents'; +import { nullAudio } from '../../fields/URLField'; + +@observer +export class BranchingTrailManager extends React.Component { + public static Instance: BranchingTrailManager; + + constructor(props: any) { + super(props); + if (!BranchingTrailManager.Instance) { + BranchingTrailManager.Instance = this; + } + } + + setupUi = () => { + OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail'}); + // OverlayView.Instance.forceUpdate(); + console.log(OverlayView.Instance); + // let hi = Docs.Create.TextDocument("beee", { + // x: 100, + // y: 100, + // }) + // hi.overlayX = 100; + // hi.overlayY = 100; + + // Doc.AddToMyOverlay(hi); + console.log(DocumentManager._overlayViews); + }; + + + // stack of the history + @observable private slideHistoryStack: String[] = []; + @action setSlideHistoryStack = action((newArr: String[]) => { + this.slideHistoryStack = newArr; + }); + + @observable private containsSet: Set<String> = new Set<String>(); + + // prev pres to copmare with + @observable private prevPresId: String | null = null; + @action setPrevPres = action((newId: String | null) => { + this.prevPresId = newId; + }); + + // docId to Doc map + @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>(); + + observeDocumentChange = (targetDoc: Doc, pres: PresBox) => { + const presId = pres.props.Document[Id]; + if (this.prevPresId === presId) { + return; + } + + const targetDocId = targetDoc[Id]; + this.docIdToDocMap.set(targetDocId, targetDoc); + + if (this.prevPresId === null) { + this.setupUi(); + } + + if (this.prevPresId === null || this.prevPresId !== presId) { + Doc.UserDoc().isBranchingMode = true; + this.setPrevPres(presId); + + // REVERT THE SET + const stringified = [presId, targetDocId].toString(); + if (this.containsSet.has([presId, targetDocId].toString())) { + // remove all the elements after the targetDocId + const newStack = this.slideHistoryStack.slice(0, this.slideHistoryStack.indexOf(stringified)); + const removed = this.slideHistoryStack.slice(this.slideHistoryStack.indexOf(stringified)); + this.setSlideHistoryStack(newStack); + + removed.forEach(info => this.containsSet.delete(info.toString())); + } else { + this.setSlideHistoryStack([...this.slideHistoryStack, stringified]); + this.containsSet.add(stringified); + } + } + console.log(this.slideHistoryStack.length); + if (this.slideHistoryStack.length === 0) { + Doc.UserDoc().isBranchingMode = false; + } + }; + + clickHandler = (e: React.PointerEvent<HTMLButtonElement>, targetDocId: string, removeIndex: number) => { + const targetDoc = this.docIdToDocMap.get(targetDocId); + if (!targetDoc) { + return; + } + + const newStack = this.slideHistoryStack.slice(0, removeIndex); + const removed = this.slideHistoryStack.slice(removeIndex); + + this.setSlideHistoryStack(newStack); + + removed.forEach(info => this.containsSet.delete(info.toString())); + DocumentManager.Instance.showDocument(targetDoc, { willZoomCentered: true }); + if (this.slideHistoryStack.length === 0) { + Doc.UserDoc().isBranchingMode = false; + } + //PresBox.NavigateToTarget(targetDoc, targetDoc); + }; + + @computed get trailBreadcrumbs() { + return ( + <div style={{ border: '.5rem solid green', padding: '5px', backgroundColor: 'white', minHeight: '50px', minWidth: '1000px' }}> + {this.slideHistoryStack.map((info, index) => { + const [presId, targetDocId] = info.split(','); + const doc = this.docIdToDocMap.get(targetDocId); + if (!doc) { + return <></>; + } + return ( + <span key={targetDocId}> + <button key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}> + {presId.slice(0, 3) + ':' + doc.title} + </button> + -{'>'} + </span> + ); + })} + </div> + ); + } + + render() { + return <div>{BranchingTrailManager.Instance.trailBreadcrumbs}</div>; + } +} diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d52e389d6..cc8f72ddf 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -8,17 +8,18 @@ import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; import { ScriptField } from "../../fields/ScriptField"; import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types"; -import { nullAudio } from "../../fields/URLField"; +import { nullAudio, WebField } from "../../fields/URLField"; import { SetCachedGroups, SharingPermissions } from "../../fields/util"; import { GestureUtils } from "../../pen-gestures/GestureUtils"; -import { addStyleSheetRule, OmitKeys, Utils } from "../../Utils"; +import { OmitKeys, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { Docs, DocumentOptions, DocUtils, FInfo } from "../documents/Documents"; import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { TreeViewType } from "../views/collections/CollectionTreeView"; import { DashboardView } from "../views/DashboardView"; import { Colors } from "../views/global/globalEnums"; -import { OpenWhere } from "../views/nodes/DocumentView"; +import { media_state } from "../views/nodes/AudioBox"; +import { DocumentView, OpenWhere } from "../views/nodes/DocumentView"; import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox"; import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox"; import { OverlayView } from "../views/OverlayView"; @@ -27,7 +28,7 @@ import { MakeTemplate } from "./DropConverter"; import { FollowLinkScript } from "./LinkFollower"; import { LinkManager } from "./LinkManager"; import { ScriptingGlobals } from "./ScriptingGlobals"; -import { ColorScheme, SettingsManager } from "./SettingsManager"; +import { ColorScheme } from "./SettingsManager"; import { UndoManager } from "./UndoManager"; interface Button { @@ -51,7 +52,7 @@ interface Button { // fields that do not correspond to DocumentOption fields scripts?: { script?: string; onClick?: string; onDoubleClick?: string } - funcs?: { [key:string]: string }; + funcs?: { [key:string]: any}; subMenu?: Button[]; } @@ -299,7 +300,7 @@ export class CurrentUserUtils { { toolTip: "Tap or drag to create a comparison box", title: "Compare", icon: "columns", dragFactory: doc.emptyComparison as Doc, clickFactory: DocCast(doc.emptyComparison)}, { toolTip: "Tap or drag to create an audio recorder", title: "Audio", icon: "microphone", dragFactory: doc.emptyAudio as Doc, clickFactory: DocCast(doc.emptyAudio), openFactoryLocation: OpenWhere.overlay}, { toolTip: "Tap or drag to create a map", title: "Map", icon: "map-marker-alt", dragFactory: doc.emptyMap as Doc, clickFactory: DocCast(doc.emptyMap)}, - { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay}, + { toolTip: "Tap or drag to create a screen grabber", title: "Grab", icon: "photo-video", dragFactory: doc.emptyScreengrab as Doc, clickFactory: DocCast(doc.emptyScreengrab), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a WebCam recorder", title: "WebCam", icon: "photo-video", dragFactory: doc.emptyWebCam as Doc, clickFactory: DocCast(doc.emptyWebCam), openFactoryLocation: OpenWhere.overlay, funcs: { hidden: "IsNoviceMode()"}}, { toolTip: "Tap or drag to create a button", title: "Button", icon: "bolt", dragFactory: doc.emptyButton as Doc, clickFactory: DocCast(doc.emptyButton)}, { toolTip: "Tap or drag to create a scripting box", title: "Script", icon: "terminal", dragFactory: doc.emptyScript as Doc, clickFactory: DocCast(doc.emptyScript), funcs: { hidden: "IsNoviceMode()"}}, @@ -475,7 +476,6 @@ export class CurrentUserUtils { static setupDashboards(doc: Doc, field:string) { var myDashboards = DocCast(doc[field]); - const toggleDarkTheme = `this.colorScheme = this.colorScheme ? undefined : "${ColorScheme.Dark}"`; const newDashboard = `createNewDashboard()`; const reqdBtnOpts:DocumentOptions = { _forceActive: true, _width: 30, _height: 30, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, @@ -486,14 +486,14 @@ export class CurrentUserUtils { const contextMenuScripts = [/*newDashboard*/] as string[]; const contextMenuLabels = [/*"Create New Dashboard"*/] as string[]; const contextMenuIcons = [/*"plus"*/] as string[]; - const childContextMenuScripts = [toggleDarkTheme, `toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters - const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, undefined as any];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts - const childContextMenuLabels = ["Toggle Dark Theme", "Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters - const childContextMenuIcons = ["chalkboard", "tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters + const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(self)`, 'removeDashboard(self)', 'resetDashboard(self)']; // entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuFilters + const childContextMenuFilters = ['!IsNoviceMode()', '!IsNoviceMode()', undefined as any, undefined as any, '!IsNoviceMode()'];// entries must be kept in synch with childContextMenuLabels, childContextMenuIcons, and childContextMenuScripts + const childContextMenuLabels = ["Toggle Comic Mode", "Snapshot Dashboard", "Share Dashboard", "Remove Dashboard", "Reset Dashboard"];// entries must be kept in synch with childContextMenuScripts, childContextMenuIcons, and childContextMenuFilters + const childContextMenuIcons = ["tv", "camera", "users", "times", "trash"]; // entries must be kept in synch with childContextMenuScripts, childContextMenuLabels, and childContextMenuFilters const reqdOpts:DocumentOptions = { title: "My Dashboards", childHideLinkButton: true, treeView_FreezeChildren: "remove|add", treeView_HideTitle: true, layout_boxShadow: "0 0", childDontRegisterViews: true, - dropAction: "same", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true, - layout_headerButton: newDashboardButton, childDragAction: "none", + dropAction: "inSame", treeView_Type: TreeViewType.fileSystem, isFolder: true, isSystem: true, treeView_TruncateTitleWidth: 350, ignoreClick: true, + layout_headerButton: newDashboardButton, childDragAction: "inSame", _layout_showTitle: "title", _height: 400, _gridGap: 5, _forceActive: true, _lockedPosition: true, contextMenuLabels:new List<string>(contextMenuLabels), contextMenuIcons:new List<string>(contextMenuIcons), @@ -591,7 +591,7 @@ export class CurrentUserUtils { static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({ btnType: ButtonType.ToolButton, _forceActive: true, _layout_hideContextMenu: true, _dropPropertiesToRemove: new List<string>([ "_layout_hideContextMenu"]), - _nativeWidth: 40, _nativeHeight: 40, _width: 40, _height: 40, isSystem: true, ...opts, + /*_nativeWidth: 40, _nativeHeight: 40, */ _width: 40, _height: 40, isSystem: true, ...opts, }) /// initializes the required buttons in the expanding button menu at the bottom of the Dash window @@ -607,6 +607,7 @@ export class CurrentUserUtils { { scripts: { }, opts: { title: "undoStack", layout: "<UndoStack>", toolTip: "Undo/Redo Stack"}}, // note: layout fields are hacks -- they don't actually run through the JSX parser (yet) { scripts: { }, opts: { title: "linker", layout: "<LinkingUI>", toolTip: "link started"}}, { scripts: { }, opts: { title: "currently playing", layout: "<CurrentlyPlayingUI>", toolTip: "currently playing media"}}, + { scripts: { }, opts: { title: "Branching", layout: "<Branching>", toolTip: "Branch, baby!"}} ]; const btns = btnDescs.map(desc => dockBtn({_width: 30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts)); const dockBtnsReqdOpts:DocumentOptions = { @@ -625,6 +626,11 @@ export class CurrentUserUtils { { title: "Z order", icon: "z", toolTip: "Keep Z order on Drag", btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: '{ return toggleRaiseOnDrag(_readOnly_);}'}}, // Only when floating document is selected in freeform ] } + static stackTools(): Button[] { + return [ + { title: "Center", icon: "align-center", toolTip: "Center Align Stack", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"center", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform + ] + } static viewTools(): Button[] { return [ { title: "Snap", icon: "th", toolTip: "Show Snap Lines", btnType: ButtonType.ToggleButton, ignoreClick: true, expertMode: false, toolType:"snaplines", funcs: {}, scripts: { onClick: '{ return showFreeform(self.toolType, _readOnly_);}'}}, // Only when floating document is selected in freeform @@ -668,7 +674,7 @@ export class CurrentUserUtils { return [ { title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }}, { title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}'} }, - { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }}, + { title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(self.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }}, { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType:GestureUtils.Gestures.Circle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType:GestureUtils.Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType:GestureUtils.Gestures.Line, scripts: {onClick:`{ return setActiveTool(self.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(self.toolType, true, _readOnly_);}`} }, @@ -680,8 +686,8 @@ export class CurrentUserUtils { static schemaTools():Button[] { return [ - {title: "Show preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, buttonText: "Show Preview", icon: "eye", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} }, - {title: "Single Lines", toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, buttonText: "Single Line", icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} }, + {title: "Preview", toolTip: "Show selection preview", btnType: ButtonType.ToggleButton, icon: "portrait", scripts:{ onClick: '{ return toggleSchemaPreview(_readOnly_); }'} }, + {title: "1 Line",toolTip: "Single Line Rows", btnType: ButtonType.ToggleButton, icon: "eye", scripts:{ onClick: '{ return toggleSingleLineSchema(_readOnly_); }'} }, ]; } @@ -692,7 +698,6 @@ export class CurrentUserUtils { { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} }, ]; } - static contextMenuTools():Button[] { return [ { btnList: new List<string>([CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Tree, @@ -700,22 +705,22 @@ export class CurrentUserUtils { CollectionViewType.Multirow, CollectionViewType.Time, CollectionViewType.Carousel, CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map, CollectionViewType.Grid, CollectionViewType.NoteTaking]), - title: "Perspective", toolTip: "View", btnType: ButtonType.DropdownList, ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}}, + 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)'}, funcs: {hidden: "IsNoneSelected()"}}, - { title: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected { title: "Header", icon: "heading", toolTip: "Header Color", btnType: ButtonType.ColorButton, expertMode: true, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, - { 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: "Fill", icon: "fill-drip", toolTip: "Background Fill Color",btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, width: 30, scripts: { script: 'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected + { 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_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: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, 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: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, 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 - { title: "Audio", icon: "microphone", toolTip: "Dictate", btnType: ButtonType.ToggleButton, expertMode: false, ignoreClick: true, scripts: { onClick: 'return toggleRecording(_readOnly_)'}, funcs: { }} - ]; + { 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 + ]; } /// initializes a context menu button for the top bar context menu @@ -734,6 +739,7 @@ export class CurrentUserUtils { return DocUtils.AssignScripts(DocUtils.AssignOpts(btnDoc, reqdOpts) ?? Docs.Create.FontIconDocument(reqdOpts), params.scripts, reqdFuncs); } + static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc { const menuBtnDoc = DocListCast(menuDoc?.data).find(doc => doc.title === params.title); const subMenu = params.subMenu; @@ -743,7 +749,7 @@ export class CurrentUserUtils { // linear view const reqdSubMenuOpts = { ...OmitKeys(params, ["scripts", "funcs", "subMenu"]).omit, undoIgnoreFields: new List<string>(['width', "linearView_IsOpen"]), childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: params.scripts?.onClick ? false : true, - linearView_SubMenu: true, linearView_Expandable: params.btnType !== ButtonType.MultiToggleButton}; + linearView_SubMenu: true, linearView_Expandable: true}; const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) ); const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList; @@ -760,6 +766,32 @@ export class CurrentUserUtils { const ctxtMenuBtns = CurrentUserUtils.contextMenuTools().map(params => this.setupContextMenuBtn(params, ctxtMenuBtnsDoc) ); return DocUtils.AssignOpts(ctxtMenuBtnsDoc, reqdCtxtOpts, ctxtMenuBtns); } + /// Initializes all the default buttons for the top bar context menu + static setupTopbarButtons(doc: Doc, field="myTopBarBtns") { + Doc.UserDoc().currentRecording = undefined; + Doc.UserDoc().workspaceRecordingState = undefined; + Doc.UserDoc().workspaceReplayingState = undefined; + const dockedBtns = DocCast(doc[field]); + const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:any}) => + DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ?? + 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 + { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true}, scripts: { onClick: `snapshotDashboard()`}}, + { opts: { title: "Recordings", toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script: `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}}, + { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`}, scripts: { onClick: `stopWorkspaceRecording()`}}, + { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}}, + { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}}, + { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}}, + { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`}, scripts: { onClick: `removeWorkspaceReplaying(getCurrentRecording())`}} + ]; + const btns = btnDescs.map(desc => dockBtn({_width: desc.opts.width??30, _height: 30, defaultDoubleClick: 'ignore', undoIgnoreFields: new List<string>(['opacity']), _dragOnlyWithinContainer: true, ...desc.opts}, desc.scripts, desc.funcs)); + const dockBtnsReqdOpts:DocumentOptions = { + title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: 'move', + childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: false, ignoreClick: true + }; + return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns); + } /// collection of documents rendered in the overlay layer above all tabs and other UI static setupOverlays(doc: Doc, field = "myOverlayDocs") { @@ -788,7 +820,7 @@ export class CurrentUserUtils { // When the user views one of these documents, it will be added to the sharing documents 'viewed' list field // The sharing document also stores the user's color value which helps distinguish shared documents from personal documents static setupSharedDocs(doc: Doc, sharingDocumentId: string) { - const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(scriptContext.props.treeView.props.Document, 'viewed', documentView.rootDoc);}"; + const dblClkScript = "{scriptContext.openLevel(documentView); addDocToList(documentView.props.treeViewDoc, 'viewed', documentView.rootDoc);}"; const sharedScripts = { treeView_ChildDoubleClick: dblClkScript, } const sharedDocOpts:DocumentOptions = { @@ -859,7 +891,6 @@ export class CurrentUserUtils { doc.defaultAclPrivate ?? (doc.defaultAclPrivate = false); doc.savedFilters ?? (doc.savedFilters = new List<Doc>()); 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); @@ -873,6 +904,7 @@ export class CurrentUserUtils { this.setupOverlays(doc); // sets up the overlay panel where documents and other widgets can be added to float over the rest of the dashboard this.setupPublished(doc); // sets up the list doc of all docs that have been published (meaning that they can be auto-linked by typing their title into another text box) this.setupContextMenuButtons(doc); // set up the row of buttons at the top of the dashboard that change depending on what is selected + this.setupTopbarButtons(doc); this.setupDockedButtons(doc); // the bottom bar of font icons this.setupLeftSidebarMenu(doc); // the left-side column of buttons that open their contents in a flyout panel on the left this.setupDocTemplates(doc); // sets up the template menu of templates @@ -884,10 +916,8 @@ export class CurrentUserUtils { Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs) Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed) - if (doc.activeDashboard instanceof Doc) { - // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) - doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; - } + Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org") + new LinkManager(); DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500); @@ -992,7 +1022,8 @@ export class CurrentUserUtils { } ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs"); +ScriptingGlobals.add(function IsExploreMode() { return DocumentView.ExploreMode; }, "is Dash in exploration mode"); ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode"); ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering"); ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar"); -ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
\ No newline at end of file +ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; }); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index c2827dac7..b9f6059f4 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,9 +1,9 @@ -import { action, computed, observable, ObservableSet, observe, reaction } from 'mobx'; +import { action, computed, observable, ObservableSet, observe } from 'mobx'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { AclAdmin, AclEdit, Animation } from '../../fields/DocSymbols'; import { Id } from '../../fields/FieldSymbols'; import { listSpec } from '../../fields/Schema'; -import { Cast, DocCast, StrCast } from '../../fields/Types'; +import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types'; import { AudioField } from '../../fields/URLField'; import { GetEffectiveAcl } from '../../fields/util'; import { CollectionViewType } from '../documents/DocumentTypes'; @@ -11,7 +11,6 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie import { TabDocView } from '../views/collections/TabDocView'; import { LightboxView } from '../views/LightboxView'; import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; -import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox'; import { KeyValueBox } from '../views/nodes/KeyValueBox'; import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox'; import { PresBox } from '../views/nodes/trails'; @@ -101,8 +100,6 @@ export class DocumentManager { }) ); this.LinkAnchorBoxViews.push(view); - // this.LinkedDocumentViews.forEach(view => console.log(" LV = " + view.a.props.Document.title + "/" + view.a.props.LayoutTemplateString + " --> " + - // view.b.props.Document.title + "/" + view.b.props.LayoutTemplateString)); } else { this.AddDocumentView(view); } @@ -322,9 +319,12 @@ export class DocumentManager { @action restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) { if (viewSpec && docView) { - if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options); + //if (docView.ComponentView instanceof FormattedTextBox) + //viewSpec !== docView.rootDoc && + docView.ComponentView?.focus?.(viewSpec, options); PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500); Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec] : docView.rootDoc, undefined, options.effect); + if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.rootDoc._layout_currentTimecode)); 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[Animation] = options.effect; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 831a22866..4f30e92ce 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -8,14 +8,13 @@ import { BoolCast, ScriptCast, StrCast } from '../../fields/Types'; import { emptyFunction, Utils } from '../../Utils'; import { Docs, DocUtils } from '../documents/Documents'; import * as globalCssVariables from '../views/global/globalCssVariables.scss'; -import { Colors } from '../views/global/globalEnums'; import { DocumentView } from '../views/nodes/DocumentView'; import { ScriptingGlobals } from './ScriptingGlobals'; import { SelectionManager } from './SelectionManager'; import { SnappingManager } from './SnappingManager'; import { UndoManager } from './UndoManager'; -export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove +export type dropActionType = 'embed' | 'copy' | 'move' | 'add' | 'same' | 'inSame' | 'proto' | 'none' | undefined; // undefined = move, "same" = move but don't call dropPropertiesToRemove /** * Initialize drag @@ -109,9 +108,11 @@ export namespace DragManager { constructor(dragDoc: Doc[], dropAction?: dropActionType) { this.draggedDocuments = dragDoc; this.droppedDocuments = []; + this.draggedViews = []; this.offset = [0, 0]; this.dropAction = dropAction; } + draggedViews: DocumentView[]; draggedDocuments: Doc[]; droppedDocuments: Doc[]; treeViewDoc?: Doc; @@ -190,6 +191,13 @@ export namespace DragManager { // drag a document and drop it (or make an embed/copy on drop) export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) { + dragData.draggedViews.forEach( + action(view => { + const ffview = view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + ffview && (ffview.GroupChildDrag = BoolCast(ffview.Document._isGroup)); + ffview?.setupDragLines(false); + }) + ); const addAudioTag = (dropDoc: any) => { dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField()); dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc); @@ -197,6 +205,14 @@ export namespace DragManager { }; const finishDrag = async (e: DragCompleteEvent) => { const docDragData = e.docDragData; + setTimeout(() => + dragData.draggedViews.forEach( + action(view => { + const ffview = view.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; + ffview && (ffview.GroupChildDrag = false); + }) + ) + ); onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails if (docDragData && !docDragData.droppedDocuments.length) { docDragData.dropAction = dragData.userDropAction || dragData.dropAction; @@ -325,7 +341,7 @@ export namespace DragManager { 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, dragUndoName?: string) { - if (dragData.dropAction === 'none') return; + if (dragData.dropAction === 'none' || DocumentView.ExploreMode) return; DocDragData = dragData as DocumentDragData; const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag'); eles = eles.filter(e => e); diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index d0f459291..4e32ed67f 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -108,7 +108,8 @@ export namespace InteractionUtils { opacity: number, nodefs: boolean, downHdlr?: (e: React.PointerEvent) => void, - mask?: boolean + mask?: boolean, + dropshadow?: string ) { const pts = shape ? makePolygon(shape, points) : points; @@ -161,7 +162,6 @@ export namespace InteractionUtils { <polygon style={{ stroke: color }} strokeLinejoin={lineJoin as any} - strokeDasharray={dashArray} strokeWidth={(markerStrokeWidth * 2) / 3} points={`0 ${-markerStrokeWidth * arrowWidthFactor}, ${markerStrokeWidth * arrowNotchFactor} 0, 0 ${markerStrokeWidth * arrowWidthFactor}, ${arrowLengthFactor * markerStrokeWidth} 0`} /> @@ -172,6 +172,7 @@ export namespace InteractionUtils { <Tag d={bezier ? strpts : undefined} points={bezier ? undefined : strpts} + filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) drop-shadow(2px -1px 0px ${dropshadow}) drop-shadow(2px 2px 0px ${dropshadow}) drop-shadow(-1px 2px 0px ${dropshadow})`} style={{ // filter: drawHalo ? "url(#inkSelectionHalo)" : undefined, fill: fill && fill !== 'transparent' ? fill : 'none', diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts index b8fea340f..146eed6c2 100644 --- a/src/client/util/LinkFollower.ts +++ b/src/client/util/LinkFollower.ts @@ -68,21 +68,23 @@ export class LinkFollower { ? linkDoc.link_anchor_2 : linkDoc.link_anchor_1 ) as Doc; + const srcAnchor = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc; if (target) { const doFollow = (canToggle?: boolean) => { const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle); const options: DocFocusOptions = { - playAudio: BoolCast(sourceDoc.followLinkAudio), + playAudio: BoolCast(srcAnchor.followLinkAudio), + playMedia: BoolCast(srcAnchor.followLinkVideo), toggleTarget, noSelect: true, willPan: true, - willZoomCentered: BoolCast(sourceDoc.followLinkZoom, false), - zoomTime: NumCast(sourceDoc.followLinkTransitionTime, 500), - zoomScale: Cast(sourceDoc.followLinkZoomScale, 'number', null), - easeFunc: StrCast(sourceDoc.followLinkEase, 'ease') as any, - openLocation: StrCast(sourceDoc.followLinkLocation, OpenWhere.lightbox) as OpenWhere, - effect: sourceDoc, - zoomTextSelections: BoolCast(sourceDoc.followLinkZoomText), + willZoomCentered: BoolCast(srcAnchor.followLinkZoom, false), + zoomTime: NumCast(srcAnchor.followLinkTransitionTime, 500), + zoomScale: Cast(srcAnchor.followLinkZoomScale, 'number', null), + easeFunc: StrCast(srcAnchor.followLinkEase, 'ease') as any, + openLocation: StrCast(srcAnchor.followLinkLocation, OpenWhere.lightbox) as OpenWhere, + effect: srcAnchor, + zoomTextSelections: BoolCast(srcAnchor.followLinkZoomText), }; if (target.type === DocumentType.PRES) { const containerDocContext = DocumentManager.GetContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail @@ -96,7 +98,7 @@ export class LinkFollower { } }; let movedTarget = false; - if (sourceDoc.followLinkLocation === OpenWhere.inParent) { + if (srcAnchor.followLinkLocation === OpenWhere.inParent) { const sourceDocParent = DocCast(sourceDoc.embedContainer); if (target.embedContainer instanceof Doc && target.embedContainer !== sourceDocParent) { Doc.RemoveDocFromList(target.embedContainer, Doc.LayoutFieldKey(target.embedContainer), target); @@ -108,11 +110,11 @@ export class LinkFollower { } Doc.SetContainer(target, sourceDocParent); const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)]; - if (sourceDoc.followLinkXoffset !== undefined && moveTo[0] !== target.x) { + if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) { target.x = moveTo[0]; movedTarget = true; } - if (sourceDoc.followLinkYoffset !== undefined && moveTo[1] !== target.y) { + if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) { target.y = moveTo[1]; movedTarget = true; } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index ef4b21b05..a533fdd1f 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -56,10 +56,12 @@ export class LinkManager { Promise.all([lproto?.link_anchor_1 as Doc, lproto?.link_anchor_2 as Doc].map(PromiseValue)).then((lAnchs: Opt<Doc>[]) => Promise.all(lAnchs.map(lAnch => PromiseValue(lAnch?.proto as Doc))).then((lAnchProtos: Opt<Doc>[]) => Promise.all(lAnchProtos.map(lAnchProto => PromiseValue(lAnchProto?.proto as Doc))).then( - action(lAnchProtoProtos => { - link && lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link); - link && lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link); - }) + link && + action(lAnchProtoProtos => { + Doc.AddDocToList(Doc.UserDoc(), 'links', link); + lAnchs[0] && Doc.GetProto(lAnchs[0])[DirectLinks].add(link); + lAnchs[1] && Doc.GetProto(lAnchs[1])[DirectLinks].add(link); + }) ) ) ) @@ -145,6 +147,7 @@ export class LinkManager { }; public addLink(linkDoc: Doc, checkExists = false) { + Doc.AddDocToList(Doc.UserDoc(), 'links', linkDoc); if (!checkExists || !DocListCast(Doc.LinkDBDoc().data).includes(linkDoc)) { Doc.AddDocToList(Doc.LinkDBDoc(), 'data', linkDoc); setTimeout(DocServer.UPDATE_SERVER_CACHE, 100); diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx index 78069d323..c8940194c 100644 --- a/src/client/util/RTFMarkup.tsx +++ b/src/client/util/RTFMarkup.tsx @@ -33,7 +33,7 @@ export class RTFMarkup extends React.Component<{}> { */ @computed get cheatSheet() { return ( - <div style={{ background: SettingsManager.Instance?.userBackgroundColor, color: SettingsManager.Instance?.userColor, textAlign: 'initial', height: '100%' }}> + <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}> <p> <b style={{ fontSize: 'larger' }}>{`wiki:phrase`}</b> {` display wikipedia page for entered text (terminate with carriage return)`} @@ -137,7 +137,7 @@ export class RTFMarkup extends React.Component<{}> { render() { return ( <MainViewModal - dialogueBoxStyle={{ backgroundColor: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor), padding: '16px' }} + dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, padding: '16px' }} contents={this.cheatSheet} isDisplayed={this.isOpen} interactive={true} diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 64aa7ba9b..560d6b30f 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -57,8 +57,8 @@ export namespace SearchUtil { const hlights = new Set<string>(); SearchUtil.documentKeys(doc).forEach( key => - Field.toString(doc[key] as Field) - .toLowerCase() + (Field.toString(doc[key] as Field) + Field.toScriptString(doc[key] as Field)) + .toLowerCase() // .includes(query) && hlights.add(key) ); blockedKeys.forEach(key => hlights.delete(key)); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index dbf33fcf5..d0f66d124 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -26,9 +26,6 @@ export namespace SelectionManager { // if doc is not in SelectedDocuments, add it if (!manager.SelectedViewsMap.get(docView)) { if (!ctrlPressed) { - if (LinkManager.currentLink && !LinkManager.Links(docView.rootDoc).includes(LinkManager.currentLink) && docView.rootDoc !== LinkManager.currentLink) { - LinkManager.currentLink = undefined; - } this.DeselectAll(); } @@ -54,6 +51,8 @@ export namespace SelectionManager { } @action DeselectAll(): void { + LinkManager.currentLink = undefined; + LinkManager.currentLinkAnchor = undefined; manager.SelectedSchemaDocument = undefined; Array.from(manager.SelectedViewsMap.keys()).forEach(dv => dv.props.whenChildContentsActiveChanged(false)); manager.SelectedViewsMap.clear(); diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx index 3c7c35a7e..08dbaac5d 100644 --- a/src/client/util/ServerStats.tsx +++ b/src/client/util/ServerStats.tsx @@ -46,12 +46,12 @@ export class ServerStats extends React.Component<{}> { <div style={{ display: 'flex', - height: 300, + height: '100%', width: 400, - background: SettingsManager.Instance?.userBackgroundColor, + background: SettingsManager.userBackgroundColor, opacity: 0.6, }}> - <div style={{ width: 300, height: 100, margin: 'auto', display: 'flex', flexDirection: 'column' }}> + <div style={{ width: 300, margin: 'auto', display: 'flex', flexDirection: 'column' }}> {PingManager.Instance.IsBeating ? 'The server connection is active' : 'The server connection has been interrupted.NOTE: Any changes made will appear to persist but will be lost after a browser refreshes.'} <br /> diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 720badd40..dc852596f 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -20,10 +20,9 @@ import { undoBatch } from './UndoManager'; export enum ColorScheme { Dark = 'Dark', Light = 'Light', - CoolBlue = 'Cool Blue', - Cupcake = 'Cupcake', - System = 'Match System', Custom = 'Custom', + CoolBlue = 'CoolBlue', + Cupcake = 'Cupcake', } export enum freeformScrollMode { @@ -50,7 +49,21 @@ export class SettingsManager extends React.Component<{}> { constructor(props: {}) { super(props); SettingsManager.Instance = this; + this.matchSystem(); + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { + if (Doc.UserDoc().userThemeSystem) { + if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark); + if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light); + } + // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) + }); } + matchSystem = () => { + if (Doc.UserDoc().userThemeSystem) { + if (window.matchMedia('(prefers-color-scheme: dark)').matches) this.changeColorScheme(ColorScheme.Dark); + if (window.matchMedia('(prefers-color-scheme: light)').matches) this.changeColorScheme(ColorScheme.Light); + } + }; public close = action(() => (this.isOpen = false)); public open = action(() => (this.isOpen = true)); @@ -88,6 +101,10 @@ export class SettingsManager extends React.Component<{}> { }); @undoBatch switchUserColor = action((color: string) => (Doc.UserDoc().userColor = color)); @undoBatch switchUserVariantColor = action((color: string) => (Doc.UserDoc().userVariantColor = color)); + @undoBatch userThemeSystemToggle = action(() => { + Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem; + this.matchSystem(); + }); @undoBatch playgroundModeToggle = action(() => { this.playgroundMode = !this.playgroundMode; if (this.playgroundMode) { @@ -123,18 +140,11 @@ export class SettingsManager extends React.Component<{}> { break; case ColorScheme.Custom: break; - case ColorScheme.System: - default: - window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => { - 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; } }); @computed get colorsContent() { - const colorSchemes = [ColorScheme.Light, ColorScheme.Dark, ColorScheme.Cupcake, ColorScheme.CoolBlue, ColorScheme.Custom, ColorScheme.System]; - const schemeMap = ['Light', 'Dark', 'Cupcake', 'Cool Blue', 'Custom', 'Match System']; + const schemeMap = Array.from(Object.keys(ColorScheme)); const userTheme = StrCast(Doc.UserDoc().userTheme); return ( <div style={{ width: '100%' }}> @@ -142,16 +152,22 @@ export class SettingsManager extends React.Component<{}> { formLabel="Theme" size={Size.SMALL} type={Type.TERT} + closeOnSelect={false} selectedVal={userTheme} - setSelectedVal={scheme => this.changeColorScheme(scheme as string)} - items={colorSchemes.map((scheme, i) => ({ - text: schemeMap[i], + setSelectedVal={scheme => { + this.changeColorScheme(scheme as string); + Doc.UserDoc().userThemeSystem = false; + }} + items={Object.keys(ColorScheme).map((scheme, i) => ({ + text: schemeMap[i].replace(/([a-z])([A-Z])/, '$1 $2'), val: scheme, }))} dropdownType={DropdownType.SELECT} color={SettingsManager.userColor} fillWidth /> + <Toggle formLabel="Match System" toggleType={ToggleType.SWITCH} color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.UserDoc().userThemeSystem)} onClick={this.userThemeSystemToggle} /> + {userTheme === ColorScheme.Custom && ( <Group formLabel="Custom Theme"> <ColorPicker @@ -294,6 +310,7 @@ export class SettingsManager extends React.Component<{}> { }, }; })} + closeOnSelect={true} dropdownType={DropdownType.SELECT} type={Type.TERT} selectedVal={StrCast(Doc.UserDoc().fontFamily)} @@ -373,6 +390,7 @@ export class SettingsManager extends React.Component<{}> { <div className="tab-column-content"> <Dropdown formLabel={'Mode'} + closeOnSelect={true} items={[ { text: 'Novice', @@ -403,7 +421,8 @@ export class SettingsManager extends React.Component<{}> { </div> <div className="tab-column-content"> <Dropdown - formLabel={'Scroll Mode'} + formLabel="Scroll Mode" + closeOnSelect={true} items={[ { text: 'Scroll to Pan', @@ -436,6 +455,7 @@ export class SettingsManager extends React.Component<{}> { toggleStatus={BoolCast(Doc.defaultAclPrivate)} onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} /> + <Toggle toggleType={ToggleType.SWITCH} formLabel={'Enable Sharing UI'} color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsSharingEnabled)} onClick={action(() => (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} /> </div> </div> </div> diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index 6a236face..b25d51b41 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -308,6 +308,7 @@ export class ReportManager extends React.Component<{}> { <Dropdown color={StrCast(Doc.UserDoc().userColor)} formLabel={'Type'} + closeOnSelect={true} items={bugDropdownItems} selectedVal={this.formData.type} setSelectedVal={val => { @@ -320,6 +321,7 @@ export class ReportManager extends React.Component<{}> { <Dropdown color={StrCast(Doc.UserDoc().userColor)} formLabel={'Priority'} + closeOnSelect={true} items={priorityDropdownItems} selectedVal={this.formData.priority} setSelectedVal={val => { @@ -330,25 +332,15 @@ export class ReportManager extends React.Component<{}> { fillWidth /> </div> - <Dropzone - onDrop={this.onDrop} - accept={{ - 'image/*': ['.png', '.jpg', '.jpeg', '.gif'], - 'video/*': ['.mp4', '.mpeg', '.webm', '.mov'], - 'audio/mpeg': ['.mp3'], - 'audio/wav': ['.wav'], - 'audio/ogg': ['.ogg'], - }}> - {({ getRootProps, getInputProps }) => ( - <div {...getRootProps({ className: 'dropzone' })} style={{ borderColor: isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor)) ? darkColors.border : lightColors.border }}> - <input {...getInputProps()} /> - <div className="dropzone-instructions"> - <AiOutlineUpload size={25} /> - <p>Drop or select media that shows the bug (optional)</p> - </div> - </div> - )} - </Dropzone> + <input + type="file" + accept="image/*, video/*, audio/*" + multiple + onChange={e => { + if (!e.target.files) return; + this.setFormData({ ...this.formData, mediaFiles: [...this.formData.mediaFiles, ...Array.from(e.target.files).map(file => ({ _id: v4(), file }))] }); + }} + /> {this.formData.mediaFiles.length > 0 && <ul className="file-list">{this.formData.mediaFiles.map(file => this.getMediaPreview(file))}</ul>} {this.submitting ? ( <Button diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts index b95417aa1..22e5eebbb 100644 --- a/src/client/util/reportManager/reportManagerUtils.ts +++ b/src/client/util/reportManager/reportManagerUtils.ts @@ -88,8 +88,13 @@ export const fileLinktoServerLink = (fileLink: string): string => { const regex = 'public'; const publicIndex = fileLink.indexOf(regex) + regex.length; + let finalUrl: string = ''; + if (fileLink.includes('.png') || fileLink.includes('.jpg') || fileLink.includes('.jpeg') || fileLink.includes('.gif')) { + finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`; + } else { + finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1)}`; + } - const finalUrl = `${serverUrl}${fileLink.substring(publicIndex + 1).replace('.', '_l.')}`; return finalUrl; }; |
