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 | 111 | ||||
| -rw-r--r-- | src/client/util/DictationManager.ts | 2 | ||||
| -rw-r--r-- | src/client/util/DocumentManager.ts | 32 | ||||
| -rw-r--r-- | src/client/util/DragManager.ts | 31 | ||||
| -rw-r--r-- | src/client/util/DropConverter.ts | 14 | ||||
| -rw-r--r-- | src/client/util/GroupManager.tsx | 18 | ||||
| -rw-r--r-- | src/client/util/GroupMemberView.tsx | 2 | ||||
| -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 | 104 | ||||
| -rw-r--r-- | src/client/util/SelectionManager.ts | 36 | ||||
| -rw-r--r-- | src/client/util/ServerStats.tsx | 6 | ||||
| -rw-r--r-- | src/client/util/SettingsManager.tsx | 155 | ||||
| -rw-r--r-- | src/client/util/SharingManager.tsx | 39 | ||||
| -rw-r--r-- | src/client/util/reportManager/ReportManager.tsx | 37 | ||||
| -rw-r--r-- | src/client/util/reportManager/reportManagerUtils.ts | 7 |
19 files changed, 538 insertions, 237 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 62b99d752..2ea5972ee 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,7 +1,6 @@ import { observable, reaction, runInAction } from "mobx"; import * as rp from 'request-promise'; import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; -import { FieldLoader } from "../../fields/FieldLoader"; import { InkTool } from "../../fields/InkField"; import { List } from "../../fields/List"; import { PrefetchProxy } from "../../fields/Proxy"; @@ -9,28 +8,28 @@ 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 { OmitKeys, Utils, addStyleSheetRule } 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 { MainView } from "../views/MainView"; +import { media_state } from "../views/nodes/AudioBox"; +import { DocumentView, OpenWhere } from "../views/nodes/DocumentView"; import { ButtonType } from "../views/nodes/FontIconBox/FontIconBox"; -import { OpenWhere } from "../views/nodes/DocumentView"; +import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox"; import { OverlayView } from "../views/OverlayView"; import { DragManager, dropActionType } from "./DragManager"; 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"; -import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox"; interface Button { // DocumentOptions fields a button can set @@ -53,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[]; } @@ -272,7 +271,7 @@ export class CurrentUserUtils { {key: "Webpage", creator: opts => Docs.Create.WebDocument("",opts), opts: { _width: 400, _height: 512, _nativeWidth: 850, data_useCors: true, }}, {key: "Comparison", creator: Docs.Create.ComparisonDocument, opts: { _width: 300, _height: 300 }}, {key: "Audio", creator: opts => Docs.Create.AudioDocument(nullAudio, opts),opts: { _width: 200, _height: 100, }}, - {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, _layout_showSidebar: true, }}, + {key: "Map", creator: opts => Docs.Create.MapDocument([], opts), opts: { _width: 800, _height: 600, _layout_fitWidth: true, }}, {key: "Screengrab", creator: Docs.Create.ScreenshotDocument, opts: { _width: 400, _height: 200 }}, {key: "WebCam", creator: opts => Docs.Create.WebCamDocument("", opts), opts: { _width: 400, _height: 200, recording:true, isSystem: true, cloneFieldFilter: new List<string>(["isSystem"]) }}, {key: "Button", creator: Docs.Create.ButtonDocument, opts: { _width: 150, _height: 50, _xPadding: 10, _yPadding: 10}, scripts: {onClick: FollowLinkScript()?.script.originalScript ?? ""}}, @@ -301,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()"}}, @@ -477,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, @@ -488,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), @@ -593,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 @@ -609,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 = { @@ -622,9 +621,14 @@ export class CurrentUserUtils { static freeTools(): Button[] { return [ - { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform - { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform - { title: "Z order", icon: "z", toolTip: "Bring Forward on Drag (double click to set for all)",waitForDoubleClickToClick:true, btnType: ButtonType.ToggleButton, expertMode: false, funcs: {}, scripts: { onClick: 'toggleRaiseOnDrag(false, _readOnly_)', onDoubleClick:`{ return toggleRaiseOnDrag(true, _readOnly_)`}}, // Only when floating document is selected in freeform + { title: "Bottom", icon: "arrows-down-to-line",toolTip: "Make doc topmost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'sendToBack()'}}, // Only when floating document is selected in freeform + { title: "Top", icon: "arrows-up-to-line", toolTip: "Make doc bottommost", btnType: ButtonType.ClickButton, expertMode: false, funcs: {}, scripts: { onClick: 'bringToFront()'}}, // Only when floating document is selected in freeform + { 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[] { @@ -670,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_);}`} }, @@ -682,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_); }'} }, ]; } @@ -694,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, @@ -702,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 @@ -736,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; @@ -745,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; @@ -762,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") { @@ -842,7 +872,6 @@ export class CurrentUserUtils { doc.isSystem ?? (doc.isSystem = true); doc.title ?? (doc.title = Doc.CurrentUserEmail); Doc.noviceMode ?? (Doc.noviceMode = true); - doc._raiseWhenDragged ?? (doc._raiseWhenDragged = true); doc._showLabel ?? (doc._showLabel = true); doc.textAlign ?? (doc.textAlign = "left"); doc.activeTool = InkTool.None; @@ -862,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); @@ -876,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 @@ -887,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); @@ -995,10 +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 createNewPresentation() { return MainView.Instance.createNewPresentation(); }, "creates a new presentation when called"); -ScriptingGlobals.add(function openPresentation(pres:Doc) { return MainView.Instance.openPresentation(pres); }, "creates a new presentation when called"); -ScriptingGlobals.add(function createNewFolder() { return MainView.Instance.createNewFolder(); }, "creates a new folder in myFiles when called"); 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/DictationManager.ts b/src/client/util/DictationManager.ts index 717473aa1..0fd7e840c 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -11,7 +11,7 @@ import { Utils } from '../../Utils'; import { Docs } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; import { DictationOverlay } from '../views/DictationOverlay'; -import { DocumentView, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView'; +import { DocumentView, OpenWhere } from '../views/nodes/DocumentView'; import { SelectionManager } from './SelectionManager'; import { UndoManager } from './UndoManager'; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 7c3b5be05..b9f6059f4 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,9 +1,9 @@ -import { action, computed, observable, ObservableSet } 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'; @@ -23,7 +22,6 @@ export class DocumentManager { //global holds all of the nodes (regardless of which collection they're in) @observable _documentViews = new Set<DocumentView>(); @observable public LinkAnchorBoxViews: DocumentView[] = []; - @observable public RecordingEvent = 0; @observable public LinkedDocumentViews: { a: DocumentView; b: DocumentView; l: Doc }[] = []; @computed public get DocumentViews() { return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.IsLightboxDocView(view.docViewPath))); @@ -41,7 +39,22 @@ export class DocumentManager { } //private constructor so no other class can create a nodemanager - private constructor() {} + private constructor() { + if (!Doc.CurrentlyLoading) Doc.CurrentlyLoading = []; + observe(Doc.CurrentlyLoading, change => { + // watch CurrentlyLoading-- when something is loaded, it's removed from the list and we have to update its icon if it were iconified since LoadingBox icons are different than the media they become + switch (change.type as any) { + case 'update': + break; + case 'remove': + // DocumentManager.Instance.getAllDocumentViews(change as any).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())); + break; + case 'splice': + (change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.rootDoc.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify()))); + break; + } + }); + } private _viewRenderedCbs: { doc: Doc; func: (dv: DocumentView) => any }[] = []; public AddViewRenderedCb = (doc: Opt<Doc>, func: (dv: DocumentView) => any) => { @@ -87,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); } @@ -308,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(docView.rootDoc, undefined, options.effect); + 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 489c9df4a..f86f9a3e5 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -4,18 +4,17 @@ import { Doc, Field, Opt, StrListCast } from '../../fields/Doc'; import { List } from '../../fields/List'; import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; -import { BoolCast, ScriptCast, StrCast } from '../../fields/Types'; +import { 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 @@ -61,12 +60,6 @@ export namespace DragManager { export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => void>; export let CompleteWindowDrag: Opt<(aborted: boolean) => void>; - export function GetRaiseWhenDragged() { - return BoolCast(Doc.UserDoc()._raiseWhenDragged); - } - export function SetRaiseWhenDragged(val: boolean) { - Doc.UserDoc()._raiseWhenDragged = val; - } export function Root() { const root = document.getElementById('root'); if (!root) { @@ -331,7 +324,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); @@ -345,6 +338,7 @@ export namespace DragManager { dragLabel.style.zIndex = '100001'; dragLabel.style.fontSize = '10px'; dragLabel.style.position = 'absolute'; + dragLabel.style.background = '#ffffff90'; dragLabel.innerText = 'drag titlebar to embed on drop'; // bcz: need to move this to a status bar dragDiv.appendChild(dragLabel); DragManager.Root().appendChild(dragDiv); @@ -460,7 +454,7 @@ export namespace DragManager { runInAction(() => docsBeingDragged.push(...docsToDrag)); const hideDragShowOriginalElements = (hide: boolean) => { - dragLabel.style.display = hide ? '' : 'none'; + dragLabel.style.display = hide && !CanEmbed ? '' : 'none'; !hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); setTimeout(() => eles.forEach(ele => (ele.hidden = hide))); }; @@ -605,18 +599,9 @@ export namespace DragManager { } } -ScriptingGlobals.add(function toggleRaiseOnDrag(forAllDocs: boolean, readOnly?: boolean) { +ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) { if (readOnly) { - if (SelectionManager.Views().length) - return SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged) - ? Colors.MEDIUM_BLUE - : SelectionManager.Views().some(dv => dv.rootDoc.raiseWhenDragged === false) - ? 'transparent' - : DragManager.GetRaiseWhenDragged() - ? Colors.MEDIUM_BLUE_ALT - : Colors.LIGHT_BLUE; - return DragManager.GetRaiseWhenDragged() ? Colors.MEDIUM_BLUE_ALT : 'transparent'; + return SelectionManager.Views().some(dv => dv.rootDoc.keepZWhenDragged); } - if (!forAllDocs) SelectionManager.Views().map(dv => (dv.rootDoc.raiseWhenDragged ? (dv.rootDoc.raiseWhenDragged = undefined) : dv.rootDoc.raiseWhenDragged === false ? (dv.rootDoc.raiseWhenDragged = true) : (dv.rootDoc.raiseWhenDragged = false))); - else DragManager.SetRaiseWhenDragged(!DragManager.GetRaiseWhenDragged()); + SelectionManager.Views().map(dv => (dv.rootDoc.keepZWhenDragged = !dv.rootDoc.keepZWhenDragged)); }); diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts index f235be192..dbdf580cd 100644 --- a/src/client/util/DropConverter.ts +++ b/src/client/util/DropConverter.ts @@ -1,15 +1,15 @@ -import { DragManager } from './DragManager'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { DocumentType } from '../documents/DocumentTypes'; import { ObjectField } from '../../fields/ObjectField'; -import { StrCast, Cast } from '../../fields/Types'; -import { Docs } from '../documents/Documents'; -import { ScriptField, ComputedField } from '../../fields/ScriptField'; import { RichTextField } from '../../fields/RichTextField'; -import { ImageField } from '../../fields/URLField'; -import { ScriptingGlobals } from './ScriptingGlobals'; import { listSpec } from '../../fields/Schema'; +import { ScriptField } from '../../fields/ScriptField'; +import { Cast, StrCast } from '../../fields/Types'; +import { ImageField } from '../../fields/URLField'; +import { Docs } from '../documents/Documents'; +import { DocumentType } from '../documents/DocumentTypes'; import { ButtonType } from '../views/nodes/FontIconBox/FontIconBox'; +import { DragManager } from './DragManager'; +import { ScriptingGlobals } from './ScriptingGlobals'; export function MakeTemplate(doc: Doc, first: boolean = true, rename: Opt<string> = undefined, templateField: string = '') { if (templateField) Doc.GetProto(doc).title = templateField; /// the title determines which field is being templated diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index f35844020..8973306bf 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,22 +1,22 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Button, IconButton, Size, Type } from 'browndash-components'; import { action, computed, observable } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import Select from 'react-select'; import * as RequestPromise from 'request-promise'; -import { Doc, DocListCast, DocListCastAsync, Opt } from '../../fields/Doc'; -import { StrCast, Cast } from '../../fields/Types'; +import { DateField } from '../../fields/DateField'; +import { Doc, DocListCast, Opt } from '../../fields/Doc'; +import { Id } from '../../fields/FieldSymbols'; +import { listSpec } from '../../fields/Schema'; +import { Cast, StrCast } from '../../fields/Types'; import { Utils } from '../../Utils'; import { MainViewModal } from '../views/MainViewModal'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; import './GroupManager.scss'; import { GroupMemberView } from './GroupMemberView'; -import { SharingManager, User } from './SharingManager'; -import { listSpec } from '../../fields/Schema'; -import { DateField } from '../../fields/DateField'; -import { Id } from '../../fields/FieldSymbols'; -import { Button, IconButton, Size, Type } from 'browndash-components'; import { SettingsManager } from './SettingsManager'; +import { SharingManager, User } from './SharingManager'; /** * Interface for options for the react-select component @@ -282,7 +282,7 @@ export class GroupManager extends React.Component<{}> { */ private get groupCreationModal() { const contents = ( - <div className="group-create" style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}> + <div className="group-create" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> <div className="group-heading" style={{ marginBottom: 0 }}> <p> <b>New Group</b> @@ -367,7 +367,7 @@ export class GroupManager extends React.Component<{}> { const groups = this.groupSort === 'ascending' ? this.allGroups.sort(sortGroups) : this.groupSort === 'descending' ? this.allGroups.sort(sortGroups).reverse() : this.allGroups; return ( - <div className="group-interface" style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}> + <div className="group-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> {this.groupCreationModal} {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null} <div className="group-heading"> diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index 535d8ccc2..7de0f336f 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -29,7 +29,7 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> { const hasEditAccess = GroupManager.Instance.hasEditAccess(this.props.group); return !this.props.group ? null : ( - <div className="editing-interface" style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}> + <div className="editing-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> <div className="editing-header"> <input className="group-title" 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 d154c48a4..560d6b30f 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -1,13 +1,115 @@ import * as rp from 'request-promise'; import { DocServer } from '../DocServer'; -import { Doc } from '../../fields/Doc'; +import { Doc, DocListCast, Field, Opt } from '../../fields/Doc'; import { Id } from '../../fields/FieldSymbols'; import { Utils } from '../../Utils'; import { DocumentType } from '../documents/DocumentTypes'; +import { StrCast } from '../../fields/Types'; export namespace SearchUtil { export type HighlightingResult = { [id: string]: { [key: string]: string[] } }; + export function SearchCollection(rootDoc: Opt<Doc>, query: string) { + const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; + const blockedKeys = [ + 'x', + 'y', + 'proto', + 'width', + 'layout_autoHeight', + 'acl-Override', + 'acl-Guest', + 'embedContainer', + 'zIndex', + 'height', + 'text_scrollHeight', + 'text_height', + 'cloneFieldFilter', + 'isDataDoc', + 'text_annotations', + 'dragFactory_count', + 'text_noTemplate', + 'proto_embeddings', + 'isSystem', + 'layout_fieldKey', + 'isBaseProto', + 'xMargin', + 'yMargin', + 'links', + 'layout', + 'layout_keyValue', + 'layout_fitWidth', + 'type_collection', + 'title_custom', + 'freeform_panX', + 'freeform_panY', + 'freeform_scale', + ]; + query = query.toLowerCase(); + + const results = new Map<Doc, string[]>(); + if (rootDoc) { + const docs = DocListCast(rootDoc[Doc.LayoutFieldKey(rootDoc)]); + const docIDs: String[] = []; + SearchUtil.foreachRecursiveDoc(docs, (depth: number, doc: Doc) => { + const dtype = StrCast(doc.type) as DocumentType; + if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) { + const hlights = new Set<string>(); + SearchUtil.documentKeys(doc).forEach( + key => + (Field.toString(doc[key] as Field) + Field.toScriptString(doc[key] as Field)) + .toLowerCase() // + .includes(query) && hlights.add(key) + ); + blockedKeys.forEach(key => hlights.delete(key)); + + if (Array.from(hlights.keys()).length > 0) { + results.set(doc, Array.from(hlights.keys())); + } + } + docIDs.push(doc[Id]); + }); + } + return results; + } + /** + * @param {Doc} doc - doc for which keys are returned + * + * This method returns a list of a document doc's keys. + */ + export function documentKeys(doc: Doc) { + const keys: { [key: string]: boolean } = {}; + Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false))); + return Array.from(Object.keys(keys)); + } + + /** + * @param {Doc[]} docs - docs to be searched through recursively + * @param {number, Doc => void} func - function to be called on each doc + * + * This method iterates through an array of docs and all docs within those docs, calling + * the function func on each doc. + */ + export function foreachRecursiveDoc(docs: Doc[], func: (depth: number, doc: Doc) => void) { + let newarray: Doc[] = []; + var depth = 0; + const visited: Doc[] = []; + while (docs.length > 0) { + newarray = []; + docs.filter(d => d && !visited.includes(d)).forEach(d => { + visited.push(d); + const fieldKey = Doc.LayoutFieldKey(d); + 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; + depth++; + } + } export interface IdSearchResult { ids: string[]; lines: string[][]; diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 4be9448b3..d0f66d124 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,11 +1,14 @@ import { action, observable, ObservableMap } from 'mobx'; import { computedFn } from 'mobx-utils'; import { Doc, Opt } from '../../fields/Doc'; -import { DocCast } from '../../fields/Types'; -import { CollectionViewType } from '../documents/DocumentTypes'; +import { List } from '../../fields/List'; +import { listSpec } from '../../fields/Schema'; +import { Cast, DocCast } from '../../fields/Types'; +import { CollectionViewType, DocumentType } from '../documents/DocumentTypes'; import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; import { ScriptingGlobals } from './ScriptingGlobals'; +import { UndoManager } from './UndoManager'; export namespace SelectionManager { class Manager { @@ -23,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(); } @@ -51,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(); @@ -124,3 +126,27 @@ ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, exp ScriptingGlobals.add(function deselectAll() { SelectionManager.DeselectAll(); }); +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(); +}); +ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) { + const docs = SelectionManager.Views() + .map(dv => dv.props.Document) + .filter(d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))); + return docs.length ? new List(docs) : prevValue; +}); 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 8133e9eff..dc852596f 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -13,21 +13,16 @@ import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager import { DocServer } from '../DocServer'; import { Networking } from '../Network'; import { MainViewModal } from '../views/MainViewModal'; -import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox'; import { GroupManager } from './GroupManager'; import './SettingsManager.scss'; import { undoBatch } from './UndoManager'; -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; export enum ColorScheme { Dark = 'Dark', Light = 'Light', - CoolBlue = 'Cool Blue', - Cupcake = 'Cupcake', - System = 'Match System', Custom = 'Custom', + CoolBlue = 'CoolBlue', + Cupcake = 'Cupcake', } export enum freeformScrollMode { @@ -51,17 +46,24 @@ export class SettingsManager extends React.Component<{}> { @observable public static propertiesWidth: number = 0; @observable public static headerBarHeight: number = 0; - @computed get backgroundColor() { - return Doc.UserDoc().activeCollectionBackground; - } - @computed get userTheme() { - return Doc.UserDoc().userTheme; - } - 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)); @@ -77,15 +79,15 @@ export class SettingsManager extends React.Component<{}> { } }; - @computed get userColor() { + @computed public static get userColor() { return StrCast(Doc.UserDoc().userColor); } - @computed get userVariantColor() { + @computed public static get userVariantColor() { return StrCast(Doc.UserDoc().userVariantColor); } - @computed get userBackgroundColor() { + @computed public static get userBackgroundColor() { return StrCast(Doc.UserDoc().userBackgroundColor); } @@ -97,11 +99,11 @@ export class SettingsManager extends React.Component<{}> { Doc.UserDoc().userBackgroundColor = color; addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` }); }); - @undoBatch switchUserColor = action((color: string) => { - Doc.UserDoc().userColor = color; - }); - @undoBatch switchUserVariantColor = action((color: string) => { - Doc.UserDoc().userVariantColor = color; + @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; @@ -138,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%' }}> @@ -157,42 +152,48 @@ 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={this.userColor} + 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 tooltip={'User Color'} // - color={this.userColor} + color={SettingsManager.userColor} type={Type.SEC} icon={<FaFillDrip />} - selectedColor={this.userColor} + selectedColor={SettingsManager.userColor} setSelectedColor={this.switchUserColor} setFinalColor={this.switchUserColor} /> <ColorPicker tooltip={'User Background Color'} - color={this.userColor} + color={SettingsManager.userColor} type={Type.SEC} icon={<FaPalette />} - selectedColor={this.userBackgroundColor} + selectedColor={SettingsManager.userBackgroundColor} setSelectedColor={this.switchUserBackgroundColor} setFinalColor={this.switchUserBackgroundColor} /> <ColorPicker tooltip={'User Variant Color'} - color={this.userColor} + color={SettingsManager.userColor} type={Type.SEC} icon={<FaPalette />} - selectedColor={this.userVariantColor} + selectedColor={SettingsManager.userVariantColor} setSelectedColor={this.switchUserVariantColor} setFinalColor={this.switchUserVariantColor} /> @@ -212,7 +213,7 @@ export class SettingsManager extends React.Component<{}> { onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')} toggleStatus={Doc.UserDoc().layout_showTitle !== undefined} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> <Toggle formLabel={'Show Full Toolbar'} @@ -221,25 +222,25 @@ export class SettingsManager extends React.Component<{}> { onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])} toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> <Toggle formLabel={'Show Button Labels'} formLabelPlacement={'right'} toggleType={ToggleType.SWITCH} - onClick={e => FontIconBox.SetShowLabels(!FontIconBox.GetShowLabels())} - toggleStatus={FontIconBox.GetShowLabels()} + onClick={e => Doc.SetShowIconLabels(!Doc.GetShowIconLabels())} + toggleStatus={Doc.GetShowIconLabels()} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> <Toggle formLabel={'Recognize Ink Gestures'} formLabelPlacement={'right'} toggleType={ToggleType.SWITCH} - onClick={e => FontIconBox.SetRecognizeGestures(!FontIconBox.GetRecognizeGestures())} - toggleStatus={FontIconBox.GetRecognizeGestures()} + onClick={e => Doc.SetRecognizeGestures(!Doc.GetRecognizeGestures())} + toggleStatus={Doc.GetRecognizeGestures()} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> <Toggle formLabel={'Hide Labels In Ink Shapes'} @@ -248,7 +249,7 @@ export class SettingsManager extends React.Component<{}> { onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)} toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> <Toggle formLabel={'Open Ink Docs in Lightbox'} @@ -257,7 +258,7 @@ export class SettingsManager extends React.Component<{}> { onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)} toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> <Toggle formLabel={'Show Link Lines'} @@ -266,7 +267,7 @@ export class SettingsManager extends React.Component<{}> { onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)} toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)} size={Size.XSMALL} - color={this.userColor} + color={SettingsManager.userColor} /> </div> ); @@ -298,7 +299,7 @@ export class SettingsManager extends React.Component<{}> { <div className="tab-column-content"> {/* <NumberInput/> */} <Group formLabel={'Default Font'}> - <NumberDropdown color={this.userColor} numberDropdownType={'input'} min={0} max={50} step={2} type={Type.TERT} number={0} unit={'px'} setNumber={() => {}} /> + <NumberDropdown color={SettingsManager.userColor} numberDropdownType={'input'} min={0} max={50} step={2} type={Type.TERT} number={0} unit={'px'} setNumber={() => {}} /> <Dropdown items={fontFamilies.map(val => { return { @@ -309,13 +310,14 @@ export class SettingsManager extends React.Component<{}> { }, }; })} + closeOnSelect={true} dropdownType={DropdownType.SELECT} type={Type.TERT} selectedVal={StrCast(Doc.UserDoc().fontFamily)} setSelectedVal={val => { this.changeFontFamily(val as string); }} - color={this.userColor} + color={SettingsManager.userColor} fillWidth /> </Group> @@ -343,12 +345,12 @@ export class SettingsManager extends React.Component<{}> { @computed get passwordContent() { return ( <div className="password-content"> - <EditableText placeholder="Current password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password /> - <EditableText placeholder="New password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password /> - <EditableText placeholder="Confirm new password" type={Type.SEC} color={this.userColor} val={''} setVal={val => this.changeVal(val as string, 'conf')} fillWidth password /> + <EditableText placeholder="Current password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'curr')} fillWidth password /> + <EditableText placeholder="New password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'new')} fillWidth password /> + <EditableText placeholder="Confirm new password" type={Type.SEC} color={SettingsManager.userColor} val={''} setVal={val => this.changeVal(val as string, 'conf')} fillWidth password /> {!this.passwordResultText ? null : <div className={`${this.passwordResultText.startsWith('Error') ? 'error' : 'success'}-text`}>{this.passwordResultText}</div>} - <Button type={Type.SEC} text={'Forgot Password'} color={this.userColor} /> - <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={this.userColor} /> + <Button type={Type.SEC} text={'Forgot Password'} color={SettingsManager.userColor} /> + <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={SettingsManager.userColor} /> </div> ); } @@ -388,6 +390,7 @@ export class SettingsManager extends React.Component<{}> { <div className="tab-column-content"> <Dropdown formLabel={'Mode'} + closeOnSelect={true} items={[ { text: 'Novice', @@ -408,17 +411,18 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement="bottom-start" - color={this.userColor} + color={SettingsManager.userColor} fillWidth /> - <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={this.userColor} /> + <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.userColor} /> </div> <div className="tab-column-title" style={{ marginTop: 20, marginBottom: 10 }}> Freeform Navigation </div> <div className="tab-column-content"> <Dropdown - formLabel={'Scroll Mode'} + formLabel="Scroll Mode" + closeOnSelect={true} items={[ { text: 'Scroll to Pan', @@ -436,15 +440,22 @@ export class SettingsManager extends React.Component<{}> { dropdownType={DropdownType.SELECT} type={Type.TERT} placement="bottom-start" - color={this.userColor} + color={SettingsManager.userColor} /> </div> </div> <div className="tab-column"> <div className="tab-column-title">Permissions</div> <div className="tab-column-content"> - <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={this.userColor} /> - <Toggle toggleType={ToggleType.SWITCH} formLabel={'Default access private'} color={this.userColor} toggleStatus={BoolCast(Doc.defaultAclPrivate)} onClick={action(() => (Doc.defaultAclPrivate = !Doc.defaultAclPrivate))} /> + <Button text={'Manage Groups'} type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} /> + <Toggle + toggleType={ToggleType.SWITCH} + formLabel={'Default access private'} + color={SettingsManager.userColor} + 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> @@ -463,7 +474,7 @@ export class SettingsManager extends React.Component<{}> { ]; return ( <div className="settings-interface"> - <div className="settings-panel" style={{ background: this.userColor }}> + <div className="settings-panel" style={{ background: SettingsManager.userColor }}> <div className="settings-tabs"> {tabs.map(tab => { const isActive = this.activeTab === tab.title; @@ -471,8 +482,8 @@ export class SettingsManager extends React.Component<{}> { <div key={tab.title} style={{ - background: isActive ? this.userBackgroundColor : this.userColor, - color: isActive ? this.userColor : this.userBackgroundColor, + background: isActive ? SettingsManager.userBackgroundColor : SettingsManager.userColor, + color: isActive ? SettingsManager.userColor : SettingsManager.userBackgroundColor, }} className={'tab-control ' + (isActive ? 'active' : 'inactive')} onClick={action(() => (this.activeTab = tab.title))}> @@ -483,19 +494,19 @@ export class SettingsManager extends React.Component<{}> { </div> <div className="settings-user"> - <div style={{ color: this.userBackgroundColor }}>{DashVersion}</div> - <div className="settings-username" style={{ color: this.userBackgroundColor }}> + <div style={{ color: SettingsManager.userBackgroundColor }}>{DashVersion}</div> + <div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}> {Doc.CurrentUserEmail} </div> - <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={this.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> + <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.userVariantColor} onClick={() => window.location.assign(Utils.prepend('/logout'))} /> </div> </div> <div className="close-button"> - <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={this.userColor} /> + <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} /> </div> - <div className="settings-content" style={{ color: this.userColor, background: this.userBackgroundColor }}> + <div className="settings-content" style={{ color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}> {tabs.map(tab => ( <div key={tab.title} className={'tab-section ' + (this.activeTab === tab.title ? 'active' : 'inactive')}> {tab.ele} diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 6171c01d7..8d59426ec 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -18,11 +18,10 @@ import { DictationOverlay } from '../views/DictationOverlay'; import { MainViewModal } from '../views/MainViewModal'; import { DocumentView } from '../views/nodes/DocumentView'; import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox'; -import { SearchBox } from '../views/search/SearchBox'; import { DocumentManager } from './DocumentManager'; import { GroupManager, UserOptions } from './GroupManager'; import { GroupMemberView } from './GroupMemberView'; -import { LinkManager } from './LinkManager'; +import { SearchUtil } from './SearchUtil'; import { SelectionManager } from './SelectionManager'; import { SettingsManager } from './SettingsManager'; import './SharingManager.scss'; @@ -379,15 +378,17 @@ export class SharingManager extends React.Component<{}> { } }); - const { left, width, top, height } = this.shareDocumentButtonRef.current!.getBoundingClientRect(); - TaskCompletionBox.popupX = left - 1.5 * width; - TaskCompletionBox.popupY = top - 1.5 * height; - TaskCompletionBox.textDisplayed = 'Document shared!'; - TaskCompletionBox.taskCompleted = true; - setTimeout( - action(() => (TaskCompletionBox.taskCompleted = false)), - 2000 - ); + if (this.shareDocumentButtonRef.current) { + const { left, width, top, height } = this.shareDocumentButtonRef.current.getBoundingClientRect(); + TaskCompletionBox.popupX = left - 1.5 * width; + TaskCompletionBox.popupY = top - 1.5 * height; + TaskCompletionBox.textDisplayed = 'Document shared!'; + TaskCompletionBox.taskCompleted = true; + setTimeout( + action(() => (TaskCompletionBox.taskCompleted = false)), + 2000 + ); + } this.layoutDocAcls = false; this.selectedUsers = null; @@ -447,7 +448,7 @@ export class SharingManager extends React.Component<{}> { if (this.myDocAcls) { const newDocs: Doc[] = []; - SearchBox.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc)); + SearchUtil.foreachRecursiveDoc(docs, (depth, doc) => newDocs.push(doc)); docs = newDocs.filter(doc => GetEffectiveAcl(doc) === AclAdmin); } @@ -528,10 +529,10 @@ export class SharingManager extends React.Component<{}> { const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-'; return !permissions ? null : ( - <div key={groupKey} className={'container'} style={{ background: SettingsManager.Instance.userBackgroundColor, color: SettingsManager.Instance.userColor }}> + <div key={groupKey} className={'container'} style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}> <div className={'padding'}>{StrCast(group.title)}</div> - {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.Instance.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null} + {group instanceof Doc ? <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={SettingsManager.userColor} onClick={action(() => (GroupManager.Instance.currentGroup = group))} /> : null} <div className={'edit-actions'}> {admin || this.myDocAcls ? ( <select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)}> @@ -553,10 +554,10 @@ export class SharingManager extends React.Component<{}> { <div className="sharing-contents" style={{ - background: SettingsManager.Instance.userBackgroundColor, + background: SettingsManager.userBackgroundColor, color: StrCast(Doc.UserDoc().userColor), }}> - <p className="share-title" style={{ color: StrCast(Doc.UserDoc().userColor) }}> + <p className="share-title" style={{ color: SettingsManager.userColor }}> <div className="share-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')}> <FontAwesomeIcon icon={'question-circle'} size={'sm'} onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} /> </div> @@ -564,10 +565,10 @@ export class SharingManager extends React.Component<{}> { {this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')} </p> <div className="share-copy-link"> - <Button type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} /> + <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} /> </div> <div className="close-button"> - <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} /> + <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} /> </div> {admin ? ( <div className="share-container"> @@ -609,7 +610,7 @@ export class SharingManager extends React.Component<{}> { </select> </div> <div className="share-button"> - <Button text={'SHARE'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.share} /> + <Button text={'SHARE'} type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} /> </div> </div> <div className="sort-checkboxes"> diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx index 7aad0f2b1..b25d51b41 100644 --- a/src/client/util/reportManager/ReportManager.tsx +++ b/src/client/util/reportManager/ReportManager.tsx @@ -20,9 +20,6 @@ import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './Re import { StrCast } from '../../../fields/Types'; import { MdRefresh } from 'react-icons/md'; import { SettingsManager } from '../SettingsManager'; -const higflyout = require('@hig/flyout'); -export const { anchorPoints } = higflyout; -export const Flyout = higflyout.default; /** * Class for reporting and viewing Github issues within the app. @@ -214,11 +211,11 @@ export class ReportManager extends React.Component<{}> { * @returns the component that dispays all issues */ private viewIssuesComponent = () => { - const darkMode = isDarkMode(SettingsManager.Instance.userBackgroundColor); + const darkMode = isDarkMode(SettingsManager.userBackgroundColor); const colors = darkMode ? darkColors : lightColors; return ( - <div className="view-issues" style={{ backgroundColor: SettingsManager.Instance.userBackgroundColor, color: colors.text }}> + <div className="view-issues" style={{ backgroundColor: SettingsManager.userBackgroundColor, color: colors.text }}> <div className="left" style={{ display: this.rightExpanded ? 'none' : 'flex' }}> <div className="report-header"> <h2 style={{ color: colors.text }}>Open Issues</h2> @@ -311,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 => { @@ -323,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 => { @@ -333,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; }; |
