aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/BranchingTrailManager.tsx137
-rw-r--r--src/client/util/CurrentUserUtils.ts111
-rw-r--r--src/client/util/DictationManager.ts2
-rw-r--r--src/client/util/DocumentManager.ts32
-rw-r--r--src/client/util/DragManager.ts31
-rw-r--r--src/client/util/DropConverter.ts14
-rw-r--r--src/client/util/GroupManager.tsx18
-rw-r--r--src/client/util/GroupMemberView.tsx2
-rw-r--r--src/client/util/InteractionUtils.tsx5
-rw-r--r--src/client/util/LinkFollower.ts24
-rw-r--r--src/client/util/LinkManager.ts11
-rw-r--r--src/client/util/RTFMarkup.tsx4
-rw-r--r--src/client/util/SearchUtil.ts104
-rw-r--r--src/client/util/SelectionManager.ts36
-rw-r--r--src/client/util/ServerStats.tsx6
-rw-r--r--src/client/util/SettingsManager.tsx155
-rw-r--r--src/client/util/SharingManager.tsx39
-rw-r--r--src/client/util/reportManager/ReportManager.tsx37
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts7
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>
&nbsp;
- {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;
};