aboutsummaryrefslogtreecommitdiff
path: root/src/client/util
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/util')
-rw-r--r--src/client/util/BranchingTrailManager.tsx46
-rw-r--r--src/client/util/CalendarManager.tsx58
-rw-r--r--src/client/util/CaptureManager.tsx28
-rw-r--r--src/client/util/CurrentUserUtils.ts328
-rw-r--r--src/client/util/DictationManager.ts123
-rw-r--r--src/client/util/DocumentManager.ts221
-rw-r--r--src/client/util/DragManager.ts205
-rw-r--r--src/client/util/DropActionTypes.ts9
-rw-r--r--src/client/util/DropConverter.ts77
-rw-r--r--src/client/util/GroupManager.tsx113
-rw-r--r--src/client/util/GroupMemberView.tsx36
-rw-r--r--src/client/util/History.ts38
-rw-r--r--src/client/util/HypothesisUtils.ts37
-rw-r--r--src/client/util/Import & Export/DirectoryImportBox.tsx1
-rw-r--r--src/client/util/Import & Export/ImageUtils.ts12
-rw-r--r--src/client/util/Import & Export/ImportMetadataEntry.tsx40
-rw-r--r--src/client/util/InteractionUtils.tsx317
-rw-r--r--src/client/util/KeyCodes.ts2
-rw-r--r--src/client/util/LinkFollower.ts46
-rw-r--r--src/client/util/LinkManager.ts90
-rw-r--r--src/client/util/PingManager.ts8
-rw-r--r--src/client/util/RTFMarkup.tsx25
-rw-r--r--src/client/util/ReplayMovements.ts30
-rw-r--r--src/client/util/ScriptManager.ts10
-rw-r--r--src/client/util/Scripting.ts63
-rw-r--r--src/client/util/ScriptingGlobals.ts48
-rw-r--r--src/client/util/SearchUtil.ts26
-rw-r--r--src/client/util/SelectionManager.ts45
-rw-r--r--src/client/util/SerializationHelper.ts33
-rw-r--r--src/client/util/ServerStats.tsx47
-rw-r--r--src/client/util/SettingsManager.tsx420
-rw-r--r--src/client/util/SharingManager.tsx751
-rw-r--r--src/client/util/SnappingManager.ts51
-rw-r--r--src/client/util/TrackMovements.ts24
-rw-r--r--src/client/util/Transform.ts14
-rw-r--r--src/client/util/TypedEvent.ts18
-rw-r--r--src/client/util/UndoManager.ts99
-rw-r--r--src/client/util/bezierFit.ts401
-rw-r--r--src/client/util/reportManager/ReportManager.tsx67
-rw-r--r--src/client/util/reportManager/ReportManagerComponents.tsx131
-rw-r--r--src/client/util/reportManager/reportManagerSchema.ts1
-rw-r--r--src/client/util/reportManager/reportManagerUtils.ts17
-rw-r--r--src/client/util/request-image-size.ts26
43 files changed, 2202 insertions, 1980 deletions
diff --git a/src/client/util/BranchingTrailManager.tsx b/src/client/util/BranchingTrailManager.tsx
index 02879e3c4..119d103c5 100644
--- a/src/client/util/BranchingTrailManager.tsx
+++ b/src/client/util/BranchingTrailManager.tsx
@@ -1,18 +1,31 @@
+/* eslint-disable react/no-unused-class-component-methods */
+/* eslint-disable react/no-array-index-key */
import { action, computed, makeObservable, 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';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { PresBox } from '../views/nodes/trails';
@observer
export class BranchingTrailManager extends React.Component {
+ // eslint-disable-next-line no-use-before-define
public static Instance: BranchingTrailManager;
+ // stack of the history
+ @observable private slideHistoryStack: String[] = [];
+ @observable private containsSet: Set<String> = new Set<String>();
+ // docId to Doc map
+ @observable private docIdToDocMap: Map<String, Doc> = new Map<String, Doc>();
+
+ // prev pres to copmare with
+ @observable private prevPresId: String | null = null;
+ @action setPrevPres = action((newId: String | null) => {
+ this.prevPresId = newId;
+ });
+
constructor(props: any) {
super(props);
makeObservable(this);
@@ -22,7 +35,7 @@ export class BranchingTrailManager extends React.Component {
}
setupUi = () => {
- OverlayView.Instance.addWindow(<BranchingTrailManager></BranchingTrailManager>, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
+ OverlayView.Instance.addWindow(<BranchingTrailManager />, { x: 100, y: 150, width: 1000, title: 'Branching Trail' });
// OverlayView.Instance.forceUpdate();
console.log(OverlayView.Instance);
// let hi = Docs.Create.TextDocument("beee", {
@@ -33,26 +46,13 @@ export class BranchingTrailManager extends React.Component {
// 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>();
-
+ // eslint-disable-next-line react/sort-comp
observeDocumentChange = (targetDoc: Doc, pres: PresBox) => {
const presId = pres.Document[Id];
if (this.prevPresId === presId) {
@@ -102,11 +102,11 @@ export class BranchingTrailManager extends React.Component {
this.setSlideHistoryStack(newStack);
removed.forEach(info => this.containsSet.delete(info.toString()));
- DocumentManager.Instance.showDocument(targetDoc, { willZoomCentered: true });
+ DocumentView.showDocument(targetDoc, { willZoomCentered: true });
if (this.slideHistoryStack.length === 0) {
Doc.UserDoc().isBranchingMode = false;
}
- //PresBox.NavigateToTarget(targetDoc, targetDoc);
+ // PresBox.NavigateToTarget(targetDoc, targetDoc);
};
@computed get trailBreadcrumbs() {
@@ -116,11 +116,11 @@ export class BranchingTrailManager extends React.Component {
const [presId, targetDocId] = info.split(',');
const doc = this.docIdToDocMap.get(targetDocId);
if (!doc) {
- return <></>;
+ return null;
}
return (
<span key={targetDocId}>
- <button key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
+ <button type="button" key={index} onPointerDown={e => this.clickHandler(e, targetDocId, index)}>
{presId.slice(0, 3) + ':' + doc.title}
</button>
-{'>'}
diff --git a/src/client/util/CalendarManager.tsx b/src/client/util/CalendarManager.tsx
index 6e9094b3a..77cf80151 100644
--- a/src/client/util/CalendarManager.tsx
+++ b/src/client/util/CalendarManager.tsx
@@ -1,4 +1,10 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
+import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { TextField } from '@mui/material';
+import { Button } from 'browndash-components';
import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -6,21 +12,13 @@ import Select from 'react-select';
import { Doc, DocListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { StrCast } from '../../fields/Types';
-import { DictationOverlay } from '../views/DictationOverlay';
+import { Docs } from '../documents/Documents';
import { MainViewModal } from '../views/MainViewModal';
+import { ObservableReactComponent } from '../views/ObservableReactComponent';
import { DocumentView } from '../views/nodes/DocumentView';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
import './CalendarManager.scss';
-import { DocumentManager } from './DocumentManager';
-import { SelectionManager } from './SelectionManager';
-import { SettingsManager } from './SettingsManager';
-// import { DateRange, Range, RangeKeyDict } from 'react-date-range';
-import { DateRangePicker, Provider, defaultTheme } from '@adobe/react-spectrum';
-import { IconLookup, faPlus } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Button } from 'browndash-components';
-import { Docs } from '../documents/Documents';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
+import { SnappingManager } from './SnappingManager';
// import 'react-date-range/dist/styles.css';
// import 'react-date-range/dist/theme/default.css';
@@ -47,12 +45,12 @@ const formatCalendarDateToString = (calendarDate: any) => {
@observer
export class CalendarManager extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: CalendarManager;
@observable private isOpen = false;
@observable private targetDoc: Doc | undefined = undefined; // the target document
@observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the target doc
@observable private dialogueBoxOpacity = 1; // for the modal
- @observable private overlayOpacity = 0.4; // for the modal
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@@ -83,12 +81,10 @@ export class CalendarManager extends ObservableReactComponent<{}> {
this.creationType = type;
};
- public open = (target?: DocumentView, target_doc?: Doc) => {
- console.log('hi');
+ public open = (target?: DocumentView, targetDoc?: Doc) => {
runInAction(() => {
- this.targetDoc = target_doc || target?.Document;
+ this.targetDoc = targetDoc || target?.Document;
this.targetDocView = target;
- DictationOverlay.Instance.hasActiveModal = true;
this.isOpen = this.targetDoc !== undefined;
});
};
@@ -98,7 +94,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
TaskCompletionBox.taskCompleted = false;
setTimeout(
action(() => {
- DictationOverlay.Instance.hasActiveModal = false;
this.targetDoc = undefined;
}),
500
@@ -117,7 +112,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@action
handleSelectChange = (option: any) => {
if (option) {
- let selectOpt = option as CalendarSelectOptions;
+ const selectOpt = option as CalendarSelectOptions;
this.selectedExistingCalendarOption = selectOpt;
this.calendarName = selectOpt.value; // or label
}
@@ -136,7 +131,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
// TODO: Make undoable
private addToCalendar = () => {
- let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData]; // doc to add to calendar
console.log(targetDoc);
@@ -159,7 +154,7 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}
} else {
// find existing calendar based on selected name (should technically always find one)
- const existingCalendar = this.existingCalendars.find(calendar => StrCast(calendar.title) === this.calendarName);
+ const existingCalendar = this.existingCalendars.find(findCal => StrCast(findCal.title) === this.calendarName);
if (existingCalendar) calendar = existingCalendar;
else {
this.errorMessage = 'Must select an existing calendar';
@@ -189,28 +184,26 @@ export class CalendarManager extends ObservableReactComponent<{}> {
private focusOn = (contents: string) => {
const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
- const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.Document) : [this.targetDoc];
+ const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.Document) : [this.targetDoc];
return (
<span
className="focus-span"
title={title}
onClick={() => {
if (this.targetDoc && this.targetDocView && docs.length === 1) {
- DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
+ DocumentView.showDocument(this.targetDoc, { willZoomCentered: true });
}
}}
onPointerEnter={action(() => {
if (docs.length) {
docs.forEach(doc => doc && Doc.BrushDoc(doc));
this.dialogueBoxOpacity = 0.1;
- this.overlayOpacity = 0.1;
}
})}
onPointerLeave={action(() => {
if (docs.length) {
docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
this.dialogueBoxOpacity = 1;
- this.overlayOpacity = 0.4;
}
})}>
{contents}
@@ -252,26 +245,24 @@ export class CalendarManager extends ObservableReactComponent<{}> {
@computed
get calendarInterface() {
- let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ const docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
const targetDoc = this.layoutDocAcls ? docs[0] : docs[0]?.[DocData];
- const currentDate = new Date();
-
return (
<div
className="calendar-interface"
style={{
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
color: StrCast(Doc.UserDoc().userColor),
}}>
- <p className="selected-doc-title" style={{ color: SettingsManager.userColor }}>
+ <p className="selected-doc-title" style={{ color: SnappingManager.userColor }}>
<b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}</b>
</p>
<div className="creation-type-container">
- <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('new-calendar')}>
+ <div className={`calendar-creation ${this.creationType === 'new-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('new-calendar')}>
Add to New Calendar
</div>
- <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={e => this.setInterationType('existing-calendar')}>
+ <div className={`calendar-creation ${this.creationType === 'existing-calendar' ? 'calendar-creation-selected' : ''}`} onClick={() => this.setInterationType('existing-calendar')}>
Add to Existing calendar
</div>
</div>
@@ -317,7 +308,8 @@ export class CalendarManager extends ObservableReactComponent<{}> {
color: StrCast(Doc.UserDoc().userColor),
width: '100%',
}),
- }}></Select>
+ }}
+ />
)}
</div>
<div className="description-container">
@@ -351,6 +343,6 @@ export class CalendarManager extends ObservableReactComponent<{}> {
}
render() {
- return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.calendarInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/CaptureManager.tsx b/src/client/util/CaptureManager.tsx
index 8451357ef..4fd934774 100644
--- a/src/client/util/CaptureManager.tsx
+++ b/src/client/util/CaptureManager.tsx
@@ -1,30 +1,35 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
-import { addStyleSheet } from '../../Utils';
+import { addStyleSheet } from '../../ClientUtils';
import { Doc } from '../../fields/Doc';
import { DocCast, StrCast } from '../../fields/Types';
import { LightboxView } from '../views/LightboxView';
import { MainViewModal } from '../views/MainViewModal';
import './CaptureManager.scss';
-import { LinkManager } from './LinkManager';
-import { SelectionManager } from './SelectionManager';
+import { DocumentView } from '../views/nodes/DocumentView';
@observer
export class CaptureManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: CaptureManager;
static _settingsStyle = addStyleSheet();
@observable _document: any = undefined;
@observable isOpen: boolean = false; // whether the CaptureManager is to be displayed or not.
+ // eslint-disable-next-line react/sort-comp
constructor(props: {}) {
super(props);
makeObservable(this);
CaptureManager.Instance = this;
}
- public close = action(() => (this.isOpen = false));
+ public close = action(() => {
+ this.isOpen = false;
+ });
public open = action((doc: Doc) => {
this.isOpen = true;
this._document = doc;
@@ -50,7 +55,7 @@ export class CaptureManager extends React.Component<{}> {
const doc = this._document;
const order: JSX.Element[] = [];
if (doc) {
- LinkManager.Links(doc).forEach((l, i) =>
+ Doc.Links(doc).forEach((l, i) =>
order.push(
<div className="list-item">
<div className="number">{i}</div>
@@ -82,8 +87,8 @@ export class CaptureManager extends React.Component<{}> {
<div
className="cancel"
onClick={() => {
- const selected = SelectionManager.Views.slice();
- SelectionManager.DeselectAll();
+ const selected = DocumentView.Selected();
+ DocumentView.DeselectAll();
selected.map(dv => dv.props.removeDocument?.(dv.Document));
this.close();
}}>
@@ -99,15 +104,15 @@ export class CaptureManager extends React.Component<{}> {
<div className="capture-interface">
<div className="capture-t1">
<div className="recordButtonOutline" style={{}}>
- <div className="recordButtonInner" style={{}}></div>
+ <div className="recordButtonInner" style={{}} />
</div>
Conversation Capture
</div>
- <div className="capture-t2"></div>
+ <div className="capture-t2" />
{this.visibilityContent}
{this.linksContent}
<div className="close-button" onClick={this.close}>
- <FontAwesomeIcon icon={'times'} color="black" size={'lg'} />
+ <FontAwesomeIcon icon="times" color="black" size="lg" />
</div>
{this.closeButtons}
</div>
@@ -119,11 +124,10 @@ export class CaptureManager extends React.Component<{}> {
<MainViewModal
contents={this.captureInterface}
isDisplayed={this.isOpen}
- interactive={true}
+ interactive
closeOnExternalClick={this.close}
dialogueBoxStyle={{ width: '500px', height: '350px', border: 'none', background: 'whitesmoke' }}
overlayStyle={{ background: 'black' }}
- overlayDisplayedOpacity={0.6}
/>
);
}
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 081115879..ffc1304bf 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -1,6 +1,6 @@
-import { observable, reaction, runInAction } from "mobx";
+import { reaction, runInAction } from "mobx";
import * as rp from 'request-promise';
-import { OmitKeys, Utils } from "../../Utils";
+import { ClientUtils, OmitKeys } from "../../ClientUtils";
import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc";
import { DocData } from "../../fields/DocSymbols";
import { InkTool } from "../../fields/InkField";
@@ -12,28 +12,31 @@ import { ScriptField } from "../../fields/ScriptField";
import { Cast, DateCast, DocCast, StrCast } from "../../fields/Types";
import { WebField, nullAudio } from "../../fields/URLField";
import { SetCachedGroups, SharingPermissions } from "../../fields/util";
-import { GestureUtils } from "../../pen-gestures/GestureUtils";
+import { Gestures } from "../../pen-gestures/GestureTypes";
import { DocServer } from "../DocServer";
+import { DocUtils, FollowLinkScript } from '../documents/DocUtils';
import { CollectionViewType, DocumentType } from "../documents/DocumentTypes";
-import { DocUtils, Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
+import { Docs, DocumentOptions, FInfo, FInfoFieldType } from "../documents/Documents";
import { DashboardView } from "../views/DashboardView";
import { OverlayView } from "../views/OverlayView";
-import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView";
+import { CollectionTreeView } from "../views/collections/CollectionTreeView";
+import { TreeViewType } from "../views/collections/CollectionTreeViewType";
import { Colors } from "../views/global/globalEnums";
-import { media_state } from "../views/nodes/AudioBox";
-import { OpenWhere } from "../views/nodes/DocumentView";
+import { mediaState } from "../views/nodes/AudioBox";
import { ButtonType, FontIconBox } from "../views/nodes/FontIconBox/FontIconBox";
+import { ImageBox } from "../views/nodes/ImageBox";
+import { LabelBox } from "../views/nodes/LabelBox";
+import { OpenWhere } from "../views/nodes/OpenWhere";
import { ImportElementBox } from "../views/nodes/importBox/ImportElementBox";
-import { DragManager, dropActionType } from "./DragManager";
+import { DragManager } from "./DragManager";
+import { dropActionType } from "./DropActionTypes";
import { MakeTemplate } from "./DropConverter";
-import { FollowLinkScript } from "./LinkFollower";
-import { LinkManager } from "./LinkManager";
+import { UPDATE_SERVER_CACHE } from "./LinkManager";
import { ScriptingGlobals } from "./ScriptingGlobals";
+import { SelectionManager } from "./SelectionManager";
import { ColorScheme } from "./SettingsManager";
import { SnappingManager } from "./SnappingManager";
import { UndoManager } from "./UndoManager";
-import { LabelBox } from "../views/nodes/LabelBox";
-import { ImageBox } from "../views/nodes/ImageBox";
interface Button {
// DocumentOptions fields a button can set
@@ -60,12 +63,13 @@ interface Button {
subMenu?: Button[];
}
+// eslint-disable-next-line import/no-mutable-exports
export let resolvedPorts: { server: number, socket: number };
export class CurrentUserUtils {
// initializes experimental advanced template views - slideView, headerView
static setupUserDocumentCreatorButtons(doc: Doc, userDocTemplates: Opt<Doc>) {
- const userTemplates = DocListCast(userDocTemplates?.data).filter(doc => !Doc.IsSystem(doc));
+ const userTemplates = DocListCast(userDocTemplates?.data).filter(fdoc => !Doc.IsSystem(fdoc));
const reqdOpts:DocumentOptions = {
title: "User Tools", _xMargin: 0, _layout_showTitle: "title", _chromeHidden: true, hidden: false,
_dragOnlyWithinContainer: true, layout_hideContextMenu: true, isSystem: true, _forceActive: true,
@@ -85,11 +89,12 @@ export class CurrentUserUtils {
{ opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}];
const reqdClickList = reqdTempOpts.map(opts => {
const allOpts = {...reqdClickOpts, ...opts.opts};
- const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === opts.opts.title): undefined;
return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
});
const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, isSystem: true};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
}
@@ -107,13 +112,14 @@ export class CurrentUserUtils {
const reqdClickList = reqdTempOpts.map(opts => {
const title = opts.opts.title?.toString();
const allOpts = {...reqdClickOpts, ...opts.opts};
- const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === title): undefined;
+ const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(fdoc => fdoc.title === title): undefined;
const script = ScriptField.MakeScript(opts.script, {heading:Doc.name, checked:"boolean", containingTreeView:Doc.name});
const scriptDoc = Docs.Create.ScriptingDocument(script, allOpts, title)
return DocUtils.AssignOpts(clickDoc, allOpts) ?? MakeTemplate(scriptDoc);
});
const reqdOpts:DocumentOptions = {title: "click editor templates", _height:75, isSystem: true};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempClicks, reqdOpts, reqdClickList) ?? (doc[field] = Docs.Create.TreeDocument(reqdClickList, reqdOpts));
}
@@ -126,11 +132,12 @@ export class CurrentUserUtils {
{ title: "Topic", backgroundColor: "lightblue", icon: "book-open" , _layout_showTitle: "title"}];
const reqdNoteList = [...reqdTempOpts.map(opts => {
const reqdOpts = {...opts, isSystem:true, width:200, layout_autoHeight: true, layout_fitWidth: true};
- const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(doc => doc.title === opts.title): undefined;
+ const noteTemp = tempNotes ? DocListCast(tempNotes.data).find(fdoc => fdoc.title === opts.title): undefined;
return DocUtils.AssignOpts(noteTemp, reqdOpts) ?? MakeTemplate(Docs.Create.TextDocument("",reqdOpts));
}), ... DocListCast(tempNotes?.data).filter(note => !reqdTempOpts.find(reqd => reqd.title === note.title))];
const reqdOpts:DocumentOptions = { title: "Note Layouts", _height: 75, isSystem: true };
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempNotes, reqdOpts, reqdNoteList) ?? (doc[field] = Docs.Create.TreeDocument(reqdNoteList, reqdOpts));
}
static setupUserTemplates(doc: Doc, field="template_user") {
@@ -138,6 +145,7 @@ export class CurrentUserUtils {
const reqdUserList = DocListCast(tempUsers?.data);
const reqdOpts:DocumentOptions = { title: "User Layouts", _height: 75, isSystem: true };
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignOpts(tempUsers, reqdOpts, reqdUserList) ?? (doc[field] = Docs.Create.TreeDocument(reqdUserList, reqdOpts));
}
@@ -159,24 +167,26 @@ export class CurrentUserUtils {
const reqdOpts = { title: "icon templates", _height: 75, isSystem: true };
const templateIconsDoc = DocUtils.AssignOpts(DocCast(doc[field]), reqdOpts) ?? (doc[field] = Docs.Create.TreeDocument([], reqdOpts));
+ const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
+ layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
+ });
+ const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts });
+ const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
+
const makeIconTemplate = (type: DocumentType | undefined, templateField: string, opts:DocumentOptions) => {
const title = "icon" + (type ? "_" + type : "");
const curIcon = DocCast(templateIconsDoc[title]);
- let creator = labelBox;
- switch (opts.iconTemplate) {
- case DocumentType.IMG : creator = imageBox; break;
- case DocumentType.FONTICON: creator = fontBox; break;
- }
+ const creator = (() => { switch (opts.iconTemplate) {
+ case DocumentType.IMG : return imageBox;
+ case DocumentType.FONTICON: return fontBox;
+ default: return labelBox;
+ }})();
const allopts = {isSystem: true, onClickScriptDisable: "never", ...opts, title};
+ // eslint-disable-next-line no-return-assign
return DocUtils.AssignScripts( (curIcon?.iconTemplate === opts.iconTemplate ?
DocUtils.AssignOpts(curIcon, allopts):undefined) ?? ((templateIconsDoc[title] = MakeTemplate(creator(allopts, templateField)))),
{onClick:"deiconifyView(documentView)", onDoubleClick: "deiconifyViewToLightbox(documentView)", });
};
- const labelBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.LabelDocument({
- layout: LabelBox.LayoutString(fieldKey), textTransform: "unset", letterSpacing: "unset", _singleLine: false, _label_minFontSize: 14, _label_maxFontSize: 14, layout_borderRounding: "5px", _width: 150, _height: 70, _xPadding: 10, _yPadding: 10, ...opts
- });
- const imageBox = (opts: DocumentOptions, fieldKey:string) => Docs.Create.ImageDocument( "http://www.cs.brown.edu/~bcz/noImage.png", { layout:ImageBox.LayoutString(fieldKey), "icon_nativeWidth": 360 / 4, "icon_nativeHeight": 270 / 4, iconTemplate:DocumentType.IMG, _width: 360 / 4, _height: 270 / 4, _layout_showTitle: "title", ...opts });
- const fontBox = (opts:DocumentOptions, fieldKey:string) => Docs.Create.FontIconDocument({ layout:FontIconBox.LayoutString(fieldKey), _nativeHeight: 30, _nativeWidth: 30, _width: 30, _height: 30, ...opts });
const iconTemplates = [
makeIconTemplate(undefined, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "dimgray"}),
makeIconTemplate(DocumentType.AUDIO, "title", { iconTemplate:DocumentType.LABEL, backgroundColor: "lightgreen"}),
@@ -188,9 +198,9 @@ export class CurrentUserUtils {
makeIconTemplate(DocumentType.COL, "icon", { iconTemplate:DocumentType.IMG}),
makeIconTemplate(DocumentType.VID, "icon", { iconTemplate:DocumentType.IMG}),
makeIconTemplate(DocumentType.BUTTON,"title", { iconTemplate:DocumentType.FONTICON}),
- //nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
+ // nasty hack .. templates are looked up exclusively by type -- but we want a template for a document with a certain field (transcription) .. so this hack and the companion hack in createCustomView does this for now
makeIconTemplate("transcription" as any, "transcription", { iconTemplate:DocumentType.LABEL, backgroundColor: "orange" }),
- //makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
+ // makeIconTemplate(DocumentType.PDF, "icon", {iconTemplate:DocumentType.IMG}, (opts) => imageBox("http://www.cs.brown.edu/~bcz/noImage.png", opts))
].filter(d => d).map(d => d!);
DocUtils.AssignOpts(DocCast(doc[field]), {}, iconTemplates);
}
@@ -241,14 +251,15 @@ export class CurrentUserUtils {
Docs.Create.TextDocument("", { title: "text", _layout_fitWidth:true, _height: 100, isSystem: true, _text_fontFamily: StrCast(Doc.UserDoc().fontFamily), _text_fontSize: StrCast(Doc.UserDoc().fontSize) })
], {...opts, title: "Slide View Template"}));
const plotlyApi = () => {
- var plotly = Doc.MyPublishedDocs.find(doc => doc.title === "@plotly");
+ let plotly = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@plotly");
if (!plotly) {
- const plotly = Docs.Create.TextDocument(
+ plotly = Docs.Create.TextDocument(
`await import("https://cdn.plot.ly/plotly-2.27.0.min.js");
Plotly.newPlot(dashDiv.id, [ --DOCDATA-- ])`
, {title: "@plotly", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
Doc.AddToMyPublished(plotly);
}
+ return plotly;
}
const plotlyView = (opts:DocumentOptions) => {
const rtfield = new RichTextField(JSON.stringify(
@@ -280,10 +291,10 @@ export class CurrentUserUtils {
return slide;
}
const mermaidsApi = () => {
- var mermaids = Doc.MyPublishedDocs.find(doc => doc.title === "@mermaids");
+ let mermaids = Doc.MyPublishedDocs.find(fdoc => fdoc.title === "@mermaids");
if (!mermaids) {
- const mermaids = Docs.Create.TextDocument(
- `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid\@10.8.0/dist/mermaid.esm.min.mjs")).default;
+ mermaids = Docs.Create.TextDocument(
+ `const mdef = (await import("https://cdn.jsdelivr.net/npm/mermaid@10.8.0/dist/mermaid.esm.min.mjs")).default;
window["callb"] = (x) => {
alert(x);
}
@@ -301,6 +312,7 @@ export class CurrentUserUtils {
, {title: "@mermaids", title_custom: true, _layout_showTitle:"title", _width:300,_height:400});
Doc.AddToMyPublished(mermaids);
}
+ return mermaids;
}
const mermaidsView = (opts:DocumentOptions) => {
const rtfield = new RichTextField(JSON.stringify(
@@ -333,7 +345,7 @@ pie title Minerals in my tap water
slide.onPaint = ScriptField.MakeScript(`toggleDetail(documentView, "textPainted")`, {documentView:"any"});
return slide;
}
- const apis = [plotlyApi(), mermaidsApi()]
+ plotlyApi(); mermaidsApi();
const emptyThings:{key:string, // the field name where the empty thing will be stored
opts:DocumentOptions, // the document options that are required for the empty thing
funcs?:{[key:string]: any}, // computed fields that are rquired for the empth thing
@@ -406,7 +418,7 @@ pie title Minerals in my tap water
/// Initalizes the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools
static setupCreatorButtons(doc: Doc, dragCreatorDoc?:Doc):Doc {
const creatorBtns = CurrentUserUtils.creatorBtnDescriptors(doc).map((reqdOpts) => {
- const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(doc => doc.title === reqdOpts.title): undefined;
+ const btn = dragCreatorDoc ? DocListCast(dragCreatorDoc.data).find(fdoc => fdoc.title === reqdOpts.title): undefined;
const opts:DocumentOptions = {...OmitKeys(reqdOpts, ["funcs", "scripts", "backgroundColor"]).omit,
_width: 60, _height: 60, _dragOnlyWithinContainer: true,
btnType: ButtonType.ToolButton, backgroundColor: reqdOpts.backgroundColor ?? Colors.DARK_GRAY, color: Colors.WHITE, isSystem: true,
@@ -450,7 +462,7 @@ pie title Minerals in my tap water
this.setupLeftSidebarPanel(doc);
const myLeftSidebarMenu = DocCast(doc[field]);
const menuBtns = CurrentUserUtils.leftSidebarMenuBtnDescriptions(doc).map(({ title, target, icon, toolTip, hidden, scripts, funcs }) => {
- const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(doc => doc.title === title) : undefined;
+ const btnDoc = myLeftSidebarMenu ? DocListCast(myLeftSidebarMenu.data).find(fdoc => fdoc.title === title) : undefined;
const reqdBtnOpts:DocumentOptions = {
title, icon, target, toolTip, hidden, btnType: ButtonType.MenuButton, isSystem: true, undoIgnoreFields: new List<string>(['height', 'data_columnHeaders']), dontRegisterView: true,
_width: 60, _height: 60, _dragOnlyWithinContainer: true,
@@ -465,83 +477,9 @@ pie title Minerals in my tap water
return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(items??[], opts), reqdStackOpts, menuBtns, { dropConverter: "convertToButtons(dragData)" });
}
- // Sets up mobile menu if it is undefined creates a new one, otherwise returns existing menu
- static setupActiveMobileMenu(doc: Doc, field="activeMobileMenu") {
- const reqdOpts = { _width: 980, ignoreClick: true, _lockedPosition: false, title: "home", _yMargin: 100, isSystem: true, _chromeHidden: true,};
- DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.StackingDocument(this.setupMobileButtons(), opts), reqdOpts);
- }
-
- // Sets up mobile buttons for inside mobile menu
- static setupMobileButtons(doc?: Doc, buttons?: string[]) {
- return [];
- const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [
- { title: "DASHBOARDS", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Dashboards from your mobile, and navigate through all of your documents. " },
- { title: "UPLOAD", icon: "upload", click: 'openMobileUploads()', backgroundColor: "lightgrey", info: "Upload files from your mobile device so they can be accessed on Dash Web." },
- { title: "MOBILE UPLOAD", icon: "mobile", click: 'switchToMobileUploadCollection()', backgroundColor: "lightgrey", info: "Access the collection of your mobile uploads." },
- { title: "RECORD", icon: "microphone", click: 'openMobileAudio()', backgroundColor: "lightgrey", info: "Use your phone to record, dictate and then upload audio onto Dash Web." },
- { title: "PRESENTATION", icon: "desktop", click: 'switchToMobilePresentation()', backgroundColor: "lightgrey", info: "Use your phone as a remote for you presentation." },
- { title: "SETTINGS", icon: "cog", click: 'openMobileSettings()', backgroundColor: "lightgrey", info: "Change your password, log out, or manage your account security." }
- ];
- // returns a list of mobile buttons
- return docProtoData.filter(d => !buttons || !buttons.includes(d.title)).map(data =>
- this.mobileButton({
- title: data.title,
- _lockedPosition: true,
- onClick: data.click ? ScriptField.MakeScript(data.click) : undefined,
- backgroundColor: data.backgroundColor, isSystem: true
- },
- [this.createToolButton({ ignoreClick: true, icon: data.icon, backgroundColor: "rgba(0,0,0,0)", isSystem: true, btnType: ButtonType.ClickButton, }), this.mobileTextContainer({}, [this.mobileButtonText({}, data.title), this.mobileButtonInfo({}, data.info)])])
- );
- }
-
- // sets up the main document for the mobile button
- static mobileButton = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MulticolumnDocument(docs, {
- ...opts,
- _nativeWidth: 900, _nativeHeight: 250, _width: 900, _height: 250, _yMargin: 15,
- layout_borderRounding: "5px", layout_boxShadow: "0 0", isSystem: true
- }) as any as Doc
-
- // sets up the text container for the information contained within the mobile button
- static mobileTextContainer = (opts: DocumentOptions, docs: Doc[]) => Docs.Create.MultirowDocument(docs, {
- ...opts,
- _nativeWidth: 450, _nativeHeight: 250, _width: 450, _height: 250, _yMargin: 25,
- backgroundColor: "rgba(0,0,0,0)", layout_borderRounding: "0", layout_boxShadow: "0 0", ignoreClick: true, isSystem: true
- }) as any as Doc
-
- // Sets up the title of the button
- static mobileButtonText = (opts: DocumentOptions, buttonTitle: string) => Docs.Create.TextDocument(buttonTitle, {
- ...opts,
- title: buttonTitle, _text_fontSize: "37px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", isSystem: true
- }) as any as Doc
-
- // Sets up the description of the button
- static mobileButtonInfo = (opts: DocumentOptions, buttonInfo: string) => Docs.Create.TextDocument(buttonInfo, {
- ...opts,
- title: "info", _text_fontSize: "25px", _xMargin: 0, _yMargin: 0, ignoreClick: true, backgroundColor: "rgba(0,0,0,0)", _dimMagnitude: 2, isSystem: true
- }) as any as Doc
-
-
-
- static setupMobileInkingDoc(userDoc: Doc) {
- return Docs.Create.FreeformDocument([], { title: "Mobile Inking", backgroundColor: "white", isSystem: true });
- }
-
- static setupMobileUploadDoc(userDoc: Doc) {
- // const addButton = Docs.Create.FontIconDocument({ onDragStart: ScriptField.MakeScript('addWebToMobileUpload()'), title: "Add Web Doc to Upload Collection", icon: "plus", backgroundColor: "black" })
- const webDoc = Docs.Create.WebDocument("https://www.britannica.com/biography/Miles-Davis", {
- title: "Upload Images From the Web", _lockedPosition: true, isSystem: true
- });
- const uploadDoc = Docs.Create.StackingDocument([], {
- title: "Mobile Upload Collection", backgroundColor: "white", _lockedPosition: true, isSystem: true, _chromeHidden: true,
- });
- return Docs.Create.StackingDocument([webDoc, uploadDoc], {
- _width: screen.width, _lockedPosition: true, title: "Upload", _layout_autoHeight: true, _yMargin: 80, backgroundColor: "lightgray", isSystem: true, _chromeHidden: true,
- });
- }
-
/// Search option on the left side button panel
static setupSearcher(doc: Doc, field:string) {
- return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.SearchDocument(opts), {
+ return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.SearchDocument(opts), {
dontRegisterView: true, backgroundColor: "dimgray", ignoreClick: true, title: "Search Panel", isSystem: true, childDragAction: dropActionType.embed,
_lockedPosition: true, _type_collection: CollectionViewType.Schema });
}
@@ -552,7 +490,7 @@ pie title Minerals in my tap water
const creatorBtns = CurrentUserUtils.setupCreatorButtons(doc, allTools?.length ? allTools[0]:undefined);
const userTools = allTools && allTools?.length > 1 ? allTools[1]:undefined;
const userBtns = CurrentUserUtils.setupUserDocumentCreatorButtons(doc, userTools);
- //doc.myUserBtns = new PrefetchProxy(userBtns);
+ // doc.myUserBtns = new PrefetchProxy(userBtns);
const reqdToolOps:DocumentOptions = {
title: "My Tools", isSystem: true, ignoreClick: true, layout_boxShadow: "0 0",
layout_explainer: "This is a palette of documents that can be created.",
@@ -563,7 +501,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar dashboard pane
static setupDashboards(doc: Doc, field:string) {
- var myDashboards = DocCast(doc[field]);
+ let myDashboards = DocCast(doc[field]);
const newDashboard = `createNewDashboard()`;
@@ -572,9 +510,9 @@ pie title Minerals in my tap water
const reqdBtnScript = {onClick: newDashboard,}
const newDashboardButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myDashboards?.layout_headerButton), reqdBtnOpts) ?? Docs.Create.FontIconDocument(reqdBtnOpts), reqdBtnScript);
- const contextMenuScripts = [/*newDashboard*/] as string[];
- const contextMenuLabels = [/*"Create New Dashboard"*/] as string[];
- const contextMenuIcons = [/*"plus"*/] as string[];
+ const contextMenuScripts = [/* newDashboard */] as string[];
+ const contextMenuLabels = [/* "Create New Dashboard" */] as string[];
+ const contextMenuIcons = [/* "plus" */] as string[];
const childContextMenuScripts = [`toggleComicMode()`, `snapshotDashboard()`, `shareDashboard(this)`, 'removeDashboard(this)', 'resetDashboard(this)']; // 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
@@ -605,7 +543,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar File system pane
static setupFilesystem(doc: Doc, field:string) {
- var myFilesystem = DocCast(doc[field]);
+ const myFilesystem = DocCast(doc[field]);
const newFolderOpts: DocumentOptions = {
_forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _width: 30, _height: 30, undoIgnoreFields:new List<string>(['treeView_SortCriterion']),
@@ -650,7 +588,7 @@ pie title Minerals in my tap water
/// initializes the left sidebar panel view of the UserDoc
static setupUserDocView(doc: Doc, field:string) {
const reqdOpts:DocumentOptions = {
- _lockedPosition: true, _gridGap: 5, _forceActive: true, title: Doc.CurrentUserEmail +"-view",
+ _lockedPosition: true, _gridGap: 5, _forceActive: true, title: ClientUtils.CurrentUserEmail() +"-view",
layout_boxShadow: "0 0", childDontRegisterViews: true, dropAction: dropActionType.same, ignoreClick: true, isSystem: true,
treeView_HideTitle: true, treeView_TruncateTitleWidth: 350
};
@@ -671,14 +609,14 @@ pie title Minerals in my tap water
static createToolButton = (opts: DocumentOptions) => Docs.Create.FontIconDocument({
btnType: ButtonType.ToolButton, _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
static setupDockedButtons(doc: Doc, field="myDockedBtns") {
const dockedBtns = DocCast(doc[field]);
const dockBtn = (opts: DocumentOptions, scripts: {[key:string]:string|undefined}, funcs?: {[key:string]:string}) =>
- DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(doc => doc.title === opts.title), opts) ??
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.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
@@ -694,8 +632,8 @@ pie title Minerals in my tap water
title: "docked buttons", _height: 40, flexGap: 0, layout_boxShadow: "standard", childDragAction: dropActionType.move,
childDontRegisterViews: true, linearView_IsOpen: true, linearView_Expandable: true, ignoreClick: true
};
- reaction(() => UndoManager.redoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true });
- reaction(() => UndoManager.undoStack.slice(), () => Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true });
+ reaction(() => UndoManager.redoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Redo")!).opacity = UndoManager.CanRedo() ? 1 : 0.4; }, { fireImmediately: true });
+ reaction(() => UndoManager.undoStack.slice(), () => { Doc.GetProto(btns.find(btn => btn.title === "Undo")!).opacity = UndoManager.CanUndo() ? 1 : 0.4; }, { fireImmediately: true });
return DocUtils.AssignDocField(doc, field, (opts, items) => this.linearButtonList(opts, items??[]), dockBtnsReqdOpts, btns);
}
@@ -752,9 +690,9 @@ pie title Minerals in my tap water
{ title: "Pen", toolTip: "Pen (Ctrl+P)", btnType: ButtonType.ToggleButton, icon: "pen-nib", toolType: "pen", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }},
{ title: "Write", toolTip: "Write (Ctrl+Shift+P)", btnType: ButtonType.ToggleButton, icon: "pen", toolType: "write", scripts: {onClick:'{ return setActiveTool(this.toolType, false, _readOnly_);}' }, funcs: {hidden:"IsNoviceMode()" }},
{ title: "Eraser", toolTip: "Eraser (Ctrl+E)", btnType: ButtonType.ToggleButton, icon: "eraser", toolType: "eraser", scripts: {onClick:'{ return setActiveTool(this.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(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.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(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.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(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Circle", toolTip: "Circle (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "circle", toolType: Gestures.Circle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Square", toolTip: "Square (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "square", toolType: Gestures.Rectangle, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
+ { title: "Line", toolTip: "Line (double tap to lock mode)", btnType: ButtonType.ToggleButton, icon: "minus", toolType: Gestures.Line, scripts: {onClick:`{ return setActiveTool(this.toolType, false, _readOnly_);}`, onDoubleClick:`{ return setActiveTool(this.toolType, true, _readOnly_);}`} },
{ title: "Mask", toolTip: "Mask", btnType: ButtonType.ToggleButton, icon: "user-circle",toolType: "inkMask", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, funcs: {hidden:"IsNoviceMode()" } },
{ title: "Labels", toolTip: "Lab els", btnType: ButtonType.ToggleButton, icon: "text-width", toolType: "labels", scripts: {onClick:'{ return setInkProperty(this.toolType, value, _readOnly_);}'}, },
{ title: "Width", toolTip: "Stroke width", btnType: ButtonType.NumberSliderButton, toolType: "strokeWidth", ignoreClick: true, scripts: {script: '{ return setInkProperty(this.toolType, value, _readOnly_);}'}, numBtnMin: 1},
@@ -773,7 +711,7 @@ pie title Minerals in my tap water
return [
{ title: "Back", toolTip: "Go back", btnType: ButtonType.ClickButton, icon: "arrow-left", scripts: { onClick: '{ return webBack(); }' }},
{ title: "Forward", toolTip: "Go forward", btnType: ButtonType.ClickButton, icon: "arrow-right", scripts: { onClick: '{ return webForward(); }'}},
- { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditableText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
+ { title: "URL", toolTip: "URL", width: 250, btnType: ButtonType.EditText, icon: "lock", ignoreClick: true, scripts: { script: '{ return webSetURL(value, _readOnly_); }'} },
];
}
static videoTools() {
@@ -794,24 +732,24 @@ pie title Minerals in my tap water
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: '{ return 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: "Header", icon: "heading", toolTip: "Doc Titlebar Color", btnType: ButtonType.ColorButton, expertMode: false, ignoreClick: true, scripts: { script: 'return setHeaderColor(value, _readOnly_)'} },
{ title: "Template",icon: "scroll", toolTip: "Default Note Template",btnType: ButtonType.ToggleButton, expertMode: false, toolType:DocumentType.RTF, scripts: { onClick: '{ return setDefaultTemplate(_readOnly_); }'} },
{ title: "Fill", icon: "fill-drip", toolTip: "Fill/Background 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(this.toolType, this.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(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
- { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(this.toolType, this.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(this.toolType, this.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(this.toolType, this.expertMode)`} }, // Always available
- { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.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(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
- { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
- { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Always available
- { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
- { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
- { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
- { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectionManager_selectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
+ { title: "Overlay", icon: "layer-group", toolTip: "Overlay", btnType: ButtonType.ToggleButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.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: '!SelectedDocType(this.toolType, this.expertMode)'}, width: 30, scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
+ { title: "Num", icon:"", toolTip: "Frame # (click to toggle edit mode)",btnType: ButtonType.TextButton, expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectedDocType(this.toolType, this.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: '!SelectedDocType(this.toolType, this.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: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Ink", icon: "Ink", toolTip: "Ink functions", subMenu: CurrentUserUtils.inkTools(), expertMode: false, toolType:DocumentType.INK, funcs: {hidden: `IsExploreMode()`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`}, scripts: { onClick: 'setInkToolDefaults()'} }, // Always available
+ { title: "Doc", icon: "Doc", toolTip: "Freeform Doc tools", subMenu: CurrentUserUtils.freeTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode, true)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "View", icon: "View", toolTip: "View tools", subMenu: CurrentUserUtils.viewTools(), expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Stack", icon: "View", toolTip: "Stacking tools", subMenu: CurrentUserUtils.stackTools(), expertMode: false, toolType:CollectionViewType.Stacking, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Always available
+ { title: "Web", icon: "Web", toolTip: "Web functions", subMenu: CurrentUserUtils.webTools(), expertMode: false, toolType:DocumentType.WEB, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Web is selected
+ { title: "Video", icon: "Video", toolTip: "Video functions", subMenu: CurrentUserUtils.videoTools(), expertMode: false, toolType:DocumentType.VID, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when video is selected
+ { title: "Image", icon: "Image", toolTip: "Image functions", subMenu: CurrentUserUtils.imageTools(), expertMode: false, toolType:DocumentType.IMG, funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when image is selected
+ { title: "Schema", icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(),expertMode: false,toolType:CollectionViewType.Schema,funcs: {hidden: `!SelectedDocType(this.toolType, this.expertMode)`, linearView_IsOpen: `SelectedDocType(this.toolType, this.expertMode)`} }, // Only when Schema is selected
];
}
@@ -835,16 +773,16 @@ pie title Minerals in my tap water
static setupContextMenuBtn(params:Button, menuDoc:Doc):Doc {
const menuBtnDoc = DocListCast(menuDoc?.data).find( doc => doc.title === params.title);
- const subMenu = params.subMenu;
+ const {subMenu} = params;
if (!subMenu) { // button does not have a sub menu
return this.setupContextMenuButton(params, menuBtnDoc, menuDoc);
}
// 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,
+ childDontRegisterViews: true, flexGap: 0, _height: 30, ignoreClick: !params.scripts?.onClick,
linearView_SubMenu: true, linearView_Expandable: true, embedContainer: menuDoc};
- const items = (menuBtnDoc?:Doc) => !menuBtnDoc ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menuBtnDoc) );
+ const items = (menutBtn?:Doc) => !menutBtn ? [] : subMenu.map(sub => this.setupContextMenuBtn(sub, menutBtn) );
const creator = params.btnType === ButtonType.MultiToggleButton ? this.multiToggleList : this.linearButtonList;
const btnDoc = DocUtils.AssignScripts( DocUtils.AssignDocField(menuDoc, StrCast(params.title),
(opts) => creator(opts, items(menuBtnDoc)), reqdSubMenuOpts, items(menuBtnDoc)), params.scripts, params.funcs);
@@ -866,17 +804,17 @@ pie title Minerals in my tap water
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) ??
+ DocUtils.AssignScripts(DocUtils.AssignOpts(DocListCast(dockedBtns?.data)?.find(fdoc => fdoc.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())`}}
+ { opts: { title: "Play", icon: "play", toolTip: "Play recording", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Pause", icon: "pause",toolTip: "Pause playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Playing}"`}, scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Stop", icon: "stop", toolTip: "Stop playback", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.Paused}"`}, scripts: { onClick: `stopWorkspaceReplaying(getCurrentRecording())`}},
+ { opts: { title: "Delete", icon: "trash",toolTip: "delete selected rec", btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${mediaState.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 = {
@@ -890,15 +828,17 @@ pie title Minerals in my tap water
return DocUtils.AssignDocField(doc, field, (opts) => Docs.Create.TreeDocument([], opts), { title: "published docs", backgroundColor: "#aca3a6", isSystem: true });
}
- /// The database of all links on all documents
+ static newAccount: boolean = false;
+
+ // The database of all links on all documents
static setupLinkDocs(doc: Doc, linkDatabaseId: string) {
- if (!(Docs.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
+ if (!(CurrentUserUtils.newAccount ? undefined : DocCast(doc.myLinkDatabase))) {
const linkDocs = new Doc(linkDatabaseId, true);
- linkDocs.title = "LINK DATABASE: " + Doc.CurrentUserEmail;
- linkDocs.author = Doc.CurrentUserEmail;
+ linkDocs.title = "LINK DATABASE: " + ClientUtils.CurrentUserEmail();
+ linkDocs.author = ClientUtils.CurrentUserEmail();
linkDocs.isSystem = true;
linkDocs.data = new List<Doc>([]);
- linkDocs["acl-Guest"] = SharingPermissions.Augment;
+ linkDocs.acl_Guest = SharingPermissions.Augment;
doc.myLinkDatabase = new PrefetchProxy(linkDocs);
}
}
@@ -919,7 +859,7 @@ pie title Minerals in my tap water
// childContextMenuScripts: new List<ScriptField>([addToDashboards!,]),
// childContextMenuLabels: new List<string>(["Add to Dashboards",]),
// childContextMenuIcons: new List<string>(["user-plus",]),
- "acl-Guest": SharingPermissions.Augment, "_acl-Guest": SharingPermissions.Augment,
+ acl_Guest: SharingPermissions.Augment, _acl_Guest: SharingPermissions.Augment,
childDragAction: dropActionType.embed, isSystem: true, childContentPointerEvents: "none", childLimitHeight: 0, _yMargin: 0, _gridGap: 15, childDontRegisterViews:true,
// NOTE: treeView_HideTitle & _layout_showTitle is for a TreeView's editable title, _layout_showTitle is for DocumentViews title bar
_layout_showTitle: "title", treeView_HideTitle: true, ignoreClick: true, _lockedPosition: true, layout_boxShadow: "0 0", _chromeHidden: true, dontRegisterView: true,
@@ -949,16 +889,17 @@ pie title Minerals in my tap water
/// Updates the UserDoc to have all required fields, docs, etc. No changes should need to be
/// written to the server if the code hasn't changed. However, choices need to be made for each Doc/field
/// whether to revert to "default" values, or to leave them as the user/system last set them.
- static updateUserDocument(doc: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ static updateUserDocument(docIn: Doc, sharingDocumentId: string, linkDatabaseId: string) {
+ const doc = docIn;
DocUtils.AssignDocField(doc, "globalGroupDatabase", () => Docs.Prototypes.MainGroupDocument(), {});
- reaction(() => DateCast(DocCast(doc.globalGroupDatabase)["data_modificationDate"]),
+ reaction(() => DateCast(DocCast(doc.globalGroupDatabase).data_modificationDate),
async () => {
const groups = await DocListCastAsync(DocCast(doc.globalGroupDatabase).data);
- const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(Doc.CurrentUserEmail)) || [];
- SetCachedGroups(["Guest", ...mygroups?.map(g => StrCast(g.title))]);
+ const mygroups = groups?.filter(group => JSON.parse(StrCast(group.members)).includes(ClientUtils.CurrentUserEmail())) || [];
+ SetCachedGroups(["Guest", ...(mygroups?.map(g => StrCast(g.title))??[])]);
}, { fireImmediately: true });
doc.isSystem ?? (doc.isSystem = true);
- doc.title ?? (doc.title = Doc.CurrentUserEmail);
+ doc.title ?? (doc.title = ClientUtils.CurrentUserEmail());
Doc.noviceMode ?? (Doc.noviceMode = true);
doc._showLabel ?? (doc._showLabel = true);
doc.textAlign ?? (doc.textAlign = "left");
@@ -971,7 +912,7 @@ pie title Minerals in my tap water
doc.activeFillColor ?? (doc.activeFillColor = "");
doc.activeArrowStart ?? (doc.activeArrowStart = "");
doc.activeArrowEnd ?? (doc.activeArrowEnd = "");
- doc.activeDash ?? (doc.activeDash == "0");
+ doc.activeDash ?? (doc.activeDash === "0");
doc.fontSize ?? (doc.fontSize = "12px");
doc.fontFamily ?? (doc.fontFamily = "Arial");
doc.fontColor ?? (doc.fontColor = "black");
@@ -988,27 +929,26 @@ pie title Minerals in my tap water
this.setupLinkDocs(doc, linkDatabaseId);
this.setupSharedDocs(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing
this.setupDefaultIconTemplates(doc); // creates a set of icon templates triggered by the document deoration icon
- this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile
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
- //this.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
- DocUtils.AssignDocField(doc, "globalScriptDatabase", (opts) => Docs.Prototypes.MainScriptDocument(), {});
+ // sthis.setupFieldInfos(doc); // sets up the collection of field info descriptions for each possible DocumentOption
+ DocUtils.AssignDocField(doc, "globalScriptDatabase", () => Docs.Prototypes.MainScriptDocument(), {});
DocUtils.AssignDocField(doc, "myHeaderBar", (opts) => Docs.Create.MulticolumnDocument([], opts), { title: "My Header Bar", isSystem: true, _chromeHidden:true, layout_maxShown: 10, childLayoutFitWidth:false, childDocumentsActive:false, dropAction: dropActionType.move}); // drop down panel at top of dashboard for stashing documents
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards)
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs)
- Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed)
-
- Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org")
+ SelectionManager.DeselectAll(); // this forces SelectionManager implementation to copy over to DocumentView's API. This also triggers the LinkManager to be created
+
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyDashboards);
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MySharedDocs);
+ Doc.AddDocToList(Doc.MyFilesystem, undefined, Doc.MyRecentlyClosed);
- new LinkManager();
+ Doc.GetProto(DocCast(Doc.UserDoc().emptyWebpage)).data = new WebField("https://www.wikipedia.org");
- DocServer.CacheNeedsUpdate && setTimeout(DocServer.UPDATE_SERVER_CACHE, 2500);
- setInterval(DocServer.UPDATE_SERVER_CACHE, 120000);
+ DocServer.CacheNeedsUpdate() && setTimeout(UPDATE_SERVER_CACHE, 2500);
+ setInterval(UPDATE_SERVER_CACHE, 120000);
return doc;
}
static setupFieldInfos(doc:Doc, field="fieldInfos") {
@@ -1025,18 +965,17 @@ pie title Minerals in my tap water
case FInfoFieldType.Doc: opts.fieldValues = new List<Doc>(options.values as any); break;
default: opts.fieldValues = new List<string>(options.values as any); break;// string, pointerEvents, dimUnit, dropActionType
}
- DocUtils.AssignDocField(infos, pair[0], opts => Doc.assign(new Doc(), OmitKeys(opts,["values"]).omit), opts);
+ DocUtils.AssignDocField(infos, pair[0], docOpts => Doc.assign(new Doc(), OmitKeys(docOpts,["values"]).omit), opts);
}
});
}
- @observable public static ServerVersion: string = ';'
public static async loadCurrentUser() {
- return rp.get(Utils.prepend("/getCurrentUser")).then(async response => {
+ return rp.get(ClientUtils.prepend("/getCurrentUser")).then(async response => {
if (response) {
const result: { version: string, userDocumentId: string, sharingDocumentId: string, linkDatabaseId: string, email: string, cacheDocumentIds: string, resolvedPorts: string } = JSON.parse(response);
- runInAction(() => CurrentUserUtils.ServerVersion = result.version);
- Doc.CurrentUserEmail = result.email;
+ runInAction(() => { SnappingManager.SetServerVersion(result.version); });
+ ClientUtils.SetCurrentUserEmail(result.email);
resolvedPorts = result.resolvedPorts as any;
DocServer.init(window.location.protocol, window.location.hostname, resolvedPorts?.socket, result.email);
if (result.cacheDocumentIds)
@@ -1044,13 +983,13 @@ pie title Minerals in my tap water
const ids = result.cacheDocumentIds.split(";");
const batch = 30000;
for (let i = 0; i < ids.length; i += batch) {
+ // eslint-disable-next-line no-await-in-loop
await DocServer.GetRefFields(ids.slice(i, i+batch));
}
}
return result;
- } else {
- throw new Error("There should be a user! Why does Dash think there isn't one?");
- }
+ }
+ throw new Error("There should be a user! Why does Dash think there isn't one?");
});
}
@@ -1060,12 +999,12 @@ pie title Minerals in my tap water
linkDatabaseId: string;
}) {
return DocServer.GetRefField(info.userDocumentId).then(async field => {
- Docs.newAccount = !(field instanceof Doc);
+ CurrentUserUtils.newAccount = !(field instanceof Doc);
await Docs.Prototypes.initialize();
- const userDoc = Docs.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
+ const userDoc = CurrentUserUtils.newAccount ? new Doc(info.userDocumentId, true) : field as Doc;
this.updateUserDocument(Doc.SetUserDoc(userDoc), info.sharingDocumentId, info.linkDatabaseId);
- if (Docs.newAccount) {
- if (Doc.CurrentUserEmail === "guest") {
+ if (CurrentUserUtils.newAccount) {
+ if (ClientUtils.CurrentUserEmail() === "guest") {
DashboardView.createNewDashboard(undefined, "guest dashboard");
} else {
userDoc.activePage = "home";
@@ -1082,14 +1021,10 @@ pie title Minerals in my tap water
input.type = "file";
input.multiple = true;
input.accept = ".zip, application/pdf, video/*, image/*, audio/*";
- input.onchange = async _e => {
+ input.onchange = async () => {
const file = input.files?.[0];
if (file?.type === 'application/zip' || file?.type === 'application/x-zip-compressed') {
const doc = await Doc.importDocument(file);
- // NOT USING SOLR, so need to replace this with something else // if (doc instanceof Doc) {
- // setTimeout(() => SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs =>
- // docs.docs.forEach(d => LinkManager.Instance.addLink(d))), 2000); // need to give solr some time to update so that this query will find any link docs we've added.
- // }
const list = Cast(Doc.MyImports.data, listSpec(Doc), null);
doc instanceof Doc && list?.splice(0, 0, doc);
} else if (input.files && input.files.length !== 0) {
@@ -1109,10 +1044,17 @@ pie title Minerals in my tap water
}
}
-ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
-ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
-ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function MySharedDocs() { return Doc.MySharedDocs; }, "document containing all shared Docs");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsExploreMode() { return SnappingManager.ExploreMode; }, "is Dash in exploration mode");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function IsNoviceMode() { return Doc.noviceMode; }, "is Dash in novice mode");
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function toggleComicMode() { Doc.UserDoc().renderStyle = Doc.UserDoc().renderStyle === "comic" ? undefined : "comic"; }, "switches between comic and normal document rendering");
-ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function importDocument() { return CurrentUserUtils.importDocument(); }, "imports files from device directly into the import sidebar");
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function setInkToolDefaults() { Doc.ActiveTool = InkTool.None; });
-ScriptingGlobals.add(function getSharingDoc() {return Doc.SharingDoc() }); \ No newline at end of file
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function getSharingDoc() { return Doc.SharingDoc() });
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 82c63695c..bc9fe813f 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -1,18 +1,22 @@
+/* eslint-disable no-use-before-define */
import * as interpreter from 'words-to-numbers';
// @ts-ignore bcz: how are you supposed to include these definitions since dom-speech-recognition isn't a module?
import type {} from '@types/dom-speech-recognition';
+import { ClientUtils } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { RichTextField } from '../../fields/RichTextField';
import { listSpec } from '../../fields/Schema';
-import { Cast, CastCtor, DocCast } from '../../fields/Types';
+import { Cast, CastCtor } from '../../fields/Types';
import { AudioField, ImageField } from '../../fields/URLField';
-import { Utils } from '../../Utils';
-import { Docs } from '../documents/Documents';
+import { AudioAnnoState } from '../../server/SharedMediaTypes';
+import { Networking } from '../Network';
import { DocumentType } from '../documents/DocumentTypes';
+import { Docs } from '../documents/Documents';
import { DictationOverlay } from '../views/DictationOverlay';
-import { DocumentView, OpenWhere } from '../views/nodes/DocumentView';
-import { SelectionManager } from './SelectionManager';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { OpenWhere } from '../views/nodes/OpenWhere';
import { UndoManager } from './UndoManager';
/**
@@ -61,12 +65,13 @@ export namespace DictationManager {
const intraSession = '. ';
const interSession = ' ... ';
- export let isListening = false;
+ let isListening = false;
let isManuallyStopped = false;
- let current: string | undefined = undefined;
+ let current: string | undefined;
let sessionResults: string[] = [];
+ // eslint-disable-next-line new-cap
const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
export type InterimResultHandler = (results: string) => any;
@@ -87,7 +92,7 @@ export namespace DictationManager {
let pendingListen: Promise<string> | string | undefined;
export const listen = async (options?: Partial<ListeningOptions>) => {
- if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options));
+ if (pendingListen instanceof Promise) return pendingListen.then(() => innerListen(options));
return innerListen(options);
};
const innerListen = async (options?: Partial<ListeningOptions>) => {
@@ -103,7 +108,7 @@ export namespace DictationManager {
results = await (pendingListen = listenImpl(options));
pendingListen = undefined;
if (results) {
- Utils.CopyText(results);
+ ClientUtils.CopyText(results);
if (overlay) {
DictationOverlay.Instance.isListening = false;
const execute = options?.tryExecute;
@@ -150,29 +155,29 @@ export namespace DictationManager {
recognizer.start();
- return new Promise<string>((resolve, reject) => {
+ return new Promise<string>(resolve => {
recognizer.onerror = (e: any) => {
// e is SpeechRecognitionError but where is that defined?
if (!(indefinite && e.error === 'no-speech')) {
recognizer.stop();
resolve(e);
- //reject(e);
}
};
recognizer.onresult = (e: SpeechRecognitionEvent) => {
current = synthesize(e, intra);
- let matchedTerminator: string | undefined;
- if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
+ const matchedTerminator = options?.terminators?.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false));
+ if (options?.terminators && matchedTerminator) {
current = matchedTerminator;
recognizer.abort();
return complete();
}
!isManuallyStopped && handler?.(current);
- //isManuallyStopped && complete();
+ // isManuallyStopped && complete()
+ return undefined;
};
- recognizer.onend = (e: Event) => {
+ recognizer.onend = () => {
if (!indefinite || isManuallyStopped) {
return complete();
}
@@ -182,6 +187,7 @@ export namespace DictationManager {
current = undefined;
}
recognizer.start();
+ return undefined;
};
const complete = () => {
@@ -202,7 +208,7 @@ export namespace DictationManager {
});
};
- export const stop = (salvageSession = true) => {
+ export const stop = (/* salvageSession = true */) => {
if (!isListening || !recognizer) {
return;
}
@@ -212,7 +218,7 @@ export namespace DictationManager {
};
const synthesize = (e: SpeechRecognitionEvent, delimiter?: string) => {
- const results = e.results;
+ const { results } = e;
const transcripts: string[] = [];
for (let i = 0; i < results.length; i++) {
transcripts.push(results.item(i).item(0).transcript.trim());
@@ -231,24 +237,30 @@ export namespace DictationManager {
export type DependentEntry = { expression: RegExp; action: DependentAction; restrictTo?: DocumentType[] };
export const RegisterIndependent = (key: string, value: IndependentEntry) => Independent.set(key, value);
- export const RegisterDependent = (entry: DependentEntry) => Dependent.push(entry);
+ export const RegisterDependent = (entry: DependentEntry) => {
+ const { expression, action, restrictTo } = entry;
+ return Dependent.push({ expression, action, restrictTo: restrictTo ?? [] });
+ };
- export const execute = async (phrase: string) => {
- return UndoManager.RunInBatch(async () => {
+ export const execute = async (phrase: string) =>
+ UndoManager.RunInBatch(async () => {
console.log('PHRASE: ' + phrase);
- const targets = SelectionManager.Views;
+ const targets = DocumentView.Selected();
if (!targets || !targets.length) {
- return;
+ return undefined;
}
+ // eslint-disable-next-line no-param-reassign
phrase = phrase.toLowerCase();
const entry = Independent.get(phrase);
if (entry) {
let success = false;
- const restrictTo = entry.restrictTo;
+ const { restrictTo } = entry;
+ // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
+ // eslint-disable-next-line no-await-in-loop
await entry.action(target);
success = true;
}
@@ -256,16 +268,19 @@ export namespace DictationManager {
return success;
}
- for (const entry of Dependent) {
- const regex = entry.expression;
+ // eslint-disable-next-line no-restricted-syntax
+ for (const depEntry of Dependent) {
+ const regex = depEntry.expression;
const matches = regex.exec(phrase);
regex.lastIndex = 0;
if (matches !== null) {
let success = false;
- const restrictTo = entry.restrictTo;
+ const { restrictTo } = depEntry;
+ // eslint-disable-next-line no-restricted-syntax
for (const target of targets) {
if (!restrictTo || validate(target, restrictTo)) {
- await entry.action(target, matches);
+ // eslint-disable-next-line no-await-in-loop
+ await depEntry.action(target, matches);
success = true;
}
}
@@ -275,7 +290,6 @@ export namespace DictationManager {
return false;
}, 'Execute Command');
- };
const ConstructorMap = new Map<DocumentType, CastCtor>([
[DocumentType.COL, listSpec(Doc)],
@@ -294,6 +308,7 @@ export namespace DictationManager {
};
const validate = (target: DocumentView, types: DocumentType[]) => {
+ // eslint-disable-next-line no-restricted-syntax
for (const type of types) {
if (tryCast(target, type)) {
return true;
@@ -318,7 +333,9 @@ export namespace DictationManager {
[
'clear',
{
- action: (target: DocumentView) => (Doc.GetProto(target.Document).data = new List()),
+ action: (target: DocumentView) => {
+ Doc.GetProto(target.Document).data = new List();
+ },
restrictTo: [DocumentType.COL],
},
],
@@ -328,7 +345,7 @@ export namespace DictationManager {
{
action: (target: DocumentView) => {
const newBox = Docs.Create.TextDocument('', { _width: 400, _height: 200, title: 'My Outline', _layout_autoHeight: true });
- const proto = DocCast(newBox.proto);
+ const proto = newBox[DocData];
const prompt = 'Press alt + r to start dictating here...';
const head = 3;
const anchor = head + prompt.length;
@@ -341,7 +358,7 @@ export namespace DictationManager {
],
]);
- const Dependent = new Array<DependentEntry>(
+ const Dependent = [
{
expression: /create (\w+) documents of type (image|nested collection)/g,
action: (target: DocumentView, matches: RegExpExecArray) => {
@@ -361,6 +378,7 @@ export namespace DictationManager {
case 'nested collection':
created = Docs.Create.FreeformDocument([], {});
break;
+ default:
}
created && Doc.AddDocToList(dataDoc, fieldKey, created);
}
@@ -375,7 +393,46 @@ export namespace DictationManager {
mode && (target.Document._type_collection = mode);
},
restrictTo: [DocumentType.COL],
- }
- );
+ },
+ ];
+ }
+ export function recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
+ let gumStream: any;
+ let recorder: any;
+ navigator.mediaDevices.getUserMedia({ audio: true }).then(stream => {
+ let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+ if (audioTextAnnos) audioTextAnnos.push('');
+ else audioTextAnnos = dataDoc[field + '_audioAnnotations_text'] = new List<string>(['']);
+ DictationManager.Controls.listen({
+ interimHandler: value => { audioTextAnnos[audioTextAnnos.length - 1] = value; }, // prettier-ignore
+ continuous: { indefinite: false },
+ }).then(results => {
+ if (results && [DictationManager.Controls.Infringed].includes(results)) {
+ DictationManager.Controls.stop();
+ }
+ onEnd?.();
+ });
+
+ gumStream = stream;
+ recorder = new MediaRecorder(stream);
+ recorder.ondataavailable = async (e: any) => {
+ const [{ result }] = await Networking.UploadFilesToServer({ file: e.data });
+ if (!(result instanceof Error)) {
+ const audioField = new AudioField(result.accessPaths.agnostic.client);
+ const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+ if (audioAnnos) audioAnnos.push(audioField);
+ else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+ }
+ };
+ recorder.start();
+ const stopFunc = () => {
+ recorder.stop();
+ DictationManager.Controls.stop(/* false */);
+ dataDoc.audioAnnoState = AudioAnnoState.stopped;
+ gumStream.getAudioTracks()[0].stop();
+ };
+ if (onRecording) onRecording(stopFunc);
+ else setTimeout(stopFunc, 5000);
+ });
}
}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 40d28c690..5bcac7330 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -1,35 +1,31 @@
import { Howl } from 'howler';
import { action, computed, makeObservable, observable, ObservableSet, observe } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
-import { AclAdmin, AclEdit, Animation, DocData } from '../../fields/DocSymbols';
+import { Animation, DocData } from '../../fields/DocSymbols';
import { Id } from '../../fields/FieldSymbols';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { AudioField } from '../../fields/URLField';
-import { GetEffectiveAcl } from '../../fields/util';
import { CollectionViewType } from '../documents/DocumentTypes';
-import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { TabDocView } from '../views/collections/TabDocView';
import { LightboxView } from '../views/LightboxView';
-import { DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FieldView';
-import { KeyValueBox } from '../views/nodes/KeyValueBox';
-import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
+import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { OpenWhere } from '../views/nodes/OpenWhere';
import { PresBox } from '../views/nodes/trails';
-import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
+type childIterator = { viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean; contextPath: Doc[] };
export class DocumentManager {
+ // eslint-disable-next-line no-use-before-define
private static _instance: DocumentManager;
public static get Instance(): DocumentManager {
+ // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
- //global holds all of the nodes (regardless of which collection they're in)
- @observable _documentViews = new Set<DocumentView>();
- @observable.shallow public CurrentlyLoading: Doc[] = [];
+ // global holds all of the nodes (regardless of which collection they're in)
+ @observable private _documentViews = new Set<DocumentView>();
@computed public get DocumentViews() {
- return Array.from(this._documentViews).filter(view => !(view.ComponentView instanceof KeyValueBox) && (!LightboxView.LightboxDoc || LightboxView.Contains(view)));
+ return Array.from(this._documentViews).filter(view => (!view.ComponentView?.dontRegisterView?.() && !LightboxView.LightboxDoc) || LightboxView.Contains(view));
}
public AddDocumentView(dv: DocumentView) {
this._documentViews.add(dv);
@@ -38,10 +34,22 @@ export class DocumentManager {
this._documentViews.delete(dv);
}
- //private constructor so no other class can create a nodemanager
+ // private constructor so no other class can create a nodemanager
private constructor() {
makeObservable(this);
- observe(this.CurrentlyLoading, change => {
+
+ DocumentView.allViews = () => this.DocumentViews;
+ DocumentView.addView = this.AddView;
+ DocumentView.removeView = this.RemoveView;
+ DocumentView.showDocument = this.showDocument;
+ DocumentView.showDocumentView = this.showDocumentView;
+ DocumentView.linkCommonAncestor = DocumentManager.LinkCommonAncestor;
+ DocumentView.addViewRenderedCb = this.AddViewRenderedCb;
+ DocumentView.getFirstDocumentView = this.getFirstDocumentView;
+ DocumentView.getDocumentView = this.getDocumentView;
+ DocumentView.getContextPath = DocumentManager.GetContextPath;
+ DocumentView.getLightboxDocumentView = this.getLightboxDocumentView;
+ 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':
@@ -52,6 +60,7 @@ export class DocumentManager {
case 'splice':
(change as any).removed.forEach((doc: Doc) => DocumentManager.Instance.getAllDocumentViews(doc).forEach(dv => StrCast(dv.Document.layout_fieldKey) === 'layout_icon' && dv.iconify(() => dv.iconify())));
break;
+ default:
}
});
}
@@ -88,20 +97,15 @@ export class DocumentManager {
@action
public AddView = (view: DocumentView) => {
- if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) &&
- !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- this.AddDocumentView(view);
- this.callAddViewFuncs(view);
- } // prettier-ignore
+ this.AddDocumentView(view);
+ this.callAddViewFuncs(view);
};
public RemoveView = action((view: DocumentView) => {
- if (!view._props.LayoutTemplateString?.includes(KeyValueBox.name) && !view._props.LayoutTemplateString?.includes(LinkAnchorBox.name)) {
- this.DeleteDocumentView(view);
- }
- SelectionManager.DeselectView(view);
+ this.DeleteDocumentView(view);
+ DocumentView.DeselectView(view);
});
- //gets all views
+ // gets all views
public getDocumentViewsById(id: string) {
const toReturn: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => {
@@ -135,25 +139,17 @@ export class DocumentManager {
);
}
- public getLightboxDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
+ public getLightboxDocumentView = (toFind: Doc): DocumentView | undefined => {
const views: DocumentView[] = [];
DocumentManager.Instance.DocumentViews.forEach(view => LightboxView.Contains(view) && Doc.AreProtosEqual(view.Document, toFind) && views.push(view));
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
};
- public getFirstDocumentView = (toFind: Doc, originatingDoc: Opt<Doc> = undefined): DocumentView | undefined => {
- if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind, originatingDoc);
- const views = this.getDocumentViews(toFind); //.filter(view => view.Document !== originatingDoc);
- return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /*&& view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse*/) || (views.length ? views[0] : undefined);
+ public getFirstDocumentView = (toFind: Doc): DocumentView | undefined => {
+ if (LightboxView.LightboxDoc) return DocumentManager.Instance.getLightboxDocumentView(toFind);
+ const views = this.getDocumentViews(toFind); // .filter(view => view.Document !== originatingDoc);
+ return views?.find(view => view.ContentDiv?.getBoundingClientRect().width /* && view._props.focus !== returnFalse) || views?.find(view => view._props.focus !== returnFalse */) || (views.length ? views[0] : undefined);
};
- public getDocumentViews(toFindIn: Doc): DocumentView[] {
- const toFind =
- // Array.from(DocumentManager.Instance.DocumentViews).find(
- // dv =>
- // ((dv.Document.data as any)?.url?.href && (dv.Document.data as any)?.url?.href === (toFindIn.data as any)?.url?.href) ||
- // ((DocCast(dv.Document.annotationOn)?.data as any)?.url?.href && (DocCast(dv.Document.annotationOn)?.data as any)?.url?.href === (DocCast(toFindIn.annotationOn)?.data as any)?.url?.href)
- // )?.Document ??
- toFindIn;
-
+ public getDocumentViews(toFind: Doc): DocumentView[] {
const toReturn: DocumentView[] = [];
const docViews = DocumentManager.Instance.DocumentViews.filter(view => !LightboxView.Contains(view));
const lightViews = DocumentManager.Instance.DocumentViews.filter(view => LightboxView.Contains(view));
@@ -172,7 +168,7 @@ export class DocumentManager {
static GetContextPath(doc: Opt<Doc>, includeExistingViews?: boolean) {
if (!doc) return [];
const srcContext = DocCast(doc.annotationOn, DocCast(doc.embedContainer));
- var containerDocContext = srcContext ? [srcContext, doc] : [doc];
+ let containerDocContext = srcContext ? [srcContext, doc] : [doc];
while (
containerDocContext.length &&
DocCast(containerDocContext[0]?.embedContainer) &&
@@ -184,11 +180,13 @@ export class DocumentManager {
return containerDocContext;
}
+ static _howl: Howl;
static playAudioAnno(doc: Doc) {
const anno = Cast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations'], listSpec(AudioField), null)?.lastElement();
if (anno) {
+ this._howl?.stop();
if (anno instanceof AudioField) {
- new Howl({
+ this._howl = new Howl({
src: [anno.url.href],
format: ['mp3'],
autoplay: true,
@@ -204,19 +202,20 @@ export class DocumentManager {
DocumentManager._overlayViews?.clear();
}
static _overlayViews = new ObservableSet<DocumentView>();
- static addView = (doc: Doc, finished?: () => void) => {
- CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
- finished?.();
- };
+ /**
+ * Find the nearest common ancestor collection that contains a link's source and target
+ * @param linkDoc
+ * @returns common ancestor DocumentView
+ */
public static LinkCommonAncestor(linkDoc: Doc) {
- const anchor = (which: number) => {
+ const getAnchor = (which: number) => {
const anch = DocCast(linkDoc['link_anchor_' + which]);
const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch;
return DocumentManager.Instance.getDocumentView(anchor);
};
- const anchor1 = anchor(1);
- const anchor2 = anchor(2);
+ const anchor1 = getAnchor(1);
+ const anchor2 = getAnchor(2);
return anchor1
?.docViewPath()
.reverse()
@@ -228,8 +227,10 @@ export class DocumentManager {
// focusing on each context
public showDocumentView = async (targetDocView: DocumentView, options: FocusViewOptions) => {
const docViewPath = [...(targetDocView.containerViewPath?.() ?? []), targetDocView];
- let rootContextView = docViewPath.shift();
- await (rootContextView && this.focusViewsInPath(rootContextView, options, async () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false })));
+ const rootContextView = docViewPath.shift();
+ const iterator = () => ({ childDocView: docViewPath.shift(), viewSpec: undefined, focused: false, contextPath: docViewPath.map(dv => dv.Document) });
+ options.contextPath = docViewPath.map(dv => dv.Document);
+ await (rootContextView && this.focusViewsInPath(rootContextView, options, iterator));
if (options.toggleTarget && (!options.didMove || targetDocView.Document.hidden)) targetDocView.Document.hidden = !targetDocView.Document.hidden;
else if (options.openLocation?.startsWith(OpenWhere.toggle) && !options.didMove && rootContextView) DocumentViewInternal.addDocTabFunc(rootContextView.Document, options.openLocation);
};
@@ -242,27 +243,26 @@ export class DocumentManager {
// and finally restoring the targetDoc to the viewSpec specified by the last document which may either be the targetDoc, or a viewSpec that describes the targetDoc configuration
public showDocument = async (
targetDoc: Doc, // document to display
- options: FocusViewOptions, // options for how to navigate to target
+ optionsIn: FocusViewOptions, // options for how to navigate to target
finished?: (changed: boolean) => void // func called after focusing on target with flag indicating whether anything needed to be done.
) => {
+ const options = optionsIn;
Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, targetDoc);
const docContextPath = DocumentManager.GetContextPath(targetDoc, true);
if (docContextPath.some(doc => doc.hidden)) options.toggleTarget = false;
- const tabView = Array.from(TabDocView._allTabs).find(view => view._document === docContextPath[0]);
- if (!tabView?._activated && tabView?._document) {
- options.toggleTarget = false;
- TabDocView.Activate(tabView?._document);
- }
- let rootContextView =
+ if (DocumentView.activateTabView(docContextPath[0])) options.toggleTarget = false;
+
+ const rootContextView =
docContextPath.length &&
(await new Promise<DocumentView>(res => {
const viewIndex = docContextPath.findIndex(doc => this.getDocumentView(doc));
if (viewIndex !== -1) {
viewIndex && docContextPath.splice(0, viewIndex);
- return res(this.getDocumentView(docContextPath[0])!);
+ res(this.getDocumentView(docContextPath[0])!);
+ return;
}
options.didMove = true;
- (!LightboxView.LightboxDoc && docContextPath.some(doc => TabDocView.Activate(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
+ (!LightboxView.LightboxDoc && docContextPath.some(doc => DocumentView.activateTabView(doc))) || DocumentViewInternal.addDocTabFunc(docContextPath[0], options.openLocation ?? OpenWhere.addRight);
this.AddViewRenderedCb(docContextPath[0], dv => res(dv));
}));
if (options.openLocation === OpenWhere.lightbox) {
@@ -270,48 +270,73 @@ export class DocumentManager {
const target = DocCast(targetDoc.annotationOn, targetDoc);
const contextView = this.getDocumentView(DocCast(target.embedContainer));
if (contextView?.ComponentView?.addDocTab?.(target, OpenWhere.lightbox)) {
- await new Promise<void>(waitres => setTimeout(() => waitres()));
+ await new Promise<void>(waitres => {
+ setTimeout(() => waitres());
+ });
}
}
- docContextPath.shift();
- const childViewIterator = async (docView: DocumentView) => {
- const innerDoc = docContextPath.shift();
- return { focused: false, viewSpec: innerDoc, childDocView: innerDoc && !innerDoc.layout_unrendered ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc) : undefined };
- };
if (rootContextView) {
+ const childViewIterator = async (docView: DocumentView): Promise<childIterator> => {
+ const innerDoc = docContextPath.shift();
+ const childDocView = innerDoc && !innerDoc.layout_unrendered
+ ? (await docView.ComponentView?.getView?.(innerDoc, options)) ?? this.getDocumentView(innerDoc):
+ undefined; // prettier-ignore
+ return { focused: false, viewSpec: innerDoc, childDocView, contextPath: docContextPath };
+ };
+ docContextPath.shift();
+ options.contextPath = docContextPath;
const target = await this.focusViewsInPath(rootContextView, options, childViewIterator);
- this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
- finished?.(target.focused);
- } else finished?.(false);
+ if (target) {
+ this.restoreDocView(target.viewSpec, target.docView, options, target.contextView ?? target.docView, targetDoc);
+ finished?.(target.focused);
+ return;
+ }
+ }
+ finished?.(false);
};
focusViewsInPath = async (
- docView: DocumentView, //
- options: FocusViewOptions,
- iterator: (docView: DocumentView) => Promise<{ viewSpec: Opt<Doc>; childDocView: Opt<DocumentView>; focused: boolean }>
+ docViewIn: DocumentView, //
+ optionsIn: FocusViewOptions,
+ iterator: (docView: DocumentView) => childIterator | Promise<childIterator>
) => {
let contextView: DocumentView | undefined; // view containing context that contains target
let focused = false;
- while (true) {
+ let docView = docViewIn;
+ let anchor = docView.Document;
+ const options = optionsIn;
+ const maxFocusLength = 100; // want to keep focusing until we get to target, but avoid an infinite loop
+ for (let i = 0; i < maxFocusLength; i++) {
if (docView.Document.layout_fieldKey === 'layout_icon') {
- await new Promise<void>(res => docView.iconify(res));
+ // eslint-disable-next-line no-loop-func
+ const prom = new Promise<void>(res => {
+ docView.iconify(res);
+ });
+ // eslint-disable-next-line no-await-in-loop
+ await prom;
options.didMove = true;
}
- const nextFocus = docView._props.focus(docView.Document, options); // focus the view within its container
- focused = focused || (nextFocus === undefined ? false : true); // keep track of whether focusing on a view needed to actually change anything
- const { childDocView, viewSpec } = await iterator(docView);
- if (!childDocView) return { viewSpec: options.anchorDoc ?? viewSpec ?? docView.Document, docView, contextView, focused };
- contextView = options.anchorDoc?.layout_unrendered && !childDocView.Document.layout_unrendered ? childDocView : docView;
+ const nextFocus = docView._props.focus(anchor, options); // focus the view within its container
+ focused = focused || nextFocus !== undefined; // keep track of whether focusing on a view needed to actually change anything
+ // eslint-disable-next-line no-await-in-loop
+ const { childDocView, viewSpec, contextPath } = await iterator(docView);
+ if (!childDocView) return { viewSpec: viewSpec ?? docView.Document, docView, contextView, focused };
+ contextView = !childDocView.Document.layout_unrendered ? childDocView : docView;
docView = childDocView;
+ anchor = viewSpec ?? docView.Document;
+ options.contextPath = contextPath;
}
+ options.contextPath = undefined;
+ return undefined;
};
@action
- restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ restoreDocView(viewSpec: Opt<Doc>, docViewIn: DocumentView, options: FocusViewOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
+ const docView = docViewIn;
if (viewSpec && docView) {
- //if (docView.ComponentView instanceof FormattedTextBox)
- //viewSpec !== docView.Document &&
+ // if (docView.ComponentView instanceof FormattedTextBox)
+ // viewSpec !== docView.Document &&
docView.ComponentView?.focus?.(viewSpec, options);
PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
@@ -320,7 +345,7 @@ export class DocumentManager {
if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden;
if (options.effect) docView.Document[Animation] = options.effect;
- if (options.zoomTextSelections && Doc.UnhighlightTimer && contextView && targetDoc.text_html) {
+ if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
// if the docView is a text anchor, the contextView is the PDF/Web/Text doc
contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
DocumentManager._overlayViews.add(contextView);
@@ -332,30 +357,4 @@ export class DocumentManager {
}
}
}
-export function DocFocusOrOpen(doc: Doc, options: FocusViewOptions = { willZoomCentered: true, zoomScale: 0, openLocation: OpenWhere.toggleRight }, containingDoc?: Doc) {
- const func = () => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- if (dv && (!containingDoc || dv.containerViewPath?.().lastElement()?.Document === containingDoc)) {
- DocumentManager.Instance.showDocumentView(dv, options).then(() => dv && Doc.linkFollowHighlight(dv.Document));
- } else {
- const container = DocCast(containingDoc ?? doc.embedContainer ?? Doc.BestEmbedding(doc));
- const showDoc = !Doc.IsSystem(container) && !cv ? container : doc;
- options.toggleTarget = undefined;
- DocumentManager.Instance.showDocument(showDoc, options, () => DocumentManager.Instance.showDocument(doc, { ...options, openLocation: undefined })).then(() => {
- const cv = DocumentManager.Instance.getDocumentView(containingDoc);
- const dv = DocumentManager.Instance.getDocumentView(doc, cv);
- dv && Doc.linkFollowHighlight(dv.Document);
- });
- }
- };
- if (Doc.IsDataProto(doc) && Doc.GetEmbeddings(doc).some(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))) {
- doc = Doc.GetEmbeddings(doc).find(embed => embed.hidden && [AclAdmin, AclEdit].includes(GetEffectiveAcl(embed)))!;
- }
- if (doc.hidden) {
- doc.hidden = false;
- options.toggleTarget = false;
- setTimeout(func);
- } else func();
-}
-ScriptingGlobals.add(DocFocusOrOpen);
+setTimeout(() => DocumentManager.Instance);
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 9627c5df2..fda505420 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -1,3 +1,5 @@
+/* eslint-disable import/no-mutable-exports */
+/* eslint-disable no-use-before-define */
/**
* The DragManager handles all dragging interactions that occur entirely within Dash (as opposed to external drag operations from the file system, etc)
*
@@ -13,32 +15,22 @@
*/
import { action, observable, runInAction } from 'mobx';
+import { ClientUtils } from '../../ClientUtils';
+import { emptyFunction } from '../../Utils';
import { DateField } from '../../fields/DateField';
-import { Doc, Field, Opt, StrListCast } from '../../fields/Doc';
+import { CreateLinkToActiveAudio, Doc, FieldType, Opt, StrListCast } from '../../fields/Doc';
+import { DocData } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { PrefetchProxy } from '../../fields/Proxy';
import { ScriptField } from '../../fields/ScriptField';
import { ScriptCast } from '../../fields/Types';
-import { emptyFunction, Utils } from '../../Utils';
-import { Docs, DocUtils } from '../documents/Documents';
-import { CollectionFreeFormDocumentView } from '../views/nodes/CollectionFreeFormDocumentView';
+import { Docs } from '../documents/Documents';
import { DocumentView } from '../views/nodes/DocumentView';
-import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
+import { dropActionType } from './DropActionTypes';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
-import { DocData } from '../../fields/DocSymbols';
-const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
-export enum dropActionType {
- embed = 'embed', // create a new embedding of the dragged document for the new location
- copy = 'copy', // copy the dragged document
- move = 'move', // move the dragged document to the drop location after removing it from where it was
- add = 'add', // add the dragged document to the drop location without removing it from where it was
- same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
- inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
- proto = 'proto',
-} // undefined = move, same = move but doesn't call dropPropertiesToRemove
+const { contextMenuZindex } = require('../views/global/globalCssVariables.module.scss'); // prettier-ignore
/**
* Initialize drag
@@ -79,10 +71,14 @@ export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: ()
}
export namespace DragManager {
+ export const dragClassName = 'collectionFreeFormDocumentView-container';
let dragDiv: HTMLDivElement;
let dragLabel: HTMLDivElement;
export let StartWindowDrag: Opt<(e: { pageX: number; pageY: number }, dragDocs: Doc[], finishDrag?: (aborted: boolean) => void) => boolean>;
export let CompleteWindowDrag: Opt<(aborted: boolean) => void>;
+ export let AbortDrag: () => void = emptyFunction;
+ export const docsBeingDragged: Doc[] = observable([]);
+ export let DocDragData: DocumentDragData | undefined;
export function Root() {
const root = document.getElementById('root');
@@ -91,7 +87,6 @@ export namespace DragManager {
}
return root;
}
- export let AbortDrag: () => void = emptyFunction;
export type MoveFunction = (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean;
export type RemoveFunction = (document: Doc | Doc[]) => boolean;
@@ -106,6 +101,7 @@ export namespace DragManager {
// event called when the drag operation results in a drop action
export class DropEvent {
+ // eslint-disable-next-line no-useless-constructor
constructor(
readonly x: number,
readonly y: number,
@@ -115,7 +111,9 @@ export namespace DragManager {
readonly metaKey: boolean,
readonly ctrlKey: boolean,
readonly embedKey: boolean
- ) {}
+ ) {
+ /* empty */
+ }
}
// event called when the drag operation has completed (aborted or completed a drop) -- this will be after any drop event has been generated
@@ -139,11 +137,11 @@ export namespace DragManager {
constructor(dragDoc: Doc[], dropAction?: dropActionType) {
this.draggedDocuments = dragDoc;
this.droppedDocuments = [];
- this.draggedViews = [];
this.offset = [0, 0];
this.dropAction = dropAction;
}
- draggedViews: DocumentView[];
+ dragEnding?: () => void;
+ dragStarting?: () => void;
draggedDocuments: Doc[];
droppedDocuments: Doc[];
treeViewDoc?: Doc;
@@ -157,6 +155,7 @@ export namespace DragManager {
removeDocument?: RemoveFunction;
isDocDecorationMove?: boolean; // Flags that Document decorations are used to drag document which allows suppression of onDragStart scripts
}
+ Doc.SetDocDragDataName(DocumentDragData.name);
export class LinkDragData {
constructor(dragView: DocumentView, linkSourceGetAnchor: () => Doc) {
this.linkDragView = dragView;
@@ -194,7 +193,7 @@ export namespace DragManager {
userDropAction?: dropActionType;
}
- let defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
+ const defaultPreDropFunc = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => {
if (de.complete.docDragData) {
targetAction && (de.complete.docDragData.dropAction = targetAction);
e.stopPropagation();
@@ -224,12 +223,12 @@ export namespace DragManager {
export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions, onDropCompleted?: (e?: DragCompleteEvent) => any) {
const addAudioTag = (dropDoc: any) => {
dropDoc && !dropDoc.author_date && (dropDoc.author_date = new DateField());
- dropDoc instanceof Doc && DocUtils.MakeLinkToActiveAudio(() => dropDoc);
+ dropDoc instanceof Doc && CreateLinkToActiveAudio(() => dropDoc);
return dropDoc;
};
const finishDrag = async (e: DragCompleteEvent) => {
- const docDragData = e.docDragData;
- setTimeout(() => dragData.draggedViews.forEach(view => view.props.dragEnding?.()));
+ const { docDragData } = e;
+ setTimeout(() => dragData.dragEnding?.());
onDropCompleted?.(e); // glr: optional additional function to be called - in this case with presentation trails
if (docDragData && !docDragData.droppedDocuments.length) {
docDragData.dropAction = dragData.userDropAction || dragData.dropAction;
@@ -256,27 +255,32 @@ export namespace DragManager {
.forEach((drop: Doc, i: number) => {
const dragProps = StrListCast(dragData.draggedDocuments[i].dropPropertiesToRemove);
const remProps = (dragData?.dropPropertiesToRemove || []).concat(Array.from(dragProps));
- [...remProps, 'dropPropertiesToRemove'].map(prop => (drop[prop] = undefined));
+ [...remProps, 'dropPropertiesToRemove'].forEach(prop => {
+ drop[prop] = undefined;
+ });
});
}
return e;
};
dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded
StartDrag(eles, dragData, downX, downY, options, finishDrag);
- dragData.draggedViews.forEach(view => view.props.dragStarting?.());
+ dragData.dragStarting?.();
return true;
}
// drag a button template and drop a new button
- export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: Field }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
+ export function StartButtonDrag(eles: HTMLElement[], script: string, title: string, vars: { [name: string]: FieldType }, params: string[], initialize: (button: Doc) => void, downX: number, downY: number, options?: DragOptions) {
const finishDrag = (e: DragCompleteEvent) => {
const bd = Docs.Create.ButtonDocument({ toolTip: title, z: 1, _width: 150, _height: 50, title, onClick: ScriptField.MakeScript(script) });
- params.map(p => Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc))); // copy all "captured" arguments into document parameterfields
+ params.forEach(p => {
+ Object.keys(vars).indexOf(p) !== -1 && (bd[DocData][p] = new PrefetchProxy(vars[p] as Doc));
+ }); // copy all "captured" arguments into document parameterfields
initialize?.(bd);
bd[DocData]['onClick-paramFieldKeys'] = new List<string>(params);
e.docDragData && (e.docDragData.droppedDocuments = [bd]);
return e;
};
+ // eslint-disable-next-line no-param-reassign
options = options ?? {};
options.noAutoscroll = true; // these buttons are being dragged on the overlay layer, so scrollin the underlay is not appropriate
StartDrag(eles, new DragManager.DocumentDragData([]), downX, downY, options, finishDrag);
@@ -298,7 +302,7 @@ export namespace DragManager {
}
export function snapDragAspect(dragPt: number[], snapAspect: number) {
- let closest = Utils.SNAP_THRESHOLD;
+ let closest = ClientUtils.SNAP_THRESHOLD;
let near = dragPt;
const intersect = (x1: number, y1: number, x2: number, y2: number, x3: number, y3: number, x4: number, y4: number, dragx: number, dragy: number) => {
if ((x1 === x2 && y1 === y2) || (x3 === x4 && y3 === y4)) return undefined; // Check if none of the lines are of length 0
@@ -307,7 +311,7 @@ export namespace DragManager {
const ua = ((x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3)) / denominator;
// let ub = ((x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3)) / denominator;
- //if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
+ // if (ua < 0 || ua > 1 || ub < 0 || ub > 1) return undefined; // is the intersection along the segments
// Return a object with the x and y coordinates of the intersection
const x = x1 + ua * (x2 - x1);
@@ -315,14 +319,14 @@ export namespace DragManager {
const dist = Math.sqrt((dragx - x) * (dragx - x) + (dragy - y) * (dragy - y));
return { pt: [x, y], dist };
};
- SnappingManager.VertSnapLines.forEach((xCoord, i) => {
+ SnappingManager.VertSnapLines.forEach(xCoord => {
const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, xCoord, -1, xCoord, 1, dragPt[0], dragPt[1]);
if (pt && pt.dist < closest) {
closest = pt.dist;
near = pt.pt;
}
});
- SnappingManager.HorizSnapLines.forEach((yCoord, i) => {
+ SnappingManager.HorizSnapLines.forEach(yCoord => {
const pt = intersect(dragPt[0], dragPt[1], dragPt[0] + snapAspect, dragPt[1] + 1, -1, yCoord, 1, yCoord, dragPt[0], dragPt[1]);
if (pt && pt.dist < closest) {
closest = pt.dist;
@@ -333,7 +337,7 @@ export namespace DragManager {
}
// snap to the active snap lines - if oneAxis is set (eg, for maintaining aspect ratios), then it only snaps to the nearest horizontal/vertical line
export function snapDrag(e: PointerEvent, xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number) {
- const snapThreshold = Utils.SNAP_THRESHOLD;
+ const snapThreshold = ClientUtils.SNAP_THRESHOLD;
const snapVal = (pts: number[], drag: number, snapLines: number[]) => {
if (snapLines.length) {
const offs = [pts[0], (pts[0] - pts[1]) / 2, -pts[1]]; // offsets from drag pt
@@ -350,14 +354,33 @@ export namespace DragManager {
y: snapVal([yFromTop, yFromBottom], e.pageY, SnappingManager.HorizSnapLines),
};
}
- export let docsBeingDragged: Doc[] = observable([]);
- 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) {
+
+ async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
+ const dropArgs = {
+ cancelable: true, // allows preventDefault() to be called to cancel the drop
+ bubbles: true,
+ detail: {
+ ...pos,
+ complete,
+ shiftKey: e.shiftKey,
+ altKey: e.altKey,
+ metaKey: e.metaKey,
+ ctrlKey: e.ctrlKey,
+ embedKey: SnappingManager.CanEmbed,
+ },
+ };
+ target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
+ UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
+ await finishDrag?.(complete);
+ UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
+ options?.dragComplete?.(complete);
+ endDrag?.();
+ }
+ export function StartDrag(elesIn: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: DragCompleteEvent) => void, dragUndoName?: string) {
if (dragData.dropAction === 'none' || SnappingManager.ExploreMode) return;
DocDragData = dragData as DocumentDragData;
const batch = UndoManager.StartBatch(dragUndoName ?? 'document drag');
- eles = eles.filter(e => e);
+ const eles = elesIn.filter(e => e);
SnappingManager.SetCanEmbed(dragData.canEmbed || false);
if (!dragDiv) {
dragDiv = document.createElement('div');
@@ -375,9 +398,9 @@ export namespace DragManager {
}
Object.assign(dragDiv.style, { width: '', height: '', overflow: '' });
dragDiv.hidden = false;
- const scalings: number[] = [],
- xs: number[] = [],
- ys: number[] = [];
+ const scalings: number[] = [];
+ const xs: number[] = [];
+ const ys: number[] = [];
const elesCont = {
left: Number.MAX_SAFE_INTEGER,
@@ -385,7 +408,7 @@ export namespace DragManager {
top: Number.MAX_SAFE_INTEGER,
bottom: Number.MIN_SAFE_INTEGER,
};
- let rot: number[] = [];
+ const rot: number[] = [];
const docsToDrag = dragData instanceof DocumentDragData ? dragData.draggedDocuments : dragData instanceof AnchorAnnoDragData ? [dragData.dragDocument] : [];
const dragElements = eles.map(ele => {
// bcz: very hacky -- if dragged element is a freeForm view with a rotation, then extract the rotation in order to apply it to the dragged element
@@ -394,7 +417,7 @@ export namespace DragManager {
// if the parent isn't a freeform view, then the element's width and height are presumed to match the acutal doc's dimensions (eg, dragging from import sidebar menu)
let rotation: number | undefined;
for (let parEle: HTMLElement | null | undefined = ele.parentElement; parEle; parEle = parEle?.parentElement) {
- if (parEle.className === CollectionFreeFormDocumentView.CollectionFreeFormDocViewClassName) {
+ if (parEle.className === DragManager.dragClassName) {
rotation = (rotation ?? 0) + Number(parEle.style.transform.replace(/.*rotate\(([-0-9.e]*)deg\).*/, '$1') || 0);
}
parEle = parEle.parentElement;
@@ -471,16 +494,20 @@ export namespace DragManager {
.filter(pb => pb.width && pb.height)
.map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0));
}
- [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => (ele as any).style && ((ele as any).style.pointerEvents = 'none'));
+ [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))]
+ .map(dele => (dele as any).style)
+ .forEach(style => {
+ style && (style.pointerEvents = 'none');
+ });
dragDiv.appendChild(dragElement);
if (dragElement !== ele) {
- const children = [Array.from(ele.children), Array.from(dragElement.children)];
- while (children[0].length) {
- const childs = [children[0].pop(), children[1].pop()];
+ const dragChildren = [Array.from(ele.children), Array.from(dragElement.children)];
+ while (dragChildren[0].length) {
+ const childs = [dragChildren[0].pop(), dragChildren[1].pop()];
if (childs[0]?.children) {
- children[0].push(...Array.from(childs[0].children));
- children[1].push(...Array.from(childs[1]!.children));
+ dragChildren[0].push(...Array.from(childs[0].children));
+ dragChildren[1].push(...Array.from(childs[1]!.children));
}
if (childs[0]?.scrollTop) childs[1]!.scrollTop = childs[0].scrollTop;
}
@@ -493,7 +520,11 @@ export namespace DragManager {
const hideDragShowOriginalElements = (hide: boolean) => {
dragLabel.style.display = hide && !SnappingManager.CanEmbed ? '' : 'none';
!hide && dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement));
- setTimeout(() => eles.forEach(ele => (ele.hidden = hide)));
+ setTimeout(() =>
+ eles.forEach(ele => {
+ ele.hidden = hide;
+ })
+ );
};
options?.hideSource && hideDragShowOriginalElements(true);
@@ -505,22 +536,7 @@ export namespace DragManager {
const yFromBottom = elesCont.bottom - downY;
let scrollAwaiter: Opt<NodeJS.Timeout>;
- AbortDrag = () => {
- options?.dragComplete?.(new DragCompleteEvent(true, dragData));
- cleanupDrag(true);
- };
-
- const cleanupDrag = action((undo: boolean) => {
- (dragData as DocumentDragData).draggedViews?.forEach(view => view.props.dragEnding?.());
- hideDragShowOriginalElements(false);
- document.removeEventListener('pointermove', moveHandler, true);
- document.removeEventListener('pointerup', upHandler, true);
- SnappingManager.SetIsDragging(false);
- if (batch.end() && undo) UndoManager.Undo();
- docsBeingDragged.length = 0;
- SnappingManager.SetCanEmbed(false);
- });
- var startWindowDragTimer: any;
+ let startWindowDragTimer: any;
const moveHandler = (e: PointerEvent) => {
e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop
if (dragData instanceof DocumentDragData) {
@@ -580,10 +596,10 @@ export namespace DragManager {
defaultPrevented: true,
eventPhase: e.eventPhase,
isTrusted: true,
- preventDefault: () => ('not implemented for this event' ? false : false),
- isDefaultPrevented: () => ('not implemented for this event' ? false : false),
- stopPropagation: () => ('not implemented for this event' ? false : false),
- isPropagationStopped: () => ('not implemented for this event' ? false : false),
+ preventDefault: () => 'not implemented for this event' && false,
+ isDefaultPrevented: () => 'not implemented for this event' && false,
+ stopPropagation: () => 'not implemented for this event' && false,
+ isPropagationStopped: () => 'not implemented for this event' && false,
persist: emptyFunction,
timeStamp: e.timeStamp,
type: 'dashDragMovePause',
@@ -602,7 +618,9 @@ export namespace DragManager {
const moveVec = { x: x - lastPt.x, y: y - lastPt.y };
lastPt = { x, y };
- dragElements.map((dragElement, i) => (dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`));
+ dragElements.forEach((dragElement, i) => {
+ dragElement.style.transform = `translate(${(xs[i] += moveVec.x)}px, ${(ys[i] += moveVec.y)}px) rotate(${rot[i]}deg) scale(${scalings[i]})`;
+ });
dragLabel.style.transform = `translate(${xs[0]}px, ${ys[0] - 20}px)`;
};
const upHandler = (e: PointerEvent) => {
@@ -610,36 +628,21 @@ export namespace DragManager {
startWindowDragTimer = undefined;
dispatchDrag(document.elementFromPoint(e.x, e.y) || document.body, e, new DragCompleteEvent(false, dragData), snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom), finishDrag, options, () => cleanupDrag(false));
};
+ const cleanupDrag = action((undo: boolean) => {
+ (dragData as DocumentDragData).dragEnding?.();
+ hideDragShowOriginalElements(false);
+ document.removeEventListener('pointermove', moveHandler, true);
+ document.removeEventListener('pointerup', upHandler, true);
+ SnappingManager.SetIsDragging(false);
+ if (batch.end() && undo) UndoManager.Undo();
+ docsBeingDragged.length = 0;
+ SnappingManager.SetCanEmbed(false);
+ });
+ AbortDrag = () => {
+ options?.dragComplete?.(new DragCompleteEvent(true, dragData));
+ cleanupDrag(true);
+ };
document.addEventListener('pointermove', moveHandler, true);
document.addEventListener('pointerup', upHandler, true);
}
-
- async function dispatchDrag(target: Element, e: PointerEvent, complete: DragCompleteEvent, pos: { x: number; y: number }, finishDrag?: (e: DragCompleteEvent) => void, options?: DragOptions, endDrag?: () => void) {
- const dropArgs = {
- cancelable: true, // allows preventDefault() to be called to cancel the drop
- bubbles: true,
- detail: {
- ...pos,
- complete,
- shiftKey: e.shiftKey,
- altKey: e.altKey,
- metaKey: e.metaKey,
- ctrlKey: e.ctrlKey,
- embedKey: SnappingManager.CanEmbed,
- },
- };
- target.dispatchEvent(new CustomEvent<DropEvent>('dashPreDrop', dropArgs));
- UndoManager.StartTempBatch(); // run drag/drop in temp batch in case drop is not allowed (so we can undo any intermediate changes)
- await finishDrag?.(complete);
- UndoManager.EndTempBatch(target.dispatchEvent(new CustomEvent<DropEvent>('dashOnDrop', dropArgs))); // event return val is true unless the event preventDefault() is called
- options?.dragComplete?.(complete);
- endDrag?.();
- }
}
-
-ScriptingGlobals.add(function toggleRaiseOnDrag(readOnly?: boolean) {
- if (readOnly) {
- return SelectionManager.Views.some(dv => dv.Document.keepZWhenDragged);
- }
- SelectionManager.Views.map(dv => (dv.Document.keepZWhenDragged = !dv.Document.keepZWhenDragged));
-});
diff --git a/src/client/util/DropActionTypes.ts b/src/client/util/DropActionTypes.ts
new file mode 100644
index 000000000..45b294d97
--- /dev/null
+++ b/src/client/util/DropActionTypes.ts
@@ -0,0 +1,9 @@
+export enum dropActionType {
+ embed = 'embed', // create a new embedding of the dragged document for the new location
+ copy = 'copy', // copy the dragged document
+ move = 'move', // move the dragged document to the drop location after removing it from where it was
+ add = 'add', // add the dragged document to the drop location without removing it from where it was
+ same = 'same', // only allow drop within same collection (or same hierarchical tree collection)
+ inPlace = 'inSame', // keep document in place (unless overridden by a drag modifier)
+ proto = 'proto',
+} // undefined = move, same = move but doesn't call dropPropertiesToRemove
diff --git a/src/client/util/DropConverter.ts b/src/client/util/DropConverter.ts
index ed5749d06..0314af06b 100644
--- a/src/client/util/DropConverter.ts
+++ b/src/client/util/DropConverter.ts
@@ -1,10 +1,9 @@
-import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, StrListCast } from '../../fields/Doc';
import { DocData } from '../../fields/DocSymbols';
import { ObjectField } from '../../fields/ObjectField';
import { RichTextField } from '../../fields/RichTextField';
-import { listSpec } from '../../fields/Schema';
import { ComputedField, ScriptField } from '../../fields/ScriptField';
-import { Cast, StrCast } from '../../fields/Types';
+import { StrCast } from '../../fields/Types';
import { ImageField } from '../../fields/URLField';
import { Docs } from '../documents/Documents';
import { DocumentType } from '../documents/DocumentTypes';
@@ -13,21 +12,6 @@ import { DragManager } from './DragManager';
import { ScriptingGlobals } from './ScriptingGlobals';
/**
- * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
- * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
- * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is
- * "expanded" to create an template instance for the rendered Doc.
- *
- *
- * @param doc the doc to convert to a template
- * @returns 'doc'
- */
-export function MakeTemplate(doc: Doc) {
- doc.isTemplateDoc = makeTemplate(doc, true);
- return doc;
-}
-
-/**
*
* Recursively converts 'doc' into a template that can be used to render other documents.
*
@@ -63,26 +47,22 @@ function makeTemplate(doc: Doc, first: boolean = true): boolean {
}
return isTemplate;
}
-export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
- data?.draggedDocuments.map((doc, i) => {
- let dbox = doc;
- // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
- if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
- if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
- //dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
- dbox = Doc.MakeEmbedding(dbox);
- const dragProps = Cast(dbox.dropPropertiesToRemove, listSpec('string'), []);
- const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
- remProps.map(prop => (dbox[prop] = undefined));
- }
- } else if (!doc.onDragStart && !doc.isButtonBar) {
- dbox = makeUserTemplateButton(doc);
- } else if (doc.isButtonBar) {
- dbox.ignoreClick = true;
- }
- data.droppedDocuments[i] = dbox;
- });
+
+/**
+ * Converts a Doc to a render template that can be applied to other Docs to customize how they render while
+ * still using the other Doc as the backing data store (ie, dataDoc). During rendering, if a layout Doc is provided
+ * with 'isTemplateDoc' set, then the layout Doc is treated as a template for the rendered Doc. The template Doc is
+ * "expanded" to create an template instance for the rendered Doc.
+ *
+ *
+ * @param doc the doc to convert to a template
+ * @returns 'doc'
+ */
+export function MakeTemplate(doc: Doc) {
+ doc.isTemplateDoc = makeTemplate(doc, true);
+ return doc;
}
+
export function makeUserTemplateButton(doc: Doc) {
const layoutDoc = doc; // doc.layout instanceof Doc && doc.layout.isTemplateForField ? doc.layout : doc;
if (layoutDoc.type !== DocumentType.FONTICON) {
@@ -106,7 +86,30 @@ export function makeUserTemplateButton(doc: Doc) {
dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory)');
return dbox;
}
+export function convertDropDataToButtons(data: DragManager.DocumentDragData) {
+ data?.draggedDocuments.forEach((doc, i) => {
+ let dbox = doc;
+ // bcz: isButtonBar is intended to allow a collection of linear buttons to be dropped and nested into another collection of buttons... it's not being used yet, and isn't very elegant
+ if (doc.type === DocumentType.FONTICON || StrCast(Doc.Layout(doc).layout).includes(FontIconBox.name)) {
+ if (data.dropPropertiesToRemove || dbox.dropPropertiesToRemove) {
+ // dbox = Doc.MakeEmbedding(doc); // don't need to do anything if dropping an icon doc onto an icon bar since there should be no layout data for an icon
+ dbox = Doc.MakeEmbedding(dbox);
+ const dragProps = StrListCast(dbox.dropPropertiesToRemove);
+ const remProps = (data.dropPropertiesToRemove || []).concat(Array.from(dragProps));
+ remProps.forEach(prop => {
+ dbox[prop] = undefined;
+ });
+ }
+ } else if (!doc.onDragStart && !doc.isButtonBar) {
+ dbox = makeUserTemplateButton(doc);
+ } else if (doc.isButtonBar) {
+ dbox.ignoreClick = true;
+ }
+ data.droppedDocuments[i] = dbox;
+ });
+}
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function convertToButtons(dragData: any) {
convertDropDataToButtons(dragData as DragManager.DocumentDragData);
},
diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx
index f4f879208..5701a22c0 100644
--- a/src/client/util/GroupManager.tsx
+++ b/src/client/util/GroupManager.tsx
@@ -1,3 +1,5 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { action, computed, makeObservable, observable } from 'mobx';
@@ -5,19 +7,20 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
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 { ObservableReactComponent } from '../views/ObservableReactComponent';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
import './GroupManager.scss';
import { GroupMemberView } from './GroupMemberView';
-import { SettingsManager } from './SettingsManager';
import { SharingManager, User } from './SharingManager';
-import { ObservableReactComponent } from '../views/ObservableReactComponent';
+import { SnappingManager } from './SnappingManager';
/**
* Interface for options for the react-select component
@@ -29,6 +32,7 @@ export interface UserOptions {
@observer
export class GroupManager extends ObservableReactComponent<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: GroupManager;
@observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not.
@observable private users: string[] = []; // list of users populated from the database.
@@ -55,7 +59,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
*/
populateUsers = async () => {
if (Doc.UserDoc()[Id] !== Utils.GuestID()) {
- const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
+ const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
const raw = JSON.parse(userList) as User[];
raw.map(action(user => !this.users.some(umail => umail === user.email) && this.users.push(user.email)));
}
@@ -73,7 +77,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
*/
@action
open = () => {
- // SelectionManager.DeselectAll();
+ // DocumentView.DeselectAll();
this.isOpen = true;
this.populateUsers();
};
@@ -135,7 +139,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
hasEditAccess(groupDoc: Doc): boolean {
if (!groupDoc) return false;
const accessList: string[] = JSON.parse(StrCast(groupDoc.owners));
- return accessList.includes(Doc.CurrentUserEmail) || this.adminGroupMembers?.includes(Doc.CurrentUserEmail);
+ return accessList.includes(ClientUtils.CurrentUserEmail()) || this.adminGroupMembers?.includes(ClientUtils.CurrentUserEmail());
}
/**
@@ -147,7 +151,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
const name = groupName.toLowerCase() === 'admin' ? 'Admin' : groupName;
const groupDoc = new Doc('GROUP:' + name, true);
groupDoc.title = name;
- groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]);
+ groupDoc.owners = JSON.stringify([ClientUtils.CurrentUserEmail()]);
groupDoc.members = JSON.stringify(memberEmails);
this.addGroup(groupDoc);
}
@@ -159,7 +163,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
addGroup(groupDoc: Doc): boolean {
if (this.GroupManagerDoc) {
Doc.AddDocToList(this.GroupManagerDoc, 'data', groupDoc);
- this.GroupManagerDoc['data_modificationDate'] = new DateField();
+ this.GroupManagerDoc.data_modificationDate = new DateField();
return true;
}
return false;
@@ -176,11 +180,11 @@ export class GroupManager extends ObservableReactComponent<{}> {
Doc.RemoveDocFromList(this.GroupManagerDoc, 'data', group);
SharingManager.Instance.removeGroup(group);
const members = JSON.parse(StrCast(group.members));
- if (members.includes(Doc.CurrentUserEmail)) {
+ if (members.includes(ClientUtils.CurrentUserEmail())) {
const index = DocListCast(this.GroupManagerDoc.data).findIndex(grp => grp === group);
index !== -1 && Cast(this.GroupManagerDoc.data, listSpec(Doc), [])?.splice(index, 1);
}
- this.GroupManagerDoc['data_modificationDate'] = new DateField();
+ this.GroupManagerDoc.data_modificationDate = new DateField();
if (group === this.currentGroup) {
this.currentGroup = undefined;
}
@@ -201,7 +205,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
!memberList.includes(email) && memberList.push(email);
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.shareWithAddedMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+ this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
}
}
@@ -215,10 +219,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
const memberList = JSON.parse(StrCast(groupDoc.members));
const index = memberList.indexOf(email);
if (index !== -1) {
- const user = memberList.splice(index, 1)[0];
groupDoc.members = JSON.stringify(memberList);
SharingManager.Instance.removeMember(groupDoc, email);
- this.GroupManagerDoc && (this.GroupManagerDoc['data_modificationDate'] = new DateField());
+ this.GroupManagerDoc && (this.GroupManagerDoc.data_modificationDate = new DateField());
}
}
}
@@ -260,7 +263,10 @@ export class GroupManager extends ObservableReactComponent<{}> {
alert('Please select a unique group name');
return;
}
- this.createGroupDoc(value, this.selectedUsers?.map(user => user.value));
+ this.createGroupDoc(
+ value,
+ this.selectedUsers?.map(user => user.value)
+ );
this.selectedUsers = null;
this.inputRef.current!.value = '';
this.buttonColour = '#979797';
@@ -271,7 +277,9 @@ export class GroupManager extends ObservableReactComponent<{}> {
TaskCompletionBox.textDisplayed = 'Group created!';
TaskCompletionBox.taskCompleted = true;
setTimeout(
- action(() => (TaskCompletionBox.taskCompleted = false)),
+ action(() => {
+ TaskCompletionBox.taskCompleted = false;
+ }),
2000
);
};
@@ -281,14 +289,14 @@ export class GroupManager extends ObservableReactComponent<{}> {
*/
private get groupCreationModal() {
const contents = (
- <div className="group-create" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="group-create" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
<div className="group-heading" style={{ marginBottom: 0 }}>
<p>
<b>New Group</b>
</p>
<div className="close-button">
<Button
- icon={<FontAwesomeIcon icon={'times'} size={'lg'} />}
+ icon={<FontAwesomeIcon icon="times" size="lg" />}
onClick={action(() => {
this.createGroupModalOpen = false;
TaskCompletionBox.taskCompleted = false;
@@ -298,7 +306,17 @@ export class GroupManager extends ObservableReactComponent<{}> {
</div>
</div>
<div className="group-input" style={{ border: StrCast(Doc.UserDoc().userColor) }}>
- <input ref={this.inputRef} onKeyDown={this.handleKeyDown} autoFocus type="text" placeholder="Group name" onChange={action(() => (this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797'))} />
+ <input
+ ref={this.inputRef}
+ onKeyDown={this.handleKeyDown}
+ // eslint-disable-next-line jsx-a11y/no-autofocus
+ autoFocus
+ type="text"
+ placeholder="Group name"
+ onChange={action(() => {
+ this.buttonColour = this.inputRef.current?.value ? 'black' : '#979797';
+ })}
+ />
</div>
<div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
@@ -306,7 +324,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
isMulti
options={this.options}
onChange={this.handleChange}
- placeholder={'Select users'}
+ placeholder="Select users"
value={this.selectedUsers}
closeMenuOnSelect={false}
styles={{
@@ -331,8 +349,8 @@ export class GroupManager extends ObservableReactComponent<{}> {
}}
/>
</div>
- <div className={'create-button'}>
- <Button text={'Create'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
+ <div className="create-button">
+ <Button text="Create" type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={this.createGroup} />
</div>
</div>
);
@@ -340,7 +358,7 @@ export class GroupManager extends ObservableReactComponent<{}> {
return (
<MainViewModal
isDisplayed={this.createGroupModalOpen}
- interactive={true}
+ interactive
contents={contents}
dialogueBoxStyle={{ width: '90%', height: '70%' }}
closeOnExternalClick={action(() => {
@@ -366,30 +384,61 @@ export class GroupManager extends ObservableReactComponent<{}> {
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.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="group-interface" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
{this.groupCreationModal}
- {this.currentGroup ? <GroupMemberView group={this.currentGroup} onCloseButtonClick={action(() => (this.currentGroup = undefined))} /> : null}
+ {this.currentGroup ? (
+ <GroupMemberView
+ group={this.currentGroup}
+ onCloseButtonClick={action(() => {
+ this.currentGroup = undefined;
+ })}
+ />
+ ) : null}
<div className="group-heading">
<p>
<b>Manage Groups</b>
</p>
- <Button icon={<FontAwesomeIcon icon={'plus'} />} iconPlacement={'left'} text={'Create Group'} type={Type.TERT} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.createGroupModalOpen = true))} />
- <div className={'close-button'}>
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
+ <Button
+ icon={<FontAwesomeIcon icon="plus" />}
+ iconPlacement="left"
+ text="Create Group"
+ type={Type.TERT}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={action(() => {
+ this.createGroupModalOpen = true;
+ })}
+ />
+ <div className="close-button">
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={StrCast(Doc.UserDoc().userColor)} />
</div>
</div>
<div className="main-container">
- <div className="sort-groups" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ <div
+ className="sort-groups"
+ onClick={action(() => {
+ this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+ })}>
Name
<IconButton icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} />
</div>
- <div className={'style-divider'} style={{ background: StrCast(Doc.UserDoc().userColor) }} />
+ <div className="style-divider" style={{ background: StrCast(Doc.UserDoc().userColor) }} />
<div className="group-body" style={{ background: StrCast(Doc.UserDoc().userBackgroundColor), color: StrCast(Doc.UserDoc().userColor) }}>
{groups.map(group => (
<div className="group-row" key={StrCast(group.title || group.groupName)}>
<div className="group-name">{StrCast(group.title || group.groupName)}</div>
- <div className="group-info" onClick={action(() => (this.currentGroup = group))}>
- <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => (this.currentGroup = group))} />
+ <div
+ className="group-info"
+ onClick={action(() => {
+ this.currentGroup = group;
+ })}>
+ <IconButton
+ icon={<FontAwesomeIcon icon="info-circle" />}
+ size={Size.XSMALL}
+ color={StrCast(Doc.UserDoc().userColor)}
+ onClick={action(() => {
+ this.currentGroup = group;
+ })}
+ />
</div>
</div>
))}
@@ -400,6 +449,6 @@ export class GroupManager extends ObservableReactComponent<{}> {
}
render() {
- return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.groupInterface} isDisplayed={this.isOpen} interactive dialogueBoxStyle={{ zIndex: 1002 }} overlayStyle={{ zIndex: 1001 }} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx
index 894583711..da9e1aa28 100644
--- a/src/client/util/GroupMemberView.tsx
+++ b/src/client/util/GroupMemberView.tsx
@@ -1,4 +1,7 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Button, IconButton, Size, Type } from 'browndash-components';
import { action, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
@@ -8,8 +11,7 @@ import { StrCast } from '../../fields/Types';
import { MainViewModal } from '../views/MainViewModal';
import { GroupManager, UserOptions } from './GroupManager';
import './GroupMemberView.scss';
-import { Button, IconButton, Size, Type } from 'browndash-components';
-import { SettingsManager } from './SettingsManager';
+import { SnappingManager } from './SnappingManager';
interface GroupMemberViewProps {
group: Doc;
@@ -32,26 +34,29 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
const hasEditAccess = GroupManager.Instance.hasEditAccess(this.group);
return !this.group ? null : (
- <div className="editing-interface" style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
+ <div className="editing-interface" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
<div className="editing-header">
<input
className="group-title"
style={{ marginLeft: !hasEditAccess ? '-14%' : 0 }}
value={StrCast(this.group.title || this.group.groupName)}
- onChange={e => (this.group.title = e.currentTarget.value)}
- disabled={!hasEditAccess}></input>
- <div className={'memberView-closeButton'}>
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
+ onChange={e => {
+ this.group.title = e.currentTarget.value;
+ }}
+ disabled={!hasEditAccess}
+ />
+ <div className="memberView-closeButton">
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={action(this.props.onCloseButtonClick)} color={StrCast(Doc.UserDoc().userColor)} />
</div>
{GroupManager.Instance.hasEditAccess(this.group) ? (
<div className="group-buttons">
<div style={{ border: StrCast(Doc.UserDoc().userColor) }}>
<Select
className="add-member-dropdown"
- isSearchable={true}
+ isSearchable
options={options}
onChange={selectedOption => GroupManager.Instance.addMemberToGroup(this.group, (selectedOption as UserOptions).value)}
- placeholder={'Add members'}
+ placeholder="Add members"
value={null}
styles={{
control: () => ({
@@ -80,7 +85,12 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
</div>
</div>
) : null}
- <div className="sort-emails" style={{ paddingTop: hasEditAccess ? 0 : 35 }} onClick={action(() => (this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending'))}>
+ <div
+ className="sort-emails"
+ style={{ paddingTop: hasEditAccess ? 0 : 35 }}
+ onClick={action(() => {
+ this.memberSort = this.memberSort === 'ascending' ? 'descending' : this.memberSort === 'descending' ? 'none' : 'ascending';
+ })}>
Emails {this.memberSort === 'ascending' ? '↑' : this.memberSort === 'descending' ? '↓' : ''} {/* → */}
</div>
</div>
@@ -90,8 +100,8 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
<div className="editing-row" key={member}>
<div className="user-email">{member}</div>
{hasEditAccess ? (
- <div className={'remove-button'} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
- <IconButton icon={<FontAwesomeIcon icon={'trash-alt'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
+ <div className="remove-button" onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)}>
+ <IconButton icon={<FontAwesomeIcon icon="trash-alt" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.group, member)} />
</div>
) : null}
</div>
@@ -102,6 +112,6 @@ export class GroupMemberView extends React.Component<GroupMemberViewProps> {
}
render() {
- return <MainViewModal isDisplayed={true} interactive={true} contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
+ return <MainViewModal isDisplayed interactive contents={this.editingInterface} dialogueBoxStyle={{ width: 400, height: 250 }} closeOnExternalClick={this.props.onCloseButtonClick} />;
}
}
diff --git a/src/client/util/History.ts b/src/client/util/History.ts
index 2f1a336cc..52d0223d5 100644
--- a/src/client/util/History.ts
+++ b/src/client/util/History.ts
@@ -1,6 +1,12 @@
+/* eslint-disable no-use-before-define */
+/* eslint-disable no-empty */
+/* eslint-disable no-continue */
+/* eslint-disable guard-for-in */
+/* eslint-disable no-restricted-syntax */
+/* eslint-disable no-param-reassign */
import * as qs from 'query-string';
import { Doc } from '../../fields/Doc';
-import { OmitKeys, Utils } from '../../Utils';
+import { OmitKeys, ClientUtils } from '../../ClientUtils';
import { DocServer } from '../DocServer';
import { DashboardView } from '../views/DashboardView';
@@ -32,6 +38,7 @@ export namespace HistoryUtil {
case 'doc':
onDocUrl(url);
break;
+ default:
}
}
}
@@ -124,11 +131,11 @@ export namespace HistoryUtil {
const val = customParser(pathname, opts, current);
if (val === null) {
return undefined;
- } else if (val === undefined) {
+ }
+ if (val === undefined) {
return current;
- } else {
- return val;
}
+ return val;
}
return current;
};
@@ -136,32 +143,33 @@ export namespace HistoryUtil {
function addStringifier(type: string, keys: string[], customStringifier?: (state: ParsedUrl, current: string) => string) {
stringifiers[type] = state => {
- let path = Utils.prepend(`/${type}`);
+ let path = ClientUtils.prepend(`/${type}`);
if (customStringifier) {
path = customStringifier(state, path);
}
const queryObj = OmitKeys(state, keys).extract;
const query: any = {};
- Object.keys(queryObj).forEach(key => (query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key])));
+ Object.keys(queryObj).forEach(key => {
+ query[key] = queryObj[key] === null ? null : JSON.stringify(queryObj[key]);
+ });
const queryString = qs.stringify(query);
return path + (queryString ? `?${queryString}` : '');
};
}
addParser('doc', {}, { readonly: true, initializers: true, nro: true, sharing: true }, (pathname, opts, current) => {
- if (pathname.length !== 2) return undefined;
-
- current.initializers = current.initializers || {};
- const docId = pathname[1];
- current.docId = docId;
- });
- addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => {
- return `${current}/${state.docId}`;
+ if (pathname.length === 2) {
+ current.initializers = current.initializers || {};
+ const docId = pathname[1];
+ current.docId = docId;
+ }
+ return undefined;
});
+ addStringifier('doc', ['initializers', 'readonly', 'nro'], (state, current) => `${current}/${state.docId}`);
export function parseUrl(location: Location | URL): ParsedUrl | undefined {
const pathname = location.pathname.substring(1);
- const search = location.search;
+ const { search } = location;
const opts = search.length ? qs.parse(search, { sort: false }) : {};
const pathnameSplit = pathname.split('/');
diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts
index c5f307f44..5a6522dc8 100644
--- a/src/client/util/HypothesisUtils.ts
+++ b/src/client/util/HypothesisUtils.ts
@@ -1,15 +1,11 @@
import { action, runInAction } from 'mobx';
-import { simulateMouseClick } from '../../Utils';
+import { simulateMouseClick } from '../../ClientUtils';
import { Doc, Opt } from '../../fields/Doc';
import { Cast, StrCast } from '../../fields/Types';
import { WebField } from '../../fields/URLField';
-import { DocumentType } from '../documents/DocumentTypes';
import { Docs } from '../documents/Documents';
import { DocumentLinksButton } from '../views/nodes/DocumentLinksButton';
import { DocumentView } from '../views/nodes/DocumentView';
-import { DocumentManager } from './DocumentManager';
-import { SearchUtil } from './SearchUtil';
-import { SelectionManager } from './SelectionManager';
export namespace Hypothesis {
/**
@@ -17,6 +13,7 @@ export namespace Hypothesis {
* If none exist, create and return a new WebDocument.
*/
export const getSourceWebDoc = async (uri: string) => {
+ // eslint-disable-next-line no-use-before-define
const result = await findWebDoc(uri);
console.log(result ? 'existing doc found' : 'existing doc NOT found');
return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _height: 512, _width: 400, data_useCors: true }); // create and return a new Web doc with given uri if no matching docs are found
@@ -26,21 +23,21 @@ export namespace Hypothesis {
* Search for a WebDocument whose url field matches the given uri, return undefined if not found
*/
export const findWebDoc = async (uri: string) => {
- const currentDoc = SelectionManager.Docs.lastElement();
+ const currentDoc = DocumentView.Selected().lastElement()?.Document;
if (currentDoc && Cast(currentDoc.data, WebField)?.url.href === uri) return currentDoc; // always check first whether the currently selected doc is the annotation's source, only use Search otherwise
const results: Doc[] = [];
// await SearchUtil.Search('web', true).then(
// action(async (res: SearchUtil.DocSearchResult) => {
// const docs = res.docs;
- // const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data);
+ // const filteredDocs = docs.filter(doc => doc.author === ClientUtils.CurrentUserEmail() && doc.type === DocumentType.WEB && doc.data);
// filteredDocs.forEach(doc => {
// uri === Cast(doc.data, WebField)?.url.href && results.push(doc); // TODO check visited sites history?
// });
// })
// );
- const onScreenResults = results.filter(doc => DocumentManager.Instance.getFirstDocumentView(doc));
+ const onScreenResults = results.filter(doc => DocumentView.getFirstDocumentView(doc));
return onScreenResults.length ? onScreenResults[0] : results.length ? results[0] : undefined; // prioritize results that are currently on the screen
};
@@ -66,7 +63,7 @@ export namespace Hypothesis {
DocumentLinksButton.AnnotationId = annotationId;
DocumentLinksButton.AnnotationUri = annotationUri;
});
- const endLinkView = DocumentManager.Instance.getFirstDocumentView(sourceDoc);
+ const endLinkView = DocumentView.getFirstDocumentView(sourceDoc);
const rect = document.body.getBoundingClientRect();
const x = rect.x + rect.width / 2;
const y = 250;
@@ -77,17 +74,19 @@ export namespace Hypothesis {
/**
* Send message to Hypothes.is client to edit an annotation to add a Dash hyperlink
*/
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
export const makeLink = async (title: string, url: string, annotationId: string, annotationSourceDoc: Doc) => {
// if the annotation's source webpage isn't currently loaded in Dash, we're not able to access and edit the annotation from the client
// so we're loading the webpage and its annotations invisibly in a WebBox in MainView.tsx, until the editing is done
- //!DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc);
+ //! DocumentManager.Instance.getFirstDocumentView(annotationSourceDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = annotationSourceDoc);
- var success = false;
+ let success = false;
const onSuccess = action(() => {
console.log('Edit success!!');
success = true;
+ // eslint-disable-next-line no-use-before-define
clearTimeout(interval);
- //DocumentLinksButton.invisibleWebDoc = undefined;
+ // DocumentLinksButton.invisibleWebDoc = undefined;
document.removeEventListener('editSuccess', onSuccess);
});
@@ -109,7 +108,7 @@ export namespace Hypothesis {
action(() => {
if (!success) {
clearInterval(interval);
- //DocumentLinksButton.invisibleWebDoc = undefined;
+ // DocumentLinksButton.invisibleWebDoc = undefined;
}
}),
10000
@@ -123,12 +122,13 @@ export namespace Hypothesis {
export const deleteLink = async (linkDoc: Doc, sourceDoc: Doc, destinationDoc: Doc) => {
if (Cast(destinationDoc.data, WebField)?.url.href !== StrCast(linkDoc.annotationUri)) return; // check that the destinationDoc is a WebDocument containing the target annotation
- //!DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink
+ //! DocumentManager.Instance.getFirstDocumentView(destinationDoc) && runInAction(() => DocumentLinksButton.invisibleWebDoc = destinationDoc); // see note in makeLink
- var success = false;
+ let success = false;
const onSuccess = action(() => {
console.log('Edit success!');
success = true;
+ // eslint-disable-next-line no-use-before-define
clearTimeout(interval);
// DocumentLinksButton.invisibleWebDoc = undefined;
document.removeEventListener('editSuccess', onSuccess);
@@ -151,7 +151,7 @@ export namespace Hypothesis {
action(() => {
if (!success) {
clearInterval(interval);
- //DocumentLinksButton.invisibleWebDoc = undefined;
+ // DocumentLinksButton.invisibleWebDoc = undefined;
}
}),
10000
@@ -163,10 +163,11 @@ export namespace Hypothesis {
* Send message to Hypothes.is client to scroll to an annotation when it loads
*/
export const scrollToAnnotation = (annotationId: string, target: Doc) => {
- var success = false;
+ let success = false;
const onSuccess = () => {
console.log('Scroll success!!');
document.removeEventListener('scrollSuccess', onSuccess);
+ // eslint-disable-next-line no-use-before-define
clearInterval(interval);
success = true;
};
@@ -179,7 +180,7 @@ export namespace Hypothesis {
bubbles: true,
})
);
- const targetView: Opt<DocumentView> = DocumentManager.Instance.getFirstDocumentView(target);
+ const targetView: Opt<DocumentView> = DocumentView.getFirstDocumentView(target);
const position = targetView?.screenToViewTransform().inverse().transformPoint(0, 0);
targetView && position && simulateMouseClick(targetView.ContentDiv!, position[0], position[1], position[0], position[1], false);
}, 300);
diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx
index 398ba3c04..baa0786b7 100644
--- a/src/client/util/Import & Export/DirectoryImportBox.tsx
+++ b/src/client/util/Import & Export/DirectoryImportBox.tsx
@@ -17,7 +17,6 @@
// import { DocumentType } from '../../documents/DocumentTypes';
// import { Networking } from '../../Network';
// import { FieldView, FieldViewProps } from '../../views/nodes/FieldView';
-// import { DocumentManager } from '../DocumentManager';
// import './DirectoryImportBox.scss';
// import ImportMetadataEntry, { keyPlaceholder, valuePlaceholder } from './ImportMetadataEntry';
// import * as React from 'react';
diff --git a/src/client/util/Import & Export/ImageUtils.ts b/src/client/util/Import & Export/ImageUtils.ts
index d99828956..8d4eefa7e 100644
--- a/src/client/util/Import & Export/ImageUtils.ts
+++ b/src/client/util/Import & Export/ImageUtils.ts
@@ -1,10 +1,10 @@
+import { ClientUtils } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
+import { DocData } from '../../../fields/DocSymbols';
+import { Id } from '../../../fields/FieldSymbols';
+import { Cast, NumCast, StrCast } from '../../../fields/Types';
import { ImageField } from '../../../fields/URLField';
-import { Cast, StrCast, NumCast } from '../../../fields/Types';
import { Networking } from '../../Network';
-import { Id } from '../../../fields/FieldSymbols';
-import { Utils } from '../../../Utils';
-import { DocData } from '../../../fields/DocSymbols';
export namespace ImageUtils {
export type imgInfo = {
@@ -16,7 +16,7 @@ export namespace ImageUtils {
};
export const ExtractImgInfo = async (document: Doc): Promise<imgInfo | undefined> => {
const field = Cast(document.data, ImageField);
- return field ? await Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
+ return field ? Networking.PostToServer('/inspectImage', { source: field.url.href }) : undefined;
};
export const AssignImgInfo = (document: Doc, data?: imgInfo) => {
@@ -35,7 +35,7 @@ export namespace ImageUtils {
export const ExportHierarchyToFileSystem = async (collection: Doc): Promise<void> => {
const a = document.createElement('a');
- a.href = Utils.prepend(`/imageHierarchyExport/${collection[Id]}`);
+ a.href = ClientUtils.prepend(`/imageHierarchyExport/${collection[Id]}`);
a.download = `Dash Export [${StrCast(collection.title)}].zip`;
a.click();
};
diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx
index 58a09b9c9..db1e3d6cd 100644
--- a/src/client/util/Import & Export/ImportMetadataEntry.tsx
+++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx
@@ -1,10 +1,13 @@
-import * as React from 'react';
-import { observer } from 'mobx-react';
-import { EditableView } from '../../views/EditableView';
-import { action, computed } from 'mobx';
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
import { Doc } from '../../../fields/Doc';
-import { StrCast, BoolCast } from '../../../fields/Types';
+import { BoolCast, StrCast } from '../../../fields/Types';
+import { EditableView } from '../../views/EditableView';
interface KeyValueProps {
Document: Doc;
@@ -93,18 +96,27 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps>
alignItems: 'center',
alignContent: 'center',
}}>
- <input onChange={e => (this.onDataDoc = e.target.checked)} ref={this.checkRef} style={{ margin: '0 10px 0 15px' }} type="checkbox" title={'Add to Data Document?'} checked={this.onDataDoc} />
- <div className={'key_container'} style={keyValueStyle}>
- <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine={true} />
+ <input
+ onChange={e => {
+ this.onDataDoc = e.target.checked;
+ }}
+ ref={this.checkRef}
+ style={{ margin: '0 10px 0 15px' }}
+ type="checkbox"
+ title="Add to Data Document?"
+ checked={this.onDataDoc}
+ />
+ <div className="key_container" style={keyValueStyle}>
+ <EditableView ref={this.keyRef} contents={this.key} SetValue={this.updateKey} GetValue={() => ''} oneLine />
</div>
- <div className={'value_container'} style={keyValueStyle}>
- <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine={true} />
+ <div className="value_container" style={keyValueStyle}>
+ <EditableView ref={this.valueRef} contents={this.value} SetValue={this.updateValue} GetValue={() => ''} oneLine />
</div>
- <div onClick={() => this.props.remove(this)} title={'Delete Entry'}>
+ <div onClick={() => this.props.remove(this)} title="Delete Entry">
<FontAwesomeIcon
- icon={'plus'}
- color={'red'}
- size={'1x'}
+ icon="plus"
+ color="red"
+ size="1x"
style={{
marginLeft: 15,
marginRight: 15,
diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx
index a2f5826fe..a07550e09 100644
--- a/src/client/util/InteractionUtils.tsx
+++ b/src/client/util/InteractionUtils.tsx
@@ -1,6 +1,6 @@
import * as React from 'react';
-import { GestureUtils } from '../../pen-gestures/GestureUtils';
import { Utils } from '../../Utils';
+import { Gestures } from '../../pen-gestures/GestureTypes';
import './InteractionUtils.scss';
export namespace InteractionUtils {
@@ -9,81 +9,86 @@ export namespace InteractionUtils {
export const PENTYPE = 'pen';
export const ERASERTYPE = 'eraser';
- const POINTER_PEN_BUTTON = -1;
- const REACT_POINTER_PEN_BUTTON = 0;
const ERASER_BUTTON = 5;
- export class MultiTouchEvent<T extends React.TouchEvent | TouchEvent> {
- constructor(
- readonly fingers: number,
- readonly targetTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly touches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly changedTouches: T extends React.TouchEvent ? React.Touch[] : Touch[],
- readonly touchEvent: T extends React.TouchEvent ? React.TouchEvent : TouchEvent
- ) {}
- }
-
- export interface MultiTouchEventDisposer {
- (): void;
- }
-
- /**
- *
- * @param element - element to turn into a touch target
- * @param startFunc - event handler, typically Touchable.onTouchStart (classes that inherit touchable can pass in this.onTouchStart)
- */
- export function MakeMultiTouchTarget(element: HTMLElement, startFunc: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
- const onMultiTouchStartHandler = (e: Event) => startFunc(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
- // const onMultiTouchMoveHandler = moveFunc ? (e: Event) => moveFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
- // const onMultiTouchEndHandler = endFunc ? (e: Event) => endFunc(e, (e as CustomEvent<MultiTouchEvent<TouchEvent>>).detail) : undefined;
- element.addEventListener('dashOnTouchStart', onMultiTouchStartHandler);
- // if (onMultiTouchMoveHandler) {
- // element.addEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
- // }
- // if (onMultiTouchEndHandler) {
- // element.addEventListener("dashOnTouchEnd", onMultiTouchEndHandler);
- // }
- return () => {
- element.removeEventListener('dashOnTouchStart', onMultiTouchStartHandler);
- // if (onMultiTouchMoveHandler) {
- // element.removeEventListener("dashOnTouchMove", onMultiTouchMoveHandler);
- // }
- // if (onMultiTouchEndHandler) {
- // element.removeEventListener("dashOnTouchend", onMultiTouchEndHandler);
- // }
- };
- }
-
- /**
- * Turns an element onto a target for touch hold handling.
- * @param element - element to add events to
- * @param func - function to add to the event
- */
- export function MakeHoldTouchTarget(element: HTMLElement, func: (e: Event, me: MultiTouchEvent<React.TouchEvent>) => void): MultiTouchEventDisposer {
- const handler = (e: Event) => func(e, (e as CustomEvent<MultiTouchEvent<React.TouchEvent>>).detail);
- element.addEventListener('dashOnTouchHoldStart', handler);
- return () => {
- element.removeEventListener('dashOnTouchHoldStart', handler);
- };
- }
-
- export function GetMyTargetTouches(mte: InteractionUtils.MultiTouchEvent<React.TouchEvent | TouchEvent>, prevPoints: Map<number, React.Touch>, ignorePen: boolean): React.Touch[] {
- const myTouches = new Array<React.Touch>();
- for (const pt of mte.touches) {
- if (!ignorePen || ((pt as any).radiusX > 1 && (pt as any).radiusY > 1)) {
- for (const tPt of mte.targetTouches) {
- if (tPt?.screenX === pt?.screenX && tPt?.screenY === pt?.screenY) {
- if (pt && prevPoints.has(pt.identifier)) {
- myTouches.push(pt);
- }
- }
+ export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
+ // if arrow/line/circle, the two end points should be the starting and the ending point
+ let left = points[0].X;
+ let top = points[0].Y;
+ let right = points[1].X;
+ let bottom = points[1].Y;
+ if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
+ // pointer is up (first and last points are the same)
+ if (![Gestures.Arrow, Gestures.Line, Gestures.Circle].includes(shape as any as Gestures)) {
+ // otherwise take max and min
+ const xs = points.map(p => p.X);
+ const ys = points.map(p => p.Y);
+ right = Math.max(...xs);
+ left = Math.min(...xs);
+ bottom = Math.max(...ys);
+ top = Math.min(...ys);
+ }
+ } else {
+ // if in the middle of drawing
+ // take first and last points
+ right = points[points.length - 1].X;
+ left = points[0].X;
+ bottom = points[points.length - 1].Y;
+ top = points[0].Y;
+ if (shape !== Gestures.Arrow && shape !== Gestures.Line && shape !== Gestures.Circle) {
+ // switch left/right and top/bottom if needed
+ if (left > right) {
+ const temp = right;
+ right = left;
+ left = temp;
+ }
+ if (top > bottom) {
+ const temp = top;
+ top = bottom;
+ bottom = temp;
}
}
}
- // if (mte.touches.length !== myTouches.length) {
- // throw Error("opo")
- // }
- return myTouches;
+ const polyPts = [];
+ switch (shape) {
+ case Gestures.Rectangle:
+ polyPts.push({ X: left, Y: top });
+ polyPts.push({ X: right, Y: top });
+ polyPts.push({ X: right, Y: bottom });
+ polyPts.push({ X: left, Y: bottom });
+ polyPts.push({ X: left, Y: top });
+ break;
+ case Gestures.Triangle:
+ polyPts.push({ X: left, Y: bottom });
+ polyPts.push({ X: right, Y: bottom });
+ polyPts.push({ X: (right + left) / 2, Y: top });
+ polyPts.push({ X: left, Y: bottom });
+ break;
+ case Gestures.Circle:
+ {
+ const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
+ const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
+ const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
+ for (let x = centerX - radius; x < centerX + radius; x++) {
+ const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+ polyPts.push({ X: x, Y: y });
+ }
+ for (let x = centerX + radius; x > centerX - radius; x--) {
+ const y = Math.sqrt(radius ** 2 - (x - centerX) ** 2) + centerY;
+ const newY = centerY - (y - centerY);
+ polyPts.push({ X: x, Y: newY });
+ }
+ polyPts.push({ X: centerX - radius, Y: Math.sqrt(radius ** 2 - (-radius) ** 2) + centerY });
+ }
+ break;
+
+ case Gestures.Line:
+ default:
+ polyPts.push({ X: left, Y: top });
+ polyPts.push({ X: right, Y: bottom });
+ break;
+ }
+ return polyPts;
}
export function CreatePolyline(
@@ -101,20 +106,20 @@ export namespace InteractionUtils {
arrowEnd: string,
markerScale: number,
dash: string | undefined,
- scalex: number,
- scaley: number,
+ scalexIn: number,
+ scaleyIn: number,
shape: string,
pevents: string,
opacity: number,
nodefs: boolean,
downHdlr?: (e: React.PointerEvent) => void,
- mask?: boolean,
- dropshadow?: string
+ mask?: boolean
+ // dropshadow?: string
) {
const pts = shape ? makePolygon(shape, points) : points;
- if (isNaN(scalex)) scalex = 1;
- if (isNaN(scaley)) scaley = 1;
+ const scalex = isNaN(scalexIn) ? 1 : scalexIn;
+ const scaley = isNaN(scaleyIn) ? 1 : scaleyIn;
const toScr = (p: { X: number; Y: number }) => ` ${!p ? 0 : (p.X - left - width / 2) * scalex + width / 2}, ${!p ? 0 : (p.Y - top - width / 2) * scaley + width / 2} `;
const strpts = bezier
@@ -137,7 +142,7 @@ export namespace InteractionUtils {
<defs>
{!mask ? null : (
<filter id={`mask${defGuid}`} x="-1" y="-1" width="500%" height="500%">
- <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5"></feGaussianBlur>
+ <feGaussianBlur result="blurOut" in="offOut" stdDeviation="5" />
</filter>
)}
{arrowStart !== 'dot' && arrowEnd !== 'dot' ? null : (
@@ -172,7 +177,7 @@ export namespace InteractionUtils {
<Tag
d={bezier ? strpts + (arrowStart || arrowEnd ? ' ' : '') : undefined}
points={bezier ? undefined : strpts}
- //filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
+ // filter={!dropshadow ? undefined : `drop-shadow(-1px -1px 0px ${dropshadow}) `}
style={{
// filter: drawHalo ? "url(#inkSelectionHalo)" : undefined,
fill: fill && fill !== 'transparent' ? fill : 'none',
@@ -193,83 +198,6 @@ export namespace InteractionUtils {
);
}
- export function makePolygon(shape: string, points: { X: number; Y: number }[]) {
- if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) {
- //pointer is up (first and last points are the same)
- if (shape === GestureUtils.Gestures.Arrow || shape === GestureUtils.Gestures.Line || shape === GestureUtils.Gestures.Circle) {
- //if arrow or line, the two end points should be the starting and the ending point
- var left = points[0].X;
- var top = points[0].Y;
- var right = points[1].X;
- var bottom = points[1].Y;
- } else {
- //otherwise take max and min
- const xs = points.map(p => p.X);
- const ys = points.map(p => p.Y);
- right = Math.max(...xs);
- left = Math.min(...xs);
- bottom = Math.max(...ys);
- top = Math.min(...ys);
- }
- } else {
- //if in the middle of drawing
- //take first and last points
- right = points[points.length - 1].X;
- left = points[0].X;
- bottom = points[points.length - 1].Y;
- top = points[0].Y;
- if (shape !== GestureUtils.Gestures.Arrow && shape !== GestureUtils.Gestures.Line && shape !== GestureUtils.Gestures.Circle) {
- //switch left/right and top/bottom if needed
- if (left > right) {
- const temp = right;
- right = left;
- left = temp;
- }
- if (top > bottom) {
- const temp = top;
- top = bottom;
- bottom = temp;
- }
- }
- }
- points = [];
- switch (shape) {
- case GestureUtils.Gestures.Rectangle:
- points.push({ X: left, Y: top });
- points.push({ X: right, Y: top });
- points.push({ X: right, Y: bottom });
- points.push({ X: left, Y: bottom });
- points.push({ X: left, Y: top });
- break;
- case GestureUtils.Gestures.Triangle:
- points.push({ X: left, Y: bottom });
- points.push({ X: right, Y: bottom });
- points.push({ X: (right + left) / 2, Y: top });
- points.push({ X: left, Y: bottom });
- break;
- case GestureUtils.Gestures.Circle:
- const centerX = (Math.max(left, right) + Math.min(left, right)) / 2;
- const centerY = (Math.max(top, bottom) + Math.min(top, bottom)) / 2;
- const radius = Math.max(centerX - Math.min(left, right), centerY - Math.min(top, bottom));
- for (var x = centerX - radius; x < centerX + radius; x++) {
- const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
- points.push({ X: x, Y: y });
- }
- for (var x = centerX + radius; x > centerX - radius; x--) {
- const y = Math.sqrt(Math.pow(radius, 2) - Math.pow(x - centerX, 2)) + centerY;
- const newY = centerY - (y - centerY);
- points.push({ X: x, Y: newY });
- }
- points.push({ X: centerX - radius, Y: Math.sqrt(Math.pow(radius, 2) - Math.pow(-radius, 2)) + centerY });
- break;
-
- case GestureUtils.Gestures.Line:
- points.push({ X: left, Y: top });
- points.push({ X: right, Y: bottom });
- break;
- }
- return points;
- }
/**
* Returns whether or not the pointer event passed in is of the type passed in
* @param e - pointer event. this event could be from a mouse, a pen, or a finger
@@ -279,10 +207,11 @@ export namespace InteractionUtils {
// prettier-ignore
switch (type) {
// pen and eraser are both pointer type 'pen', but pen is button 0 and eraser is button 5. -syip2
- case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
+ case PENTYPE: return e.pointerType === PENTYPE && (e.button === -1 || e.button === 0);
case ERASERTYPE: return e.pointerType === PENTYPE && e.button === (e instanceof PointerEvent ? ERASER_BUTTON : ERASER_BUTTON);
- case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
- }
+ case TOUCHTYPE: return e.pointerType === TOUCHTYPE;
+ default:
+ } // prettier-ignore
return e.pointerType === type;
}
@@ -292,7 +221,7 @@ export namespace InteractionUtils {
* @param pt2
*/
export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
- return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+ return Math.sqrt((pt1.clientX - pt2.clientX) ** 2 + (pt1.clientY - pt2.clientY) ** 2);
}
/**
@@ -349,72 +278,4 @@ export namespace InteractionUtils {
}
return 0;
}
-
- export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: React.Touch[], leniency: number): boolean {
- for (const touch of newTouches) {
- if (touch) {
- const oldTouch = oldTouches.get(touch.identifier);
- if (oldTouch) {
- if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
- return true;
- }
- }
- }
- }
- return false;
- }
-
- // These might not be very useful anymore, but I'll leave them here for now -syip2
- {
- /**
- * Returns the type of Touch Interaction from a list of points.
- * Also returns any data that is associated with a Touch Interaction
- * @param pts - List of points
- */
- // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
- // const leniency = 200;
- // switch (pts.length) {
- // case 1:
- // return { type: OneFinger };
- // case 2:
- // return { type: TwoSeperateFingers };
- // case 3:
- // let pt1 = pts[0];
- // let pt2 = pts[1];
- // let pt3 = pts[2];
- // if (pt1 && pt2 && pt3) {
- // let dist12 = TwoPointEuclidist(pt1, pt2);
- // let dist23 = TwoPointEuclidist(pt2, pt3);
- // let dist13 = TwoPointEuclidist(pt1, pt3);
- // let dist12close = dist12 < leniency;
- // let dist23close = dist23 < leniency;
- // let dist13close = dist13 < leniency;
- // let xor2313 = dist23close ? !dist13close : dist13close;
- // let xor = dist12close ? !xor2313 : xor2313;
- // // three input xor because javascript doesn't have logical xor's
- // if (xor) {
- // let points: number[] = [];
- // let min = Math.min(dist12, dist23, dist13);
- // switch (min) {
- // case dist12:
- // points = [0, 1, 2];
- // break;
- // case dist23:
- // points = [1, 2, 0];
- // break;
- // case dist13:
- // points = [0, 2, 1];
- // break;
- // }
- // return { type: TwoToOneFingers, data: points };
- // }
- // else {
- // return { type: ThreeSeperateFingers, data: null };
- // }
- // }
- // default:
- // return { type: undefined };
- // }
- // }
- }
}
diff --git a/src/client/util/KeyCodes.ts b/src/client/util/KeyCodes.ts
index de2457a5a..fb21f78ee 100644
--- a/src/client/util/KeyCodes.ts
+++ b/src/client/util/KeyCodes.ts
@@ -133,4 +133,4 @@ export class KeyCodes {
public static NUM_9: number = 57;
public static SUBTRACT: number = 189;
public static ADD: number = 187;
-} \ No newline at end of file
+}
diff --git a/src/client/util/LinkFollower.ts b/src/client/util/LinkFollower.ts
index 6c0bf3242..9a0edcfec 100644
--- a/src/client/util/LinkFollower.ts
+++ b/src/client/util/LinkFollower.ts
@@ -1,15 +1,12 @@
import { action, runInAction } from 'mobx';
-import { Doc, DocListCast, Field, FieldResult, Opt } from '../../fields/Doc';
-import { ScriptField } from '../../fields/ScriptField';
-import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../fields/Types';
+import { Doc, DocListCast, Opt } from '../../fields/Doc';
+import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
-import { OpenWhere } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FieldView';
+import { DocumentView } from '../views/nodes/DocumentView';
+import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { OpenWhere } from '../views/nodes/OpenWhere';
import { PresBox } from '../views/nodes/trails';
-import { DocumentManager } from './DocumentManager';
-import { LinkManager } from './LinkManager';
import { ScriptingGlobals } from './ScriptingGlobals';
-import { SelectionManager } from './SelectionManager';
import { SnappingManager } from './SnappingManager';
import { UndoManager } from './UndoManager';
/*
@@ -25,6 +22,9 @@ import { UndoManager } from './UndoManager';
* - user defined kvps
*/
export class LinkFollower {
+ public static Init() {
+ DocumentView.FollowLink = LinkFollower.FollowLink;
+ }
// follows a link - if the target is on screen, it highlights/pans to it.
// if the target isn't onscreen, then it will open up the target in the lightbox, or in place
// depending on the followLinkLocation property of the source (or the link itself as a fallback);
@@ -43,9 +43,9 @@ export class LinkFollower {
};
public static traverseLink(link: Opt<Doc>, sourceDoc: Doc, finished?: () => void, traverseBacklink?: boolean) {
- const getView = (doc: Doc) => DocumentManager.Instance.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
- const isAnchor = (sourceDoc: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, sourceDoc) || Doc.AreProtosEqual(anchor.annotationOn as Doc, sourceDoc);
- const linkDocs = link ? [link] : LinkManager.Links(sourceDoc);
+ const getView = (doc: Doc) => DocumentView.getFirstDocumentView(DocCast(doc.layout_unrendered ? doc.annotationOn : doc));
+ const isAnchor = (source: Doc, anchor: Doc) => Doc.AreProtosEqual(anchor, source) || Doc.AreProtosEqual(anchor.annotationOn as Doc, source);
+ const linkDocs = link ? [link] : Doc.Links(sourceDoc);
const fwdLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_1 as Doc)); // link docs where 'sourceDoc' is link_anchor_1
const backLinks = linkDocs.filter(l => isAnchor(sourceDoc, l.link_anchor_2 as Doc)); // link docs where 'sourceDoc' is link_anchor_2
const fwdLinkWithoutTargetView = fwdLinks.find(l => !getView(DocCast(l.link_anchor_2)));
@@ -53,7 +53,7 @@ export class LinkFollower {
const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView ?? backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView;
const linkDocList = linkWithoutTargetDoc && !sourceDoc.followAllLinks ? [linkWithoutTargetDoc] : traverseBacklink === undefined ? fwdLinks.concat(backLinks) : traverseBacklink ? backLinks : fwdLinks;
const followLinks = sourceDoc.followLinkToggle || sourceDoc.followAllLinks ? linkDocList : linkDocList.slice(0, 1);
- var count = 0;
+ let count = 0;
const allFinished = () => ++count === followLinks.length && finished?.();
if (!followLinks.length) {
finished?.();
@@ -69,7 +69,7 @@ export class LinkFollower {
? linkDoc.link_anchor_2
: linkDoc.link_anchor_1
) as Doc;
- const srcAnchor = LinkManager.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
+ const srcAnchor: Doc = Doc.getOppositeAnchor(linkDoc, target) ?? sourceDoc;
if (target) {
const doFollow = (canToggle?: boolean) => {
const toggleTarget = canToggle && BoolCast(sourceDoc.followLinkToggle);
@@ -88,14 +88,14 @@ export class LinkFollower {
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
- SelectionManager.DeselectAll();
- if (!DocumentManager.Instance.AddViewRenderedCb(target, dv => containerDocContext.length && (dv.ComponentView as PresBox).PlayTrail(containerDocContext))) {
+ const containerDocContext = DocumentView.getContextPath(sourceDoc, true); // gather all views that affect layout of sourceDoc so we can revert them after playing the rail
+ DocumentView.DeselectAll();
+ if (!DocumentView.addViewRenderedCb(target, dv => containerDocContext.length && dv.ComponentView?.playTrail?.(containerDocContext))) {
PresBox.OpenPresMinimized(target, [0, 0]);
}
finished?.();
} else {
- DocumentManager.Instance.showDocument(target, options, allFinished);
+ DocumentView.showDocument(target, options, allFinished);
}
};
let movedTarget = false;
@@ -113,10 +113,12 @@ export class LinkFollower {
}
const moveTo = [NumCast(sourceDoc.x) + NumCast(sourceDoc.followLinkXoffset), NumCast(sourceDoc.y) + NumCast(sourceDoc.followLinkYoffset)];
if (srcAnchor.followLinkXoffset !== undefined && moveTo[0] !== target.x) {
+ // eslint-disable-next-line prefer-destructuring
target.x = moveTo[0];
movedTarget = true;
}
if (srcAnchor.followLinkYoffset !== undefined && moveTo[1] !== target.y) {
+ // eslint-disable-next-line prefer-destructuring
target.y = moveTo[1];
movedTarget = true;
}
@@ -130,14 +132,8 @@ export class LinkFollower {
}
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function followLink(doc: Doc, altKey: boolean) {
- SelectionManager.DeselectAll();
+ DocumentView.DeselectAll();
return LinkFollower.FollowLink(undefined, doc, altKey) ? undefined : { select: true };
});
-
-export function FollowLinkScript() {
- return ScriptField.MakeScript('return followLink(this,altKey)', { altKey: 'boolean' });
-}
-export function IsFollowLinkScript(field: FieldResult<Field>) {
- return ScriptCast(field)?.script.originalScript.includes('return followLink(');
-}
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 8972bf705..56d5dce4e 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -1,12 +1,16 @@
-import { action, makeObservable, observable, observe, runInAction } from 'mobx';
+import { action, makeObservable, observable, observe } from 'mobx';
import { computedFn } from 'mobx-utils';
-import { Doc, DocListCast, DocListCastAsync, Field, Opt } from '../../fields/Doc';
+import * as rp from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Doc, DocListCast, DocListCastAsync, FieldType, Opt } from '../../fields/Doc';
import { DirectLinks, DocData } from '../../fields/DocSymbols';
import { FieldLoader } from '../../fields/FieldLoader';
+import { Id } from '../../fields/FieldSymbols';
import { List } from '../../fields/List';
import { ProxyField } from '../../fields/Proxy';
import { Cast, DocCast, PromiseValue, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
+import { DocumentType } from '../documents/DocumentTypes';
import { ScriptingGlobals } from './ScriptingGlobals';
/*
* link doc:
@@ -21,12 +25,13 @@ import { ScriptingGlobals } from './ScriptingGlobals';
* - user defined kvps
*/
export class LinkManager {
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: LinkManager;
@observable.shallow userLinkDBs: Doc[] = [];
@observable public currentLink: Opt<Doc> = undefined;
@observable public currentLinkAnchor: Opt<Doc> = undefined;
- public static get Instance() {
- return LinkManager._instance;
+ public static get Instance(): LinkManager {
+ return Doc.UserDoc() ? LinkManager._instance ?? new LinkManager() : (undefined as any as LinkManager);
}
public static Links(doc: Doc | undefined) {
@@ -42,9 +47,13 @@ export class LinkManager {
this.userLinkDBs.push(linkDb);
};
public static AutoKeywords = 'keywords:Usages';
- constructor() {
+ private constructor() {
makeObservable(this);
LinkManager._instance = this;
+ Doc.AddLink = this.addLink;
+ Doc.DeleteLink = this.deleteLink;
+ Doc.Links = LinkManager.Links;
+ Doc.getOppositeAnchor = LinkManager.getOppositeAnchor;
this.createlink_relationshipLists();
// since this is an action, not a reaction, we get only one shot to add this link to the Anchor docs
// Thus make sure all promised values are resolved from link -> link.proto -> link.link_anchor_[1,2] -> link.link_anchor_[1,2].proto
@@ -56,7 +65,7 @@ export class LinkManager {
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(
link &&
- action(lAnchProtoProtos => {
+ action(() => {
Doc.AddDocToList(Doc.UserDoc(), 'links', link);
lAnchs[0]?.[DocData][DirectLinks].add(link);
lAnchs[1]?.[DocData][DirectLinks].add(link);
@@ -73,7 +82,7 @@ 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 => {
+ action(() => {
link && lAnchs[0] && lAnchs[0][DocData][DirectLinks].delete(link);
link && lAnchs[1] && lAnchs[1][DocData][DirectLinks].delete(link);
})
@@ -84,7 +93,7 @@ export class LinkManager {
);
const watchUserLinkDB = (userLinkDBDoc: Doc) => {
- const toRealField = (field: Field) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
+ const toRealField = (field: FieldType) => (field instanceof ProxyField ? field.value : field); // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
if (userLinkDBDoc.data) {
observe(
userLinkDBDoc.data,
@@ -95,7 +104,8 @@ export class LinkManager {
(change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
(change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
break;
- case 'update': //let oldValue = change.oldValue;
+ case 'update': // let oldValue = change.oldValue;
+ default:
}
},
true
@@ -115,6 +125,8 @@ export class LinkManager {
added?.forEach((link: any) => addLinkToDoc(toRealField(link)));
removed?.forEach((link: any) => remLinkFromDoc(toRealField(link)));
});
+ break;
+ default:
}
},
true
@@ -128,7 +140,8 @@ export class LinkManager {
case 'splice':
(change as any).added.forEach(watchUserLinkDB);
break;
- case 'update': //let oldValue = change.oldValue;
+ case 'update': // let oldValue = change.oldValue;
+ default:
}
},
true
@@ -138,7 +151,7 @@ export class LinkManager {
}
public createlink_relationshipLists = () => {
- //create new lists for link relations and their associated colors if the lists don't already exist
+ // create new lists for link relations and their associated colors if the lists don't already exist
!Doc.UserDoc().link_relationshipList && (Doc.UserDoc().link_relationshipList = new List<string>());
!Doc.UserDoc().link_ColorList && (Doc.UserDoc().link_ColorList = new List<string>());
!Doc.UserDoc().link_relationshipSizes && (Doc.UserDoc().link_relationshipSizes = new List<number>());
@@ -148,7 +161,8 @@ export class LinkManager {
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);
+ // eslint-disable-next-line no-use-before-define
+ setTimeout(UPDATE_SERVER_CACHE, 100);
}
}
public deleteLink(linkDoc: Doc) {
@@ -188,9 +202,7 @@ export class LinkManager {
);
};
- relatedLinker = computedFn(function relatedLinker(this: any, anchor: Doc): Doc[] {
- return this.computedRelatedLinks(anchor, [anchor]);
- }, true);
+ relatedLinker = computedFn((anchor: Doc): Doc[] => this.computedRelatedLinks(anchor, [anchor]), true);
// returns map of group type to anchor's links in that group type
public getRelatedGroupedLinks(anchor: Doc): Map<string, Array<Doc>> {
@@ -198,7 +210,7 @@ export class LinkManager {
this.relatedLinker(anchor).forEach(link => {
if (link.link_relationship && link.link_relationship !== '-ungrouped-') {
const relation = StrCast(link.link_relationship);
- const anchorRelation = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
+ const anchorRelation: string = relation.indexOf(':') !== -1 ? relation.split(':')[Doc.AreProtosEqual(Cast(link.link_anchor_1, Doc, null), anchor) ? 0 : 1] : relation;
const group = anchorGroups.get(anchorRelation);
anchorGroups.set(anchorRelation, group ? [...group, link] : [link]);
} else {
@@ -229,18 +241,48 @@ export class LinkManager {
if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a1?.annotationOn, a1))) return '1';
if (Doc.AreProtosEqual(DocCast(anchor?.annotationOn, anchor), DocCast(a2?.annotationOn, a2))) return '2';
if (Doc.AreProtosEqual(anchor, linkDoc)) return '0';
-
- // const a1 = DocCast(linkDoc.link_anchor_1);
- // const a2 = DocCast(linkDoc.link_anchor_2);
- // if (linkDoc.link_matchEmbeddings) {
- // return [a2, a2.annotationOn].includes(anchor) ? '2' : '1';
- // }
- // if (Doc.AreProtosEqual(a2, anchor) || Doc.AreProtosEqual(a2.annotationOn as Doc, anchor)) return '2';
- // return Doc.AreProtosEqual(a1, anchor) || Doc.AreProtosEqual(a1.annotationOn as Doc, anchor) ? '1' : '2';
+ return undefined;
}
}
+let cacheDocumentIds = ''; // ; separate string of all documents ids in the user's working set (cached on the server)
+export function UPDATE_SERVER_CACHE() {
+ const prototypes = Object.values(DocumentType)
+ .filter(type => type !== DocumentType.NONE)
+ .map(type => DocServer.GetCachedRefField(type + 'Proto'))
+ .filter(doc => doc instanceof Doc)
+ .map(doc => doc as Doc);
+ const references = new Set<Doc>(prototypes);
+ Doc.FindReferences(Doc.UserDoc(), references, undefined);
+ DocListCast(DocCast(Doc.UserDoc().myLinkDatabase).data).forEach(link => {
+ if (!references.has(DocCast(link.link_anchor_1)) && !references.has(DocCast(link.link_anchor_2))) {
+ Doc.RemoveDocFromList(DocCast(Doc.UserDoc().myLinkDatabase), 'data', link);
+ Doc.AddDocToList(Doc.MyRecentlyClosed, undefined, link);
+ }
+ });
+ LinkManager.Instance.userLinkDBs.forEach(linkDb => Doc.FindReferences(linkDb, references, undefined));
+ const filtered = Array.from(references);
+
+ const newCacheUpdate = filtered.map(doc => doc[Id]).join(';');
+ if (newCacheUpdate === cacheDocumentIds) return;
+ cacheDocumentIds = newCacheUpdate;
+
+ // print out cached docs
+ Doc.MyDockedBtns.linearView_IsOpen && console.log('Set cached docs = ');
+ const isFiltered = filtered.filter(doc => !Doc.IsSystem(doc));
+ const strings = isFiltered.map(doc => StrCast(doc.title) + ' ' + (Doc.IsDataProto(doc) ? '(data)' : '(embedding)'));
+ Doc.MyDockedBtns.linearView_IsOpen && strings.sort().forEach((str, i) => console.log(i.toString() + ' ' + str));
+
+ rp.post(ClientUtils.prepend('/setCacheDocumentIds'), {
+ body: {
+ cacheDocumentIds,
+ },
+ json: true,
+ });
+}
+
ScriptingGlobals.add(
+ // eslint-disable-next-line prefer-arrow-callback
function links(doc: any) {
return new List(LinkManager.Links(doc));
},
diff --git a/src/client/util/PingManager.ts b/src/client/util/PingManager.ts
index 7638e2ce0..255e9cee0 100644
--- a/src/client/util/PingManager.ts
+++ b/src/client/util/PingManager.ts
@@ -1,8 +1,10 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import { Networking } from '../Network';
-import { CurrentUserUtils } from './CurrentUserUtils';
+import { SnappingManager } from './SnappingManager';
+
export class PingManager {
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: PingManager;
@observable IsBeating = true;
static get Instance(): PingManager {
@@ -29,7 +31,9 @@ export class PingManager {
sendPing = async (): Promise<void> => {
try {
const res = await Networking.PostToServer('/ping', { date: new Date() });
- runInAction(() => (CurrentUserUtils.ServerVersion = res.message));
+ runInAction(() => {
+ SnappingManager.SetServerVersion(res.message);
+ });
!this.IsBeating && this.setIsBeating(true);
} catch {
if (this.IsBeating) {
diff --git a/src/client/util/RTFMarkup.tsx b/src/client/util/RTFMarkup.tsx
index 35b1579df..248fda7e3 100644
--- a/src/client/util/RTFMarkup.tsx
+++ b/src/client/util/RTFMarkup.tsx
@@ -2,18 +2,17 @@ import { action, computed, makeObservable, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
-import { SettingsManager } from './SettingsManager';
+import { SnappingManager } from './SnappingManager';
@observer
export class RTFMarkup extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
static Instance: RTFMarkup;
@observable private isOpen = false; // whether the SharingManager modal is open or not
- @action
- public open = () => (this.isOpen = true);
-
- @action
- public close = () => (this.isOpen = false);
+ public setOpen = action((status: boolean) => {
+ this.isOpen = status;
+ });
constructor(props: {}) {
super(props);
@@ -21,20 +20,18 @@ export class RTFMarkup extends React.Component<{}> {
RTFMarkup.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined = undefined;
-
/**
* @returns the main interface of the SharingManager.
*/
@computed get cheatSheet() {
return (
- <div style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor, textAlign: 'initial', height: '100%' }}>
+ <div style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor, textAlign: 'initial', height: '100%' }}>
<p>
- <b style={{ fontSize: 'larger' }}>{`(@wiki:phrase)`}</b>
+ <b style={{ fontSize: 'larger' }}>(@wiki:phrase)</b>
{` display wikipedia page for entered text (terminate with carriage return)`}
</p>
<p>
- <b style={{ fontSize: 'larger' }}>{`(( any text ))`}</b>
+ <b style={{ fontSize: 'larger' }}>(( any text ))</b>
{` submit text to Chat GPT to have results appended afterward`}
</p>
<p>
@@ -132,11 +129,11 @@ export class RTFMarkup extends React.Component<{}> {
render() {
return (
<MainViewModal
- dialogueBoxStyle={{ backgroundColor: SettingsManager.userBackgroundColor, alignContent: 'normal', color: SettingsManager.userColor, padding: '16px' }}
+ dialogueBoxStyle={{ backgroundColor: SnappingManager.userBackgroundColor, alignContent: 'normal', color: SnappingManager.userColor, padding: '16px' }}
contents={this.cheatSheet}
isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
+ interactive
+ closeOnExternalClick={() => this.setOpen(false)}
/>
);
}
diff --git a/src/client/util/ReplayMovements.ts b/src/client/util/ReplayMovements.ts
index b881f18b4..530fcf211 100644
--- a/src/client/util/ReplayMovements.ts
+++ b/src/client/util/ReplayMovements.ts
@@ -2,10 +2,10 @@ import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { Doc, IdToDoc } from '../../fields/Doc';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
import { CollectionFreeFormView } from '../views/collections/collectionFreeForm';
-import { OpenWhereMod } from '../views/nodes/DocumentView';
+import { OpenWhereMod } from '../views/nodes/OpenWhere';
import { VideoBox } from '../views/nodes/VideoBox';
-import { DocumentManager } from './DocumentManager';
import { Movement, Presentation } from './TrackMovements';
+import { DocumentView } from '../views/nodes/DocumentView';
export class ReplayMovements {
private timers: NodeJS.Timeout[] | null;
@@ -14,6 +14,7 @@ export class ReplayMovements {
private isPlaying: boolean;
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: ReplayMovements;
static get Instance(): ReplayMovements {
return ReplayMovements._instance;
@@ -90,7 +91,7 @@ export class ReplayMovements {
loadPresentation = (presentation: Presentation) => {
const { movements } = presentation;
if (movements === null) {
- throw '[recordingApi.ts] followMovements() failed: no presentation data';
+ throw new Error('[recordingApi.ts] followMovements() failed: no presentation data');
}
movements.forEach((movement, i) => {
@@ -105,10 +106,8 @@ export class ReplayMovements {
// returns undefined if the docView isn't open on the screen
getCollectionFFView = (doc: Doc) => {
- const isInView = DocumentManager.Instance.getDocumentView(doc);
- if (isInView) {
- return isInView.ComponentView as CollectionFreeFormView;
- }
+ const isInView = DocumentView.getDocumentView(doc);
+ return isInView?.ComponentView as CollectionFreeFormView;
};
// will open the doc in a tab then return the CollectionFFView that holds it
@@ -119,7 +118,7 @@ export class ReplayMovements {
}
// console.log('openTab', docId, doc);
CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
- const docView = DocumentManager.Instance.getDocumentView(doc);
+ const docView = DocumentView.getDocumentView(doc);
// BUG - this returns undefined if the doc is already open
return docView?.ComponentView as CollectionFreeFormView;
};
@@ -136,9 +135,9 @@ export class ReplayMovements {
if (movements === null) return new Map();
// generate a set of all unique docIds
const docIdtoFirstMove = new Map<Doc, Movement>();
- for (const move of movements) {
+ movements.forEach(move => {
if (!docIdtoFirstMove.has(move.doc)) docIdtoFirstMove.set(move.doc, move);
- }
+ });
return docIdtoFirstMove;
};
@@ -151,10 +150,10 @@ export class ReplayMovements {
// console.info('playMovements', presentation, timeViewed, docIdtoDoc);
if (presentation.movements === null || presentation.movements.length === 0) {
- //|| this.playFFView === null) {
- return new Error('[recordingApi.ts] followMovements() failed: no presentation data');
+ // || this.playFFView === null) {
+ return '[recordingApi.ts] followMovements() failed: no presentation data';
}
- if (this.isPlaying) return;
+ if (this.isPlaying) return undefined;
this.isPlaying = true;
Doc.UserDoc().presentationMode = 'watching';
@@ -170,10 +169,10 @@ export class ReplayMovements {
// for the open tabs, set it to the first move
const docIdtoFirstMove = this.getFirstMovements(filteredMovements);
- for (const [doc, firstMove] of docIdtoFirstMove) {
+ Array.from(docIdtoFirstMove).forEach(([doc, firstMove]) => {
const colFFView = this.getCollectionFFView(doc);
if (colFFView) this.zoomAndPan(firstMove, colFFView);
- }
+ });
};
handleFirstMovements();
@@ -197,5 +196,6 @@ export class ReplayMovements {
}
}, timeDiff);
});
+ return undefined;
};
}
diff --git a/src/client/util/ScriptManager.ts b/src/client/util/ScriptManager.ts
index 87509f2ea..9158f6c0b 100644
--- a/src/client/util/ScriptManager.ts
+++ b/src/client/util/ScriptManager.ts
@@ -7,8 +7,10 @@ import { ScriptingGlobals } from './ScriptingGlobals';
export class ScriptManager {
static _initialized = false;
+ // eslint-disable-next-line no-use-before-define
private static _instance: ScriptManager;
public static get Instance(): ScriptManager {
+ // eslint-disable-next-line no-return-assign
return this._instance || (this._instance = new this());
}
private constructor() {
@@ -58,14 +60,16 @@ export class ScriptManager {
const params = Cast(scriptDoc['data-params'], listSpec('string'), []);
const paramNames = params.reduce((o: string, p: string) => {
+ let out = o;
if (params.indexOf(p) === params.length - 1) {
- o = o + p.split(':')[0].trim();
+ out += p.split(':')[0].trim();
} else {
- o = o + p.split(':')[0].trim() + ',';
+ out += p.split(':')[0].trim() + ',';
}
- return o;
+ return out;
}, '' as string);
+ // eslint-disable-next-line no-new-func
const f = new Function(paramNames, StrCast(scriptDoc.script));
Object.defineProperty(f, 'name', { value: StrCast(scriptDoc.name), writable: false });
diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts
index 422e708bc..6948469cc 100644
--- a/src/client/util/Scripting.ts
+++ b/src/client/util/Scripting.ts
@@ -1,15 +1,17 @@
+/* eslint-disable import/no-unresolved */
+/* eslint-disable import/no-webpack-loader-syntax */
// export const ts = (window as any).ts;
// // @ts-ignore
// import * as typescriptlib from '!!raw-loader!../../../node_modules/typescript/lib/lib.d.ts'
-// // @ts-ignore
// import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts'
-// @ts-ignore
+// eslint-disable-next-line node/no-unpublished-import
import * as typescriptlib from '!!raw-loader!./type_decls.d';
import * as ts from 'typescript';
-import { Doc, Field } from '../../fields/Doc';
+import { Doc, FieldType } from '../../fields/Doc';
import { RefField } from '../../fields/RefField';
import { ScriptField } from '../../fields/ScriptField';
-import { scriptingGlobals, ScriptingGlobals } from './ScriptingGlobals';
+import { ScriptingGlobals, scriptingGlobals } from './ScriptingGlobals';
+
export { ts };
export interface ScriptSuccess {
@@ -30,6 +32,7 @@ export type ScriptParam = { [name: string]: string };
export interface CompiledScript {
readonly compiled: true;
readonly originalScript: string;
+ // eslint-disable-next-line no-use-before-define
readonly options: Readonly<ScriptOptions>;
run(args?: { [name: string]: any }, onError?: (res: any) => void, errorVal?: any): ScriptResult;
}
@@ -47,6 +50,7 @@ export function isCompileError(toBeDetermined: CompileResult): toBeDetermined is
return false;
}
+// eslint-disable-next-line no-use-before-define
function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult {
const errors = diagnostics.filter(diag => diag.category === ts.DiagnosticCategory.Error);
if ((options.typecheck !== false && errors.length) || !script) {
@@ -60,6 +64,7 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
// let params: any[] = [Docs, ...fieldTypes];
const compiledFunction = (() => {
try {
+ // eslint-disable-next-line no-new-func
return new Function(...paramNames, `return ${script}`);
} catch (e) {
console.log(e);
@@ -68,20 +73,17 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an
})();
if (!compiledFunction) return { compiled: false, errors };
const { capturedVariables = {} } = options;
+ // eslint-disable-next-line default-param-last
const run = (args: { [name: string]: any } = {}, onError?: (e: any) => void, errorVal?: any): ScriptResult => {
const argsArray: any[] = [];
+ // eslint-disable-next-line no-restricted-syntax
for (const name of customParams) {
- if (name === 'this') {
- continue;
- }
- if (name in args) {
- argsArray.push(args[name]);
- } else {
- argsArray.push(capturedVariables[name]);
+ if (name !== 'this') {
+ argsArray.push(name in args ? args[name] : capturedVariables[name]);
}
}
const thisParam = args.this || capturedVariables.this;
- let batch: { end(): void } | undefined = undefined;
+ let batch: { end(): void } | undefined;
try {
if (!options.editable) {
batch = Doc.MakeReadOnly();
@@ -109,7 +111,7 @@ class ScriptingCompilerHost {
files: File[] = [];
// getSourceFile(fileName: string, languageVersion: ts.ScriptTarget, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): ts.SourceFile | undefined {
- getSourceFile(fileName: string, languageVersion: any, onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined): any | undefined {
+ getSourceFile(fileName: string, languageVersion: any /* , onError?: ((message: string) => void) | undefined, shouldCreateNewSourceFile?: boolean | undefined */): any | undefined {
const contents = this.readFile(fileName);
if (contents !== undefined) {
return ts.createSourceFile(fileName, contents, languageVersion, true);
@@ -118,11 +120,11 @@ class ScriptingCompilerHost {
}
// getDefaultLibFileName(options: ts.CompilerOptions): string {
- getDefaultLibFileName(options: any): string {
+ getDefaultLibFileName(/* options: any */): string {
return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means...
}
writeFile(fileName: string, content: string) {
- const file = this.files.find(file => file.fileName === fileName);
+ const file = this.files.find(f => f.fileName === fileName);
if (file) {
file.content = content;
} else {
@@ -145,7 +147,7 @@ class ScriptingCompilerHost {
return this.files.some(file => file.fileName === fileName);
}
readFile(fileName: string): string | undefined {
- const file = this.files.find(file => file.fileName === fileName);
+ const file = this.files.find(f => f.fileName === fileName);
if (file) {
return file.content;
}
@@ -157,7 +159,7 @@ export type Traverser = (node: ts.Node, indentation: string) => boolean | void;
export type TraverserParam = Traverser | { onEnter: Traverser; onLeave: Traverser };
export type Transformer = {
transformer: ts.TransformerFactory<ts.Node>;
- getVars?: () => { [name: string]: Field };
+ getVars?: () => { [name: string]: FieldType };
};
export interface ScriptOptions {
requiredType?: string; // does function required a typed return value
@@ -193,7 +195,6 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
if (found) return found as CompiledScript;
const { requiredType = '', addReturn = false, params = {}, capturedVariables = {}, typecheck = true } = options;
if (options.params && !options.params.this) options.params.this = Doc.name;
- if (options.params && !options.params.self) options.params.self = Doc.name;
if (options.globals) {
ScriptingGlobals.setScriptingGlobals(options.globals);
}
@@ -210,12 +211,13 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
const newCaptures = options.transformer.getVars?.();
if (Object.keys(newCaptures ?? {}).length) {
// tslint:disable-next-line: prefer-object-spread
- //options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
+ // options.capturedVariables = Object.assign(capturedVariables, newCaptures!) as any;
- const transformed = result.transformed;
+ const { transformed } = result;
const printer = ts.createPrinter({
newLine: ts.NewLineKind.LineFeed,
});
+ // eslint-disable-next-line no-param-reassign
script = printer.printFile(transformed[0].getSourceFile());
}
result.dispose();
@@ -224,19 +226,23 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
if ('this' in params || 'this' in capturedVariables) {
paramNames.push('this');
}
+ // eslint-disable-next-line no-restricted-syntax
for (const key in params) {
- if (key === 'this') continue;
- paramNames.push(key);
+ if (key !== 'this') {
+ paramNames.push(key);
+ }
}
const paramList = paramNames.map(key => {
const val = params[key];
return `${key}: ${val}`;
});
+ // eslint-disable-next-line no-restricted-syntax
for (const key in capturedVariables) {
- if (key === 'this') continue;
- const val = capturedVariables[key];
- paramNames.push(key);
- paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ if (key !== 'this') {
+ const val = capturedVariables[key];
+ paramNames.push(key);
+ paramList.push(`${key}: ${typeof val === 'object' ? Object.getPrototypeOf(val).constructor.name : typeof val}`);
+ }
}
const paramString = paramList.join(', ');
const body = addReturn && !script.startsWith('{ return') ? `return ${script};` : script;
@@ -259,8 +265,3 @@ export function CompileScript(script: string, options: ScriptOptions = {}): Comp
!signature.includes('XXX') && ScriptField._scriptFieldCache.set(script + ':' + signature, result as CompiledScript);
return result;
}
-
-ScriptingGlobals.add(CompileScript);
-ScriptingGlobals.add(function runScript(doc: Doc, script: ScriptField) {
- return script?.script.run({ this: doc }).result;
-});
diff --git a/src/client/util/ScriptingGlobals.ts b/src/client/util/ScriptingGlobals.ts
index f151acd81..ac524394a 100644
--- a/src/client/util/ScriptingGlobals.ts
+++ b/src/client/util/ScriptingGlobals.ts
@@ -1,8 +1,18 @@
+import ts from 'typescript';
-import * as ts from "typescript";
export { ts };
+const _scriptingGlobals: { [name: string]: any } = {};
+const _scriptingDescriptions: { [name: string]: any } = {};
+const _scriptingParams: { [name: string]: any } = {};
+// eslint-disable-next-line import/no-mutable-exports
+export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
export namespace ScriptingGlobals {
+ export function getGlobals() { return Object.keys(_scriptingGlobals); } // prettier-ignore
+ export function getGlobalObj() { return _scriptingGlobals; } // prettier-ignore
+ export function getDescriptions() { return _scriptingDescriptions; } // prettier-ignore
+ export function getParameters() { return _scriptingParams; } // prettier-ignore
+
export function add(global: { name: string }): void;
export function add(name: string, global: any): void;
export function add(global: { name: string }, decription?: string, params?: string): void;
@@ -11,7 +21,7 @@ export namespace ScriptingGlobals {
let obj: any;
if (second !== undefined) {
- if (typeof first === "string") {
+ if (typeof first === 'string') {
n = first;
obj = second;
} else {
@@ -22,18 +32,21 @@ export namespace ScriptingGlobals {
_scriptingParams[n] = third;
}
}
- } else if (first && typeof first.name === "string") {
+ } else if (first && typeof first.name === 'string') {
n = first.name;
obj = first;
} else {
- throw new Error("Must either register an object with a name, or give a name and an object");
+ throw new Error('Must either register an object with a name, or give a name and an object');
}
- if (n === undefined || n === "undefined") {
+ if (n === undefined || n === 'undefined') {
return false;
- } else if (_scriptingGlobals.hasOwnProperty(n)) {
+ }
+ // eslint-disable-next-line no-prototype-builtins
+ if (_scriptingGlobals.hasOwnProperty(n)) {
throw new Error(`Global with name ${n} is already registered, choose another name`);
}
_scriptingGlobals[n] = obj;
+ return true;
}
export function makeMutableGlobalsCopy(globals?: { [name: string]: any }) {
return { ..._scriptingGlobals, ...(globals || {}) };
@@ -57,25 +70,16 @@ export namespace ScriptingGlobals {
return false;
}
- export function resetScriptingGlobals() { scriptingGlobals = _scriptingGlobals; }
+ export function resetScriptingGlobals() {
+ scriptingGlobals = _scriptingGlobals;
+ }
// const types = Object.keys(ts.SyntaxKind).map(kind => ts.SyntaxKind[kind]);
- export function printNodeType(node: any, indentation = "") { console.log(indentation + ts.SyntaxKind[node.kind]); }
-
- export function getGlobals() { return Object.keys(_scriptingGlobals); }
-
- export function getGlobalObj() { return _scriptingGlobals; }
-
- export function getDescriptions() { return _scriptingDescriptions; }
-
- export function getParameters() { return _scriptingParams; }
+ export function printNodeType(node: any, indentation = '') {
+ console.log(indentation + ts.SyntaxKind[node.kind]);
+ }
}
-export function scriptingGlobal(constructor: { new(...args: any[]): any }) {
+export function scriptingGlobal(constructor: { new (...args: any[]): any }) {
ScriptingGlobals.add(constructor);
}
-
-const _scriptingGlobals: { [name: string]: any } = {};
-export let scriptingGlobals: { [name: string]: any } = _scriptingGlobals;
-const _scriptingDescriptions: { [name: string]: any } = {};
-const _scriptingParams: { [name: string]: any } = {}; \ No newline at end of file
diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index fff2737b6..609fedfa9 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -1,5 +1,5 @@
import { ObservableMap } from 'mobx';
-import { Doc, DocListCast, Field, Opt } from '../../fields/Doc';
+import { Doc, DocListCast, Field, FieldType, Opt } from '../../fields/Doc';
import { Id } from '../../fields/FieldSymbols';
import { StrCast } from '../../fields/Types';
import { DocumentType } from '../documents/DocumentTypes';
@@ -8,16 +8,16 @@ import { DocOptions, FInfo } from '../documents/Documents';
export namespace SearchUtil {
export type HighlightingResult = { [id: string]: { [key: string]: string[] } };
- export function SearchCollection(collectionDoc: Opt<Doc>, query: string, matchKeyNames: boolean, onlyKeys?: string[]) {
+ export function SearchCollection(collectionDoc: Opt<Doc>, queryIn: string, matchKeyNames: boolean, onlyKeys?: string[]) {
const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING];
const blockedKeys = matchKeyNames
? []
: Object.entries(DocOptions)
- .filter(([key, info]: [string, FInfo]) => !info?.searchable())
+ .filter(([, info]: [string, FInfo]) => !info?.searchable())
.map(([key]) => key);
- const exact = query.startsWith('=');
- query = query.toLowerCase().split('=').lastElement();
+ const exact = queryIn.startsWith('=');
+ const query = queryIn.toLowerCase().split('=').lastElement();
const results = new ObservableMap<Doc, string[]>();
if (collectionDoc) {
@@ -30,7 +30,7 @@ export namespace SearchUtil {
(onlyKeys ?? SearchUtil.documentKeys(doc)).forEach(
key =>
(val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))(
- matchKeyNames ? key : Field.toString(doc[key] as Field))
+ matchKeyNames ? key : Field.toString(doc[key] as FieldType))
&& hlights.add(key)
); // prettier-ignore
blockedKeys.forEach(key => hlights.delete(key));
@@ -51,7 +51,11 @@ export namespace SearchUtil {
*/
export function documentKeys(doc: Doc) {
const keys: { [key: string]: boolean } = {};
- Doc.GetAllPrototypes(doc).map(proto => Object.keys(proto).forEach(key => (keys[key] = false)));
+ Doc.GetAllPrototypes(doc).map(proto =>
+ Object.keys(proto).forEach(key => {
+ keys[key] = false;
+ })
+ );
return Array.from(Object.keys(keys));
}
@@ -62,16 +66,18 @@ export namespace SearchUtil {
* 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) {
+ export function foreachRecursiveDoc(docsIn: Doc[], func: (depth: number, doc: Doc) => void) {
+ let docs = docsIn;
let newarray: Doc[] = [];
- var depth = 0;
+ let depth = 0;
const visited: Doc[] = [];
while (docs.length > 0) {
newarray = [];
+ // eslint-disable-next-line no-loop-func
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 annos = !Field.toString(Doc.LayoutField(d) as FieldType).includes('CollectionView');
const data = d[annos ? fieldKey + '_annotations' : fieldKey];
data && newarray.push(...DocListCast(data));
const sidebar = d[fieldKey + '_sidebar'];
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 36b926053..0b942116c 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,6 +1,5 @@
import { action, makeObservable, observable, runInAction } from 'mobx';
import { Doc, Opt } from '../../fields/Doc';
-import { DocViews } from '../../fields/DocSymbols';
import { List } from '../../fields/List';
import { listSpec } from '../../fields/Schema';
import { Cast, DocCast } from '../../fields/Types';
@@ -11,6 +10,7 @@ import { ScriptingGlobals } from './ScriptingGlobals';
import { UndoManager } from './UndoManager';
export class SelectionManager {
+ // eslint-disable-next-line no-use-before-define
private static _manager: SelectionManager;
private static get Instance() {
return SelectionManager._manager ?? new SelectionManager();
@@ -23,6 +23,13 @@ export class SelectionManager {
private constructor() {
SelectionManager._manager = this;
makeObservable(this);
+ DocumentView.DeselectAll = SelectionManager.DeselectAll;
+ DocumentView.DeselectView = SelectionManager.DeselectView;
+ DocumentView.SelectView = SelectionManager.SelectView;
+ DocumentView.SelectedDocs = SelectionManager.Docs;
+ DocumentView.Selected = SelectionManager.Views;
+ DocumentView.SelectSchemaDoc = SelectionManager.SelectSchemaViewDoc;
+ DocumentView.SelectedSchemaDoc = () => this.SelectedSchemaDocument;
}
@action
@@ -53,35 +60,41 @@ export class SelectionManager {
public static DeselectAll = (except?: Doc): void => {
const found = this.Instance.SelectedViews.find(dv => dv.Document === except);
runInAction(() => {
- LinkManager.Instance.currentLink = undefined;
- LinkManager.Instance.currentLinkAnchor = undefined;
+ if (LinkManager.Instance) {
+ LinkManager.Instance.currentLink = undefined;
+ LinkManager.Instance.currentLinkAnchor = undefined;
+ }
this.Instance.SelectedSchemaDocument = undefined;
});
this.Instance.SelectedViews.forEach(dv => {
dv.IsSelected = false;
dv._props.whenChildContentsActiveChanged(false);
});
- runInAction(() => (this.Instance.SelectedViews.length = 0));
+ runInAction(() => {
+ this.Instance.SelectedViews.length = 0;
+ });
if (found) this.SelectView(found, false);
};
- public static IsSelected = (doc?: Doc) => Array.from(doc?.[DocViews] ?? []).some(dv => dv?.IsSelected);
- public static get Views() { return this.Instance.SelectedViews; } // prettier-ignore
- public static get SelectedSchemaDoc() { return this.Instance.SelectedSchemaDocument; } // prettier-ignore
- public static get Docs() { return this.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
+ public static Views() { return SelectionManager.Instance.SelectedViews; } // prettier-ignore
+ public static get SelectedSchemaDoc() { return SelectionManager.Instance.SelectedSchemaDocument; } // prettier-ignore
+ public static Docs() { return SelectionManager.Instance.SelectedViews.map(dv => dv.Document).filter(doc => doc?._type_collection !== CollectionViewType.Docking); } // prettier-ignore
}
-ScriptingGlobals.add(function SelectionManager_selectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
+// eslint-disable-next-line prefer-arrow-callback
+ScriptingGlobals.add(function SelectedDocType(type: string, expertMode: boolean, checkContext?: boolean) {
if (Doc.noviceMode && expertMode) return false;
if (type === 'tab') {
- return SelectionManager.Views.lastElement()?._props.renderDepth === 0;
+ return DocumentView.Selected().lastElement()?._props.renderDepth === 0;
}
- let selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(SelectionManager.SelectedSchemaDoc ?? SelectionManager.Docs.lastElement());
+ const selected = (sel => (checkContext ? DocCast(sel?.embedContainer) : sel))(DocumentView.SelectedSchemaDoc() ?? SelectionManager.Docs().lastElement());
return selected?.type === type || selected?.type_collection === type || !type;
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function deselectAll() {
SelectionManager.DeselectAll();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function undo() {
SelectionManager.DeselectAll();
return UndoManager.Undo();
@@ -89,20 +102,20 @@ ScriptingGlobals.add(function undo() {
export function ShowUndoStack() {
SelectionManager.DeselectAll();
- var buffer = '';
+ let buffer = '';
UndoManager.undoStack.forEach((batch, i) => {
buffer += 'Batch => ' + UndoManager.undoStackNames[i] + '\n';
- ///batch.forEach(undo => (buffer += ' ' + undo.prop + '\n'));
+ // /batch.forEach(undo => (buffer += ' ' + undo.prop + '\n'));
});
alert(buffer);
}
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function redo() {
SelectionManager.DeselectAll();
return UndoManager.Redo();
});
+// eslint-disable-next-line prefer-arrow-callback
ScriptingGlobals.add(function selectedDocs(container: Doc, excludeCollections: boolean, prevValue: any) {
- const docs = SelectionManager.Views.map(dv => dv.Document).filter(
- d => !Doc.AreProtosEqual(d, container) && !d.annotationOn && d.type !== DocumentType.KVP && (!excludeCollections || d.type !== DocumentType.COL || !Cast(d.data, listSpec(Doc), null))
- );
+ const docs = SelectionManager.Docs().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/SerializationHelper.ts b/src/client/util/SerializationHelper.ts
index 8daa69890..d9d22437c 100644
--- a/src/client/util/SerializationHelper.ts
+++ b/src/client/util/SerializationHelper.ts
@@ -1,5 +1,5 @@
import { PropSchema, serialize, deserialize, custom, setDefaultModelSchema, getDefaultModelSchema } from 'serializr';
-import { Field } from '../../fields/Doc';
+// import { Field } from '../../fields/Doc';
let serializing = 0;
export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any, newValue: any) {
@@ -7,12 +7,16 @@ export function afterDocDeserialize(cb: (err: any, val: any) => void, err: any,
cb(err, newValue);
serializing--;
}
+
+const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
+const reverseMap: { [ctor: string]: string } = {};
+
export namespace SerializationHelper {
export function IsSerializing() {
return serializing > 0;
}
- export function Serialize(obj: Field): any {
+ export function Serialize(obj: any /* Field */): any {
if (obj === undefined || obj === null) {
return null;
}
@@ -24,7 +28,7 @@ export namespace SerializationHelper {
serializing++;
if (!(obj.constructor.name in reverseMap)) {
serializing--;
- throw Error('Error: ' + `type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
+ throw Error(`Error: type '${obj.constructor.name}' not registered. Make sure you register it using a @Deserializable decorator`);
}
const json = serialize(obj);
@@ -52,30 +56,29 @@ export namespace SerializationHelper {
}
const type = serializationTypes[obj.__type];
- const value = await new Promise(res => deserialize(type.ctor, obj, (err, result) => res(result)));
+ const value = await new Promise(res => {
+ deserialize(type.ctor, obj, (err, result) => res(result));
+ });
type.afterDeserialize?.(value);
return value;
}
}
-const serializationTypes: { [name: string]: { ctor: { new (): any }; afterDeserialize?: (obj: any) => void | Promise<any> } } = {};
-const reverseMap: { [ctor: string]: string } = {};
-
-export function Deserializable(className: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
- function addToMap(className: string, ctor: { new (...args: any[]): any }) {
- const schema = getDefaultModelSchema(ctor) as any;
- if (schema.targetClass !== ctor || constructorArgs) {
- setDefaultModelSchema(ctor, { ...schema, factory: (context: any) => new ctor(...(constructorArgs ?? [])?.map(arg => context.json[arg])) });
+export function Deserializable(classNameForSerializer: string, afterDeserialize?: (obj: any) => void | Promise<any>, constructorArgs?: [string]): (constructor: { new (...args: any[]): any }) => void {
+ function addToMap(className: string, Ctor: { new (...args: any[]): any }) {
+ const schema = getDefaultModelSchema(Ctor) as any;
+ if (schema.targetClass !== Ctor || constructorArgs) {
+ setDefaultModelSchema(Ctor, { ...schema, factory: (context: any) => new Ctor(...(constructorArgs ?? []).map(arg => context.json[arg])) });
}
if (!(className in serializationTypes)) {
- serializationTypes[className] = { ctor, afterDeserialize };
- reverseMap[ctor.name] = className;
+ serializationTypes[className] = { ctor: Ctor, afterDeserialize };
+ reverseMap[Ctor.name] = className;
} else {
throw new Error(`Name ${className} has already been registered as deserializable`);
}
}
- return (ctor: { new (...args: any[]): any }) => addToMap(className, ctor);
+ return (ctor: { new (...args: any[]): any }) => addToMap(classNameForSerializer, ctor);
}
export function autoObject(): PropSchema {
diff --git a/src/client/util/ServerStats.tsx b/src/client/util/ServerStats.tsx
index c8df9182d..57363663d 100644
--- a/src/client/util/ServerStats.tsx
+++ b/src/client/util/ServerStats.tsx
@@ -1,44 +1,28 @@
-import { action, computed, makeObservable, observable } from 'mobx';
+import { action, computed, makeObservable, observable, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { MainViewModal } from '../views/MainViewModal';
import './SharingManager.scss';
import { PingManager } from './PingManager';
-import { StrCast } from '../../fields/Types';
-import { Doc } from '../../fields/Doc';
import { SettingsManager } from './SettingsManager';
@observer
export class ServerStats extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: ServerStats;
@observable private isOpen = false; // whether the SharingManager modal is open or not
+ @observable _stats: { [key: string]: any } | undefined = undefined;
// private get linkVisible() {
- // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+ // return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false;
// }
- @action
- public open = async () => {
- /**
- * Populates the list of users.
- */
- fetch('/stats').then((res: Response) => res.text().then(action(stats => (this._stats = JSON.parse(stats)))));
-
- this.isOpen = true;
- };
-
- public close = action(() => {
- this.isOpen = false;
- });
-
constructor(props: {}) {
super(props);
makeObservable(this);
ServerStats.Instance = this;
}
- @observable _stats: { [key: string]: any } | undefined = undefined;
-
/**
* @returns the main interface of the SharingManager.
*/
@@ -63,7 +47,28 @@ export class ServerStats extends React.Component<{}> {
);
}
+ // eslint-disable-next-line react/sort-comp
+ public close = action(() => {
+ this.isOpen = false;
+ });
+ public open = async () => {
+ /**
+ * Populates the list of users.
+ */
+ fetch('/stats').then((res: Response) =>
+ res.text().then(
+ action(stats => {
+ this._stats = JSON.parse(stats);
+ })
+ )
+ );
+
+ runInAction(() => {
+ this.isOpen = true;
+ });
+ };
+
render() {
- return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx
index 8594a1c92..d3c10f9f4 100644
--- a/src/client/util/SettingsManager.tsx
+++ b/src/client/util/SettingsManager.tsx
@@ -1,23 +1,24 @@
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, ColorPicker, Dropdown, DropdownType, EditableText, Group, NumberDropdown, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { action, computed, makeObservable, observable, runInAction } from 'mobx';
+import { action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { BsGoogle } from 'react-icons/bs';
import { FaFillDrip, FaPalette } from 'react-icons/fa';
-import { Utils, addStyleSheet, addStyleSheetRule } from '../../Utils';
-import { Doc, Opt } from '../../fields/Doc';
+import { ClientUtils, addStyleSheet, addStyleSheetRule } from '../../ClientUtils';
+import { Doc } from '../../fields/Doc';
import { DashVersion } from '../../fields/DocSymbols';
import { BoolCast, Cast, NumCast, StrCast } from '../../fields/Types';
import { DocServer } from '../DocServer';
import { Networking } from '../Network';
import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
-import { GestureOverlay } from '../views/GestureOverlay';
import { MainViewModal } from '../views/MainViewModal';
-import { FontIconBox } from '../views/nodes/FontIconBox/FontIconBox';
import { GroupManager } from './GroupManager';
import './SettingsManager.scss';
-import { undoBatch } from './UndoManager';
+import { SnappingManager, freeformScrollMode } from './SnappingManager';
+import { undoable } from './UndoManager';
export enum ColorScheme {
Dark = 'Dark',
@@ -27,60 +28,124 @@ export enum ColorScheme {
Cupcake = 'Cupcake',
}
-export enum freeformScrollMode {
- Pan = 'pan',
- Zoom = 'zoom',
-}
-
@observer
export class SettingsManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: SettingsManager;
static _settingsStyle = addStyleSheet();
- @observable public isOpen = false;
- @observable private passwordResultText = '';
- @observable private playgroundMode = false;
+ @observable private _passwordResultText = '';
+ @observable private _playgroundMode = false;
+
+ @observable private _curr_password = '';
+ @observable private _new_password = '';
+ @observable private _new_confirm = '';
+ @observable private _activeTab = 'Accounts';
+ @observable private _isOpen = false;
- @observable private curr_password = '';
- @observable private new_password = '';
- @observable private new_confirm = '';
- @observable private _lastPressedSidebarBtn: Opt<Doc> = undefined; // bcz: this is a hack to handle highlighting buttons in the leftpanel menu .. need to find a cleaner approach
- @observable activeTab = 'Accounts';
+ private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
- @observable public propertiesWidth: number = 0;
+ public closeMgr = action(() => {
+ this._isOpen = false;
+ });
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ public openMgr = action(() => {
+ this._isOpen = true;
+ });
+
+ private matchSystem = undoable(() => {
+ 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);
+ }
+ }, 'match system theme');
+ private setFreeformScrollMode = undoable((mode: string) => {
+ Doc.UserDoc().freeformScrollMode = mode;
+ }, 'set scroll mode');
+ private selectUserMode = undoable((mode: string) => {
+ Doc.noviceMode = mode === 'Novice';
+ }, 'change user mode');
+ private changeFontFamily = undoable((font: string) => {
+ Doc.UserDoc().fontFamily = font;
+ }, 'change font family');
+ private switchUserBackgroundColor = undoable((color: string) => {
+ Doc.UserDoc().userBackgroundColor = color;
+ addStyleSheetRule(SettingsManager._settingsStyle, 'lm_header', { background: `${color} !important` });
+ }, 'change background color');
+ private switchUserColor = undoable((color: string) => {
+ Doc.UserDoc().userColor = color;
+ }, 'change user color');
+ switchUserVariantColor = undoable((color: string) => {
+ Doc.UserDoc().userVariantColor = color;
+ }, 'change variant color');
+ userThemeSystemToggle = undoable(() => {
+ Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
+ this.matchSystem();
+ }, 'change theme color');
+ playgroundModeToggle = undoable(
+ action(() => {
+ this._playgroundMode = !this._playgroundMode;
+ if (this._playgroundMode) {
+ DocServer.Control.makeReadOnly();
+ addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
+ } else ClientUtils.CurrentUserEmail() !== 'guest' && DocServer.Control.makeEditable();
+ }),
+ 'set playgorund mode'
+ );
+ changeColorScheme = undoable(
+ action((scheme: string) => {
+ Doc.UserDoc().userTheme = scheme;
+ switch (scheme) {
+ case ColorScheme.Light:
+ this.switchUserColor('#323232');
+ this.switchUserBackgroundColor('#DFDFDF');
+ this.switchUserVariantColor('#BDDDF5');
+ break;
+ case ColorScheme.Dark:
+ this.switchUserColor('#DFDFDF');
+ this.switchUserBackgroundColor('#323232');
+ this.switchUserVariantColor('#4476F7');
+ break;
+ case ColorScheme.CoolBlue:
+ this.switchUserColor('#ADEAFF');
+ this.switchUserBackgroundColor('#060A15');
+ this.switchUserVariantColor('#3C51FF');
+ break;
+ case ColorScheme.Cupcake:
+ this.switchUserColor('#3BC7FF');
+ this.switchUserBackgroundColor('#fffdf7');
+ this.switchUserVariantColor('#FFD7F3');
+ break;
+ case ColorScheme.Custom:
+ break;
+ default:
+ }
+ }),
+ 'change color scheme'
+ );
constructor(props: {}) {
super(props);
makeObservable(this);
SettingsManager.Instance = this;
this.matchSystem();
- window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', e => {
+ window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
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)
});
+ reaction(
+ () => [SettingsManager.userBackgroundColor, SettingsManager.userColor, SettingsManager.userVariantColor],
+ ([back, user, variant]) => {
+ SnappingManager.userBackgroundColor = back;
+ SnappingManager.userVariantColor = variant;
+ SnappingManager.userColor = user;
+ },
+ { fireImmediately: true }
+ );
+ SnappingManager.SettingsStyle = SettingsManager._settingsStyle;
}
- 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));
-
- private googleAuthorize = action(() => GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true));
- private changePassword = async () => {
- if (!(this.curr_password && this.new_password && this.new_confirm)) {
- runInAction(() => (this.passwordResultText = "Error: Hey, we're missing some fields!"));
- } else {
- const passwordBundle = { curr_pass: this.curr_password, new_pass: this.new_password, new_confirm: this.new_confirm };
- const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
- runInAction(() => (this.passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!'));
- }
- };
@computed public static get userColor() {
return StrCast(Doc.UserDoc().userColor);
@@ -94,60 +159,6 @@ export class SettingsManager extends React.Component<{}> {
return StrCast(Doc.UserDoc().userBackgroundColor);
}
- public get LastPressedBtn() { return this._lastPressedSidebarBtn; } // prettier-ignore
- public SetLastPressedBtn = (state?:Doc) => runInAction(() => (this._lastPressedSidebarBtn = state)); // prettier-ignore
-
- @undoBatch selectUserMode = action((mode: string) => (Doc.noviceMode = mode === 'Novice'));
- @undoBatch changelayout_showTitle = action((e: React.ChangeEvent) => (Doc.UserDoc().layout_showTitle = (e.currentTarget as any).value ? 'title' : undefined));
- @undoBatch changeFontFamily = action((font: string) => (Doc.UserDoc().fontFamily = font));
- @undoBatch changeFontSize = action((val: number) => (Doc.UserDoc().fontSize = val));
- @undoBatch switchUserBackgroundColor = action((color: string) => {
- 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 userThemeSystemToggle = action(() => {
- Doc.UserDoc().userThemeSystem = !Doc.UserDoc().userThemeSystem;
- this.matchSystem();
- });
- @undoBatch playgroundModeToggle = action(() => {
- this.playgroundMode = !this.playgroundMode;
- if (this.playgroundMode) {
- DocServer.Control.makeReadOnly();
- addStyleSheetRule(SettingsManager._settingsStyle, 'topbar-inner-container', { background: 'red !important' });
- } else Doc.CurrentUserEmail !== 'guest' && DocServer.Control.makeEditable();
- });
-
- @undoBatch
- changeColorScheme = action((scheme: string) => {
- Doc.UserDoc().userTheme = scheme;
- switch (scheme) {
- case ColorScheme.Light:
- this.switchUserColor('#323232');
- this.switchUserBackgroundColor('#DFDFDF');
- this.switchUserVariantColor('#BDDDF5');
- break;
- case ColorScheme.Dark:
- this.switchUserColor('#DFDFDF');
- this.switchUserBackgroundColor('#323232');
- this.switchUserVariantColor('#4476F7');
- break;
- case ColorScheme.CoolBlue:
- this.switchUserColor('#ADEAFF');
- this.switchUserBackgroundColor('#060A15');
- this.switchUserVariantColor('#3C51FF');
- break;
- case ColorScheme.Cupcake:
- this.switchUserColor('#3BC7FF');
- this.switchUserBackgroundColor('#fffdf7');
- this.switchUserVariantColor('#FFD7F3');
- break;
- case ColorScheme.Custom:
- break;
- }
- });
-
@computed get colorsContent() {
const schemeMap = Array.from(Object.keys(ColorScheme));
const userTheme = StrCast(Doc.UserDoc().userTheme);
@@ -176,7 +187,7 @@ export class SettingsManager extends React.Component<{}> {
{userTheme === ColorScheme.Custom && (
<Group formLabel="Custom Theme">
<ColorPicker
- tooltip={'User Color'} //
+ tooltip="User Color" //
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaFillDrip />}
@@ -185,7 +196,7 @@ export class SettingsManager extends React.Component<{}> {
setFinalColor={this.switchUserColor}
/>
<ColorPicker
- tooltip={'User Background Color'}
+ tooltip="User Background Color"
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaPalette />}
@@ -194,7 +205,7 @@ export class SettingsManager extends React.Component<{}> {
setFinalColor={this.switchUserBackgroundColor}
/>
<ColorPicker
- tooltip={'User Variant Color'}
+ tooltip="User Variant Color"
color={SettingsManager.userColor}
type={Type.SEC}
icon={<FaPalette />}
@@ -212,79 +223,84 @@ export class SettingsManager extends React.Component<{}> {
return (
<div className="prefs-content">
<Toggle
- formLabel={'Show document header'}
- formLabelPlacement={'right'}
+ formLabel="Show document header"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date')}
+ onClick={() => {
+ Doc.UserDoc().layout_showTitle = Doc.UserDoc().layout_showTitle ? undefined : 'author_date';
+ }}
toggleStatus={Doc.UserDoc().layout_showTitle !== undefined}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Show Full Toolbar'}
- formLabelPlacement={'right'}
- toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc()['documentLinksButton-fullMenu'] = !Doc.UserDoc()['documentLinksButton-fullMenu'])}
- toggleStatus={BoolCast(Doc.UserDoc()['documentLinksButton-fullMenu'])}
- size={Size.XSMALL}
- color={SettingsManager.userColor}
- />
- <Toggle
- formLabel={'Show Button Labels'}
- formLabelPlacement={'right'}
+ formLabel="Show Full Toolbar"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (FontIconBox.ShowIconLabels = !FontIconBox.ShowIconLabels)}
- toggleStatus={FontIconBox.ShowIconLabels}
+ onClick={() => {
+ Doc.UserDoc().documentLinksButton_fullMenu = !Doc.UserDoc().documentLinksButton_fullMenu;
+ }}
+ toggleStatus={BoolCast(Doc.UserDoc().documentLinksButton_fullMenu)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Recognize Ink Gestures'}
- formLabelPlacement={'right'}
+ formLabel="Recognize Ink Gestures"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (GestureOverlay.RecognizeGestures = !GestureOverlay.RecognizeGestures)}
- toggleStatus={GestureOverlay.RecognizeGestures}
+ onClick={() => {
+ Doc.UserDoc().recognizeGestures = !Doc.UserDoc().recognizeGestures;
+ }}
+ toggleStatus={BoolCast(Doc.UserDoc().recognizeGestures)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Hide Labels In Ink Shapes'}
- formLabelPlacement={'right'}
+ formLabel="Hide Labels In Ink Shapes"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels)}
+ onClick={() => {
+ Doc.UserDoc().activeInkHideTextLabels = !Doc.UserDoc().activeInkHideTextLabels;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().activeInkHideTextLabels)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
<Toggle
- formLabel={'Open Ink Docs in Lightbox'}
- formLabelPlacement={'right'}
+ formLabel="Open Ink Docs in Lightbox"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox)}
+ onClick={() => {
+ Doc.UserDoc().openInkInLightbox = !Doc.UserDoc().openInkInLightbox;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().openInkInLightbox)}
size={Size.XSMALL}
color={SettingsManager.userColor}
/>
- <Toggle
- formLabel={'Show Link Lines'}
- formLabelPlacement={'right'}
+ {/* <Toggle
+ formLabel="Show Link Lines"
+ formLabelPlacement="right"
toggleType={ToggleType.SWITCH}
- onClick={e => (Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines)}
+ onClick={() => {
+ Doc.UserDoc().showLinkLines = !Doc.UserDoc().showLinkLines;
+ }}
toggleStatus={BoolCast(Doc.UserDoc().showLinkLines)}
size={Size.XSMALL}
color={SettingsManager.userColor}
- />
+ /> */}
<Group formLabel="Title Height">
<NumberDropdown
number={NumCast(Doc.UserDoc().headerHeight, 30)}
color={SettingsManager.userColor}
- numberDropdownType={'slider'}
+ numberDropdownType="slider"
min={6}
max={60}
step={2}
type={Type.TERT}
- unit={'px'}
- setNumber={val => console.log('GOT: ' + (Doc.UserDoc().headerHeight = val))}
+ unit="px"
+ setNumber={val => {
+ Doc.UserDoc().headerHeight = val;
+ }}
/>
</Group>
</div>
@@ -308,7 +324,6 @@ export class SettingsManager extends React.Component<{}> {
@computed get textContent() {
const fontFamilies = ['Times New Roman', 'Arial', 'Georgia', 'Comic Sans MS', 'Tahoma', 'Impact', 'Crimson Text', 'Roboto'];
- const fontSizes = ['7px', '8px', '9px', '10px', '12px', '14px', '16px', '18px', '20px', '24px', '32px', '48px', '72px'];
return (
<div className="tab-content appearances-content">
@@ -316,7 +331,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-title">Text</div>
<div className="tab-column-content">
{/* <NumberInput/> */}
- <Group formLabel={'Default Font'}>
+ <Group formLabel="Default Font">
<NumberDropdown
color={SettingsManager.userColor}
numberDropdownType="slider"
@@ -325,20 +340,20 @@ export class SettingsManager extends React.Component<{}> {
step={2}
type={Type.PRIM}
number={NumCast(Doc.UserDoc().fontSize, Number(StrCast(Doc.UserDoc().fontSize).replace('px', '')))}
- unit={'px'}
- setNumber={val => (Doc.UserDoc().fontSize = val + 'px')}
+ unit="px"
+ setNumber={val => {
+ Doc.UserDoc().fontSize = val + 'px';
+ }}
/>
<Dropdown
- items={fontFamilies.map(val => {
- return {
- text: val,
- val: val,
- style: {
- fontFamily: val,
- },
- };
- })}
- closeOnSelect={true}
+ items={fontFamilies.map(val => ({
+ text: val,
+ val: val,
+ style: {
+ fontFamily: val,
+ },
+ }))}
+ closeOnSelect
dropdownType={DropdownType.SELECT}
type={Type.TERT}
selectedVal={StrCast(Doc.UserDoc().fontFamily)}
@@ -355,30 +370,15 @@ export class SettingsManager extends React.Component<{}> {
);
}
- @action
- changeVal = (value: string, pass: string) => {
- switch (pass) {
- case 'curr':
- this.curr_password = value;
- break;
- case 'new':
- this.new_password = value;
- break;
- case 'conf':
- this.new_confirm = value;
- break;
- }
- };
-
@computed get passwordContent() {
return (
<div className="password-content">
- <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={SettingsManager.userColor} />
- <Button type={Type.TERT} text={'Submit'} onClick={this.changePassword} color={SettingsManager.userColor} />
+ <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={SettingsManager.userColor} />
+ <Button type={Type.TERT} text="Submit" onClick={this.changePassword} color={SettingsManager.userColor} />
</div>
);
}
@@ -386,7 +386,7 @@ export class SettingsManager extends React.Component<{}> {
@computed get accountOthersContent() {
return (
<div className="account-others-content">
- <Button type={Type.TERT} text={'Connect to Google'} iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
+ <Button type={Type.TERT} text="Connect to Google" iconPlacement="left" icon={<BsGoogle />} onClick={() => this.googleAuthorize()} />
</div>
);
}
@@ -406,10 +406,6 @@ export class SettingsManager extends React.Component<{}> {
);
}
- setFreeformScrollMode = (mode: string) => {
- Doc.UserDoc().freeformScrollMode = mode;
- };
-
@computed get modesContent() {
return (
<div className="tab-content modes-content">
@@ -417,8 +413,8 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-title">Modes</div>
<div className="tab-column-content">
<Dropdown
- formLabel={'Mode'}
- closeOnSelect={true}
+ formLabel="Mode"
+ closeOnSelect
items={[
{
text: 'Novice',
@@ -442,7 +438,7 @@ export class SettingsManager extends React.Component<{}> {
color={SettingsManager.userColor}
fillWidth
/>
- <Toggle formLabel={'Playground Mode'} toggleType={ToggleType.SWITCH} toggleStatus={this.playgroundMode} onClick={this.playgroundModeToggle} color={SettingsManager.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
@@ -450,7 +446,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="tab-column-content">
<Dropdown
formLabel="Scroll Mode"
- closeOnSelect={true}
+ closeOnSelect
items={[
{
text: 'Scroll to Pan',
@@ -475,16 +471,34 @@ export class SettingsManager extends React.Component<{}> {
<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={SettingsManager.userColor} />
+ <Button text="Manage Groups" type={Type.TERT} onClick={() => GroupManager.Instance?.open()} color={SettingsManager.userColor} />
<Toggle
toggleType={ToggleType.SWITCH}
- formLabel={'Default access private'}
+ formLabel="Default access private"
color={SettingsManager.userColor}
toggleStatus={BoolCast(Doc.defaultAclPrivate)}
- onClick={action(() => (Doc.defaultAclPrivate = !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;
+ })}
+ />
+ <Toggle
+ toggleType={ToggleType.SWITCH}
+ formLabel="Disable Info UI"
+ color={SettingsManager.userColor}
+ toggleStatus={BoolCast(Doc.IsInfoUIDisabled)}
+ onClick={action(() => {
+ Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled;
+ })}
/>
- <Toggle toggleType={ToggleType.SWITCH} formLabel="Enable Sharing UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsSharingEnabled)} onClick={action(() => (Doc.IsSharingEnabled = !Doc.IsSharingEnabled))} />
- <Toggle toggleType={ToggleType.SWITCH} formLabel="Disable Info UI" color={SettingsManager.userColor} toggleStatus={BoolCast(Doc.IsInfoUIDisabled)} onClick={action(() => (Doc.IsInfoUIDisabled = !Doc.IsInfoUIDisabled))} />
</div>
</div>
</div>
@@ -506,7 +520,7 @@ export class SettingsManager extends React.Component<{}> {
<div className="settings-panel" style={{ background: SettingsManager.userColor }}>
<div className="settings-tabs">
{tabs.map(tab => {
- const isActive = this.activeTab === tab.title;
+ const isActive = this._activeTab === tab.title;
return (
<div
key={tab.title}
@@ -515,7 +529,9 @@ export class SettingsManager extends React.Component<{}> {
color: isActive ? SettingsManager.userColor : SettingsManager.userBackgroundColor,
}}
className={'tab-control ' + (isActive ? 'active' : 'inactive')}
- onClick={action(() => (this.activeTab = tab.title))}>
+ onClick={action(() => {
+ this._activeTab = tab.title;
+ })}>
{tab.title}
</div>
);
@@ -525,19 +541,19 @@ export class SettingsManager extends React.Component<{}> {
<div className="settings-user">
<div style={{ color: SettingsManager.userBackgroundColor }}>{DashVersion}</div>
<div className="settings-username" style={{ color: SettingsManager.userBackgroundColor }}>
- {Doc.CurrentUserEmail}
+ {ClientUtils.CurrentUserEmail()}
</div>
- <Button text={Doc.GuestDashboard ? 'Exit' : 'Log Out'} type={Type.TERT} color={SettingsManager.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(ClientUtils.prepend('/logout'))} />
</div>
</div>
<div className="close-button">
- <Button icon={<FontAwesomeIcon icon={'times'} size={'lg'} />} onClick={this.close} color={SettingsManager.userColor} />
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.closeMgr} color={SettingsManager.userColor} />
</div>
<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')}>
+ <div key={tab.title} className={'tab-section ' + (this._activeTab === tab.title ? 'active' : 'inactive')}>
{tab.ele}
</div>
))}
@@ -546,13 +562,37 @@ export class SettingsManager extends React.Component<{}> {
);
}
+ private changePassword = async () => {
+ if (!(this._curr_password && this._new_password && this._new_confirm)) {
+ runInAction(() => {
+ this._passwordResultText = "Error: Hey, we're missing some fields!";
+ });
+ } else {
+ const passwordBundle = { curr_pass: this._curr_password, new_pass: this._new_password, new_confirm: this._new_confirm };
+ const { error } = await Networking.PostToServer('/internalResetPassword', passwordBundle);
+ runInAction(() => {
+ this._passwordResultText = error ? 'Error: ' + error[0].msg + '...' : 'Password successfully updated!';
+ });
+ }
+ };
+
+ @action
+ changeVal = (value: string, pass: string) => {
+ switch (pass) {
+ case 'curr': this._curr_password = value; break;
+ case 'new': this._new_password = value; break;
+ case 'conf': this._new_confirm = value; break;
+ default:
+ } // prettier-ignore
+ };
+
render() {
return (
<MainViewModal
contents={this.settingsInterface}
- isDisplayed={this.isOpen}
- interactive={true}
- closeOnExternalClick={this.close}
+ isDisplayed={this._isOpen}
+ interactive
+ closeOnExternalClick={this.closeMgr}
dialogueBoxStyle={{ width: 'fit-content', height: '300px', background: Cast(Doc.UserDoc().userColor, 'string', null) }}
/>
);
diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx
index fddf735e3..c2a52cae9 100644
--- a/src/client/util/SharingManager.tsx
+++ b/src/client/util/SharingManager.tsx
@@ -1,3 +1,6 @@
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Button, IconButton, Size, Type } from 'browndash-components';
import { concat, intersection } from 'lodash';
@@ -6,25 +9,23 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import Select from 'react-select';
import * as RequestPromise from 'request-promise';
+import { ClientUtils } from '../../ClientUtils';
+import { Utils } from '../../Utils';
import { Doc, DocListCast, DocListCastAsync, HierarchyMapping, ReverseHierarchyMap } from '../../fields/Doc';
import { AclAdmin, AclPrivate, DocAcl, DocData } from '../../fields/DocSymbols';
import { FieldLoader } from '../../fields/FieldLoader';
import { Id } from '../../fields/FieldSymbols';
import { StrCast } from '../../fields/Types';
-import { distributeAcls, GetEffectiveAcl, normalizeEmail, SharingPermissions, TraceMobx } from '../../fields/util';
-import { Utils } from '../../Utils';
+import { GetEffectiveAcl, SharingPermissions, TraceMobx, distributeAcls, normalizeEmail } from '../../fields/util';
import { DocServer } from '../DocServer';
-import { DictationOverlay } from '../views/DictationOverlay';
import { MainViewModal } from '../views/MainViewModal';
import { DocumentView } from '../views/nodes/DocumentView';
import { TaskCompletionBox } from '../views/nodes/TaskCompletedBox';
-import { DocumentManager } from './DocumentManager';
import { GroupManager, UserOptions } from './GroupManager';
import { GroupMemberView } from './GroupMemberView';
import { SearchUtil } from './SearchUtil';
-import { SelectionManager } from './SelectionManager';
-import { SettingsManager } from './SettingsManager';
import './SharingManager.scss';
+import { SnappingManager } from './SnappingManager';
import { undoable } from './UndoManager';
export interface User {
@@ -64,63 +65,36 @@ interface ValidatedUser {
@observer
export class SharingManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: SharingManager;
+ private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
+ private populating: boolean = false; // whether the list of users is populating or not
@observable private isOpen = false; // whether the SharingManager modal is open or not
@observable public users: ValidatedUser[] = []; // the list of users with sharing docs
@observable private targetDoc: Doc | undefined = undefined; // the document being shared
@observable private targetDocView: DocumentView | undefined = undefined; // the DocumentView of the document being shared
// @observable private copied = false;
@observable private dialogueBoxOpacity = 1; // for the modal
- @observable private overlayOpacity = 0.4; // for the modal
@observable private selectedUsers: UserOptions[] | null = null; // users (individuals/groups) selected to share with
@observable private permissions: SharingPermissions = SharingPermissions.Edit; // the permission with which to share with other users
@observable private individualSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of individuals
@observable private groupSort: 'ascending' | 'descending' | 'none' = 'none'; // sorting options for the list of groups
- private shareDocumentButtonRef: React.RefObject<HTMLButtonElement> = React.createRef(); // ref for the share button, used for the position of the popup
// if both showUserOptions and showGroupOptions are false then both are displayed
@observable private showUserOptions: boolean = false; // whether to show individuals as options when sharing (in the react-select component)
@observable private showGroupOptions: boolean = false; // // whether to show groups as options when sharing (in the react-select component)
- private populating: boolean = false; // whether the list of users is populating or not
@observable private upgradeNested: boolean = false; // whether child docs in a collection/dashboard should be changed to be less private - initially selected so default is upgrade all
@observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used
@observable private myDocAcls: boolean = false; // whether the My Docs checkbox is selected or not
- @observable private _buttonDown = false;
// private get linkVisible() {
- // return this.targetDoc ? this.targetDoc["acl-" + PublicKey] !== SharingPermissions.None : false;
+ // return this.targetDoc ? this.targetDoc['acl_' + PublicKey] !== SharingPermissions.None : false;
// }
- public open = (target?: DocumentView, target_doc?: Doc) => {
- this.populateUsers();
- runInAction(() => {
- this.targetDocView = target;
- this.targetDoc = target_doc || target?.Document;
- DictationOverlay.Instance.hasActiveModal = true;
- this.isOpen = this.targetDoc !== undefined;
- this.permissions = SharingPermissions.Augment;
- this.upgradeNested = true;
- });
- };
-
- public close = action(() => {
- this.isOpen = false;
- this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
- TaskCompletionBox.taskCompleted = false;
- setTimeout(
- action(() => {
- // this.copied = false;
- DictationOverlay.Instance.hasActiveModal = false;
- this.targetDoc = undefined;
- }),
- 500
- );
- this.layoutDocAcls = false;
- });
-
constructor(props: {}) {
super(props);
makeObservable(this);
SharingManager.Instance = this;
+ DocumentView.ShareOpen = this.open;
}
/**
@@ -131,223 +105,6 @@ export class SharingManager extends React.Component<{}> {
}
/**
- * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
- */
- populateUsers = async () => {
- if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
- this.populating = true;
- const userList = await RequestPromise.get(Utils.prepend('/getUsers'));
- const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== Doc.CurrentUserEmail);
- runInAction(() => (FieldLoader.ServerLoadStatus.message = 'users'));
- const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
- raw.map(
- action((newUser: User) => {
- const sharingDoc = docs[newUser.sharingDocumentId];
- const linkDatabase = docs[newUser.linkDatabaseId];
- if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
- if (!this.users.find(user => user.user.email === newUser.email)) {
- this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
- //LinkManager.addLinkDB(linkDatabase);
- }
- }
- })
- );
- this.populating = false;
- }
- };
-
- /**
- * Shares the document with a user.
- */
- setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
- const { user, sharingDoc } = recipient;
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(user.email)}`;
- const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
- if (permission !== SharingPermissions.None) {
- Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
- } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
- });
- }, 'set Doc permissions');
-
- /**
- * Sets the permission on the target for the group.
- * @param group
- * @param permission
- */
- setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
- const target = targetDoc || this.targetDoc!;
- const acl = `acl-${normalizeEmail(StrCast(group.title))}`;
-
- const docs = SelectionManager.Views.length < 2 ? [target] : SelectionManager.Views.map(docView => docView.Document);
- docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
- distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
-
- if (group instanceof Doc) {
- Doc.AddDocToList(group, 'docsShared', doc);
-
- this.users
- .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
- .forEach(({ user, sharingDoc }) => {
- if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
- else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
- });
- }
- });
- }, 'set group permissions');
-
- /**
- * Shares the documents shared with a group with a new user who has been added to that group.
- * @param group
- * @param emailId
- */
- shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
- const user = this.users.find(({ user: { email } }) => email === emailId)!;
- const self = this;
- if (group.docsShared) {
- if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
- else {
- DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
- filtered && userdocs?.push(...filtered);
- })
- );
- DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
- filtered && userdocs?.push(...filtered);
- })
- );
- }
- }
- };
-
- /**
- * Called from the properties sidebar to change permissions of a user.
- */
- shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
- if (layout) this.layoutDocAcls = true;
- if (shareWith !== 'Guest') {
- const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? Doc.CurrentUserEmail : shareWith));
- docs.forEach(doc => {
- if (user) this.setInternalSharing(user, permission, doc);
- else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
- });
- } else {
- docs.forEach(doc => {
- if (GetEffectiveAcl(doc) === AclAdmin) {
- distributeAcls(`acl-${shareWith}`, permission, doc, undefined);
- }
- });
- }
- this.layoutDocAcls = false;
- }, 'sidebar set permissions');
-
- /**
- * Removes the documents shared with a user through a group when the user is removed from the group.
- * @param group
- * @param emailId
- */
- removeMember = (group: Doc, emailId: string) => {
- const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
-
- if (group.docsShared && user) {
- DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
- userdocs?.splice(0, userdocs.length, ...remaining);
- })
- );
- DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
- DocListCastAsync(group.docsShared).then(dl => {
- const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
- userdocs?.splice(0, userdocs.length, ...remaining);
- })
- );
- }
- };
-
- /**
- * Removes a group's permissions from documents that have been shared with it.
- * @param group
- */
- removeGroup = (group: Doc) => {
- if (group.docsShared) {
- DocListCast(group.docsShared).forEach(doc => {
- const acl = `acl-${StrCast(group.title)}`;
- distributeAcls(acl, SharingPermissions.None, doc);
-
- const members: string[] = JSON.parse(StrCast(group.members));
- const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
-
- users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
- });
- }
- };
-
- // private setExternalSharing = (permission: string) => {
- // const targetDoc = this.targetDoc;
- // if (!targetDoc) {
- // return;
- // }
- // targetDoc["acl-" + PublicKey] = permission;
- // }s
-
- /**
- * Copies the Public sharing url to the user's clipboard.
- */
- private copyURL = (e: any) => {
- Utils.CopyText(Utils.shareUrl(this.targetDoc![Id]));
- };
-
- /**
- * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
- */
- private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
- const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
- if (!uniform) dropdownValues.unshift('-multiple-');
- return dropdownValues.map(permission => (
- <option key={permission} value={permission}>
- {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
- </option>
- ));
- }
-
- private focusOn = (contents: string) => {
- const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
- const docs = SelectionManager.Views.length > 1 ? SelectionManager.Views.map(docView => docView.props.Document) : [this.targetDoc];
- return (
- <span
- className="focus-span"
- title={title}
- onClick={() => {
- if (this.targetDoc && this.targetDocView && docs.length === 1) {
- DocumentManager.Instance.showDocument(this.targetDoc, { willZoomCentered: true });
- }
- }}
- onPointerEnter={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.BrushDoc(doc));
- this.dialogueBoxOpacity = 0.1;
- this.overlayOpacity = 0.1;
- }
- })}
- onPointerLeave={action(() => {
- if (docs.length) {
- docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
- this.dialogueBoxOpacity = 1;
- this.overlayOpacity = 0.4;
- }
- })}>
- {contents}
- </span>
- );
- };
-
- /**
* Handles changes in the users selected in react-select
*/
@action
@@ -366,57 +123,6 @@ export class SharingManager extends React.Component<{}> {
);
/**
- * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
- */
- share = undoable(
- action(() => {
- if (this.selectedUsers) {
- this.selectedUsers.forEach(user => {
- if (user.value.includes(indType)) {
- this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
- } else {
- this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
- }
- });
-
- 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;
- }
- }),
- 'share Doc'
- );
-
- /**
- * Sorting algorithm to sort users.
- */
- sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
- const { email: e1 } = u1.user;
- const { email: e2 } = u2.user;
- return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
- };
-
- /**
- * Sorting algorithm to sort groups.
- */
- sortGroups = (group1: Doc, group2: Doc) => {
- const g1 = StrCast(group1.title);
- const g2 = StrCast(group2.title);
- return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
- };
-
- /**
* @returns the main interface of the SharingManager.
*/
@computed get sharingInterface() {
@@ -445,7 +151,7 @@ export class SharingManager extends React.Component<{}> {
const users = this.individualSort === 'ascending' ? this.users.slice().sort(this.sortUsers) : this.individualSort === 'descending' ? this.users.slice().sort(this.sortUsers).reverse() : this.users;
const groups = this.groupSort === 'ascending' ? groupList.slice().sort(this.sortGroups) : this.groupSort === 'descending' ? groupList.slice().sort(this.sortGroups).reverse() : groupList;
- let docs = SelectionManager.Views.length < 2 ? [this.targetDoc] : SelectionManager.Views.map(docView => docView.Document);
+ let docs = DocumentView.Selected().length < 2 ? [this.targetDoc] : DocumentView.Selected().map(docView => docView.Document);
if (this.myDocAcls) {
const newDocs: Doc[] = [];
@@ -464,18 +170,18 @@ export class SharingManager extends React.Component<{}> {
// the list of users shared with
const userListContents = users
- // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
+ // .filter(({ user }) => (docs.length > 1 ? commonKeys.includes(`acl_${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email))
.filter(({ user }) => docs[0]?.author !== user.email)
.map(({ user, linkDatabase, sharingDoc, userColor }) => {
- const userKey = `acl-${normalizeEmail(user.email)}`;
+ const userKey = `acl_${normalizeEmail(user.email)}`;
const uniform = docs.every(doc => doc?.[DocAcl]?.[userKey] === docs[0]?.[DocAcl]?.[userKey]);
// const permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
let permissions = targetDoc[DocAcl][userKey] ? HierarchyMapping.get(targetDoc[DocAcl][userKey])?.name : StrCast(targetDoc[userKey]);
permissions = uniform ? StrCast(targetDoc?.[userKey]) : '-multiple-';
return !permissions ? null : (
- <div key={userKey} className={'container'}>
- <span className={'padding'}>{user.email}</span>
+ <div key={userKey} className="container">
+ <span className="padding">{user.email}</span>
<div className="edit-actions">
{admin || this.myDocAcls ? (
<select className={`permissions-dropdown-${permissions}`} value={permissions} onChange={e => this.setInternalSharing({ user, linkDatabase, sharingDoc, userColor }, e.currentTarget.value, undefined)}>
@@ -496,21 +202,21 @@ export class SharingManager extends React.Component<{}> {
const sameAuthor = docs.every(doc => doc?.author === docs[0]?.author);
// the owner of the doc and the current user are placed at the top of the user list.
- const userKey = `acl-${normalizeEmail(Doc.CurrentUserEmail)}`;
+ const userKey = `acl_${normalizeEmail(ClientUtils.CurrentUserEmail())}`;
const curUserPermission = StrCast(targetDoc[userKey]);
// const curUserPermission = HierarchyMapping.get(effectiveAcls[0])!.name
userListContents.unshift(
sameAuthor ? (
- <div key={'owner'} className={'container'}>
- <span className="padding">{targetDoc?.author === Doc.CurrentUserEmail ? 'Me' : StrCast(targetDoc?.author)}</span>
+ <div key="owner" className="container">
+ <span className="padding">{targetDoc?.author === ClientUtils.CurrentUserEmail() ? 'Me' : StrCast(targetDoc?.author)}</span>
<div className="edit-actions">
- <div className={'permissions-dropdown'}>Owner</div>
+ <div className="permissions-dropdown">Owner</div>
</div>
</div>
) : null,
- sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? (
- <div key={'me'} className={'container'}>
- <span className={'padding'}>Me</span>
+ sameAuthor && targetDoc?.author !== ClientUtils.CurrentUserEmail() ? (
+ <div key="me" className="container">
+ <span className="padding">Me</span>
<div className="edit-actions">
<div className={`permissions-dropdown-${curUserPermission}`}>
{effectiveAcls.every(acl => acl === effectiveAcls[0]) ? concat(ReverseHierarchyMap.get(curUserPermission!)?.image, ' ', curUserPermission) : '-multiple-'}
@@ -522,19 +228,28 @@ export class SharingManager extends React.Component<{}> {
);
// the list of groups shared with
- const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(title))}`) : true));
- groupListMap.unshift({ title: 'Guest' }); //, { title: "ALL" });
+ const groupListMap: (Doc | { title: string })[] = groups.filter(({ title }) => (docs.length > 1 ? commonKeys.includes(`acl_${normalizeEmail(StrCast(title))}`) : true));
+ groupListMap.unshift({ title: 'Guest' }); // , { title: "ALL" });
const groupListContents = groupListMap.map(group => {
- let groupKey = `acl-${StrCast(group.title)}`;
+ const groupKey = `acl_${StrCast(group.title)}`;
const uniform = docs.every(doc => doc?.[DocAcl]?.[groupKey] === docs[0]?.[DocAcl]?.[groupKey]);
const permissions = uniform ? StrCast(targetDoc?.[groupKey]) : '-multiple-';
return !permissions ? null : (
- <div key={groupKey} className={'container'} style={{ background: SettingsManager.userBackgroundColor, color: SettingsManager.userColor }}>
- <div className={'padding'}>{StrCast(group.title)}</div>
+ <div key={groupKey} className="container" style={{ background: SnappingManager.userBackgroundColor, color: SnappingManager.userColor }}>
+ <div className="padding">{StrCast(group.title)}</div>
&nbsp;
- {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'}>
+ {group instanceof Doc ? (
+ <IconButton
+ icon={<FontAwesomeIcon icon="info-circle" />}
+ size={Size.XSMALL}
+ color={SnappingManager.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)}>
{this.sharingOptions(uniform, group.title === 'Guest')}
@@ -551,25 +266,32 @@ export class SharingManager extends React.Component<{}> {
});
return (
<div className="sharing-interface">
- {GroupManager.Instance?.currentGroup ? <GroupMemberView group={GroupManager.Instance.currentGroup} onCloseButtonClick={action(() => (GroupManager.Instance.currentGroup = undefined))} /> : null}
+ {GroupManager.Instance?.currentGroup ? (
+ <GroupMemberView
+ group={GroupManager.Instance.currentGroup}
+ onCloseButtonClick={action(() => {
+ GroupManager.Instance.currentGroup = undefined;
+ })}
+ />
+ ) : null}
<div
className="sharing-contents"
style={{
- background: SettingsManager.userBackgroundColor,
+ background: SnappingManager.userBackgroundColor,
color: StrCast(Doc.UserDoc().userColor),
}}>
- <p className="share-title" style={{ color: SettingsManager.userColor }}>
+ <p className="share-title" style={{ color: SnappingManager.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')} />
+ <FontAwesomeIcon icon="question-circle" size="sm" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/collaboration/', '_blank')} />
</div>
<b>Share </b>
{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, 'this document') : '-multiple-')}
</p>
<div className="share-copy-link">
- <Button type={Type.TERT} color={SettingsManager.userColor} icon={<FontAwesomeIcon icon={'copy'} size="sm" />} iconPlacement={'left'} text={'Copy Guest URL'} onClick={this.copyURL} />
+ <Button type={Type.TERT} color={SnappingManager.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={SettingsManager.userColor} />
+ <Button icon={<FontAwesomeIcon icon="times" size="lg" />} onClick={this.close} color={SnappingManager.userColor} />
</div>
{admin ? (
<div className="share-container">
@@ -611,19 +333,45 @@ export class SharingManager extends React.Component<{}> {
</select>
</div>
<div className="share-button">
- <Button text={'SHARE'} type={Type.TERT} color={SettingsManager.userColor} onClick={this.share} />
+ <Button text="SHARE" type={Type.TERT} color={SnappingManager.userColor} onClick={this.share} />
</div>
</div>
<div className="sort-checkboxes">
- <input type="checkbox" onChange={action(() => (this.showUserOptions = !this.showUserOptions))} /> <label style={{ marginRight: 10 }}>Individuals</label>
- <input type="checkbox" onChange={action(() => (this.showGroupOptions = !this.showGroupOptions))} /> <label>Groups</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.showUserOptions = !this.showUserOptions;
+ })}
+ />{' '}
+ <label style={{ marginRight: 10 }}>Individuals</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.showGroupOptions = !this.showGroupOptions;
+ })}
+ />{' '}
+ <label>Groups</label>
</div>
<div className="acl-container">
{Doc.noviceMode ? null : (
<div className="layoutDoc-acls">
- <input type="checkbox" onChange={action(() => (this.upgradeNested = !this.upgradeNested))} checked={this.upgradeNested} /> <label>Upgrade Nested </label>
- <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.upgradeNested = !this.upgradeNested;
+ })}
+ checked={this.upgradeNested}
+ />{' '}
+ <label>Upgrade Nested </label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.layoutDocAcls = !this.layoutDocAcls;
+ })}
+ checked={this.layoutDocAcls}
+ />{' '}
+ <label>Layout</label>
</div>
)}
</div>
@@ -632,14 +380,25 @@ export class SharingManager extends React.Component<{}> {
<div className="share-container">
<div className="acl-container">
<div className="layoutDoc-acls">
- <input type="checkbox" onChange={action(() => (this.layoutDocAcls = !this.layoutDocAcls))} checked={this.layoutDocAcls} /> <label>Layout</label>
+ <input
+ type="checkbox"
+ onChange={action(() => {
+ this.layoutDocAcls = !this.layoutDocAcls;
+ })}
+ checked={this.layoutDocAcls}
+ />{' '}
+ <label>Layout</label>
</div>
</div>
</div>
)}
<div className="main-container" style={{ color: StrCast(Doc.UserDoc().userColor), border: StrCast(Doc.UserDoc().userColor) }}>
- <div className={'individual-container'}>
- <div className="user-sort" onClick={action(() => (this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending'))}>
+ <div className="individual-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.individualSort = this.individualSort === 'ascending' ? 'descending' : this.individualSort === 'descending' ? 'none' : 'ascending';
+ })}>
<div className="title-individual">
Individuals
<IconButton
@@ -651,11 +410,15 @@ export class SharingManager extends React.Component<{}> {
</div>
<div className="users-list">{userListContents}</div>
</div>
- <div className={'group-container'}>
- <div className="user-sort" onClick={action(() => (this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending'))}>
+ <div className="group-container">
+ <div
+ className="user-sort"
+ onClick={action(() => {
+ this.groupSort = this.groupSort === 'ascending' ? 'descending' : this.groupSort === 'descending' ? 'none' : 'ascending';
+ })}>
<div className="title-group">
Groups
- <IconButton icon={<FontAwesomeIcon icon={'info-circle'} />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
+ <IconButton icon={<FontAwesomeIcon icon="info-circle" />} size={Size.XSMALL} color={StrCast(Doc.UserDoc().userColor)} onClick={action(() => GroupManager.Instance.open())} />
<IconButton
icon={<FontAwesomeIcon icon={this.groupSort === 'ascending' ? 'caret-up' : this.groupSort === 'descending' ? 'caret-down' : 'caret-right'} />}
size={Size.XSMALL}
@@ -663,7 +426,7 @@ export class SharingManager extends React.Component<{}> {
/>
</div>
</div>
- <div className={'groups-list'}>{groupListContents}</div>
+ <div className="groups-list">{groupListContents}</div>
</div>
</div>
</div>
@@ -671,7 +434,307 @@ export class SharingManager extends React.Component<{}> {
);
}
+ /**
+ * Shares the document with a user.
+ */
+ setInternalSharing = undoable((recipient: ValidatedUser, permission: string, targetDoc: Doc | undefined) => {
+ const { user, sharingDoc } = recipient;
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl_${normalizeEmail(user.email)}`;
+ const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
+ if (permission !== SharingPermissions.None) {
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc);
+ } else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc);
+ });
+ }, 'set Doc permissions');
+
+ /**
+ * Sets the permission on the target for the group.
+ * @param group
+ * @param permission
+ */
+ setInternalGroupSharing = undoable((group: Doc | { title: string }, permission: string, targetDoc?: Doc) => {
+ const target = targetDoc || this.targetDoc!;
+ const acl = `acl_${normalizeEmail(StrCast(group.title))}`;
+
+ const docs = DocumentView.Selected().length < 2 ? [target] : DocumentView.Selected().map(docView => docView.Document);
+ docs.map(doc => (this.layoutDocAcls || doc.dockingConfig ? doc : Doc.GetProto(doc))).forEach(doc => {
+ distributeAcls(acl, permission as SharingPermissions, doc, undefined, this.upgradeNested ? true : undefined);
+
+ if (group instanceof Doc) {
+ Doc.AddDocToList(group, 'docsShared', doc);
+
+ this.users
+ .filter(({ user: { email } }) => JSON.parse(StrCast(group.members)).includes(email))
+ .forEach(({ user, sharingDoc }) => {
+ if (permission !== SharingPermissions.None)
+ Doc.AddDocToList(sharingDoc, doc.dockingConfig ? dashStorage : storage, doc); // add the doc to the sharingDoc if it hasn't already been added
+ else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, ((doc.createdFrom as Doc) || doc).dockingConfig ? dashStorage : storage, (doc.createdFrom as Doc) || doc); // remove the doc from the list if it already exists
+ });
+ }
+ });
+ }, 'set group permissions');
+ /**
+ * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument.
+ */
+ populateUsers = async () => {
+ if (!this.populating && Doc.UserDoc()[Id] !== Utils.GuestID()) {
+ this.populating = true;
+ const userList = await RequestPromise.get(ClientUtils.prepend('/getUsers'));
+ const raw = (JSON.parse(userList) as User[]).filter(user => user.email !== 'guest' && user.email !== ClientUtils.CurrentUserEmail());
+ runInAction(() => {
+ FieldLoader.ServerLoadStatus.message = 'users';
+ });
+ const docs = await DocServer.GetRefFields(raw.reduce((list, user) => [...list, user.sharingDocumentId, user.linkDatabaseId], [] as string[]));
+ raw.map(
+ action((newUser: User) => {
+ const sharingDoc = docs[newUser.sharingDocumentId];
+ const linkDatabase = docs[newUser.linkDatabaseId];
+ if (sharingDoc instanceof Doc && linkDatabase instanceof Doc) {
+ if (!this.users.find(user => user.user.email === newUser.email)) {
+ this.users.push({ user: newUser, sharingDoc, linkDatabase, userColor: StrCast(sharingDoc.userColor) });
+ // LinkManager.addLinkDB(linkDatabase);
+ }
+ }
+ })
+ );
+ this.populating = false;
+ }
+ };
+
+ // eslint-disable-next-line react/sort-comp
+ public close = action(() => {
+ this.isOpen = false;
+ this.selectedUsers = null; // resets the list of users and selected users (in the react-select component)
+ TaskCompletionBox.taskCompleted = false;
+ setTimeout(
+ action(() => {
+ // this.copied = false;
+ this.targetDoc = undefined;
+ }),
+ 500
+ );
+ this.layoutDocAcls = false;
+ });
+
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ public open = (target?: DocumentView, targetDoc?: Doc) => {
+ this.populateUsers();
+ runInAction(() => {
+ this.targetDocView = target;
+ this.targetDoc = targetDoc || target?.Document;
+ this.isOpen = this.targetDoc !== undefined;
+ this.permissions = SharingPermissions.Augment;
+ this.upgradeNested = true;
+ });
+ };
+
+ /**
+ * Shares the documents shared with a group with a new user who has been added to that group.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ shareWithAddedMember = (group: Doc, emailId: string, retry: boolean = true) => {
+ const user = this.users.find(({ user: { email } }) => email === emailId)!;
+ const self = this;
+ if (group.docsShared) {
+ if (!user) retry && this.populateUsers().then(() => self.shareWithAddedMember(group, emailId, false));
+ else {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => !doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const filtered = dl?.filter(doc => doc.dockingConfig && !userdocs?.includes(doc));
+ filtered && userdocs?.push(...filtered);
+ })
+ );
+ }
+ }
+ };
+
+ /**
+ * Called from the properties sidebar to change permissions of a user.
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ shareFromPropertiesSidebar = undoable((shareWith: string, permission: SharingPermissions, docs: Doc[], layout: boolean) => {
+ if (layout) this.layoutDocAcls = true;
+ if (shareWith !== 'Guest') {
+ const user = this.users.find(({ user: { email } }) => email === (shareWith === 'Me' ? ClientUtils.CurrentUserEmail() : shareWith));
+ docs.forEach(doc => {
+ if (user) this.setInternalSharing(user, permission, doc);
+ else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, doc, undefined, true);
+ });
+ } else {
+ docs.forEach(doc => {
+ if (GetEffectiveAcl(doc) === AclAdmin) {
+ distributeAcls(`acl_${shareWith}`, permission, doc, undefined);
+ }
+ });
+ }
+ this.layoutDocAcls = false;
+ }, 'sidebar set permissions');
+
+ /**
+ * Removes the documents shared with a user through a group when the user is removed from the group.
+ * @param group
+ * @param emailId
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeMember = (group: Doc, emailId: string) => {
+ const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!;
+
+ if (group.docsShared && user) {
+ DocListCastAsync(user.sharingDoc[storage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
+ DocListCastAsync(user.sharingDoc[dashStorage]).then(userdocs =>
+ DocListCastAsync(group.docsShared).then(dl => {
+ const remaining = userdocs?.filter(doc => !dl?.includes(doc)) || [];
+ userdocs?.splice(0, userdocs.length, ...remaining);
+ })
+ );
+ }
+ };
+
+ /**
+ * Removes a group's permissions from documents that have been shared with it.
+ * @param group
+ */
+ // eslint-disable-next-line react/no-unused-class-component-methods
+ removeGroup = (group: Doc) => {
+ if (group.docsShared) {
+ DocListCast(group.docsShared).forEach(doc => {
+ const acl = `acl_${StrCast(group.title)}`;
+ distributeAcls(acl, SharingPermissions.None, doc);
+
+ const members: string[] = JSON.parse(StrCast(group.members));
+ const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email));
+
+ users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc));
+ });
+ }
+ };
+
+ // private setExternalSharing = (permission: string) => {
+ // const targetDoc = this.targetDoc;
+ // if (!targetDoc) {
+ // return;
+ // }
+ // targetDoc['acl_' + PublicKey] = permission;
+ // }s
+
+ /**
+ * Copies the Public sharing url to the user's clipboard.
+ */
+ private copyURL = () => {
+ ClientUtils.CopyText(ClientUtils.shareUrl(this.targetDoc![Id]));
+ };
+
+ private focusOn = (contents: string) => {
+ const title = this.targetDoc ? StrCast(this.targetDoc.title) : '';
+ const docs = DocumentView.Selected().length > 1 ? DocumentView.Selected().map(docView => docView.props.Document) : [this.targetDoc];
+ return (
+ <span
+ className="focus-span"
+ title={title}
+ onClick={() => {
+ if (this.targetDoc && this.targetDocView && docs.length === 1) {
+ DocumentView.showDocument(this.targetDoc, { willZoomCentered: true });
+ }
+ }}
+ onPointerEnter={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.BrushDoc(doc));
+ this.dialogueBoxOpacity = 0.1;
+ }
+ })}
+ onPointerLeave={action(() => {
+ if (docs.length) {
+ docs.forEach(doc => doc && Doc.UnBrushDoc(doc));
+ this.dialogueBoxOpacity = 1;
+ }
+ })}>
+ {contents}
+ </span>
+ );
+ };
+
+ /**
+ * Calls the relevant method for sharing, displays the popup, and resets the relevant variables.
+ */
+ share = undoable(
+ action(() => {
+ if (this.selectedUsers) {
+ this.selectedUsers.forEach(user => {
+ if (user.value.includes(indType)) {
+ this.setInternalSharing(this.users.find(u => u.user.email === user.label)!, this.permissions, undefined);
+ } else {
+ this.setInternalGroupSharing(GroupManager.Instance.getGroup(user.label)!, this.permissions);
+ }
+ });
+
+ 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;
+ }
+ }),
+ 'share Doc'
+ );
+
+ /**
+ * Sorting algorithm to sort users.
+ */
+ sortUsers = (u1: ValidatedUser, u2: ValidatedUser) => {
+ const { email: e1 } = u1.user;
+ const { email: e2 } = u2.user;
+ return e1 < e2 ? -1 : e1 === e2 ? 0 : 1;
+ };
+
+ /**
+ * Sorting algorithm to sort groups.
+ */
+ sortGroups = (group1: Doc, group2: Doc) => {
+ const g1 = StrCast(group1.title);
+ const g2 = StrCast(group2.title);
+ return g1 < g2 ? -1 : g1 === g2 ? 0 : 1;
+ };
+ /**
+ * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share
+ */
+ private sharingOptions(uniform: boolean, showGuestOptions?: boolean) {
+ const dropdownValues: string[] = showGuestOptions ? [SharingPermissions.None, SharingPermissions.View] : Object.values(SharingPermissions);
+ if (!uniform) dropdownValues.unshift('-multiple-');
+ return dropdownValues.map(permission => (
+ <option key={permission} value={permission}>
+ {concat(ReverseHierarchyMap.get(permission)?.image, ' ', permission)}
+ </option>
+ ));
+ }
+
render() {
- return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive={true} dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} overlayDisplayedOpacity={this.overlayOpacity} closeOnExternalClick={this.close} />;
+ return <MainViewModal contents={this.sharingInterface} isDisplayed={this.isOpen} interactive dialogueBoxDisplayedOpacity={this.dialogueBoxOpacity} closeOnExternalClick={this.close} />;
}
}
diff --git a/src/client/util/SnappingManager.ts b/src/client/util/SnappingManager.ts
index 359140732..6789c2ab8 100644
--- a/src/client/util/SnappingManager.ts
+++ b/src/client/util/SnappingManager.ts
@@ -1,7 +1,11 @@
-import { observable, action, runInAction, reaction, makeObservable } from 'mobx';
-import { Doc, Opt } from '../../fields/Doc';
+import { observable, action, runInAction, makeObservable } from 'mobx';
+export enum freeformScrollMode {
+ Pan = 'pan',
+ Zoom = 'zoom',
+}
export class SnappingManager {
+ // eslint-disable-next-line no-use-before-define
private static _manager: SnappingManager;
private static get Instance() {
return SnappingManager._manager ?? new SnappingManager();
@@ -10,20 +14,27 @@ export class SnappingManager {
@observable _shiftKey = false;
@observable _ctrlKey = false;
@observable _metaKey = false;
+ @observable _showPresPaths = false;
@observable _isLinkFollowing = false;
@observable _isDragging: boolean = false;
- @observable _isResizing: Doc | undefined = undefined;
+ @observable _isResizing: string | undefined = undefined; // the string is the Id of the document being resized
@observable _canEmbed: boolean = false;
@observable _horizSnapLines: number[] = [];
@observable _vertSnapLines: number[] = [];
@observable _exploreMode = false;
+ @observable _userPanned = false;
+ @observable _serverVersion: string = '';
+ @observable _lastBtnId: string = '';
+ @observable _propertyWid: number = 0;
private constructor() {
SnappingManager._manager = this;
makeObservable(this);
}
- @action public static clearSnapLines = () => (this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0);
+ @action public static clearSnapLines = () => {
+ this.Instance._vertSnapLines.length = this.Instance._horizSnapLines.length = 0;
+ };
@action public static addSnapLines = (horizLines: number[], vertLines: number[]) => {
this.Instance._horizSnapLines.push(...horizLines);
this.Instance._vertSnapLines.push(...vertLines);
@@ -34,17 +45,33 @@ export class SnappingManager {
public static get ShiftKey() { return this.Instance._shiftKey; } // prettier-ignore
public static get CtrlKey() { return this.Instance._ctrlKey; } // prettier-ignore
public static get MetaKey() { return this.Instance._metaKey; } // prettier-ignore
+ public static get ShowPresPaths() { return this.Instance._showPresPaths; } // prettier-ignore
public static get IsLinkFollowing(){ return this.Instance._isLinkFollowing; } // prettier-ignore
public static get IsDragging() { return this.Instance._isDragging; } // prettier-ignore
public static get IsResizing() { return this.Instance._isResizing; } // prettier-ignore
public static get CanEmbed() { return this.Instance._canEmbed; } // prettier-ignore
public static get ExploreMode() { return this.Instance._exploreMode; } // prettier-ignore
- public static SetShiftKey = (down: boolean) => runInAction(() => (this.Instance._shiftKey = down)); // prettier-ignore
- public static SetCtrlKey = (down: boolean) => runInAction(() => (this.Instance._ctrlKey = down)); // prettier-ignore
- public static SetMetaKey = (down: boolean) => runInAction(() => (this.Instance._metaKey = down)); // prettier-ignore
- public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => (this.Instance._isLinkFollowing = follow)); // prettier-ignore
- public static SetIsDragging = (drag: boolean) => runInAction(() => (this.Instance._isDragging = drag)); // prettier-ignore
- public static SetIsResizing = (doc: Opt<Doc>) => runInAction(() => (this.Instance._isResizing = doc)); // prettier-ignore
- public static SetCanEmbed = (embed:boolean) => runInAction(() => (this.Instance._canEmbed = embed)); // prettier-ignore
- public static SetExploreMode = (state:boolean) => runInAction(() => (this.Instance._exploreMode = state)); // prettier-ignore
+ public static get UserPanned() { return this.Instance._userPanned; } // prettier-ignore
+ public static get ServerVersion() { return this.Instance._serverVersion; } // prettier-ignore
+ public static get LastPressedBtn() { return this.Instance._lastBtnId; } // prettier-ignore
+ public static get PropertiesWidth(){ return this.Instance._propertyWid; } // prettier-ignore
+
+ public static SetShiftKey = (down: boolean) => runInAction(() => {this.Instance._shiftKey = down}); // prettier-ignore
+ public static SetCtrlKey = (down: boolean) => runInAction(() => {this.Instance._ctrlKey = down}); // prettier-ignore
+ public static SetMetaKey = (down: boolean) => runInAction(() => {this.Instance._metaKey = down}); // prettier-ignore
+ public static SetShowPresPaths = (paths:boolean) => runInAction(() => {this.Instance._showPresPaths = paths}); // prettier-ignore
+ public static SetIsLinkFollowing= (follow:boolean)=> runInAction(() => {this.Instance._isLinkFollowing = follow}); // prettier-ignore
+ public static SetIsDragging = (drag: boolean) => runInAction(() => {this.Instance._isDragging = drag}); // prettier-ignore
+ public static SetIsResizing = (docid?:string) => runInAction(() => {this.Instance._isResizing = docid}); // prettier-ignore
+ public static SetCanEmbed = (embed:boolean) => runInAction(() => {this.Instance._canEmbed = embed}); // prettier-ignore
+ public static SetExploreMode = (state:boolean) => runInAction(() => {this.Instance._exploreMode = state}); // prettier-ignore
+ public static TriggerUserPanned = () => runInAction(() => {this.Instance._userPanned = !this.Instance._userPanned}); // prettier-ignore
+ public static SetServerVersion = (version:string) =>runInAction(() => {this.Instance._serverVersion = version}); // prettier-ignore
+ public static SetLastPressedBtn = (id:string) =>runInAction(() => {this.Instance._lastBtnId = id}); // prettier-ignore
+ public static SetPropertiesWidth= (wid:number) =>runInAction(() => {this.Instance._propertyWid = wid}); // prettier-ignore
+
+ public static userColor: string | undefined;
+ public static userVariantColor: string | undefined;
+ public static userBackgroundColor: string | undefined;
+ public static SettingsStyle: any;
}
diff --git a/src/client/util/TrackMovements.ts b/src/client/util/TrackMovements.ts
index f9c2d522f..25a3c9ad8 100644
--- a/src/client/util/TrackMovements.ts
+++ b/src/client/util/TrackMovements.ts
@@ -1,8 +1,7 @@
-import { IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx';
+import { IReactionDisposer, makeObservable, observable, reaction } from 'mobx';
import { NumCast } from '../../fields/Types';
import { Doc, DocListCast } from '../../fields/Doc';
import { CollectionDockingView } from '../views/collections/CollectionDockingView';
-import { Id } from '../../fields/FieldSymbols';
import { CollectionViewType } from '../documents/DocumentTypes';
export type Movement = {
@@ -33,6 +32,7 @@ export class TrackMovements {
private tabChangeDisposeFunc: IReactionDisposer | null;
// create static instance and getter for global use
+ // eslint-disable-next-line no-use-before-define
@observable static _instance: TrackMovements;
static get Instance(): TrackMovements {
return TrackMovements._instance;
@@ -92,12 +92,13 @@ export class TrackMovements {
// so that the size comparisons are correct, we must filter to only the FFViews
const isFFView = (doc: Doc) => doc && doc._type_collection === CollectionViewType.Freeform;
const tabbedFFViews = new Set<Doc>();
- for (const DashDoc of tabbedDocs) {
+ tabbedDocs.forEach(DashDoc => {
if (isFFView(DashDoc)) tabbedFFViews.add(DashDoc);
- }
+ });
// new tab was added - need to add it
if (tabbedFFViews.size > this.recordingFFViews.size) {
+ // eslint-disable-next-line no-restricted-syntax
for (const DashDoc of tabbedDocs) {
if (!this.recordingFFViews.has(DashDoc)) {
if (isFFView(DashDoc)) {
@@ -111,6 +112,7 @@ export class TrackMovements {
}
// tab was removed - need to remove it from recordingFFViews
else if (tabbedFFViews.size < this.recordingFFViews.size) {
+ // eslint-disable-next-line no-restricted-syntax
for (const [doc] of this.recordingFFViews) {
if (!tabbedFFViews.has(doc)) {
this.removeRecordingFFView(doc);
@@ -208,11 +210,11 @@ export class TrackMovements {
return;
}
- for (const [id, disposeFunc] of this.recordingFFViews) {
+ Array.from(this.recordingFFViews).forEach(([id, disposeFunc]) => {
// console.info('calling dispose func : docId', id);
disposeFunc();
- this.recordingFFViews.delete(id);
- }
+ this.recordingFFViews?.delete(id);
+ });
};
private trackMovement = (panX: number, panY: number, doc: Doc, scale: number = 0) => {
@@ -241,9 +243,9 @@ export class TrackMovements {
// method that concatenates an array of presentatations into one
public concatPresentations = (presentations: Presentation[]): Presentation => {
// these three will lead to the combined presentation
- let combinedMovements: Movement[] = [];
+ const combinedMovements: Movement[] = [];
let sumTime = 0;
- let combinedMetas: any[] = [];
+ const combinedMetas: any[] = [];
presentations.forEach(presentation => {
const { movements, totalTime, meta } = presentation;
@@ -251,9 +253,7 @@ export class TrackMovements {
// update movements if they had one
if (movements) {
// add the summed time to the movements
- const addedTimeMovements = movements.map(move => {
- return { ...move, time: move.time + sumTime };
- });
+ const addedTimeMovements = movements.map(move => ({ ...move, time: move.time + sumTime }));
// concat the movements already in the combined presentation with these new ones
combinedMovements.push(...addedTimeMovements);
}
diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts
index dca37c960..1a07dd6ae 100644
--- a/src/client/util/Transform.ts
+++ b/src/client/util/Transform.ts
@@ -116,20 +116,14 @@ export class Transform {
preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform);
- transformPoint = (x: number, y: number): [number, number] => {
- x *= this._scale;
- x += this._translateX;
- y *= this._scale;
- y += this._translateY;
- return [x, y];
- };
+ transformPoint = (x: number, y: number): [number, number] => [x * this._scale + this._translateX, y * this._scale + this._translateY];
transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale];
transformBounds(x: number, y: number, width: number, height: number): { x: number; y: number; width: number; height: number } {
- [x, y] = this.transformPoint(x, y);
- [width, height] = this.transformDirection(width, height);
- return { x, y, width, height };
+ const [tx, ty] = this.transformPoint(x, y);
+ const [twidth, theight] = this.transformDirection(width, height);
+ return { x: tx, y: ty, width: twidth, height: theight };
}
inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale, -this._rotate);
diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts
index 90fd299c1..9ef2aa8d7 100644
--- a/src/client/util/TypedEvent.ts
+++ b/src/client/util/TypedEvent.ts
@@ -14,27 +14,27 @@ export class TypedEvent<T> {
on = (listener: Listener<T>): Disposable => {
this.listeners.push(listener);
return {
- dispose: () => this.off(listener)
+ dispose: () => this.off(listener),
};
- }
+ };
once = (listener: Listener<T>): void => {
this.listenersOncer.push(listener);
- }
+ };
off = (listener: Listener<T>) => {
const callbackIndex = this.listeners.indexOf(listener);
if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1);
- }
+ };
emit = (event: T) => {
/** Update any general listeners */
- this.listeners.forEach((listener) => listener(event));
+ this.listeners.forEach(listener => listener(event));
/** Clear the `once` queue */
- this.listenersOncer.forEach((listener) => listener(event));
+ this.listenersOncer.forEach(listener => listener(event));
this.listenersOncer = [];
- }
+ };
- pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e));
-} \ No newline at end of file
+ pipe = (te: TypedEvent<T>): Disposable => this.on(e => te.emit(e));
+}
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index 857ca852f..956c0e674 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -1,7 +1,11 @@
-import { observable, action, runInAction } from 'mobx';
-import { Doc, Field } from '../../fields/Doc';
-import { RichTextField } from '../../fields/RichTextField';
+/* eslint-disable prefer-spread */
+/* eslint-disable no-use-before-define */
+import { action, observable, runInAction } from 'mobx';
import { Without } from '../../Utils';
+import { RichTextField } from '../../fields/RichTextField';
+
+// eslint-disable-next-line prefer-const
+let printToConsole = false; // Doc.MyDockedBtns.linearView_IsOpen
function getBatchName(target: any, key: string | symbol): string {
const keyName = key.toString();
@@ -37,10 +41,11 @@ function propertyDecorator(target: any, key: string | symbol) {
}
export function undoable(fn: (...args: any[]) => any, batchName: string): (...args: any[]) => any {
- return function () {
+ return function (...fargs) {
const batch = UndoManager.StartBatch(batchName);
try {
- return fn.apply(undefined, arguments as any);
+ // eslint-disable-next-line prefer-rest-params
+ return fn.apply(undefined, fargs);
} finally {
batch.end();
}
@@ -48,13 +53,15 @@ export function undoable(fn: (...args: any[]) => any, batchName: string): (...ar
}
export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any;
+// eslint-disable-next-line no-redeclare
export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any;
+// eslint-disable-next-line no-redeclare
export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any {
if (!key) {
- return function () {
+ return function (...fargs: any[]) {
const batch = UndoManager.StartBatch('');
try {
- return target.apply(undefined, arguments);
+ return target.apply(undefined, fargs);
} finally {
batch.end();
}
@@ -62,7 +69,7 @@ export function undoBatch(target: any, key?: string | symbol, descriptor?: Typed
}
if (!descriptor) {
propertyDecorator(target, key);
- return;
+ return undefined;
}
const oldFunction = descriptor.value;
@@ -86,24 +93,28 @@ export namespace UndoManager {
}
type UndoBatch = UndoEvent[];
- export let undoStackNames: string[] = observable([]);
- export let redoStackNames: string[] = observable([]);
- export let undoStack: UndoBatch[] = observable([]);
- export let redoStack: UndoBatch[] = observable([]);
let currentBatch: UndoBatch | undefined;
- export let batchCounter = observable.box(0);
let undoing = false;
- export let tempEvents: UndoEvent[] | undefined = undefined;
+ let tempEvents: UndoEvent[] | undefined;
+ export const undoStackNames: string[] = observable([]);
+ export const redoStackNames: string[] = observable([]);
+ export const undoStack: UndoBatch[] = observable([]);
+ export const redoStack: UndoBatch[] = observable([]);
+ export const batchCounter = observable.box(0);
+ let _fieldPrinter: (val: any) => string = val => val?.toString();
+ export function SetFieldPrinter(printer: (val: any) => string) {
+ _fieldPrinter = printer;
+ }
export function AddEvent(event: UndoEvent, value?: any): void {
if (currentBatch && batchCounter.get() && !undoing) {
- Doc.MyDockedBtns.linearView_IsOpen &&
+ printToConsole &&
console.log(
' '.slice(0, batchCounter.get()) +
'UndoEvent : ' +
event.prop +
- ' = ' +
- (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(val => Field.toJavascriptString(val)).join(',') : Field.toJavascriptString(value))
+ ' = ' + // prettier-ignore
+ (value instanceof RichTextField ? value.Text : value instanceof Array ? value.map(_fieldPrinter).join(',') : _fieldPrinter(value))
);
currentBatch.push(event);
tempEvents?.push(event);
@@ -129,21 +140,22 @@ export namespace UndoManager {
}
export function FilterBatches(fieldTypes: string[]) {
const fieldCounts: { [key: string]: number } = {};
- const lastStack = UndoManager.undoStack.slice(-1)[0]; //.lastElement();
+ const lastStack = UndoManager.undoStack.slice(-1)[0]; // .lastElement();
if (lastStack) {
- lastStack.forEach(ev => fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1));
+ lastStack.forEach(ev => {
+ fieldTypes.includes(ev.prop) && (fieldCounts[ev.prop] = (fieldCounts[ev.prop] || 0) + 1);
+ });
const fieldCount2: { [key: string]: number } = {};
- runInAction(
- () =>
- (UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
- if (fieldTypes.includes(ev.prop)) {
- fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
- if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
- return false;
- }
- return true;
- }))
- );
+ runInAction(() => {
+ UndoManager.undoStack[UndoManager.undoStack.length - 1] = lastStack.filter(ev => {
+ if (fieldTypes.includes(ev.prop)) {
+ fieldCount2[ev.prop] = (fieldCount2[ev.prop] || 0) + 1;
+ if (fieldCount2[ev.prop] === 1 || fieldCount2[ev.prop] === fieldCounts[ev.prop]) return true;
+ return false;
+ }
+ return true;
+ });
+ });
}
}
export function TraceOpenBatches() {
@@ -160,11 +172,10 @@ export namespace UndoManager {
if (this.disposed) {
console.log('WARNING: undo batch already disposed');
return false;
- } else {
- this.disposed = true;
- openBatches.splice(openBatches.indexOf(this));
- return EndBatch(this.batchName, cancel);
}
+ this.disposed = true;
+ openBatches.splice(openBatches.indexOf(this));
+ return EndBatch(this.batchName, cancel);
};
end = () => this.dispose(false);
@@ -172,7 +183,7 @@ export namespace UndoManager {
}
export function StartBatch(batchName: string): Batch {
- Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
+ printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'Start ' + batchCounter + ' ' + batchName);
runInAction(() => batchCounter.set(batchCounter.get() + 1));
if (currentBatch === undefined) {
currentBatch = [];
@@ -182,7 +193,7 @@ export namespace UndoManager {
const EndBatch = action((batchName: string, cancel: boolean = false) => {
runInAction(() => batchCounter.set(batchCounter.get() - 1));
- Doc.MyDockedBtns.linearView_IsOpen && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + currentBatch?.length + ')');
+ printToConsole && console.log(' '.slice(0, batchCounter.get()) + 'End ' + batchName + ' (' + (currentBatch?.length ?? 0) + ')');
if (batchCounter.get() === 0 && currentBatch?.length) {
if (!cancel) {
undoStack.push(currentBatch);
@@ -199,10 +210,10 @@ export namespace UndoManager {
export function StartTempBatch() {
tempEvents = [];
}
- export function EndTempBatch<T>(success: boolean) {
+ export function EndTempBatch(success: boolean) {
UndoManager.UndoTempBatch(success);
}
- //TODO Make this return the return value
+ // TODO Make this return the return value
export function RunInBatch<T>(fn: () => T, batchName: string) {
const batch = StartBatch(batchName);
try {
@@ -234,9 +245,11 @@ export namespace UndoManager {
}
undoing = true;
- for (let i = commands.length - 1; i >= 0; i--) {
- commands[i].undo();
- }
+ // eslint-disable-next-line prettier/prettier
+ commands
+ .slice()
+ .reverse()
+ .forEach(command => command.undo());
undoing = false;
redoStackNames.push(names ?? '???');
@@ -255,9 +268,7 @@ export namespace UndoManager {
}
undoing = true;
- for (const command of commands) {
- command.redo();
- }
+ commands.forEach(command => command.redo());
undoing = false;
undoStackNames.push(names ?? '???');
diff --git a/src/client/util/bezierFit.ts b/src/client/util/bezierFit.ts
index 8fc6de6f9..d6f3f2340 100644
--- a/src/client/util/bezierFit.ts
+++ b/src/client/util/bezierFit.ts
@@ -1,4 +1,8 @@
-import { Point } from "../../pen-gestures/ndollar";
+/* eslint-disable no-use-before-define */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable no-param-reassign */
+/* eslint-disable camelcase */
+import { Point } from '../../pen-gestures/ndollar';
class SmartRect {
minx: number = 0;
@@ -6,20 +10,43 @@ class SmartRect {
maxx: number = 0;
maxy: number = 0;
- constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) { this.minx = mix; this.miny = miy; this.maxx = max; this.maxy = may; }
+ constructor(mix: number = 0, miy: number = 0, max: number = 0, may: number = 0) {
+ this.minx = mix;
+ this.miny = miy;
+ this.maxx = max;
+ this.maxy = may;
+ }
- public get Center() { return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0); }
- public get TopLeft() { return new Point(this.minx, this.miny); }
- public get TopRight() { return new Point(this.maxx, this.miny); }
- public get BotLeft() { return new Point(this.minx, this.maxy); }
- public get BotRight() { return new Point(this.maxx, this.maxy); }
- public get Width() { return this.maxx - this.minx; }
- public get Height() { return this.maxy - this.miny; }
- public static Intersect(a: SmartRect, b: SmartRect) { return a.Intersect(b); }
- public Intersect(b: SmartRect) { return !((this.minx > b.maxx) || (this.miny > b.maxy) || (b.minx > this.maxx) || (b.miny > this.maxy)); }
+ public get Center() {
+ return new Point((this.maxx + this.minx) / 2.0, (this.maxy + this.miny) / 2.0);
+ }
+ public get TopLeft() {
+ return new Point(this.minx, this.miny);
+ }
+ public get TopRight() {
+ return new Point(this.maxx, this.miny);
+ }
+ public get BotLeft() {
+ return new Point(this.minx, this.maxy);
+ }
+ public get BotRight() {
+ return new Point(this.maxx, this.maxy);
+ }
+ public get Width() {
+ return this.maxx - this.minx;
+ }
+ public get Height() {
+ return this.maxy - this.miny;
+ }
+ public static Intersect(a: SmartRect, b: SmartRect) {
+ return a.Intersect(b);
+ }
+ public Intersect(b: SmartRect) {
+ return !(this.minx > b.maxx || this.miny > b.maxy || b.minx > this.maxx || b.miny > this.maxy);
+ }
public ContainsPercentage(other: SmartRect, axis: Point) {
- var ret = 0;
+ let ret = 0;
const minx = Math.max(other.TopLeft.X * axis.X + other.TopLeft.Y * axis.Y, this.TopLeft.X * axis.X + this.TopLeft.Y * axis.Y);
const maxx = Math.max(other.BotRight.X * axis.X + other.BotRight.Y * axis.Y, this.BotRight.X * axis.X + this.BotRight.Y * axis.Y);
ret = maxx > minx ? (maxx - minx) / (axis === new Point(1, 0) ? other.Width : other.Height) : 0;
@@ -36,7 +63,7 @@ class SmartRect {
if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny];
- for (const pt of p) {
+ p.forEach(pt => {
if (pt.X < r.minx) {
r.minx = pt.X;
} else if (pt.X > r.maxx) {
@@ -47,7 +74,7 @@ class SmartRect {
} else if (pt.Y > r.maxy) {
r.maxy = pt.Y;
}
- }
+ });
}
return r;
}
@@ -64,18 +91,18 @@ export function Normalize(p: Point) {
function ReparameterizeBezier(d: Point[], first: number, last: number, u: number[], bezCurve: Point[]) {
const uPrime = new Array<number>(last - first + 1); // New parameter values
- for (var i = first; i <= last; i++) {
+ for (let i = first; i <= last; i++) {
uPrime[i - first] = NewtonRaphsonRootFind(bezCurve, d[i], u[i - first]);
}
return uPrime;
}
function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Point[], u: number[]) {
- var maxError = 0; // Maximum error
- var splitPoint2D = (last - first + 1) / 2;
- for (var i = first + 1; i < last; i++) {
- const P = [0, 0]; // point on curve
+ let maxError = 0; // Maximum error
+ let splitPoint2D = (last - first + 1) / 2;
+ for (let i = first + 1; i < last; i++) {
+ const P = [0, 0]; // point on curve
EvalBezierFast(bezCurve, u[i - first], P);
- const dx = P[0] - d[i].X;// offset from point to curve
+ const dx = P[0] - d[i].X; // offset from point to curve
const dy = P[1] - d[i].Y;
const dist = Math.sqrt(dx * dx + dy * dy); // Current error
if (dist >= maxError) {
@@ -88,11 +115,11 @@ function ComputeMaxError(d: Point[], first: number, last: number, bezCurve: Poin
return { maxError, splitPoint2D };
}
function ChordLengthParameterize(d: Point[], first: number, last: number) {
- const u = new Array<number>(last - first + 1);// Parameterization
+ const u = new Array<number>(last - first + 1); // Parameterization
- var prev = 0.0;
+ let prev = 0.0;
u[0] = prev;
- for (var i = first + 1; i <= last; i++) {
+ for (let i = first + 1; i <= last; i++) {
const lastd = d[i - 1];
const curd = d[i];
const dx = lastd.X - curd.X;
@@ -101,27 +128,38 @@ function ChordLengthParameterize(d: Point[], first: number, last: number) {
}
const ulastfirst = u[last - first];
- for (var i = first + 1; i <= last; i++) {
+ for (let i = first + 1; i <= last; i++) {
u[i - first] /= ulastfirst;
}
return u;
}
/*
-* B0, B1, B2, B3 :
-* Bezier multipliers
-*/
-function B0(u: number) { const tmp = 1.0 - u; return tmp * tmp * tmp; }
-function B1(u: number) { const tmp = 1.0 - u; return 3 * u * tmp * tmp; }
-function B2(u: number) { const tmp = 1.0 - u; return 3 * u * u * tmp; }
-function B3(u: number) { return u * u * u; }
+ * B0, B1, B2, B3 :
+ * Bezier multipliers
+ */
+function B0(u: number) {
+ const tmp = 1.0 - u;
+ return tmp * tmp * tmp;
+}
+function B1(u: number) {
+ const tmp = 1.0 - u;
+ return 3 * u * tmp * tmp;
+}
+function B2(u: number) {
+ const tmp = 1.0 - u;
+ return 3 * u * u * tmp;
+}
+function B3(u: number) {
+ return u * u * u;
+}
function bounds(p: Point[]) {
const r = new SmartRect(p[0].X, p[0].Y, p[3].X, p[3].Y); // These are the most likely to be extremal
- if (r.minx > r.maxx) (r.minx, r.maxx);
+ if (r.minx > r.maxx) [r.minx, r.maxx] = [r.maxx, r.minx];
if (r.miny > r.maxy) [r.miny, r.maxy] = [r.maxy, r.miny]; // swap min & max
- for (var i = 1; i < 3; i++) {
+ for (let i = 1; i < 3; i++) {
if (p[i].X < r.minx) r.minx = p[i].X;
else if (p[i].X > r.maxx) r.maxx = p[i].X;
@@ -131,37 +169,36 @@ function bounds(p: Point[]) {
return r;
}
-
function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
const sz = 4;
const Vtemp = new Array<Array<Point>>(4);
- for (var i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
+ for (let i = 0; i < 4; i++) Vtemp[i] = new Array<Point>(4);
/* Copy control points */
// std::copy(p.begin(), p.end(), Vtemp[0]);
- for (var i = 0; i < sz; i++) {
+ for (let i = 0; i < sz; i++) {
Vtemp[0][i].X = p[i].X;
Vtemp[0][i].Y = p[i].Y;
}
/* Triangle computation */
- for (var i = 1; i < sz; i++) {
- for (var j = 0; j < sz - i; j++) {
+ for (let i = 1; i < sz; i++) {
+ for (let j = 0; j < sz - i; j++) {
const a = Vtemp[i - 1][j];
const b = Vtemp[i - 1][j + 1];
Vtemp[i][j].X = b.X * t + a.X * (1 - t);
- Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
+ Vtemp[i][j].Y = b.Y * t + a.Y * (1 - t); // Vtemp[i][j] = Point2D::Lerp(Vtemp[i - 1][j], Vtemp[i - 1][j + 1], t);
}
}
if (left) {
- for (var j = 0; j < sz; j++) {
+ for (let j = 0; j < sz; j++) {
left[j].X = Vtemp[j][0].X;
left[j].Y = Vtemp[j][0].Y;
}
}
if (right) {
- for (var j = 0; j < sz; j++) {
+ for (let j = 0; j < sz; j++) {
right[j].X = Vtemp[sz - 1 - j][j].X;
right[j].Y = Vtemp[sz - 1 - j][j].Y;
}
@@ -169,51 +206,54 @@ function splitCubic(p: Point[], t: number, left: Point[], right: Point[]) {
}
/*
-* Recursively intersect two curves keeping track of their real parameters
-* and depths of intersection.
-* The results are returned in a 2-D array of doubles indicating the parameters
-* for which intersections are found. The parameters are in the order the
-* intersections were found, which is probably not in sorted order.
-* When an intersection is found, the parameter value for each of the two
-* is stored in the index elements array, and the index is incremented.
-*
-* If either of the curves has subdivisions left before it is straight
-* (depth > 0)
-* that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
-* the depth(s) is (are) decremented, and the parameter value(s) corresponding
-* to the midpoints(s) is (are) computed.
-* Then each of the subcurves of one curve is intersected with each of the
-* subcurves of the other curve, first by testing the bounding boxes for
-* interference. If there is any bounding box interference, the corresponding
-* subcurves are recursively intersected.
-*
-* If neither curve has subdivisions left, the line segments from the first
-* to last control point of each segment are intersected. (Actually the
-* only the parameter value corresponding to the intersection point is found).
-*
-* The apriori flatness test is probably more efficient than testing at each
-* level of recursion, although a test after three or four levels would
-* probably be worthwhile, since many curves become flat faster than their
-* asymptotic rate for the first few levels of recursion.
-*
-* The bounding box test fails much more frequently than it succeeds, providing
-* substantial pruning of the search space.
-*
-* Each (sub)curve is subdivided only once, hence it is not possible that for
-* one final line intersection test the subdivision was at one level, while
-* for another final line intersection test the subdivision (of the same curve)
-* was at another. Since the line segments share endpoints, the intersection
-* is robust: a near-tangential intersection will yield zero or two
-* intersections.
-*/
+ * Recursively intersect two curves keeping track of their real parameters
+ * and depths of intersection.
+ * The results are returned in a 2-D array of doubles indicating the parameters
+ * for which intersections are found. The parameters are in the order the
+ * intersections were found, which is probably not in sorted order.
+ * When an intersection is found, the parameter value for each of the two
+ * is stored in the index elements array, and the index is incremented.
+ *
+ * If either of the curves has subdivisions left before it is straight
+ * (depth > 0)
+ * that curve (possibly both) is (are) subdivided at its (their) midpoint(s).
+ * the depth(s) is (are) decremented, and the parameter value(s) corresponding
+ * to the midpoints(s) is (are) computed.
+ * Then each of the subcurves of one curve is intersected with each of the
+ * subcurves of the other curve, first by testing the bounding boxes for
+ * interference. If there is any bounding box interference, the corresponding
+ * subcurves are recursively intersected.
+ *
+ * If neither curve has subdivisions left, the line segments from the first
+ * to last control point of each segment are intersected. (Actually the
+ * only the parameter value corresponding to the intersection point is found).
+ *
+ * The apriori flatness test is probably more efficient than testing at each
+ * level of recursion, although a test after three or four levels would
+ * probably be worthwhile, since many curves become flat faster than their
+ * asymptotic rate for the first few levels of recursion.
+ *
+ * The bounding box test fails much more frequently than it succeeds, providing
+ * substantial pruning of the search space.
+ *
+ * Each (sub)curve is subdivided only once, hence it is not possible that for
+ * one final line intersection test the subdivision was at one level, while
+ * for another final line intersection test the subdivision (of the same curve)
+ * was at another. Since the line segments share endpoints, the intersection
+ * is robust: a near-tangential intersection will yield zero or two
+ * intersections.
+ */
+// eslint-disable-next-line @typescript-eslint/no-unused-vars
function recursively_intersect(a: Point[], t0: number, t1: number, deptha: number, b: Point[], u0: number, u1: number, depthb: number, parameters: number[][]) {
if (deptha > 0) {
- const a1 = new Array<Point>(4), a2 = new Array<Point>(4);
+ const a1 = new Array<Point>(4);
+ const a2 = new Array<Point>(4);
splitCubic(a, 0.5, a1, a2);
const tmid = (t0 + t1) * 0.5;
deptha--;
if (depthb > 0) {
- const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
+ const b1 = new Array<Point>(4);
+ const b2 = new Array<Point>(4);
splitCubic(b, 0.5, b1, b2);
const umid = (u0 + u1) * 0.5;
depthb--;
@@ -229,8 +269,7 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
if (SmartRect.Intersect(bounds(a2), bounds(b2))) {
recursively_intersect(a2, tmid, t1, deptha, b2, umid, u1, depthb, parameters);
}
- }
- else {
+ } else {
if (SmartRect.Intersect(bounds(a1), bounds(b))) {
recursively_intersect(a1, t0, tmid, deptha, b, u0, u1, depthb, parameters);
}
@@ -238,50 +277,45 @@ function recursively_intersect(a: Point[], t0: number, t1: number, deptha: numbe
recursively_intersect(a2, tmid, t1, deptha, b, u0, u1, depthb, parameters);
}
}
- }
+ } else if (depthb > 0) {
+ const b1 = new Array<Point>(4);
+ const b2 = new Array<Point>(4);
+ splitCubic(b, 0.5, b1, b2);
+ const umid = (u0 + u1) * 0.5;
+ depthb--;
+ if (SmartRect.Intersect(bounds(a), bounds(b1))) {
+ recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
+ }
+ if (SmartRect.Intersect(bounds(a), bounds(b2))) {
+ recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
+ }
+ } // Both segments are fully subdivided; now do line segments
else {
- if (depthb > 0) {
- const b1 = new Array<Point>(4), b2 = new Array<Point>(4);
- splitCubic(b, 0.5, b1, b2);
- const umid = (u0 + u1) * 0.5;
- depthb--;
- if (SmartRect.Intersect(bounds(a), bounds(b1))) {
- recursively_intersect(a, t0, t1, deptha, b1, u0, umid, depthb, parameters);
- }
- if (SmartRect.Intersect(bounds(a), bounds(b2))) {
- recursively_intersect(a, t0, t1, deptha, b2, umid, u1, depthb, parameters);
- }
+ const xlk = a[3].X - a[0].X;
+ const ylk = a[3].Y - a[0].Y;
+ const xnm = b[3].X - b[0].X;
+ const ynm = b[3].Y - b[0].Y;
+ const xmk = b[0].X - a[0].X;
+ const ymk = b[0].Y - a[0].Y;
+ const det = xnm * ylk - ynm * xlk;
+ if (1.0 + det === 1.0) {
+ return;
}
- else // Both segments are fully subdivided; now do line segments
- {
- const xlk = a[3].X - a[0].X;
- const ylk = a[3].Y - a[0].Y;
- const xnm = b[3].X - b[0].X;
- const ynm = b[3].Y - b[0].Y;
- const xmk = b[0].X - a[0].X;
- const ymk = b[0].Y - a[0].Y;
- const det = xnm * ylk - ynm * xlk;
- if (1.0 + det === 1.0) {
- return;
- }
- else {
- const detinv = 1.0 / det;
- const s = (xnm * ymk - ynm * xmk) * detinv;
- const t = (xlk * ymk - ylk * xmk) * detinv;
- if ((s < 0.0) || (s > 1.0) || (t < 0.0) || (t > 1.0) || Number.isNaN(s) || Number.isNaN(t)) {
- return;
- }
- parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
- }
+ const detinv = 1.0 / det;
+ const s = (xnm * ymk - ynm * xmk) * detinv;
+ const t = (xlk * ymk - ylk * xmk) * detinv;
+ if (s < 0.0 || s > 1.0 || t < 0.0 || t > 1.0 || isNaN(s) || isNaN(t)) {
+ return;
}
+ parameters.push([t0 + s * (t1 - t0), u0 + t * (u1 - u0)]);
}
}
/*
-* EvalBezier :
-* Evaluate a Bezier curve at a particular parameter value
-*
-*/
+ * EvalBezier :
+ * Evaluate a Bezier curve at a particular parameter value
+ *
+ */
const MAX_DEGREE = 5;
function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
if (degree + 1 > MAX_DEGREE) {
@@ -293,35 +327,36 @@ function EvalBezier(V: Point[], degree: number, t: number, result: number[]) {
const Vtemp = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)]; // Local copy of control points
/* Copy array */
- for (var i = 0; i <= degree; i++) {
+ for (let i = 0; i <= degree; i++) {
Vtemp[i].X = V[i].X;
Vtemp[i].Y = V[i].Y;
}
/* Triangle computation */
- for (var i = 1; i <= degree; i++) {
- for (var j = 0; j <= degree - i; j++) {
+ for (let i = 1; i <= degree; i++) {
+ for (let j = 0; j <= degree - i; j++) {
Vtemp[j].X = (1.0 - t) * Vtemp[j].X + t * Vtemp[j + 1].X;
Vtemp[j].Y = (1.0 - t) * Vtemp[j].Y + t * Vtemp[j + 1].Y;
}
}
result[0] = Vtemp[0].X;
- result[1] = Vtemp[0].Y;// Point on curve at parameter t
+ result[1] = Vtemp[0].Y; // Point on curve at parameter t
}
function EvalBezierFast(p: Point[], t: number, result: number[]) {
const n = 3;
const u = 1.0 - t;
- var bc = 1, tn = 1;
- var tmpX = p[0].X * u;
- var tmpY = p[0].Y * u;
- tn = tn * t;
- bc = bc * (n - 1 + 1) / 1;
+ let bc = 1;
+ let tn = 1;
+ let tmpX = p[0].X * u;
+ let tmpY = p[0].Y * u;
+ tn *= t;
+ bc = (bc * (n - 1 + 1)) / 1;
tmpX = (tmpX + tn * bc * p[1].X) * u;
tmpY = (tmpY + tn * bc * p[1].Y) * u;
- tn = tn * t;
- bc = bc * (n - 2 + 1) / 2;
+ tn *= t;
+ bc = (bc * (n - 2 + 1)) / 2;
tmpX = (tmpX + tn * bc * p[2].X) * u;
tmpY = (tmpY + tn * bc * p[2].Y) * u;
@@ -329,9 +364,9 @@ function EvalBezierFast(p: Point[], t: number, result: number[]) {
result[1] = tmpY + tn * t * p[3].Y;
}
/*
-* ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
-*Approximate unit tangents at endpoints and "center" of digitized curve
-*/
+ * ComputeLeftTangent, ComputeRightTangent, ComputeCenterTangent :
+ *Approximate unit tangents at endpoints and "center" of digitized curve
+ */
function ComputeLeftTangent(d: Point[], end: number) {
const use = 1;
const tHat1 = new Point(d[end + use].X - d[end].X, d[end + use].Y - d[end].Y);
@@ -348,19 +383,19 @@ function ComputeCenterTangent(d: Point[], center: number) {
}
const V1 = ComputeLeftTangent(d, center); // d[center] - d[center-1];
const V2 = ComputeRightTangent(d, center); // d[center] - d[center + 1];
- var tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
+ let tHatCenter = new Point((-V1.X + V2.X) / 2.0, (-V1.Y + V2.Y) / 2.0);
if (tHatCenter === new Point(0, 0)) {
- tHatCenter = new Point(-V1.Y, -V1.X);// V1.Perp();
+ tHatCenter = new Point(-V1.Y, -V1.X); // V1.Perp();
}
return Normalize(tHatCenter);
}
function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[], tHat1: Point, tHat2: Point, result: Point[] /* must be prealloacted to size 4 */) {
const nPts = last - first + 1; // Number of pts in sub-curve
- const Ax = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
- const Ay = new Array<number>(nPts * 2);// Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const Ax = new Array<number>(nPts * 2); // Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
+ const Ay = new Array<number>(nPts * 2); // Precomputed rhs for eqn //std::vector<Vector2D> A(nPts * 2);
/* Compute the A's */
- for (var i = 0; i < nPts; i++) {
+ for (let i = 0; i < nPts; i++) {
const uprime = uPrime[i];
const b1 = B1(uprime);
const b2 = B2(uprime);
@@ -371,39 +406,42 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
}
/* Create the C and X matrices */
- const C = [[0, 0], [0, 0]];
+ const C = [
+ [0, 0],
+ [0, 0],
+ ];
const df = d[first];
const dl = d[last];
- const X = [0, 0]; // Matrix X
- for (var i = 0; i < nPts; i++) {
- C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; //A[i+0*nPts].Dot(A[i+0*nPts]);
- C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts];//A[i+0*nPts].Dot(A[i+1*nPts]);
+ const X = [0, 0]; // Matrix X
+ for (let i = 0; i < nPts; i++) {
+ C[0][0] += Ax[i] * Ax[i] + Ay[i] * Ay[i]; // A[i+0*nPts].Dot(A[i+0*nPts]);
+ C[0][1] += Ax[i] * Ax[i + nPts] + Ay[i] * Ay[i + nPts]; // A[i+0*nPts].Dot(A[i+1*nPts]);
C[1][0] = C[0][1];
- C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts];// A[i+1*nPts].Dot(A[i+1*nPts]);
+ C[1][1] += Ax[i + nPts] * Ax[i + nPts] + Ay[i + nPts] * Ay[i + nPts]; // A[i+1*nPts].Dot(A[i+1*nPts]);
const uprime = uPrime[i];
const b0plb1 = B0(uprime) + B1(uprime);
const b2plb3 = B2(uprime) + B3(uprime);
const df1 = d[first + i];
- const tmpX = df1.X - (df.X * b0plb1 + (dl.X * b2plb3));
- const tmpY = df1.Y - (df.Y * b0plb1 + (dl.Y * b2plb3));
+ const tmpX = df1.X - (df.X * b0plb1 + dl.X * b2plb3);
+ const tmpY = df1.Y - (df.Y * b0plb1 + dl.Y * b2plb3);
X[0] += Ax[i] * tmpX + Ay[i] * tmpY; // A[i+0*nPts].Dot(tmp)
- X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; //A[i+1*nPts].Dot(tmp)
+ X[1] += Ax[i + nPts] * tmpX + Ay[i + nPts] * tmpY; // A[i+1*nPts].Dot(tmp)
}
/* Compute the determinants of C and X */
- const det_C0_C1 = (C[0][0] * C[1][1] - C[1][0] * C[0][1]) || (C[0][0] * C[1][1]) * 10e-12;
+ const det_C0_C1 = C[0][0] * C[1][1] - C[1][0] * C[0][1] || C[0][0] * C[1][1] * 10e-12;
const det_C0_X = C[0][0] * X[1] - C[0][1] * X[0];
const det_X_C1 = X[0] * C[1][1] - X[1] * C[0][1];
/* Finally, derive alpha values */
- var alpha_l = (det_C0_C1 === 0) ? 0.0 : det_X_C1 / det_C0_C1;
- var alpha_r = (det_C0_C1 === 0) ? 0.0 : det_C0_X / det_C0_C1;
+ let alpha_l = det_C0_C1 === 0 ? 0.0 : det_X_C1 / det_C0_C1;
+ let alpha_r = det_C0_C1 === 0 ? 0.0 : det_C0_X / det_C0_C1;
/* If alpha negative, use the Wu/Barsky heuristic (see text) */
/* (if alpha is 0, you get coincident control points that lead to
- * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
+ * divide by zero in any subsequent NewtonRaphsonRootFind() call. */
const segLength = Math.sqrt((df.X - dl.X) * (df.X - dl.X) + (df.Y - dl.Y) * (df.Y - dl.Y));
const epsilon = 1.0e-6 * segLength;
if (alpha_l < epsilon || alpha_r < epsilon) {
@@ -415,10 +453,10 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
/* positioned exactly at the first and last data points */
/* Control points 1 and 2 are positioned an alpha distance out */
/* on the tangent vectors, left and right, respectively */
- result[0] = df;// RETURN bezier curve ctl pts
+ result[0] = df; // RETURN bezier curve ctl pts
result[3] = dl;
- result[1] = new Point(df.X + (tHat1.X * alpha_l), df.Y + (tHat1.Y * alpha_l));
- result[2] = new Point(dl.X + (tHat2.X * alpha_r), dl.Y + (tHat2.Y * alpha_r));
+ result[1] = new Point(df.X + tHat1.X * alpha_l, df.Y + tHat1.Y * alpha_l);
+ result[2] = new Point(dl.X + tHat2.X * alpha_r, dl.Y + tHat2.Y * alpha_r);
}
/*
@@ -426,21 +464,24 @@ function GenerateBezier(d: Point[], first: number, last: number, uPrime: number[
* Use Newton-Raphson iteration to find better root.
*/
function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
- const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)], Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
- const Q_u = [0, 0], Q1_u = [0, 0], Q2_u = [0, 0]; //u evaluated at Q, Q', & Q''
+ const Q1 = [new Point(0, 0), new Point(0, 0), new Point(0, 0)];
+ const Q2 = [new Point(0, 0), new Point(0, 0)]; // Q' and Q''
+ const Q_u = [0, 0];
+ const Q1_u = [0, 0];
+ const Q2_u = [0, 0]; // u evaluated at Q, Q', & Q''
/* Compute Q(u) */
- var uPrime: number; // Improved u
+ let uPrime: number; // Improved u
EvalBezierFast(Q, u, Q_u);
/* Generate control vertices for Q' */
- for (var i = 0; i <= 2; i++) {
+ for (let i = 0; i <= 2; i++) {
Q1[i].X = (Q[i + 1].X - Q[i].X) * 3.0;
Q1[i].Y = (Q[i + 1].Y - Q[i].Y) * 3.0;
}
/* Generate control vertices for Q'' */
- for (var i = 0; i <= 1; i++) {
+ for (let i = 0; i <= 1; i++) {
Q2[i].X = (Q1[i + 1].X - Q1[i].X) * 2.0;
Q2[i].Y = (Q1[i + 1].Y - Q1[i].Y) * 2.0;
}
@@ -450,20 +491,20 @@ function NewtonRaphsonRootFind(Q: Point[], P: Point, u: number) {
EvalBezier(Q2, 1, u, Q2_u);
/* Compute f(u)/f'(u) */
- const numerator = (Q_u[0] - P.X) * (Q1_u[0]) + (Q_u[1] - P.Y) * (Q1_u[1]);
- const denominator = (Q1_u[0]) * (Q1_u[0]) + (Q1_u[1]) * (Q1_u[1]) + (Q_u[0] - P.X) * (Q2_u[0]) + (Q_u[1] - P.Y) * (Q2_u[1]);
+ const numerator = (Q_u[0] - P.X) * Q1_u[0] + (Q_u[1] - P.Y) * Q1_u[1];
+ const denominator = Q1_u[0] * Q1_u[0] + Q1_u[1] * Q1_u[1] + (Q_u[0] - P.X) * Q2_u[0] + (Q_u[1] - P.Y) * Q2_u[1];
if (denominator === 0.0) {
uPrime = u;
- } else uPrime = u - (numerator / denominator);/* u = u - f(u)/f'(u) */
+ } else uPrime = u - numerator / denominator; /* u = u - f(u)/f'(u) */
return uPrime;
}
function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2: Point, error: number, result: Point[]) {
- const bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
- const maxIterations = 4; // Max times to try iterating
+ const bezCurve = new Array<Point>(4); // Control points of fitted Bezier curve
+ const maxIterations = 4; // Max times to try iterating
const iterationError = error * error; // Error below which you try iterating
- const nPts = last - first + 1; // Number of points in subset
+ const nPts = last - first + 1; // Number of points in subset
/* Use heuristic if region only has two points in it */
if (nPts === 2) {
@@ -471,8 +512,8 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
bezCurve[0] = d[first];
bezCurve[3] = d[last];
- bezCurve[1] = new Point(bezCurve[0].X + (tHat1.X * dist), bezCurve[0].Y + (tHat1.Y * dist));
- bezCurve[2] = new Point(bezCurve[3].X + (tHat2.X * dist), bezCurve[3].Y + (tHat2.Y * dist));
+ bezCurve[1] = new Point(bezCurve[0].X + tHat1.X * dist, bezCurve[0].Y + tHat1.Y * dist);
+ bezCurve[2] = new Point(bezCurve[3].X + tHat2.X * dist, bezCurve[3].Y + tHat2.Y * dist);
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -481,11 +522,11 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
}
/* Parameterize points, and attempt to fit curve */
- var u = ChordLengthParameterize(d, first, last);
+ let u = ChordLengthParameterize(d, first, last);
GenerateBezier(d, first, last, u, tHat1, tHat2, bezCurve);
/* Find max deviation of points to fitted curve */
- const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
+ const { maxError, splitPoint2D } = ComputeMaxError(d, first, last, bezCurve, u); // Maximum fitting error
if (maxError < Math.abs(error)) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
@@ -496,11 +537,11 @@ function FitCubic(d: Point[], first: number, last: number, tHat1: Point, tHat2:
/* If error not too large, try some reparameterization */
/* and iteration */
if (maxError < iterationError) {
- for (var i = 0; i < maxIterations; i++) {
- const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
+ for (let i = 0; i < maxIterations; i++) {
+ const uPrime = ReparameterizeBezier(d, first, last, u, bezCurve); // Improved parameter values
GenerateBezier(d, first, last, uPrime, tHat1, tHat2, bezCurve);
- const { maxError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
- if (maxError < error) {
+ const { maxError: maximumError } = ComputeMaxError(d, first, last, bezCurve, uPrime);
+ if (maximumError < error) {
result.push(bezCurve[1]);
result.push(bezCurve[2]);
result.push(bezCurve[3]);
@@ -527,13 +568,13 @@ export function FitOneCurve(d: Point[], tHat1?: Point, tHat2?: Point) {
tHat1 = tHat1 ?? Normalize(ComputeLeftTangent(d, 0));
tHat2 = tHat2 ?? Normalize(ComputeRightTangent(d, d.length - 1));
tHat2 = new Point(-tHat2.X, -tHat2.Y);
- var u = ChordLengthParameterize(d, 0, d.length - 1);
+ let u = ChordLengthParameterize(d, 0, d.length - 1);
const bezCurveCtrls = [new Point(0, 0), new Point(0, 0), new Point(0, 0), new Point(0, 0)];
- GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /* Find max deviation of points to fitted curve */
- var finalCtrls = bezCurveCtrls.slice();
- var { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
- for (var i = 0; i < 10; i++) {
- const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
+ GenerateBezier(d, 0, d.length - 1, u, tHat1, tHat2, bezCurveCtrls); /* Find max deviation of points to fitted curve */
+ let finalCtrls = bezCurveCtrls.slice();
+ let { maxError: error } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, u);
+ for (let i = 0; i < 10; i++) {
+ const uPrime = ReparameterizeBezier(d, 0, d.length - 1, u, bezCurveCtrls); // Improved parameter values
GenerateBezier(d, 0, d.length - 1, uPrime, tHat1, tHat2, bezCurveCtrls);
const { maxError } = ComputeMaxError(d, 0, d.length - 1, bezCurveCtrls, uPrime);
if (maxError < error) {
@@ -1428,4 +1469,4 @@ spliceR[0] += v;
}
#endif
-*/ \ No newline at end of file
+*/
diff --git a/src/client/util/reportManager/ReportManager.tsx b/src/client/util/reportManager/ReportManager.tsx
index 0c49aeed4..2224e642d 100644
--- a/src/client/util/reportManager/ReportManager.tsx
+++ b/src/client/util/reportManager/ReportManager.tsx
@@ -1,33 +1,39 @@
-import * as React from 'react';
-import * as uuid from 'uuid';
-import '.././SettingsManager.scss';
-import './ReportManager.scss';
-import ReactLoading from 'react-loading';
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/media-has-caption */
+/* eslint-disable react/no-unused-class-component-methods */
+import { Octokit } from '@octokit/core';
+import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
import { action, makeObservable, observable } from 'mobx';
-import { BsX, BsArrowsAngleExpand, BsArrowsAngleContract } from 'react-icons/bs';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { BsArrowsAngleContract, BsArrowsAngleExpand, BsX } from 'react-icons/bs';
import { CgClose } from 'react-icons/cg';
import { HiOutlineArrowLeft } from 'react-icons/hi';
-import { Issue } from './reportManagerSchema';
-import { observer } from 'mobx-react';
+import { MdRefresh } from 'react-icons/md';
+import ReactLoading from 'react-loading';
+import * as uuid from 'uuid';
+import { ClientUtils } from '../../../ClientUtils';
import { Doc } from '../../../fields/Doc';
-import { MainViewModal } from '../../views/MainViewModal';
-import { Octokit } from '@octokit/core';
-import { Button, Dropdown, DropdownType, IconButton, Type } from 'browndash-components';
-import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
-import { Filter, FormInput, FormTextArea, IssueCard, IssueView, Tag } from './ReportManagerComponents';
import { StrCast } from '../../../fields/Types';
-import { MdRefresh } from 'react-icons/md';
+import { MainViewModal } from '../../views/MainViewModal';
+import '../SettingsManager.scss';
import { SettingsManager } from '../SettingsManager';
+import './ReportManager.scss';
+import { Filter, FormInput, FormTextArea, IssueCard, IssueView } from './ReportManagerComponents';
+import { Issue } from './reportManagerSchema';
+import { BugType, FileData, Priority, ReportForm, ViewState, bugDropdownItems, darkColors, emptyReportForm, formatTitle, getAllIssues, isDarkMode, lightColors, passesTagFilter, priorityDropdownItems, uploadFilesToServer } from './reportManagerUtils';
/**
* Class for reporting and viewing Github issues within the app.
*/
@observer
export class ReportManager extends React.Component<{}> {
+ // eslint-disable-next-line no-use-before-define
public static Instance: ReportManager;
@observable private isOpen = false;
@observable private query = '';
+ // eslint-disable-next-line react/sort-comp
@action private setQuery = (q: string) => {
this.query = q;
};
@@ -82,7 +88,9 @@ export class ReportManager extends React.Component<{}> {
this.formData = newData;
});
- public close = action(() => (this.isOpen = false));
+ public close = action(() => {
+ this.isOpen = false;
+ });
public open = action(async () => {
this.isOpen = true;
if (this.shownIssues.length === 0) {
@@ -133,7 +141,7 @@ export class ReportManager extends React.Component<{}> {
const req = await this.octokit.request('POST /repos/{owner}/{repo}/issues', {
owner: 'brown-dash',
repo: 'Dash-Web',
- title: formatTitle(this.formData.title, Doc.CurrentUserEmail),
+ title: formatTitle(this.formData.title, ClientUtils.CurrentUserEmail()),
body: `${this.formData.description} ${formattedLinks.length > 0 ? `\n\nFiles:\n${formattedLinks.join('\n')}` : ''}`,
labels: ['from-dash-app', this.formData.type, this.formData.priority],
});
@@ -164,7 +172,7 @@ export class ReportManager extends React.Component<{}> {
* @returns JSX element of a piece of media (image, video, audio)
*/
private getMediaPreview = (fileData: FileData): JSX.Element => {
- const file = fileData.file;
+ const { file } = fileData;
const mimeType = file.type;
const preview = URL.createObjectURL(file);
@@ -179,7 +187,8 @@ export class ReportManager extends React.Component<{}> {
</div>
</div>
);
- } else if (mimeType.startsWith('video/')) {
+ }
+ if (mimeType.startsWith('video/')) {
return (
<div key={fileData._id} className="report-media-wrapper">
<div className="report-media-content">
@@ -193,7 +202,8 @@ export class ReportManager extends React.Component<{}> {
</div>
</div>
);
- } else if (mimeType.startsWith('audio/')) {
+ }
+ if (mimeType.startsWith('audio/')) {
return (
<div key={fileData._id} className="report-audio-wrapper">
<audio src={preview} controls />
@@ -203,7 +213,7 @@ export class ReportManager extends React.Component<{}> {
</div>
);
}
- return <></>;
+ return <div />;
};
/**
@@ -306,8 +316,8 @@ export class ReportManager extends React.Component<{}> {
<div className="report-selects">
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
- formLabel={'Type'}
- closeOnSelect={true}
+ formLabel="Type"
+ closeOnSelect
items={bugDropdownItems}
selectedVal={this.formData.type}
setSelectedVal={val => {
@@ -319,8 +329,8 @@ export class ReportManager extends React.Component<{}> {
/>
<Dropdown
color={StrCast(Doc.UserDoc().userColor)}
- formLabel={'Priority'}
- closeOnSelect={true}
+ formLabel="Priority"
+ closeOnSelect
items={priorityDropdownItems}
selectedVal={this.formData.priority}
setSelectedVal={val => {
@@ -346,7 +356,7 @@ export class ReportManager extends React.Component<{}> {
text="Submit"
type={Type.TERT}
color={StrCast(Doc.UserDoc().userVariantColor)}
- icon={<ReactLoading type="spin" color={'#ffffff'} width={20} height={20} />}
+ icon={<ReactLoading type="spin" color="#ffffff" width={20} height={20} />}
iconPlacement="right"
onClick={() => {
this.reportIssue();
@@ -363,7 +373,7 @@ export class ReportManager extends React.Component<{}> {
/>
)}
<div style={{ position: 'absolute', top: '4px', right: '4px' }}>
- <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size={'16px'} />} onClick={this.close} />
+ <IconButton color={StrCast(Doc.UserDoc().userColor)} tooltip="close" icon={<CgClose size="16px" />} onClick={this.close} />
</div>
</div>
);
@@ -375,9 +385,8 @@ export class ReportManager extends React.Component<{}> {
private reportComponent = () => {
if (this.viewState === ViewState.VIEW) {
return this.viewIssuesComponent();
- } else {
- return this.reportIssueComponent();
}
+ return this.reportIssueComponent();
};
render() {
@@ -385,7 +394,7 @@ export class ReportManager extends React.Component<{}> {
<MainViewModal
contents={this.reportComponent()}
isDisplayed={this.isOpen}
- interactive={true}
+ interactive
closeOnExternalClick={this.close}
dialogueBoxStyle={{ width: 'auto', minWidth: '300px', height: '85vh', maxHeight: '90vh', background: StrCast(Doc.UserDoc().userBackgroundColor), borderRadius: '8px' }}
/>
diff --git a/src/client/util/reportManager/ReportManagerComponents.tsx b/src/client/util/reportManager/ReportManagerComponents.tsx
index 1e226bf6d..cecebc648 100644
--- a/src/client/util/reportManager/ReportManagerComponents.tsx
+++ b/src/client/util/reportManager/ReportManagerComponents.tsx
@@ -1,9 +1,15 @@
+/* eslint-disable react/require-default-props */
+/* eslint-disable prefer-destructuring */
+/* eslint-disable jsx-a11y/label-has-associated-control */
+/* eslint-disable jsx-a11y/no-static-element-interactions */
+/* eslint-disable jsx-a11y/click-events-have-key-events */
+/* eslint-disable no-use-before-define */
import * as React from 'react';
-import { Issue } from './reportManagerSchema';
-import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
import ReactMarkdown from 'react-markdown';
import rehypeRaw from 'rehype-raw';
import remarkGfm from 'remark-gfm';
+import { darkColors, dashBlue, getLabelColors, isDarkMode, lightColors } from './reportManagerUtils';
+import { Issue } from './reportManagerSchema';
import { StrCast } from '../../../fields/Types';
import { Doc } from '../../../fields/Doc';
@@ -18,7 +24,7 @@ interface FilterProps<T> {
}
// filter ui for issues (horizontal list of tags)
-export const Filter = <T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) => {
+export function Filter<T extends string>({ items, activeValue, setActiveValue }: FilterProps<T>) {
// establishing theme
const darkMode = isDarkMode(StrCast(Doc.UserDoc().userBackgroundColor));
const colors = darkMode ? darkColors : lightColors;
@@ -28,7 +34,7 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
return (
<div className="issues-filter">
<Tag
- text={'All'}
+ text="All"
onClick={() => {
setActiveValue(null);
}}
@@ -38,25 +44,23 @@ export const Filter = <T extends string>({ items, activeValue, setActiveValue }:
borderColor={activeValue === null ? StrCast(Doc.UserDoc().userColor) : colors.border}
border
/>
- {items.map(item => {
- return (
- <Tag
- key={item}
- text={item}
- onClick={() => {
- setActiveValue(item);
- }}
- fontSize="12px"
- backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
- color={activeValue === item ? activeTagTextColor : colors.textGrey}
- border
- borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
- />
- );
- })}
+ {items.map(item => (
+ <Tag
+ key={item}
+ text={item}
+ onClick={() => {
+ setActiveValue(item);
+ }}
+ fontSize="12px"
+ backgroundColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : 'transparent'}
+ color={activeValue === item ? activeTagTextColor : colors.textGrey}
+ border
+ borderColor={activeValue === item ? StrCast(Doc.UserDoc().userColor) : colors.border}
+ />
+ ))}
</div>
);
-};
+}
interface IssueCardProps {
issue: Issue;
@@ -64,7 +68,7 @@ interface IssueCardProps {
}
// Component for the issue cards list on the left
-export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
+export function IssueCard({ issue, onSelect }: IssueCardProps) {
const [textColor, setTextColor] = React.useState('');
const [bgColor, setBgColor] = React.useState('transparent');
const [borderColor, setBorderColor] = React.useState('transparent');
@@ -103,14 +107,14 @@ export const IssueCard = ({ issue, onSelect }: IssueCardProps) => {
<h3 className="issue-title">{issue.title}</h3>
</div>
);
-};
+}
interface IssueViewProps {
issue: Issue;
}
// Detailed issue view that displays on the right
-export const IssueView = ({ issue }: IssueViewProps) => {
+export function IssueView({ issue }: IssueViewProps) {
const [issueBody, setIssueBody] = React.useState('');
// Parses the issue body into a formatted markdown (main functionality is replacing urls with tags)
@@ -127,15 +131,16 @@ export const IssueView = ({ issue }: IssueViewProps) => {
parts.map(async part => {
if (imgTagRegex.test(part) || videoTagRegex.test(part) || audioTagRegex.test(part)) {
return `\n${await parseFileTag(part)}\n`;
- } else if (fileRegex.test(part)) {
+ }
+ if (fileRegex.test(part)) {
const tag = await parseDashFiles(part);
return tag;
- } else if (localRegex.test(part)) {
+ }
+ if (localRegex.test(part)) {
const tag = await parseLocalFiles(part);
return tag;
- } else {
- return part;
}
+ return part;
})
);
@@ -143,7 +148,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
};
// Extracts the src from an image tag and either returns the raw url if not accessible or a new image tag
- const parseFileTag = async (tag: string): Promise<string> => {
+ const parseFileTag = async (tag: string): Promise<string | undefined> => {
const regex = /src="([^"]+)"/;
let url = '';
const match = tag.match(regex);
@@ -160,18 +165,19 @@ export const IssueView = ({ issue }: IssueViewProps) => {
case '.png':
case '.jpeg':
case '.gif':
- return await getDisplayedFile(url, 'image');
+ return getDisplayedFile(url, 'image');
// video
case '.mp4':
case '.mpeg':
case '.webm':
case '.mov':
- return await getDisplayedFile(url, 'video');
- //audio
+ return getDisplayedFile(url, 'video');
+ // audio
case '.mp3':
case '.wav':
case '.ogg':
- return await getDisplayedFile(url, 'audio');
+ return getDisplayedFile(url, 'audio');
+ default:
}
return tag;
};
@@ -183,14 +189,15 @@ export const IssueView = ({ issue }: IssueViewProps) => {
const dashAudioRegex = /https:\/\/browndash\.com\/files[/\\]audio/;
if (dashImgRegex.test(url)) {
- return await getDisplayedFile(url, 'image');
- } else if (dashVideoRegex.test(url)) {
- return await getDisplayedFile(url, 'video');
- } else if (dashAudioRegex.test(url)) {
- return await getDisplayedFile(url, 'audio');
- } else {
- return url;
+ return getDisplayedFile(url, 'image');
}
+ if (dashVideoRegex.test(url)) {
+ return getDisplayedFile(url, 'video');
+ }
+ if (dashAudioRegex.test(url)) {
+ return getDisplayedFile(url, 'audio');
+ }
+ return url;
};
// Returns the corresponding HTML tag for a src url
@@ -200,31 +207,37 @@ export const IssueView = ({ issue }: IssueViewProps) => {
const dashAudioRegex = /http:\/\/localhost:1050\.com\/files[/\\]audio/;
if (imgRegex.test(url)) {
- return await getDisplayedFile(url, 'image');
- } else if (dashVideoRegex.test(url)) {
- return await getDisplayedFile(url, 'video');
- } else if (dashAudioRegex.test(url)) {
- return await getDisplayedFile(url, 'audio');
- } else {
- return url;
+ return getDisplayedFile(url, 'image');
+ }
+ if (dashVideoRegex.test(url)) {
+ return getDisplayedFile(url, 'video');
+ }
+ if (dashAudioRegex.test(url)) {
+ return getDisplayedFile(url, 'audio');
}
+ return url;
};
- const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string> => {
+ const getDisplayedFile = async (url: string, fileType: 'image' | 'video' | 'audio'): Promise<string | undefined> => {
switch (fileType) {
- case 'image':
+ case 'image': {
const imgValid = await isImgValid(url);
if (!imgValid) return `\n${url} (This image could not be loaded)\n`;
return `\n${url}\n<img width="100%" alt="Issue asset" src=${url} />\n`;
- case 'video':
+ }
+ case 'video': {
const videoValid = await isVideoValid(url);
if (!videoValid) return `\n${url} (This video could not be loaded)\n`;
return `\n${url}\n<video class="report-default-video" width="100%" controls alt="Issue asset" src=${url} />\n`;
- case 'audio':
+ }
+ case 'audio': {
const audioValid = await isAudioValid(url);
if (!audioValid) return `\n${url} (This audio could not be loaded)\n`;
return `\n${url}\n<audio src=${url} controls />\n`;
+ }
+ default:
}
+ return undefined;
};
// Loads an image and returns a promise that resolves as whether the image is valid or not
@@ -270,7 +283,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
<div className="issue-view">
<span className="issue-label">
Issue{' '}
- <a className="issue-link" href={issue.html_url} target="_blank">
+ <a className="issue-link" href={issue.html_url} target="_blank" rel="noreferrer">
#{issue.number}
</a>
</span>
@@ -292,7 +305,7 @@ export const IssueView = ({ issue }: IssueViewProps) => {
<ReactMarkdown children={issueBody} className="issue-content" remarkPlugins={[remarkGfm]} rehypePlugins={[rehypeRaw]} />
</div>
);
-};
+}
interface TagProps {
text: string;
@@ -305,7 +318,7 @@ interface TagProps {
}
// Small tag for labels of the issue
-export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) => {
+export function Tag({ text, color, backgroundColor, fontSize, border, borderColor, onClick }: TagProps) {
return (
<div
onClick={onClick ?? (() => {})}
@@ -314,14 +327,14 @@ export const Tag = ({ text, color, backgroundColor, fontSize, border, borderColo
{text}
</div>
);
-};
+}
interface FormInputProps {
value: string;
placeholder: string;
onChange: (val: string) => void;
}
-export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormInput({ value, placeholder, onChange }: FormInputProps) {
const [inputBorderColor, setInputBorderColor] = React.useState('');
return (
@@ -349,9 +362,9 @@ export const FormInput = ({ value, placeholder, onChange }: FormInputProps) => {
}}
/>
);
-};
+}
-export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) => {
+export function FormTextArea({ value, placeholder, onChange }: FormInputProps) {
const [textAreaBorderColor, setTextAreaBorderColor] = React.useState('');
return (
@@ -378,4 +391,4 @@ export const FormTextArea = ({ value, placeholder, onChange }: FormInputProps) =
}}
/>
);
-};
+}
diff --git a/src/client/util/reportManager/reportManagerSchema.ts b/src/client/util/reportManager/reportManagerSchema.ts
index 9a1c7c3e9..171c24393 100644
--- a/src/client/util/reportManager/reportManagerSchema.ts
+++ b/src/client/util/reportManager/reportManagerSchema.ts
@@ -1,3 +1,4 @@
+/* eslint-disable no-use-before-define */
/**
* Issue interface schema from Github.
*/
diff --git a/src/client/util/reportManager/reportManagerUtils.ts b/src/client/util/reportManager/reportManagerUtils.ts
index 22e5eebbb..f14967e0a 100644
--- a/src/client/util/reportManager/reportManagerUtils.ts
+++ b/src/client/util/reportManager/reportManagerUtils.ts
@@ -63,9 +63,8 @@ export const getAllIssues = async (octokit: Octokit): Promise<any[]> => {
// 200 status means success
if (res.status === 200) {
return res.data;
- } else {
- throw new Error('Error getting issues');
}
+ throw new Error('Error getting issues');
};
/**
@@ -104,9 +103,7 @@ export const fileLinktoServerLink = (fileLink: string): string => {
* @param link response from file upload
* @returns server file path
*/
-export const getServerPath = (link: any): string => {
- return link.result.accessPaths.agnostic.server as string;
-};
+export const getServerPath = (link: any): string => link.result.accessPaths.agnostic.server as string;
/**
* Uploads media files to the server.
@@ -124,6 +121,7 @@ export const uploadFilesToServer = async (mediaFiles: FileData[]): Promise<strin
alert(err);
}
}
+ return undefined;
};
// helper functions
@@ -141,18 +139,16 @@ export const passesTagFilter = (issue: Issue, priorityFilter: string | null, bug
passesPriority = issue.labels.some(label => {
if (typeof label === 'string') {
return label === priorityFilter;
- } else {
- return label.name === priorityFilter;
}
+ return label.name === priorityFilter;
});
}
if (bugFilter) {
passesBug = issue.labels.some(label => {
if (typeof label === 'string') {
return label === bugFilter;
- } else {
- return label.name === bugFilter;
}
+ return label.name === bugFilter;
});
}
return passesPriority && passesBug;
@@ -217,7 +213,8 @@ export const bugColors: { [key: string]: string[] } = {
export const getLabelColors = (label: string): string[] => {
if (prioritySet.has(label as Priority)) {
return priorityColors[label];
- } else if (bugSet.has(label as BugType)) {
+ }
+ if (bugSet.has(label as BugType)) {
return bugColors[label];
}
return ['#0f73f6', '#ffffff'];
diff --git a/src/client/util/request-image-size.ts b/src/client/util/request-image-size.ts
index 57e8516ac..0f98a2710 100644
--- a/src/client/util/request-image-size.ts
+++ b/src/client/util/request-image-size.ts
@@ -14,19 +14,17 @@ const imageSize = require('image-size');
const HttpError = require('standard-http-error');
module.exports = function requestImageSize(options: any) {
- let opts = {
+ let opts: any = {
encoding: null,
};
if (options && typeof options === 'object') {
opts = Object.assign(options, opts);
} else if (options && typeof options === 'string') {
- opts = Object.assign(
- {
- uri: options,
- },
- opts
- );
+ opts = {
+ uri: options,
+ ...opts,
+ };
} else {
return Promise.reject(new Error('You should provide an URI string or a "request" options object.'));
}
@@ -38,7 +36,8 @@ module.exports = function requestImageSize(options: any) {
req.on('response', (res: any) => {
if (res.statusCode >= 400) {
- return reject(new HttpError(res.statusCode, res.statusMessage));
+ reject(new HttpError(res.statusCode, res.statusMessage));
+ return;
}
let buffer = Buffer.from([]);
@@ -51,20 +50,23 @@ module.exports = function requestImageSize(options: any) {
size = imageSize(buffer);
if (size) {
resolve(size);
- return req.abort();
+ req.abort();
}
- } catch (err) {}
+ } catch (err) {
+ /* empty */
+ }
});
res.on('error', reject);
res.on('end', () => {
if (!size) {
- return reject(new Error('Image has no size'));
+ reject(new Error('Image has no size'));
+ return;
}
size.downloaded = buffer.length;
- return resolve(size);
+ resolve(size);
});
});