diff options
Diffstat (limited to 'src/client/util')
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 458 | ||||
-rw-r--r-- | src/client/util/DictationManager.ts | 23 | ||||
-rw-r--r-- | src/client/util/DocumentManager.ts | 20 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 10 | ||||
-rw-r--r-- | src/client/util/GroupManager.tsx | 57 | ||||
-rw-r--r-- | src/client/util/GroupMemberView.tsx | 22 | ||||
-rw-r--r-- | src/client/util/History.ts | 4 | ||||
-rw-r--r-- | src/client/util/HypothesisUtils.ts | 5 | ||||
-rw-r--r-- | src/client/util/Import & Export/DirectoryImportBox.tsx | 9 | ||||
-rw-r--r-- | src/client/util/Import & Export/ImportMetadataEntry.tsx | 8 | ||||
-rw-r--r-- | src/client/util/InteractionUtils.tsx | 62 | ||||
-rw-r--r-- | src/client/util/SearchUtil.ts | 14 | ||||
-rw-r--r-- | src/client/util/SelectionManager.ts | 30 | ||||
-rw-r--r-- | src/client/util/SettingsManager.scss | 16 | ||||
-rw-r--r-- | src/client/util/SettingsManager.tsx | 57 | ||||
-rw-r--r-- | src/client/util/SharingManager.scss | 23 | ||||
-rw-r--r-- | src/client/util/SharingManager.tsx | 289 |
17 files changed, 686 insertions, 421 deletions
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 5954e12dc..7a1c193c1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1,29 +1,35 @@ import { computed, observable, reaction } from "mobx"; import * as rp from 'request-promise'; -import { Utils } from "../../Utils"; -import { DocServer } from "../DocServer"; -import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; -import { UndoManager } from "./UndoManager"; -import { Doc, DocListCast, DocListCastAsync, DataSym } from "../../fields/Doc"; +import { DataSym, Doc, DocListCast, DocListCastAsync } from "../../fields/Doc"; +import { Id } from "../../fields/FieldSymbols"; import { List } from "../../fields/List"; +import { PrefetchProxy } from "../../fields/Proxy"; +import { RichTextField } from "../../fields/RichTextField"; import { listSpec } from "../../fields/Schema"; -import { ScriptField, ComputedField } from "../../fields/ScriptField"; -import { Cast, PromiseValue, StrCast, NumCast, BoolCast } from "../../fields/Types"; +import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; +import { ComputedField, ScriptField } from "../../fields/ScriptField"; +import { BoolCast, Cast, NumCast, PromiseValue, StrCast } from "../../fields/Types"; import { nullAudio } from "../../fields/URLField"; -import { DragManager } from "./DragManager"; -import { Scripting } from "./Scripting"; -import { CollectionViewType, CollectionView } from "../views/collections/CollectionView"; -import { makeTemplate } from "./DropConverter"; -import { RichTextField } from "../../fields/RichTextField"; -import { PrefetchProxy } from "../../fields/Proxy"; -import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; -import { MainView } from "../views/MainView"; +import { Utils } from "../../Utils"; +import { DocServer } from "../DocServer"; +import { Docs, DocumentOptions, DocUtils } from "../documents/Documents"; import { DocumentType } from "../documents/DocumentTypes"; -import { SchemaHeaderField } from "../../fields/SchemaHeaderField"; +import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DimUnit } from "../views/collections/collectionMulticolumn/CollectionMulticolumnView"; +import { CollectionView, CollectionViewType } from "../views/collections/CollectionView"; +import { MainView } from "../views/MainView"; +import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; import { LabelBox } from "../views/nodes/LabelBox"; +import { OverlayView } from "../views/OverlayView"; +import { DocumentManager } from "./DocumentManager"; +import { DragManager } from "./DragManager"; +import { makeTemplate } from "./DropConverter"; +import { HistoryUtil } from "./History"; import { LinkManager } from "./LinkManager"; -import { Id } from "../../fields/FieldSymbols"; +import { Scripting } from "./Scripting"; +import { SearchUtil } from "./SearchUtil"; +import { SelectionManager } from "./SelectionManager"; +import { UndoManager } from "./UndoManager"; export class CurrentUserUtils { private static curr_id: string; @@ -36,7 +42,7 @@ export class CurrentUserUtils { @computed public static get UserDocument() { return Doc.UserDoc(); } @observable public static GuestTarget: Doc | undefined; - @observable public static GuestWorkspace: Doc | undefined; + @observable public static GuestDashboard: Doc | undefined; @observable public static GuestMobile: Doc | undefined; @observable public static propertiesWidth: number = 0; @@ -46,14 +52,14 @@ export class CurrentUserUtils { if (doc["template-button-query"] === undefined) { const queryTemplate = Docs.Create.MulticolumnDocument( [ - Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, lockedPosition: true, title: "query", _height: 200, system: true }), + Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, _chromeStatus: "disabled", lockedPosition: true, title: "query", _height: 200, system: true }), Docs.Create.FreeformDocument([], { title: "data", _height: 100, system: true }) ], - { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true } + { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, system: true } ); queryTemplate.isTemplateDoc = makeTemplate(queryTemplate); doc["template-button-query"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, removeDropProperties: new List<string>(["dropAction"]), title: "query view", icon: "question-circle" }); @@ -72,7 +78,7 @@ export class CurrentUserUtils { this.mobileTextContainer({}, [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); doc["template-mobile-button"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(queryTemplate) as any as Doc, removeDropProperties: new List<string>(["dropAction"]), title: "mobile button", icon: "mobile" }); @@ -84,11 +90,11 @@ export class CurrentUserUtils { Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), Docs.Create.TextDocument("", { title: "text", _height: 100, system: true }) ], - { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true } + { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, system: true } ); slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); doc["template-button-slides"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(slideTemplate) as any as Doc, removeDropProperties: new List<string>(["dropAction"]), title: "presentation slide", icon: "address-card" }); @@ -96,6 +102,7 @@ export class CurrentUserUtils { if (doc["template-button-description"] === undefined) { const descriptionTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created + descriptionTemplate.system = true; descriptionTemplate[DataSym].layout = "<div>" + " <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`orange`}' fieldKey={'header'}/>" + @@ -104,14 +111,15 @@ export class CurrentUserUtils { (descriptionTemplate.proto as Doc).isTemplateDoc = makeTemplate(descriptionTemplate.proto as Doc, true, "descriptionView"); doc["template-button-description"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(descriptionTemplate) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize" + removeDropProperties: new List<string>(["dropAction"]), title: "description view", icon: "window-maximize", system: true }); } if (doc["template-button-link"] === undefined) { // set _backgroundColor to transparent to prevent link dot from obscuring document it's attached to. const linkTemplate = Doc.MakeDelegate(Docs.Create.TextDocument(" ", { title: "header", _height: 100, system: true }, "header")); // text needs to be a space to allow templateText to be created + linkTemplate.system = true; Doc.GetProto(linkTemplate).layout = "<div>" + " <FormattedTextBox {...props} height='{this._headerHeight||75}px' background='{this._headerColor||`lightGray`}' fieldKey={'header'}/>" + @@ -150,9 +158,9 @@ export class CurrentUserUtils { linkTemplate.header = new RichTextField(JSON.stringify(rtf2), ""); doc["template-button-link"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(linkTemplate) as any as Doc, - removeDropProperties: new List<string>(["dropAction"]), title: "link view", icon: "window-maximize" + removeDropProperties: new List<string>(["dropAction"]), title: "link view", icon: "window-maximize", system: true }); } @@ -182,7 +190,7 @@ export class CurrentUserUtils { box.isTemplateDoc = makeTemplate(box, true, "switch"); doc["template-button-switch"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(box) as any as Doc, removeDropProperties: new List<string>(["dropAction"]), title: "data switch", icon: "toggle-on", system: true }); @@ -232,7 +240,7 @@ export class CurrentUserUtils { long.title = "Long Description"; doc["template-button-detail"] = CurrentUserUtils.ficon({ - onDragStart: ScriptField.MakeFunction('getCopy(this.dragFactory, true)'), + onDragStart: ScriptField.MakeFunction('copyDragFactory(this.dragFactory)'), dragFactory: new PrefetchProxy(detailView) as any as Doc, removeDropProperties: new List<string>(["dropAction"]), title: "detail view", icon: "window-maximize", system: true }); @@ -392,74 +400,68 @@ export class CurrentUserUtils { }[] { if (doc.emptyPresentation === undefined) { doc.emptyPresentation = Docs.Create.PresDocument(new List<Doc>(), - { title: "Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true }); + { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true, cloneFieldFilter: new List<string>(["system"]) }); + ((doc.emptyPresentation as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyCollection === undefined) { doc.emptyCollection = Docs.Create.FreeformDocument([], - { _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform", system: true }); + { _nativeWidth: undefined, _nativeHeight: undefined, _width: 150, _height: 100, title: "freeform", system: true, cloneFieldFilter: new List<string>(["system"]) }); + ((doc.emptyCollection as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyPane === undefined) { - doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, title: "Untitled Collection", system: true }); + doc.emptyPane = Docs.Create.FreeformDocument([], { _nativeWidth: undefined, _nativeHeight: undefined, _width: 500, _height: 800, title: "Untitled Tab", system: true, cloneFieldFilter: new List<string>(["system"]) }); + ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyComparison === undefined) { - doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true }); + doc.emptyComparison = Docs.Create.ComparisonDocument({ title: "compare", _width: 300, _height: 300, system: true, cloneFieldFilter: new List<string>(["system"]) }); } if (doc.emptyScript === undefined) { - doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true }); + doc.emptyScript = Docs.Create.ScriptingDocument(undefined, { _width: 200, _height: 250, title: "script", system: true, cloneFieldFilter: new List<string>(["system"]) }); + ((doc.emptyScript as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyScreenshot === undefined) { - doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot", system: true }); + doc.emptyScreenshot = Docs.Create.ScreenshotDocument("", { _width: 400, _height: 200, title: "screen snapshot", system: true, cloneFieldFilter: new List<string>(["system"]) }); } if (doc.emptyAudio === undefined) { - doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio", system: true }); + doc.emptyAudio = Docs.Create.AudioDocument(nullAudio, { _width: 200, title: "ready to record audio", system: true, cloneFieldFilter: new List<string>(["system"]) }); + ((doc.emptyAudio as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyImage === undefined) { doc.emptyImage = Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { _width: 250, _nativeWidth: 250, title: "an image of a cat", system: true }); } if (doc.emptyButton === undefined) { - doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true }); + doc.emptyButton = Docs.Create.ButtonDocument({ _width: 150, _height: 50, _xPadding: 10, _yPadding: 10, title: "Button", system: true, cloneFieldFilter: new List<string>(["system"]) }); + ((doc.emptyButton as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptyDocHolder === undefined) { doc.emptyDocHolder = Docs.Create.DocumentDocument( ComputedField.MakeFunction("selectedDocs(this,this.excludeCollections,[_last_])?.[0]") as any, - { _width: 250, _height: 250, title: "container", system: true }); + { _width: 250, _height: 250, title: "container", system: true, cloneFieldFilter: new List<string>(["system"]) }); } if (doc.emptyWebpage === undefined) { - doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 400, UseCors: true, system: true }); + doc.emptyWebpage = Docs.Create.WebDocument("", { title: "webpage", _nativeWidth: 850, _nativeHeight: 962, _width: 400, useCors: true, system: true, cloneFieldFilter: new List<string>(["system"]) }); } if (doc.activeMobileMenu === undefined) { this.setupActiveMobileMenu(doc); } return [ - { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(getCopy(this.clickFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, - { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, - { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyImage as Doc }, - { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, - { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScreenshot as Doc }, - // { title: "Drag a webcam", title: "Cam", icon: "video", ignoreClick: true, drag: 'Docs.Create.WebCamDocument("", { _width: 400, _height: 400, title: "a test cam" })' }, - { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, - { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyButton as Doc, noviceMode: true }, - - { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Present", icon: "tv", click: 'openOnRight(Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true))', drag: `Doc.UserDoc().activePresentation = getCopy(this.dragFactory, true)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, - { toolTip: "Tap to create a search box in a new pane, drag for a search box", title: "Query", icon: "search", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptySearch as Doc }, - { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory, true)', dragFactory: doc.emptyScript as Doc }, - // { title: "Drag an import folder", title: "Load", icon: "cloud-upload-alt", ignoreClick: true, drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", _width: 400, _height: 400 })' }, + { toolTip: "Tap to create a collection in a new pane, drag for a collection", title: "Col", icon: "folder", click: 'openOnRight(copyDragFactory(this.clickFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyCollection as Doc, noviceMode: true, clickFactory: doc.emptyPane as Doc, }, + { toolTip: "Tap to create a webpage in a new pane, drag for a webpage", title: "Web", icon: "globe-asia", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyWebpage as Doc, noviceMode: true }, + { toolTip: "Tap to create a cat image in a new pane, drag for a cat image", title: "Image", icon: "cat", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyImage as Doc }, + { toolTip: "Tap to create a comparison box in a new pane, drag for a comparison box", title: "Compare", icon: "columns", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyComparison as Doc, noviceMode: true }, + { toolTip: "Tap to create a screen grabber in a new pane, drag for a screen grabber", title: "Grab", icon: "photo-video", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScreenshot as Doc }, + { toolTip: "Tap to create an audio recorder in a new pane, drag for an audio recorder", title: "Audio", icon: "microphone", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyAudio as Doc, noviceMode: true }, + { toolTip: "Tap to create a button in a new pane, drag for a button", title: "Button", icon: "bolt", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyButton as Doc, noviceMode: true }, + { toolTip: "Tap to create a presentation in a new pane, drag for a presentation", title: "Trails", icon: "pres-trail", click: 'openOnRight(Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory))', drag: `Doc.UserDoc().activePresentation = copyDragFactory(this.dragFactory)`, dragFactory: doc.emptyPresentation as Doc, noviceMode: true }, + { toolTip: "Tap to create a search box in a new pane, drag for a search box", title: "Query", icon: "search", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptySearch as Doc }, + { toolTip: "Tap to create a scripting box in a new pane, drag for a scripting box", title: "Script", icon: "terminal", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyScript as Doc }, { toolTip: "Tap to create a mobile view in a new pane, drag for a mobile view", title: "Phone", icon: "mobile", click: 'openOnRight(Doc.UserDoc().activeMobileMenu)', drag: 'this.dragFactory', dragFactory: doc.activeMobileMenu as Doc }, - // { title: "Drag an instance of the device collection", title: "Buxton", icon: "globe-asia", ignoreClick: true, drag: 'Docs.Create.Buxton()' }, - // { title: "use pen", icon: "pen-nib", click: 'activatePen(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "blue", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - // { title: "use highlighter", icon: "highlighter", click: 'activateBrush(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - // { title: "use stamp", icon: "stamp", click: 'activateStamp(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this)', backgroundColor: "orange", ischecked: `sameDocs(this.activeInkPen, this)`, activeInkPen: doc }, - // { title: "use eraser", icon: "eraser", click: 'activateEraser(this.activeInkPen = sameDocs(this.activeInkPen, this) ? undefined : this);', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "pink", activeInkPen: doc }, - // { title: "use drag", icon: "mouse-pointer", click: 'deactivateInk();this.activeInkPen = this;', ischecked: `sameDocs(this.activeInkPen, this)`, backgroundColor: "white", activeInkPen: doc }, - { toolTip: "Tap to create a document previewer in a new pane, drag for a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(getCopy(this.dragFactory, true))', drag: 'getCopy(this.dragFactory,true)', dragFactory: doc.emptyDocHolder as Doc }, + { toolTip: "Tap to create a document previewer in a new pane, drag for a document previewer", title: "Prev", icon: "expand", click: 'openOnRight(copyDragFactory(this.dragFactory))', drag: 'copyDragFactory(this.dragFactory)', dragFactory: doc.emptyDocHolder as Doc }, { toolTip: "Toggle a Calculator REPL", title: "repl", icon: "calculator", click: 'addOverlayWindow("ScriptingRepl", { x: 300, y: 100, width: 200, height: 200, title: "Scripting REPL" })' }, - { toolTip: "Connect a Google Account", title: "Google Account", icon: "external-link-alt", click: 'GoogleAuthenticationManager.Instance.fetchOrGenerateAccessToken(true)' }, ]; } - - // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools static async setupCreatorButtons(doc: Doc) { let alreadyCreatedButtons: string[] = []; @@ -488,7 +490,8 @@ export class CurrentUserUtils { dragFactory, clickFactory, userDoc: noviceMode ? undefined as any : doc, - hidden: noviceMode ? undefined as any : ComputedField.MakeFunction("self.userDoc.noviceMode"), system: true + hidden: noviceMode ? undefined as any : ComputedField.MakeFunction("self.userDoc.noviceMode"), + system: true })); if (dragCreatorSet === undefined) { @@ -504,39 +507,43 @@ export class CurrentUserUtils { } static menuBtnDescriptions(doc: Doc): { - title: string, icon: string, click: string, watchedDocuments?: Doc + title: string, target: Doc, icon: string, click: string, watchedDocuments?: Doc }[] { this.setupSharingSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing return [ - { title: "Sharing", icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc["sidebar-sharing"] as Doc }, - { title: "Workspace", icon: "desktop", click: 'selectMainMenu(self)' }, - { title: "Catalog", icon: "file", click: 'selectMainMenu(self)' }, - { title: "Archive", icon: "archive", click: 'selectMainMenu(self)' }, - { title: "Import", icon: "upload", click: 'selectMainMenu(self)' }, - { title: "Tools", icon: "wrench", click: 'selectMainMenu(self)' }, - { title: "Help", icon: "question-circle", click: 'selectMainMenu(self)' }, - { title: "Settings", icon: "cog", click: 'selectMainMenu(self)' }, - { title: "User Doc", icon: "address-card", click: 'selectMainMenu(self)' }, + { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, + { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, + { title: "Import", target: Cast(doc.myImportPanel, Doc, null), icon: "upload", click: 'selectMainMenu(self)' }, + { title: "Sharing", target: Cast(doc.mySharedDocs, Doc, null), icon: "users", click: 'selectMainMenu(self)', watchedDocuments: doc.mySharedDocs as Doc }, + { title: "Tools", target: Cast(doc.myTools, Doc, null), icon: "wrench", click: 'selectMainMenu(self)' }, + { title: "Filter", target: Cast(doc.myFilter, Doc, null), icon: "filter", click: 'selectMainMenu(self)' }, + { title: "Pres. Trails", target: Cast(doc.myPresentations, Doc, null), icon: "pres-trail", click: 'selectMainMenu(self)' }, + { title: "Catalog", target: undefined as any, icon: "file", click: 'selectMainMenu(self)' }, + { title: "Help", target: undefined as any, icon: "question-circle", click: 'selectMainMenu(self)' }, + { title: "Settings", target: undefined as any, icon: "cog", click: 'selectMainMenu(self)' }, + { title: "User Doc", target: Cast(doc.myUserDoc, Doc, null), icon: "address-card", click: 'selectMainMenu(self)' }, ]; } static setupSearchPanel(doc: Doc) { - if (doc["search-panel"] === undefined) { - doc["search-panel"] = new PrefetchProxy(Docs.Create.SearchDocument({ - _width: 500, _height: 400, backgroundColor: "dimGray", ignoreClick: true, + if (doc.mySearchPanelDoc === undefined) { + doc.mySearchPanelDoc = new PrefetchProxy(Docs.Create.SearchDocument({ + _width: 500, _height: 300, backgroundColor: "dimGray", ignoreClick: true, _searchDoc: true, childDropAction: "alias", lockedPosition: true, _viewType: CollectionViewType.Schema, _chromeStatus: "disabled", title: "sidebar search stack", system: true })) as any as Doc; } } static setupMenuPanel(doc: Doc) { if (doc.menuStack === undefined) { - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, icon, click, watchedDocuments }) => + const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments }) => Docs.Create.FontIconDocument({ icon, iconShape: "square", title, + target, _backgroundColor: "black", - _stayInCollection: true, + dropAction: "alias", + removeDropProperties: new List<string>(["dropAction"]), childDropAction: "same", _width: 60, _height: 60, @@ -588,7 +595,7 @@ export class CurrentUserUtils { // SEts up mobile buttons for inside mobile menu static setupMobileButtons(doc?: Doc, buttons?: string[]) { const docProtoData: { title: string, icon: string, drag?: string, ignoreClick?: boolean, click?: string, ischecked?: string, activePen?: Doc, backgroundColor?: string, info: string, dragFactory?: Doc }[] = [ - { title: "WORKSPACES", icon: "bars", click: 'switchToMobileLibrary()', backgroundColor: "lightgrey", info: "Access your Workspaces from your mobile, and navigate through all of your documents. " }, + { 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." }, @@ -685,11 +692,11 @@ export class CurrentUserUtils { } static setupLibrary(userDoc: Doc) { - return CurrentUserUtils.setupWorkspaces(userDoc); + return CurrentUserUtils.setupDashboards(userDoc); } - // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. - // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) + // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. + // when clicked, this panel will be displayed in the target container (ie, sidebarContainer) static async setupToolsBtnPanel(doc: Doc) { // setup a masonry view of all he creators const creatorBtns = await CurrentUserUtils.setupCreatorButtons(doc); @@ -711,92 +718,88 @@ export class CurrentUserUtils { doc.myColorPicker = new PrefetchProxy(color); } - if (doc["sidebar-tools"] === undefined) { + if (doc.myTools === undefined) { const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - title: "sidebar-tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", hideFilterView: true, forceActive: true, system: true + title: "My Tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", forceActive: true, system: true })) as any as Doc; - doc["sidebar-tools"] = toolsStack; + doc.myTools = toolsStack; } } - static setupWorkspaces(doc: Doc) { - // setup workspaces library item - doc.myWorkspaces === undefined; - if (doc.myWorkspaces === undefined) { - doc.myWorkspaces = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "WORKSPACES", _height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, treeViewOpen: true, system: true - })); - } - if (doc["sidebar-workspaces"] === undefined) { - const newWorkspace = ScriptField.MakeScript(`createNewWorkspace()`); - (doc.myWorkspaces as Doc).contextMenuScripts = new List<ScriptField>([newWorkspace!]); - (doc.myWorkspaces as Doc).contextMenuLabels = new List<string>(["Create New Workspace"]); - - const workspaces = doc.myWorkspaces as Doc; - - doc["sidebar-workspaces"] = new PrefetchProxy(Docs.Create.TreeDocument([workspaces], { + static async setupDashboards(doc: Doc) { + // setup dashboards library item + await doc.myDashboards; + if (doc.myDashboards === undefined) { + doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "My Dashboards", _height: 400, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true - })) as any as Doc; + })); + const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); + (doc.myDashboards as any as Doc).contextMenuScripts = new List<ScriptField>([newDashboard!]); + (doc.myDashboards as any as Doc).contextMenuLabels = new List<string>(["Create New Dashboard"]); } + return doc.myDashboards as any as Doc; } - static setupCatalog(doc: Doc) { - doc.myCatalog === undefined; - if (doc.myCatalog === undefined) { - doc.myCatalog = new PrefetchProxy(Docs.Create.SchemaDocument([], [], { - title: "CATALOG", _height: 1000, _fitWidth: true, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, - childDropAction: "alias", targetDropAction: "same", _stayInCollection: true, treeViewOpen: true, system: true + static async setupPresentations(doc: Doc) { + await doc.myPresentations; + if (doc.myPresentations === undefined) { + doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "My Presentations", _height: 100, + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, + lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); + const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); + (doc.myPresentations as any as Doc).contextMenuScripts = new List<ScriptField>([newPresentations!]); + (doc.myPresentations as any as Doc).contextMenuLabels = new List<string>(["Create New Presentation"]); + const presentations = doc.myPresentations as any as Doc; } + return doc.myPresentations as any as Doc; + } - if (doc["sidebar-catalog"] === undefined) { - const catalog = doc.myCatalog as Doc; - - doc["sidebar-catalog"] = new PrefetchProxy(Docs.Create.TreeDocument([catalog], { - title: "sidebar-catalog", + static setupRecentlyClosedDocs(doc: Doc) { + // setup Recently Closed library item + doc.myRecentlyClosedDocs === undefined; + if (doc.myRecentlyClosedDocs === undefined) { + doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { + title: "Recently Closed", _height: 500, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true - })) as any as Doc; + })); + const clearAll = ScriptField.MakeScript(`self.data = new List([])`); + (doc.myRecentlyClosedDocs as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]); + (doc.myRecentlyClosedDocs as any as Doc).contextMenuLabels = new List<string>(["Clear All"]); } } - static setupRecentlyClosed(doc: Doc) { + static setupFilterDocs(doc: Doc) { // setup Recently Closed library item - doc.myRecentlyClosed === undefined; - if (doc.myRecentlyClosed === undefined) { - doc.myRecentlyClosed = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "RECENTLY CLOSED", _height: 75, forceActive: true, boxShadow: "0 0", treeViewPreventOpen: false, treeViewOpen: true, _stayInCollection: true, system: true - })); - } - // this is equivalent to using PrefetchProxies to make sure the recentlyClosed doc is ready - PromiseValue(Cast(doc.myRecentlyClosed, Doc)).then(recent => recent && PromiseValue(recent.data).then(DocListCast)); - if (doc["sidebar-recentlyClosed"] === undefined) { - const clearAll = ScriptField.MakeScript(`self.data = new List([])`); - (doc.myRecentlyClosed as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]); - (doc.myRecentlyClosed as Doc).contextMenuLabels = new List<string>(["Clear All"]); - - const recentlyClosed = doc.myRecentlyClosed as Doc; - - doc["sidebar-recentlyClosed"] = new PrefetchProxy(Docs.Create.TreeDocument([recentlyClosed], { - title: "sidebar-recentlyClosed", + doc.myFilter === undefined; + if (doc.myFilter === undefined) { + doc.myFilter = new PrefetchProxy(Docs.Create.FilterDocument({ + title: "FilterDoc", _height: 500, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, treeViewOpen: true, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true - })) as any as Doc; + })); + const clearAll = ScriptField.MakeScript(`self.data = new List([])`); + (doc.myFilter as any as Doc).contextMenuScripts = new List<ScriptField>([clearAll!]); + (doc.myFilter as any as Doc).contextMenuLabels = new List<string>(["Clear All"]); } } static setupUserDoc(doc: Doc) { - if (doc["sidebar-userDoc"] === undefined) { + if (doc.myUserDoc === undefined) { doc.treeViewOpen = true; doc.treeViewExpandedView = "fields"; - doc["sidebar-userDoc"] = new PrefetchProxy(Docs.Create.TreeDocument([doc], { - treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "sidebar-userDoc", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, + doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { + treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "My UserDoc", + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } @@ -806,9 +809,8 @@ export class CurrentUserUtils { if (doc.sidebar === undefined) { const sidebarContainer = new Doc(); sidebarContainer._chromeStatus = "disabled"; - sidebarContainer.onClick = ScriptField.MakeScript("freezeSidebar()"); + sidebarContainer.system = true; doc.sidebar = new PrefetchProxy(sidebarContainer); - doc.system = true; } return doc.sidebar as Doc; } @@ -817,9 +819,10 @@ export class CurrentUserUtils { static async setupSidebarButtons(doc: Doc) { CurrentUserUtils.setupSidebarContainer(doc); await CurrentUserUtils.setupToolsBtnPanel(doc); - CurrentUserUtils.setupWorkspaces(doc); - CurrentUserUtils.setupCatalog(doc); - CurrentUserUtils.setupRecentlyClosed(doc); + CurrentUserUtils.setupDashboards(doc); + CurrentUserUtils.setupPresentations(doc); + CurrentUserUtils.setupRecentlyClosedDocs(doc); + CurrentUserUtils.setupFilterDocs(doc); CurrentUserUtils.setupUserDoc(doc); } @@ -847,8 +850,8 @@ export class CurrentUserUtils { } // sets up the default set of documents to be shown in the Overlay layer static setupOverlays(doc: Doc) { - if (doc.myOverlayDocuments === undefined) { - doc.myOverlayDocuments = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true })); + if (doc.myOverlayDocs === undefined) { + doc.myOverlayDocs = new PrefetchProxy(Docs.Create.FreeformDocument([], { title: "overlay documents", backgroundColor: "#aca3a6", system: true })); } } @@ -859,34 +862,30 @@ export class CurrentUserUtils { title: "pres element template", backgroundColor: "transparent", _xMargin: 5, _height: 46, isTemplateDoc: true, isTemplateForField: "data", system: true })); } - if (doc.activePresentation === undefined) { - doc.activePresentation = Doc.MakeCopy(doc.emptyPresentation as Doc, true); - } } // Sharing sidebar is where shared documents are contained static setupSharingSidebar(doc: Doc) { - if (doc["sidebar-sharing"] === undefined) { - doc["sidebar-sharing"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Shared Documents", childDropAction: "alias", system: true })); + if (doc.mySharedDocs === undefined) { + doc.mySharedDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true })); } } // Import sidebar is where shared documents are contained static setupImportSidebar(doc: Doc) { - if (doc["sidebar-import-documents"] === undefined) { - doc["sidebar-import-documents"] = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "Imported Documents", forceActive: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 30, lockedPosition: true, _chromeStatus: "disabled" })); + if (doc.myImportDocs === undefined) { + doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My ImportDocuments", forceActive: true, ignoreClick: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, lockedPosition: true, _chromeStatus: "disabled", system: true })); } - if (doc["sidebar-import"] === undefined) { - const uploads = Cast(doc["sidebar-import-documents"], Doc, null); + if (doc.myImportPanel === undefined) { + const uploads = Cast(doc.myImportDocs, Doc, null); const newUpload = CurrentUserUtils.ficon({ onClick: ScriptField.MakeScript("importDocument()"), toolTip: "Import External document", _backgroundColor: "black", title: "Import", icon: "upload", system: true }); - doc["sidebar-import"] = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "Imported Documents", _yMargin: 20, ignoreClick: true, lockedPosition: true })); + doc.myImportPanel = new PrefetchProxy(Docs.Create.StackingDocument([newUpload, uploads], { title: "My ImportPanel", _yMargin: 20, ignoreClick: true, lockedPosition: true, system: true })); } } - static setupClickEditorTemplates(doc: Doc) { if (doc["clickFuncs-child"] === undefined) { - // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target + // to use this function, select it from the context menu of a collection. then edit the onChildClick script. Add two Doc variables: 'target' and 'thisContainer', then assign 'target' to some target collection. After that, clicking on any document in the initial collection will open it in the target const openInTarget = Docs.Create.ScriptingDocument(ScriptField.MakeScript( "docCast(thisContainer.target).then((target) => target && (target.proto.data = new List([self]))) ", { thisContainer: Doc.name }), { @@ -933,6 +932,7 @@ export class CurrentUserUtils { } static async updateUserDocument(doc: Doc) { + doc.system = true; doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; doc.activeInkPen = doc; @@ -958,11 +958,11 @@ export class CurrentUserUtils { this.setupDocTemplates(doc); // sets up the template menu of templates this.setupImportSidebar(doc); this.setupActiveMobileMenu(doc); // sets up the current mobile menu for Dash Mobile - this.setupMenuPanel(doc); this.setupSearchPanel(doc); this.setupOverlays(doc); // documents in overlay layer this.setupDockedButtons(doc); // the bottom bar of font icons await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels + this.setupMenuPanel(doc); doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument(); doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); @@ -975,6 +975,7 @@ export class CurrentUserUtils { return doc; } + public static async loadCurrentUser() { return rp.get(Utils.prepend("/getCurrentUser")).then(response => { if (response) { @@ -998,12 +999,141 @@ export class CurrentUserUtils { } }); } -} -Scripting.addGlobal(function createNewWorkspace() { return MainView.Instance.createNewWorkspace(); }, - "creates a new workspace when called"); + public static _urlState: HistoryUtil.DocUrl; + + public static openDashboard = (userDoc: Doc, doc: Doc, fromHistory = false) => { + CurrentUserUtils.MainDocId = doc[Id]; + + if (doc) { // this has the side-effect of setting the main container since we're assigning the active/guest dashboard + !("presentationView" in doc) && (doc.presentationView = new List<Doc>([Docs.Create.TreeDocument([], { title: "Presentation" })])); + userDoc ? (userDoc.activeDashboard = doc) : (CurrentUserUtils.GuestDashboard = doc); + } + const state = CurrentUserUtils._urlState; + if (state.sharing === true && !userDoc) { + DocServer.Control.makeReadOnly(); + } else { + fromHistory || HistoryUtil.pushState({ + type: "doc", + docId: doc[Id], + readonly: state.readonly, + nro: state.nro, + sharing: false, + }); + if (state.readonly === true || state.readonly === null) { + DocServer.Control.makeReadOnly(); + } else if (state.safe) { + if (!state.nro) { + DocServer.Control.makeReadOnly(); + } + CollectionView.SetSafeMode(true); + } else if (state.nro || state.nro === null || state.readonly === false) { + } else if (doc.readOnly) { + DocServer.Control.makeReadOnly(); + } else { + DocServer.Control.makeEditable(); + } + } + + return true; + } + public static importDocument = () => { + const input = document.createElement("input"); + input.type = "file"; + input.multiple = true; + input.accept = ".zip, application/pdf, video/*, image/*, audio/*"; + input.onchange = async _e => { + const upload = Utils.prepend("/uploadDoc"); + const formData = new FormData(); + const file = input.files && input.files[0]; + if (file && file.type === 'application/zip') { + formData.append('file', file); + formData.append('remap', "true"); + const response = await fetch(upload, { method: "POST", body: formData }); + const json = await response.json(); + if (json !== "error") { + const doc = await DocServer.GetRefField(json); + 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. + } + } + } else if (input.files && input.files.length !== 0) { + const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null); + const disposer = OverlayView.ShowSpinner(); + DocListCastAsync(importDocs.data).then(async list => { + const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); + list?.splice(0, 0, ...results); + disposer(); + }); + } else { + console.log("No file selected"); + } + }; + input.click(); + } + + public static snapshotDashboard = (userDoc: Doc) => { + const copy = CollectionDockingView.Copy(CurrentUserUtils.ActiveDashboard); + Doc.AddDocToList(Cast(userDoc.myDashboards, Doc, null), "data", copy); + CurrentUserUtils.openDashboard(userDoc, copy); + } + + public static createNewDashboard = (userDoc: Doc, id?: string) => { + const myPresentations = userDoc.myPresentations as Doc; + const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); + const dashboards = Cast(userDoc.myDashboards, Doc) as Doc; + const dashboardCount = DocListCast(dashboards.data).length + 1; + const emptyPane = Cast(userDoc.emptyPane, Doc, null); + emptyPane["dragFactory-count"] = NumCast(emptyPane["dragFactory-count"]) + 1; + const freeformOptions: DocumentOptions = { + x: 0, + y: 400, + _width: 1500, + _height: 1000, + title: `Untitled Tab ${NumCast(emptyPane["dragFactory-count"])}`, + }; + const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); + const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); + Doc.AddDocToList(myPresentations, "data", presentation); + userDoc.activePresentation = presentation; + const toggleTheme = ScriptField.MakeScript(`self.darkScheme = !self.darkScheme`); + const toggleComic = ScriptField.MakeScript(`toggleComicMode()`); + const snapshotDashboard = ScriptField.MakeScript(`snapshotDashboard()`); + const createDashboard = ScriptField.MakeScript(`createNewDashboard()`); + dashboardDoc.contextMenuScripts = new List<ScriptField>([toggleTheme!, toggleComic!, snapshotDashboard!, createDashboard!]); + dashboardDoc.contextMenuLabels = new List<string>(["Toggle Theme Colors", "Toggle Comic Mode", "Snapshot Dashboard", "Create Dashboard"]); + + Doc.AddDocToList(dashboards, "data", dashboardDoc); + CurrentUserUtils.openDashboard(userDoc, dashboardDoc); + } + + public static get MySearchPanelDoc() { return Cast(Doc.UserDoc().mySearchPanelDoc, Doc, null); } + public static get ActiveDashboard() { return Cast(Doc.UserDoc().activeDashboard, Doc, null); } + public static get ActivePresentation() { return Cast(Doc.UserDoc().activePresentation, Doc, null); } + public static get MyRecentlyClosed() { return Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc, null); } + public static get MyDashboards() { return Cast(Doc.UserDoc().myDashboards, Doc, null); } + public static get EmptyPane() { return Cast(Doc.UserDoc().emptyPane, Doc, null); } +} + +Scripting.addGlobal(function openDragFactory(dragFactory: Doc) { + const copy = Doc.copyDragFactory(dragFactory); + if (copy) { + CollectionDockingView.AddSplit(copy, "right"); + const view = DocumentManager.Instance.getFirstDocumentView(copy); + view && SelectionManager.SelectDoc(view, false); + } +}); +Scripting.addGlobal(function snapshotDashboard() { CurrentUserUtils.snapshotDashboard(Doc.UserDoc()); }, + "creates a snapshot copy of a dashboard"); +Scripting.addGlobal(function createNewDashboard() { return CurrentUserUtils.createNewDashboard(Doc.UserDoc()); }, + "creates a new dashboard when called"); +Scripting.addGlobal(function createNewPresentation() { return MainView.Instance.createNewPresentation(); }, + "creates a new presentation when called"); Scripting.addGlobal(function links(doc: any) { return new List(LinkManager.Instance.getAllRelatedLinks(doc)); }, "returns all the links to the document or its annotations", "(doc: any)"); Scripting.addGlobal(function directLinks(doc: any) { return new List(LinkManager.Instance.getAllDirectLinks(doc)); }, "returns all the links directly to the document", "(doc: any)"); +Scripting.addGlobal(function importDocument() { return CurrentUserUtils.importDocument(); }, + "imports files from device directly into the import sidebar"); diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 540540642..231e1fa8d 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -6,7 +6,6 @@ import { DocumentType } from "../documents/DocumentTypes"; import { Doc, Opt } from "../../fields/Doc"; import { List } from "../../fields/List"; import { Docs } from "../documents/Documents"; -import { CollectionViewType } from "../views/collections/CollectionView"; import { Cast, CastCtor } from "../../fields/Types"; import { listSpec } from "../../fields/Schema"; import { AudioField, ImageField } from "../../fields/URLField"; @@ -88,7 +87,7 @@ export namespace DictationManager { export const listen = async (options?: Partial<ListeningOptions>) => { let results: string | undefined; - const overlay = options !== undefined && options.useOverlay; + const overlay = options?.useOverlay; if (overlay) { DictationOverlay.Instance.dictationOverlayVisible = true; DictationOverlay.Instance.isListening = { interim: false }; @@ -100,11 +99,11 @@ export namespace DictationManager { Utils.CopyText(results); if (overlay) { DictationOverlay.Instance.isListening = false; - const execute = options && options.tryExecute; + const execute = options?.tryExecute; DictationOverlay.Instance.dictatedPhrase = execute ? results.toLowerCase() : results; DictationOverlay.Instance.dictationSuccess = execute ? await DictationManager.Commands.execute(results) : true; } - options && options.tryExecute && await DictationManager.Commands.execute(results); + options?.tryExecute && await DictationManager.Commands.execute(results); } } catch (e) { if (overlay) { @@ -129,12 +128,12 @@ export namespace DictationManager { } isListening = true; - const handler = options ? options.interimHandler : undefined; - const continuous = options ? options.continuous : undefined; + const handler = options?.interimHandler; + const continuous = options?.continuous; const indefinite = continuous && continuous.indefinite; - const language = options ? options.language : undefined; - const intra = options && options.delimiters ? options.delimiters.intra : undefined; - const inter = options && options.delimiters ? options.delimiters.inter : undefined; + const language = options?.language; + const intra = options?.delimiters?.intra; + const inter = options?.delimiters?.inter; recognizer.onstart = () => console.log("initiating speech recognition session..."); recognizer.interimResults = handler !== undefined; @@ -154,7 +153,7 @@ export namespace DictationManager { recognizer.onresult = (e: SpeechRecognitionEvent) => { current = synthesize(e, intra); let matchedTerminator: string | undefined; - if (options && options.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) { + if (options?.terminators && (matchedTerminator = options.terminators.find(end => current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false))) { current = matchedTerminator; recognizer.abort(); return complete(); @@ -324,7 +323,7 @@ export namespace DictationManager { ["open fields", { action: (target: DocumentView) => { const kvp = Docs.Create.KVPDocument(target.props.Document, { _width: 300, _height: 300 }); - target.props.addDocTab(kvp, "onRight"); + target.props.addDocTab(kvp, "add:right"); } }], @@ -338,7 +337,7 @@ export namespace DictationManager { const proseMirrorState = `{"doc":{"type":"doc","content":[{"type":"ordered_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"type":"text","text":"${prompt}"}]}]}]}]},"selection":{"type":"text","anchor":${anchor},"head":${head}}}`; proto.data = new RichTextField(proseMirrorState); proto.backgroundColor = "#eeffff"; - target.props.addDocTab(newBox, "onRight"); + target.props.addDocTab(newBox, "add:right"); } }] diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 962294933..420b29559 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -124,7 +124,7 @@ export class DocumentManager { } static addRightSplit = (doc: Doc, finished?: () => void) => { - CollectionDockingView.AddRightSplit(doc); + CollectionDockingView.AddSplit(doc, "right"); finished?.(); } public jumpToDocument = async ( @@ -160,6 +160,9 @@ export class DocumentManager { docView.props.Document.hidden = !docView.props.Document.hidden; } else { + const contView = docContext && getFirstDocView(docContext, originatingDoc); + contView && contView.topMost && contView.select(false); // bcz: change this to a function prop: popTab() that will make sure the tab for the document is topmost; + docView.select(false); docView.props.Document.hidden && (docView.props.Document.hidden = undefined); docView.props.focus(docView.props.Document, willZoom, undefined, focusAndFinish); highlight(); @@ -181,7 +184,7 @@ export class DocumentManager { // now find the target document within the context if (targetDoc.displayTimecode) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; - targetDocContext.currentTimecode = targetDoc.displayTimecode; + targetDocContext._currentTimecode = targetDoc.displayTimecode; finished?.(); } else { // no timecode means we need to find the context view and focus on our target setTimeout(() => { @@ -212,8 +215,8 @@ export class DocumentManager { public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(doc.links); SelectionManager.DeselectAll(); - const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); // link docs where 'doc' is anchor1 - const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); // link docs where 'doc' is anchor2 + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1 + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2 const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; @@ -222,17 +225,16 @@ export class DocumentManager { followLinks.forEach(async linkDoc => { if (linkDoc) { const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 : - (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") : doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") : - (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number"))); + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number"))); if (target) { const containerDoc = (await Cast(target.annotationOn, Doc)) || target; - containerDoc.currentTimecode = targetTimecode; + containerDoc._currentTimecode = targetTimecode; const targetContext = await target?.context as Doc; const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; - console.log(targetNavContext); - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "onRight"), finished), targetNavContext, linkDoc, undefined, doc, finished); + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished); } else { finished?.(); } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 0cca61841..9797f5488 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -117,10 +117,11 @@ export namespace DragManager { } export class DocumentDragData { - constructor(dragDoc: Doc[]) { + constructor(dragDoc: Doc[], dropAction?: dropActionType) { this.draggedDocuments = dragDoc; this.droppedDocuments = []; this.offset = [0, 0]; + this.dropAction = dropAction; } draggedDocuments: Doc[]; droppedDocuments: Doc[]; @@ -143,7 +144,7 @@ export namespace DragManager { linkSourceDocument: Doc; dontClearTextBox?: boolean; linkDocument?: Doc; - linkDropCallback?: (data: LinkDragData) => void; + linkDropCallback?: (data: { linkDocument?: Doc }) => void; } export class ColumnDragData { constructor(colKey: SchemaHeaderField) { @@ -160,7 +161,7 @@ export namespace DragManager { this.annotationDocument = annotationDoc; this.offset = [0, 0]; } - linkedToDoc?: boolean; + linkDocument?: Doc; targetContext: Doc | undefined; dragDocument: Doc; annotationDocument: Doc; @@ -168,6 +169,7 @@ export namespace DragManager { offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; + linkDropCallback?: (data: { linkDocument?: Doc }) => void; } export function MakeDropTarget( @@ -434,7 +436,7 @@ export namespace DragManager { const target = document.elementFromPoint(e.x, e.y); - if (target && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) { + if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) { const autoScrollHandler = () => { target.dispatchEvent( new CustomEvent<React.DragEvent>("dashDragAutoScroll", { diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index d03989675..cb15b5081 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -1,24 +1,19 @@ -import * as React from "react"; -import { observable, action, runInAction, computed } from "mobx"; -import { SelectionManager } from "./SelectionManager"; -import MainViewModal from "../views/MainViewModal"; -import { observer } from "mobx-react"; -import { Doc, DocListCast, Opt, DocListCastAsync } from "../../fields/Doc"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import * as fa from '@fortawesome/free-solid-svg-icons'; -import { library } from "@fortawesome/fontawesome-svg-core"; -import SharingManager, { User } from "./SharingManager"; -import { Utils } from "../../Utils"; -import * as RequestPromise from "request-promise"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; import Select from 'react-select'; -import "./GroupManager.scss"; -import { StrCast, Cast } from "../../fields/Types"; -import GroupMemberView from "./GroupMemberView"; +import * as RequestPromise from "request-promise"; +import { Doc, DocListCast, DocListCastAsync, Opt } from "../../fields/Doc"; +import { Cast, StrCast } from "../../fields/Types"; import { setGroups } from "../../fields/util"; +import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; +import { MainViewModal } from "../views/MainViewModal"; import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; - -library.add(fa.faPlus, fa.faTimes, fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown); +import "./GroupManager.scss"; +import { GroupMemberView } from "./GroupMemberView"; +import { SharingManager, User } from "./SharingManager"; /** * Interface for options for the react-select component @@ -29,7 +24,7 @@ export interface UserOptions { } @observer -export default class GroupManager extends React.Component<{}> { +export class GroupManager extends React.Component<{}> { static Instance: GroupManager; @observable isOpen: boolean = false; // whether the GroupManager is to be displayed or not. @@ -71,7 +66,7 @@ export default class GroupManager extends React.Component<{}> { const evaluating = raw.map(async user => { const userDocument = await DocServer.GetRefField(user.userDocumentId); if (userDocument instanceof Doc) { - const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc); + const notificationDoc = await Cast(userDocument.mySharedDocs, Doc); runInAction(() => { if (notificationDoc instanceof Doc) { this.users.push(user.email); @@ -92,6 +87,7 @@ export default class GroupManager extends React.Component<{}> { const members: string[] = JSON.parse(StrCast(group.members)); if (members.includes(Doc.CurrentUserEmail)) this.currentUserGroups.push(StrCast(group.groupName)); }); + this.currentUserGroups.push("Public"); setGroups(this.currentUserGroups); }); } @@ -121,6 +117,7 @@ export default class GroupManager extends React.Component<{}> { close = () => { this.isOpen = false; this.currentGroup = undefined; + this.selectedUsers = null; // this.users = []; this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; @@ -162,7 +159,7 @@ export default class GroupManager extends React.Component<{}> { * @returns the members of the admin group. */ get adminGroupMembers(): string[] { - return this.getGroup("admin") ? JSON.parse(StrCast(this.getGroup("admin")!.members)) : ""; + return this.getGroup("Admin") ? JSON.parse(StrCast(this.getGroup("Admin")!.members)) : ""; } /** @@ -182,7 +179,7 @@ export default class GroupManager extends React.Component<{}> { */ createGroupDoc(groupName: string, memberEmails: string[] = []) { const groupDoc = new Doc; - groupDoc.groupName = groupName; + groupDoc.groupName = groupName.toLowerCase() === "admin" ? "Admin" : groupName; groupDoc.owners = JSON.stringify([Doc.CurrentUserEmail]); groupDoc.members = JSON.stringify(memberEmails); if (memberEmails.includes(Doc.CurrentUserEmail)) { @@ -285,7 +282,11 @@ export default class GroupManager extends React.Component<{}> { alert("Please enter a group name"); return; } - if (this.getAllGroups().find(group => group.groupName === this.inputRef.current!.value)) { // why do I need a null check here? + if (this.inputRef.current.value.toLowerCase() === "admin" && this.getGroup("Admin")) { + alert("You cannot override the Admin group"); + return; + } + if (this.getGroup(this.inputRef.current.value)) { alert("Please select a unique group name"); return; } @@ -314,7 +315,7 @@ export default class GroupManager extends React.Component<{}> { <div className={"close-button"} onClick={action(() => { this.createGroupModalOpen = false; TaskCompletionBox.taskCompleted = false; })}> - <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> + <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> </div> </div> <input @@ -398,19 +399,19 @@ export default class GroupManager extends React.Component<{}> { <div className="group-heading"> <p><b>Manage Groups</b></p> <button onClick={action(() => this.createGroupModalOpen = true)}> - <FontAwesomeIcon icon={fa.faPlus} size={"sm"} /> Create Group + <FontAwesomeIcon icon={"plus-hexagon"} size={"sm"} /> Create Group </button> <div className={"close-button"} onClick={this.close}> - <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> + <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> </div> </div> <div className="main-container"> <div className="sort-groups" onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} /> - : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} /> - : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} /> + Name {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> + : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> + : <FontAwesomeIcon icon={"caret-right"} size={"xs"} /> } </div> <div className="group-body"> @@ -421,7 +422,7 @@ export default class GroupManager extends React.Component<{}> { > <div className="group-name" >{group.groupName}</div> <div className="group-info" onClick={action(() => this.currentGroup = group)}> - <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> + <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> </div> </div> )} diff --git a/src/client/util/GroupMemberView.tsx b/src/client/util/GroupMemberView.tsx index 531ef988a..4ead01e9f 100644 --- a/src/client/util/GroupMemberView.tsx +++ b/src/client/util/GroupMemberView.tsx @@ -1,25 +1,21 @@ -import * as React from "react"; -import MainViewModal from "../views/MainViewModal"; -import { observer } from "mobx-react"; -import GroupManager, { UserOptions } from "./GroupManager"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { StrCast } from "../../fields/Types"; -import { action, observable } from "mobx"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import * as fa from '@fortawesome/free-solid-svg-icons'; +import { action, observable } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; import Select from "react-select"; import { Doc } from "../../fields/Doc"; +import { StrCast } from "../../fields/Types"; +import { MainViewModal } from "../views/MainViewModal"; +import { GroupManager, UserOptions } from "./GroupManager"; import "./GroupMemberView.scss"; -library.add(fa.faTimes, fa.faTrashAlt); - interface GroupMemberViewProps { group: Doc; onCloseButtonClick: () => void; } @observer -export default class GroupMemberView extends React.Component<GroupMemberViewProps> { +export class GroupMemberView extends React.Component<GroupMemberViewProps> { @observable private memberSort: "ascending" | "descending" | "none" = "none"; @@ -43,7 +39,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp > </input> <div className={"memberView-closeButton"} onClick={action(this.props.onCloseButtonClick)}> - <FontAwesomeIcon icon={fa.faTimes} color={"black"} size={"lg"} /> + <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> </div> {GroupManager.Instance.hasEditAccess(this.props.group) ? <div className="group-buttons"> @@ -88,7 +84,7 @@ export default class GroupMemberView extends React.Component<GroupMemberViewProp </div> {hasEditAccess ? <div className={"remove-button"} onClick={() => GroupManager.Instance.removeMemberFromGroup(this.props.group, member)}> - <FontAwesomeIcon icon={fa.faTrashAlt} size={"sm"} /> + <FontAwesomeIcon icon={"trash-alt"} size={"sm"} /> </div> : null} </div> diff --git a/src/client/util/History.ts b/src/client/util/History.ts index 7b7d4b835..cbe36b401 100644 --- a/src/client/util/History.ts +++ b/src/client/util/History.ts @@ -1,8 +1,8 @@ import { Doc } from "../../fields/Doc"; import { DocServer } from "../DocServer"; -import { MainView } from "../views/MainView"; import * as qs from 'query-string'; import { Utils, OmitKeys } from "../../Utils"; +import { CurrentUserUtils } from "./CurrentUserUtils"; export namespace HistoryUtil { export interface DocInitializerList { @@ -197,7 +197,7 @@ export namespace HistoryUtil { await Promise.all(Object.keys(init).map(id => initDoc(id, init[id]))); } if (field instanceof Doc) { - MainView.Instance.openWorkspace(field, true); + CurrentUserUtils.openDashboard(Doc.UserDoc(), field, true); } } diff --git a/src/client/util/HypothesisUtils.ts b/src/client/util/HypothesisUtils.ts index 9ede94e4b..4a5b52e1e 100644 --- a/src/client/util/HypothesisUtils.ts +++ b/src/client/util/HypothesisUtils.ts @@ -21,7 +21,7 @@ export namespace Hypothesis { export const getSourceWebDoc = async (uri: string) => { 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, _nativeHeight: 962, _width: 400, UseCors: true }); // create and return a new Web doc with given uri if no matching docs are found + return result || Docs.Create.WebDocument(uri, { title: uri, _nativeWidth: 850, _nativeHeight: 962, _width: 400, useCors: true }); // create and return a new Web doc with given uri if no matching docs are found }; @@ -34,7 +34,7 @@ export namespace Hypothesis { const results: Doc[] = []; await SearchUtil.Search("web", true).then(action(async (res: SearchUtil.DocSearchResult) => { - const docs = await Promise.all(res.docs.map(async doc => (await Cast(doc.extendsDoc, Doc)) || doc)); + const docs = res.docs; const filteredDocs = docs.filter(doc => doc.author === Doc.CurrentUserEmail && doc.type === DocumentType.WEB && doc.data ); @@ -60,6 +60,7 @@ export namespace Hypothesis { DocumentLinksButton.AnnotationId = annotationId; DocumentLinksButton.AnnotationUri = annotationUri; DocumentLinksButton.StartLink = sourceDoc; + DocumentLinksButton.StartLinkView = undefined; }); } else { // if a link has already been started, complete the link to sourceDoc runInAction(() => { diff --git a/src/client/util/Import & Export/DirectoryImportBox.tsx b/src/client/util/Import & Export/DirectoryImportBox.tsx index 77f13e9f4..d04270afa 100644 --- a/src/client/util/Import & Export/DirectoryImportBox.tsx +++ b/src/client/util/Import & Export/DirectoryImportBox.tsx @@ -1,5 +1,3 @@ -import { library } from '@fortawesome/fontawesome-svg-core'; -import { faCloudUploadAlt, faPlus, faTag } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { BatchedArray } from "array-batcher"; import "fs"; @@ -47,7 +45,6 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { constructor(props: FieldViewProps) { super(props); - library.add(faTag, faPlus); const doc = this.props.Document; this.editingMetadata = this.editingMetadata || false; this.persistent = this.persistent || false; @@ -301,7 +298,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { opacity: uploading ? 0 : 1, transition: "0.4s opacity ease" }}> - <FontAwesomeIcon icon={faCloudUploadAlt} color="#FFFFFF" size={"2x"} /> + <FontAwesomeIcon icon={"cloud-upload-alt"} color="#FFFFFF" size={"2x"} /> </div> <img style={{ @@ -366,7 +363,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { opacity: uploading ? 0 : 1, transition: "0.4s opacity ease" }} - icon={isEditing ? faCloudUploadAlt : faTag} + icon={isEditing ? "cloud-upload-alt" : "tag"} color="#FFFFFF" size={"1x"} /> @@ -399,7 +396,7 @@ export class DirectoryImportBox extends React.Component<FieldViewProps> { marginLeft: 6.4, marginTop: 5.2 }} - icon={faPlus} + icon={"plus"} size={"1x"} /> </div> diff --git a/src/client/util/Import & Export/ImportMetadataEntry.tsx b/src/client/util/Import & Export/ImportMetadataEntry.tsx index dcb94e2e0..1870213b9 100644 --- a/src/client/util/Import & Export/ImportMetadataEntry.tsx +++ b/src/client/util/Import & Export/ImportMetadataEntry.tsx @@ -4,7 +4,6 @@ import { EditableView } from "../../views/EditableView"; import { action, computed } from "mobx"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faPlus } from "@fortawesome/free-solid-svg-icons"; -import { library } from '@fortawesome/fontawesome-svg-core'; import { Doc } from "../../../fields/Doc"; import { StrCast, BoolCast } from "../../../fields/Types"; @@ -24,11 +23,6 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps> private valueRef = React.createRef<EditableView>(); private checkRef = React.createRef<HTMLInputElement>(); - constructor(props: KeyValueProps) { - super(props); - library.add(faPlus); - } - @computed public get valid() { return (this.key.length > 0 && this.key !== keyPlaceholder) && (this.value.length > 0 && this.value !== valuePlaceholder); @@ -132,7 +126,7 @@ export default class ImportMetadataEntry extends React.Component<KeyValueProps> </div> <div onClick={() => this.props.remove(this)} title={"Delete Entry"}> <FontAwesomeIcon - icon={faPlus} + icon={"plus"} color={"red"} size={"1x"} style={{ diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index ae3b3e064..4a203d41f 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -132,7 +132,6 @@ export namespace InteractionUtils { if (isNaN(scaley)) { scaley = 1; } - console.log(pts.length); return pts; } @@ -140,7 +139,7 @@ export namespace InteractionUtils { export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, - dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) { + dash: string | undefined, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) { let pts: { X: number; Y: number; }[] = []; if (shape) { //if any of the shape are true pts = makePolygon(shape, points); @@ -182,13 +181,13 @@ export namespace InteractionUtils { const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${(pt.X - left - width / 2) * scalex + width / 2}, ${(pt.Y - top - width / 2) * scaley + width / 2} `, ""); - const dashArray = String(Number(width) * Number(dash)); + const dashArray = dash && Number(dash) ? String(Number(width) * Number(dash)) : undefined; const defGuid = Utils.GenerateGuid(); const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth))); const addables = pts.map((pts, i) => <svg height="10" width="10"> - <circle cx={(pts.X - left - width / 2) * scalex + width / 2} cy={(pts.Y - top - width / 2) * scaley + width / 2} r={strokeWidth / 2} stroke="black" stroke-width={1} fill="blue" + <circle cx={(pts.X - left - width / 2) * scalex + width / 2} cy={(pts.Y - top - width / 2) * scaley + width / 2} r={strokeWidth / 2} stroke="black" strokeWidth={1} fill="blue" onDoubleClick={(e) => { console.log(i); }} pointerEvents="all" cursor="all-scroll" /> </svg>); @@ -210,7 +209,7 @@ export namespace InteractionUtils { points={strpts} style={{ filter: drawHalo ? "url(#inkSelectionHalo)" : undefined, - fill: fill ? fill : "transparent", + fill: fill ? fill : "none", opacity: strokeWidth !== width ? 0.5 : undefined, pointerEvents: pevents as any, stroke: color ?? "rgb(0, 0, 0)", @@ -300,19 +299,52 @@ export namespace InteractionUtils { return points; case "circle": + // const centerX = (right + left) / 2; + // const centerY = (bottom + top) / 2; + // const radius = bottom - centerY; + + // for (var y = top; y < bottom; y++) { + // const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + // points.push({ X: x, Y: y }); + // } + // for (var y = bottom; y > top; y--) { + // const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + // const newX = centerX - (x - centerX); + // points.push({ X: newX, Y: y }); + // } + // points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top }); const centerX = (right + left) / 2; const centerY = (bottom + top) / 2; - const radius = bottom - centerY; - for (var y = top; y < bottom; y++) { - const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; - points.push({ X: x, Y: y }); - } - for (var y = bottom; y > top; y--) { - const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; - const newX = centerX - (x - centerX); - points.push({ X: newX, Y: y }); + if ((bottom - centerY) < (right - centerX)) { + const radius = bottom - centerY; + for (var y = top; y < bottom; y++) { + const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + points.push({ X: x, Y: y }); + } + for (var y = bottom; y > top; y--) { + const x = Math.sqrt(Math.pow(radius, 2) - (Math.pow((y - centerY), 2))) + centerX; + const newX = centerX - (x - centerX); + points.push({ X: newX, Y: y }); + } + points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top }); + + } else { + //right = bottom + //left = top + const radius = right - centerX; + for (var x = left; x < right; x++) { + const y = Math.sqrt(Math.pow(radius, 2) - (Math.pow((x - centerX), 2))) + centerY; + points.push({ X: x, Y: y }); + } + for (var x = right; x > left; 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: left, Y: Math.sqrt(Math.pow(radius, 2) - (Math.pow((left - centerX), 2))) + centerY }); + + } - points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top }); return points; // case "arrow": // const x1 = left; diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index b63fc8991..b09eff849 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -23,12 +23,14 @@ export namespace SearchUtil { } export interface SearchParams { - hl?: boolean; + hl?: string; "hl.fl"?: string; start?: number; rows?: number; fq?: string; + sort?: string; allowAliases?: boolean; + onlyAliases?: boolean; "facet"?: string; "facet.field"?: string; } @@ -37,8 +39,12 @@ export namespace SearchUtil { export async function Search(query: string, returnDocs: boolean, options: SearchParams = {}) { query = query || "*"; //If we just have a filter query, search for * as the query const rpquery = Utils.prepend("/dashsearch"); - const replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}type_t:${arg}`); - const gotten = await rp.get(rpquery, { qs: { ...options, sort: "lastModified_d desc", q: replacedQuery } }); + let replacedQuery = query.replace(/type_t:([^ )])/g, (substring, arg) => `{!join from=id to=proto_i}*:* AND ${arg}`); + if (options.onlyAliases) { + const header = query.match(/_[atnb]?:/) ? replacedQuery : "DEFAULT:" + replacedQuery; + replacedQuery = `{!join from=id to=proto_i}${header}`; + } + const gotten = await rp.get(rpquery, { qs: { ...options, q: replacedQuery } }); const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten); if (!returnDocs) { return result; @@ -80,6 +86,8 @@ export namespace SearchUtil { if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && (options.allowAliases || testDoc.proto === undefined || theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1)) { theDocs.push(testDoc); theLines.push([]); + } else { + result.numFound--; } } diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 113278593..730db4f29 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,10 +1,10 @@ import { observable, action, runInAction, ObservableMap } from "mobx"; -import { Doc } from "../../fields/Doc"; +import { Doc, Opt } from "../../fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { computedFn } from "mobx-utils"; import { List } from "../../fields/List"; -import { Scripting } from "./Scripting"; -import { DocumentManager } from "./DocumentManager"; +import { CollectionSchemaView } from "../views/collections/CollectionSchemaView"; +import { CollectionViewType } from "../views/collections/CollectionView"; export namespace SelectionManager { @@ -12,10 +12,16 @@ export namespace SelectionManager { @observable IsDragging: boolean = false; SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap(); + @observable SelectedSchemaDocument: Doc | undefined; + @observable SelectedSchemaCollection: CollectionSchemaView | undefined; @action + SelectSchemaDoc(collectionView: Opt<CollectionSchemaView>, doc: Opt<Doc>) { + manager.SelectedSchemaDocument = doc; + manager.SelectedSchemaCollection = collectionView; + } + @action SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { - // if doc is not in SelectedDocuments, add it if (!manager.SelectedDocuments.get(docView)) { if (!ctrlPressed) { @@ -26,6 +32,8 @@ export namespace SelectionManager { docView.props.whenActiveChanged(true); } else if (!ctrlPressed && Array.from(manager.SelectedDocuments.entries()).length > 1) { Array.from(manager.SelectedDocuments.keys()).map(dv => dv !== docView && dv.props.whenActiveChanged(false)); + manager.SelectedSchemaDocument = undefined; + manager.SelectedSchemaCollection = undefined; manager.SelectedDocuments.clear(); manager.SelectedDocuments.set(docView, true); } @@ -42,7 +50,8 @@ export namespace SelectionManager { } @action DeselectAll(): void { - + manager.SelectedSchemaCollection = undefined; + manager.SelectedSchemaDocument = undefined; Array.from(manager.SelectedDocuments.keys()).map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments.clear(); Doc.UserDoc().activeSelection = new List<Doc>([]); @@ -57,6 +66,9 @@ export namespace SelectionManager { export function SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { manager.SelectDoc(docView, ctrlPressed); } + export function SelectSchemaDoc(colSchema: Opt<CollectionSchemaView>, document: Opt<Doc>): void { + manager.SelectSchemaDoc(colSchema, document); + } // computed functions, such as used in IsSelected generate errors if they're called outside of a // reaction context. Specifying the context with 'outsideReaction' allows an efficiency feature @@ -82,6 +94,12 @@ export namespace SelectionManager { } export function SelectedDocuments(): Array<DocumentView> { - return Array.from(manager.SelectedDocuments.keys()); + return Array.from(manager.SelectedDocuments.keys()).filter(dv => dv.props.Document._viewType !== CollectionViewType.Docking); + } + export function SelectedSchemaDoc(): Doc | undefined { + return manager.SelectedSchemaDocument; + } + export function SelectedSchemaCollection(): CollectionSchemaView | undefined { + return manager.SelectedSchemaCollection; } }
\ No newline at end of file diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index ec513e5d5..68e0b91b0 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -4,7 +4,6 @@ //background-color: whitesmoke !important; color: grey; width: 450px; - height: 300px; button { background: #315a96; @@ -142,7 +141,7 @@ .colorFlyout { margin-top: 2px; - margin-right: 25px; + margin-right: 18px; &:hover { cursor: pointer; @@ -163,6 +162,7 @@ .preferences-color { display: flex; + margin-top: 2px; .preferences-color-text { color: black; @@ -174,6 +174,8 @@ .preferences-font { display: flex; + height: 23px; + margin-top: 2px; .preferences-font-text { color: black; @@ -194,6 +196,16 @@ } } + .preferences-check { + color: black; + font-size: 9; + /* margin-top: 4; */ + margin-right: 4; + margin-bottom: -3; + margin-left: 5; + margin-top: -1px; + } + .size-select { width: 60px; color: black; diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index b4778d3eb..0a1890ca4 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -1,27 +1,25 @@ -import { observable, runInAction, action, computed } from "mobx"; -import * as React from "react"; -import MainViewModal from "../views/MainViewModal"; -import { observer } from "mobx-react"; -import * as fa from '@fortawesome/free-solid-svg-icons'; -import { SelectionManager } from "./SelectionManager"; -import "./SettingsManager.scss"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { Networking } from "../Network"; -import { CurrentUserUtils } from "./CurrentUserUtils"; -import { Utils, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from "../../Utils"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import * as React from "react"; +import { ColorState, SketchPicker } from "react-color"; import { Doc } from "../../fields/Doc"; -import GroupManager from "./GroupManager"; -import GoogleAuthenticationManager from "../apis/GoogleAuthenticationManager"; +import { BoolCast, StrCast } from "../../fields/Types"; +import { addStyleSheet, addStyleSheetRule, Utils } from "../../Utils"; +import { GoogleAuthenticationManager } from "../apis/GoogleAuthenticationManager"; import { DocServer } from "../DocServer"; -import { BoolCast, StrCast, NumCast } from "../../fields/Types"; +import { Networking } from "../Network"; +import { MainViewModal } from "../views/MainViewModal"; +import { CurrentUserUtils } from "./CurrentUserUtils"; +import { GroupManager } from "./GroupManager"; +import "./SettingsManager.scss"; import { undoBatch } from "./UndoManager"; -import { ColorState, SketchPicker } from "react-color"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @observer -export default class SettingsManager extends React.Component<{}> { +export class SettingsManager extends React.Component<{}> { public static Instance: SettingsManager; static _settingsStyle = addStyleSheet(); @observable private isOpen = false; @@ -32,7 +30,7 @@ export default class SettingsManager extends React.Component<{}> { @observable private new_password = ""; @observable private new_confirm = ""; - @computed get backgroundColor() { return Doc.UserDoc().defaultColor; } + @computed get backgroundColor() { return Doc.UserDoc().activeCollectionBackground; } constructor(props: {}) { super(props); @@ -54,6 +52,7 @@ export default class SettingsManager extends React.Component<{}> { } @undoBatch selectUserMode = action((e: React.ChangeEvent) => Doc.UserDoc().noviceMode = (e.currentTarget as any)?.value === "Novice"); + @undoBatch changeShowTitle = action((e: React.ChangeEvent) => Doc.UserDoc().showTitle = (e.currentTarget as any).value); @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value); @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value); @undoBatch switchColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex)); @@ -86,17 +85,31 @@ export default class SettingsManager extends React.Component<{}> { return <div className="preferences-content"> <div className="preferences-color"> - <div className="preferences-color-text">Background Color</div> + <div className="preferences-color-text">Back. Color</div> {colorFlyout} </div> <div className="preferences-font"> - <div className="preferences-font-text">Default Font</div> + <div className="preferences-font-text">Font</div> <select className="font-select" onChange={this.changeFontFamily} value={StrCast(Doc.UserDoc().fontFamily, "Times New Roman")} > {fontFamilies.map(font => <option key={font} value={font} defaultValue={StrCast(Doc.UserDoc().fontFamily)}> {font} </option>)} </select> - <select className="size-select" onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7pt")}> + <select className="size-select" style={{ marginRight: "10px" }} onChange={this.changeFontSize} value={StrCast(Doc.UserDoc().fontSize, "7pt")}> {fontSizes.map(size => <option key={size} value={size} defaultValue={StrCast(Doc.UserDoc().fontSize)}> {size} </option>)} </select> + <div> + <div className="preferences-check">Show title</div> + <input type="checkbox" onChange={e => Doc.UserDoc().showTitle = !Doc.UserDoc().showTitle} checked={BoolCast(Doc.UserDoc().showTitle)} /> + </div> + <div> + <div className="preferences-check">Alt Buttons</div> + <input type="checkbox" onChange={e => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"]} + checked={BoolCast(Doc.UserDoc()["documentLinksButton-hideEnd"])} /> + </div> + <div> + Autoscroll + <input type="checkbox" onChange={e => Doc.UserDoc()._noAutoscroll = !Doc.UserDoc()._noAutoscroll} + checked={!BoolCast(Doc.UserDoc()._noAutoscroll)} /> + </div> </div> </div>; } @@ -158,10 +171,10 @@ export default class SettingsManager extends React.Component<{}> { <div className="settings-title">Settings</div> <div className="settings-username">{Doc.CurrentUserEmail}</div> <button className="logout-button" onClick={() => window.location.assign(Utils.prepend("/logout"))} > - {CurrentUserUtils.GuestWorkspace ? "Exit" : "Log Out"} + {CurrentUserUtils.GuestDashboard ? "Exit" : "Log Out"} </button> <div className="close-button" onClick={this.close}> - <FontAwesomeIcon icon={fa.faTimes} color="black" size={"lg"} /> + <FontAwesomeIcon icon={"times"} color="black" size={"lg"} /> </div> </div> <div className="settings-content"> @@ -180,6 +193,6 @@ export default class SettingsManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: "600px", height: "340px" }} />; + dialogueBoxStyle={{ width: "600px" }} />; } }
\ No newline at end of file diff --git a/src/client/util/SharingManager.scss b/src/client/util/SharingManager.scss index 7912db74d..b756716bf 100644 --- a/src/client/util/SharingManager.scss +++ b/src/client/util/SharingManager.scss @@ -62,6 +62,7 @@ input { height: 10px; + cursor: pointer; } label { @@ -69,11 +70,29 @@ font-style: italic; } } + + .layoutDoc-acls { + display: flex; + flex-direction: column; + float: right; + margin-right: 12; + margin-top: -15; + align-items: center; + + label { + font-weight: normal; + font-style: italic; + } + + input { + cursor: pointer; + } + } } .main-container { display: flex; - margin-top: -10px; + margin-top: -25px; .individual-container, .group-container { @@ -102,7 +121,7 @@ overflow-y: scroll; overflow-x: hidden; text-align: left; - display: flex; + display: block; align-content: center; align-items: center; text-align: center; diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 48a3c023f..6fdbac41b 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -1,30 +1,26 @@ -import { observable, runInAction, action } from "mobx"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; import * as React from "react"; -import MainViewModal from "../views/MainViewModal"; -import { Doc, Opt, AclAdmin, AclPrivate, DocListCast } from "../../fields/Doc"; -import { DocServer } from "../DocServer"; -import { Cast, StrCast } from "../../fields/Types"; +import Select from "react-select"; import * as RequestPromise from "request-promise"; +import { AclAdmin, AclPrivate, DataSym, Doc, DocListCast, Opt, AclSym } from "../../fields/Doc"; +import { List } from "../../fields/List"; +import { Cast, StrCast } from "../../fields/Types"; +import { distributeAcls, GetEffectiveAcl, SharingPermissions } from "../../fields/util"; import { Utils } from "../../Utils"; -import "./SharingManager.scss"; -import { observer } from "mobx-react"; -import * as fa from '@fortawesome/free-solid-svg-icons'; -import { DocumentView } from "../views/nodes/DocumentView"; -import { SelectionManager } from "./SelectionManager"; -import { DocumentManager } from "./DocumentManager"; +import { DocServer } from "../DocServer"; import { CollectionView } from "../views/collections/CollectionView"; import { DictationOverlay } from "../views/DictationOverlay"; -import GroupManager, { UserOptions } from "./GroupManager"; -import GroupMemberView from "./GroupMemberView"; -import Select from "react-select"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { List } from "../../fields/List"; -import { distributeAcls, SharingPermissions, GetEffectiveAcl } from "../../fields/util"; +import { MainViewModal } from "../views/MainViewModal"; +import { DocumentView } from "../views/nodes/DocumentView"; import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox"; -import { library } from "@fortawesome/fontawesome-svg-core"; - -library.add(fa.faInfoCircle, fa.faCaretUp, fa.faCaretRight, fa.faCaretDown); - +import { DocumentManager } from "./DocumentManager"; +import { GroupManager, UserOptions } from "./GroupManager"; +import { GroupMemberView } from "./GroupMemberView"; +import "./SharingManager.scss"; +import { SelectionManager } from "./SelectionManager"; +import { intersection } from "lodash"; export interface User { email: string; @@ -47,6 +43,8 @@ interface GroupedOptions { const indType = "!indType/"; const groupType = "!groupType/"; +const storage = "data"; + /** * A user who also has a notificationDoc. */ @@ -55,10 +53,9 @@ interface ValidatedUser { notificationDoc: Doc; } -const storage = "data"; @observer -export default class SharingManager extends React.Component<{}> { +export class SharingManager extends React.Component<{}> { public static Instance: SharingManager; @observable private isOpen = false; // whether the SharingManager modal is open or not @observable private users: ValidatedUser[] = []; // the list of users with notificationDocs @@ -75,29 +72,28 @@ export default class SharingManager extends React.Component<{}> { // 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; + private populating: boolean = false; // whether the list of users is populating or not + @observable private layoutDocAcls: boolean = false; // whether the layout doc or data doc's acls are to be used // private get linkVisible() { // return this.sharingDoc ? this.sharingDoc[PublicKey] !== SharingPermissions.None : false; // } - public open = (target: DocumentView) => { + public open = (target?: DocumentView, target_doc?: Doc) => { runInAction(() => this.users = []); - // SelectionManager.DeselectAll(); this.populateUsers(); runInAction(() => { this.targetDocView = target; - this.targetDoc = target.props.Document; + this.targetDoc = target_doc || target?.props.Document; DictationOverlay.Instance.hasActiveModal = true; - this.isOpen = true; + this.isOpen = this.targetDoc !== undefined; this.permissions = SharingPermissions.Edit; }); - this.targetDoc!.author === Doc.CurrentUserEmail && !this.targetDoc![`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, this.targetDoc!); } public close = action(() => { this.isOpen = false; - this.selectedUsers = null; // resets the list of users and seleected users (in the react-select component) + this.selectedUsers = null; // resets the list of users and selected users (in the react-select component) TaskCompletionBox.taskCompleted = false; setTimeout(action(() => { // this.copied = false; @@ -119,7 +115,7 @@ export default class SharingManager extends React.Component<{}> { } /** - * Populates the list of validated users (this.users) by adding registered users which have a sidebar-sharing. + * Populates the list of validated users (this.users) by adding registered users which have a mySharedDocs. */ populateUsers = async () => { if (!this.populating) { @@ -132,7 +128,7 @@ export default class SharingManager extends React.Component<{}> { if (isCandidate) { const userDocument = await DocServer.GetRefField(user.userDocumentId); if (userDocument instanceof Doc) { - const notificationDoc = await Cast(userDocument["sidebar-sharing"], Doc); + const notificationDoc = await Cast(userDocument.mySharedDocs, Doc); runInAction(() => { if (notificationDoc instanceof Doc) { this.users.push({ user, notificationDoc }); @@ -151,20 +147,29 @@ export default class SharingManager extends React.Component<{}> { * @param permission */ setInternalGroupSharing = (group: Doc, permission: string, targetDoc?: Doc) => { - const members: string[] = JSON.parse(StrCast(group.members)); - const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); const target = targetDoc || this.targetDoc!; - const ACL = `ACL-${StrCast(group.groupName)}`; + const key = StrCast(group.groupName).replace(".", "_"); + const ACL = `ACL-${key}`; + + const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); + + docs.forEach(doc => { + doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); + GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc); - target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target); + if (key !== "Public") { + const members: string[] = JSON.parse(StrCast(group.members)); + const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); - // if documents have been shared, add the target to that list if it doesn't already exist, otherwise create a new list with the target - group.docsShared ? Doc.IndexOf(target, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(target) : group.docsShared = new List<Doc>([target]); + // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc + group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]); - users.forEach(({ notificationDoc }) => { - if (permission !== SharingPermissions.None) Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); // add the target to the notificationDoc if it hasn't already been added - else Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); // remove the target from the list if it already exists + users.forEach(({ user, notificationDoc }) => { + if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); // add the doc to the notificationDoc if it hasn't already been added + else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists + }); + } }); } @@ -175,17 +180,25 @@ export default class SharingManager extends React.Component<{}> { */ shareWithAddedMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; - if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); } /** * Called from the properties sidebar to change permissions of a user. */ - shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, target: Doc) => { - const user = this.users.find(({ user: { email } }) => email === (shareWith === "Me" ? Doc.CurrentUserEmail : shareWith)); - if (user) this.setInternalSharing(user, permission, target); - else this.setInternalGroupSharing(GroupManager.Instance.getGroup(shareWith)!, permission, target); + shareFromPropertiesSidebar = (shareWith: string, permission: SharingPermissions, docs: Doc[]) => { + if (shareWith !== "Public") { + 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); + }); + } + else { + docs.forEach(doc => { + if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls("ACL-Public", permission, doc); + }); + } } /** @@ -231,14 +244,16 @@ export default class SharingManager extends React.Component<{}> { const key = user.email.replace('.', '_'); const ACL = `ACL-${key}`; - target.author === Doc.CurrentUserEmail && distributeAcls(ACL, permission as SharingPermissions, target); - if (permission !== SharingPermissions.None) { - Doc.IndexOf(target, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, target); - } - else { - Doc.IndexOf(target, DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, target); - } + const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); + + docs.forEach(doc => { + doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); + GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc); + + if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); + else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, (doc.aliasOf as Doc || doc)); + }); } @@ -268,8 +283,10 @@ export default class SharingManager extends React.Component<{}> { /** * Returns the SharingPermissions (Admin, Can Edit etc) access that's used to share */ - private get sharingOptions() { - return Object.values(SharingPermissions).map(permission => + private sharingOptions(uniform: boolean) { + const dropdownValues: string[] = Object.values(SharingPermissions); + if (!uniform) dropdownValues.unshift("-multiple-"); + return dropdownValues.map(permission => ( <option key={permission} value={permission}> {permission} @@ -280,27 +297,30 @@ export default class SharingManager extends React.Component<{}> { private focusOn = (contents: string) => { const title = this.targetDoc ? StrCast(this.targetDoc.title) : ""; + const docs = SelectionManager.SelectedDocuments().length > 1 ? SelectionManager.SelectedDocuments().map(docView => docView.props.Document) : [this.targetDoc]; return ( <span className={"focus-span"} title={title} onClick={() => { let context: Opt<CollectionView>; - if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) { + if (this.targetDoc && this.targetDocView && docs.length === 1 && (context = this.targetDocView.props.ContainingCollectionView)) { DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document); } }} onPointerEnter={action(() => { - if (this.targetDoc) { - Doc.BrushDoc(this.targetDoc); + if (docs.length) { + docs.forEach(doc => doc && Doc.BrushDoc(doc)); this.dialogueBoxOpacity = 0.1; this.overlayOpacity = 0.1; } })} onPointerLeave={action(() => { - this.targetDoc && Doc.UnBrushDoc(this.targetDoc); - this.dialogueBoxOpacity = 1; - this.overlayOpacity = 0.4; + if (docs.length) { + docs.forEach(doc => doc && Doc.UnBrushDoc(doc)); + this.dialogueBoxOpacity = 1; + this.overlayOpacity = 0.4; + } })} > {contents} @@ -372,8 +392,8 @@ export default class SharingManager extends React.Component<{}> { * @returns the main interface of the SharingManager. */ private get sharingInterface() { - const groupList = GroupManager.Instance?.getAllGroups() || []; + const groupList = GroupManager.Instance?.getAllGroups() || []; const sortedUsers = this.users.slice().sort(this.sortUsers) .map(({ user: { email } }) => ({ label: email, value: indType + email })); const sortedGroups = groupList.slice().sort(this.sortGroups) @@ -406,30 +426,42 @@ export default class SharingManager extends React.Component<{}> { } } - const users = this.individualSort === "ascending" ? this.users.sort(this.sortUsers) : this.individualSort === "descending" ? this.users.sort(this.sortUsers).reverse() : this.users; - const groups = this.groupSort === "ascending" ? groupList.sort(this.sortGroups) : this.groupSort === "descending" ? groupList.sort(this.sortGroups).reverse() : groupList; + 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; + + // handles the case where multiple documents are selected + const docs = SelectionManager.SelectedDocuments().length < 2 ? + [this.layoutDocAcls ? this.targetDoc : this.targetDoc?.[DataSym]] + : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document?.[DataSym]); + + const targetDoc = docs[0]; + + // tslint:disable-next-line: no-unnecessary-callback-wrapper + const admin = docs.map(doc => GetEffectiveAcl(doc)).every(acl => acl === AclAdmin); // if the user has admin access to all selected docs - const effectiveAcl = this.targetDoc ? GetEffectiveAcl(this.targetDoc) : AclPrivate; + // users in common between all docs + const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym]))); // the list of users shared with - const userListContents: (JSX.Element | null)[] = users.map(({ user, notificationDoc }) => { - const userKey = user.email.replace('.', '_'); - const permissions = StrCast(this.targetDoc?.[`ACL-${userKey}`]); + const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`ACL-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc }) => { + const userKey = `ACL-${user.email.replace('.', '_')}`; + const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]); + const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-"; - return !permissions || user.email === this.targetDoc?.author ? null : ( + return !permissions ? (null) : ( <div key={userKey} className={"container"} > <span className={"padding"}>{user.email}</span> <div className="edit-actions"> - {effectiveAcl === AclAdmin ? ( + {admin ? ( <select className={"permissions-dropdown"} value={permissions} onChange={e => this.setInternalSharing({ user, notificationDoc }, e.currentTarget.value)} > - {this.sharingOptions} + {this.sharingOptions(uniform)} </select> ) : ( <div className={"permissions-dropdown"}> @@ -441,22 +473,26 @@ export default class SharingManager extends React.Component<{}> { ); }); + // checks if every doc has the same author + 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. userListContents.unshift( - ( - <div - key={"owner"} - className={"container"} - > - <span className={"padding"}>{this.targetDoc?.author === Doc.CurrentUserEmail ? "Me" : this.targetDoc?.author}</span> - <div className="edit-actions"> - <div className={"permissions-dropdown"}> - Owner + sameAuthor ? + ( + <div + key={"owner"} + className={"container"} + > + <span className={"padding"}>{targetDoc?.author === Doc.CurrentUserEmail ? "Me" : targetDoc?.author}</span> + <div className="edit-actions"> + <div className={"permissions-dropdown"}> + Owner + </div> </div> </div> - </div> - ), - this.targetDoc?.author !== Doc.CurrentUserEmail ? + ) : null, + sameAuthor && targetDoc?.author !== Doc.CurrentUserEmail ? ( <div key={"me"} @@ -465,42 +501,54 @@ export default class SharingManager extends React.Component<{}> { <span className={"padding"}>Me</span> <div className="edit-actions"> <div className={"permissions-dropdown"}> - {this.targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]} + {targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]} </div> </div> </div> ) : null ); + // dummy doc for public acl + const publicDoc = new Doc; + publicDoc.groupName = "Public"; // the list of groups shared with - const groupListContents = groups.map(group => { - const permissions = StrCast(this.targetDoc?.[`ACL-${StrCast(group.groupName)}`]); - - return !permissions ? null : ( + const groupListMap = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`ACL-${StrCast(groupName).replace('.', '_')}`) : true); + groupListMap.unshift(publicDoc); + const groupListContents = groupListMap.map(group => { + const groupKey = `ACL-${StrCast(group.groupName)}`; + const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]); + const permissions = uniform ? StrCast(targetDoc?.[`ACL-${StrCast(group.groupName)}`]) : "-multiple-"; + + return !permissions ? (null) : ( <div - key={StrCast(group.groupName)} + key={groupKey} className={"container"} > <div className={"padding"}>{group.groupName}</div> - <div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}> - <FontAwesomeIcon icon={fa.faInfoCircle} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> - </div> + {group.groupName !== "Public" ? + (<div className="group-info" onClick={action(() => GroupManager.Instance.currentGroup = group)}> + <FontAwesomeIcon icon={"info-circle"} color={"#e8e8e8"} size={"sm"} style={{ backgroundColor: "#1e89d7", borderRadius: "100%", border: "1px solid #1e89d7" }} /> + </div>) + : (null)} <div className="edit-actions"> - <select - className={"permissions-dropdown"} - value={permissions} - onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)} - > - {this.sharingOptions} - </select> + {admin ? ( + <select + className={"permissions-dropdown"} + value={permissions} + onChange={e => this.setInternalGroupSharing(group, e.currentTarget.value)} + > + {this.sharingOptions(uniform)} + </select> + ) : ( + <div className={"permissions-dropdown"}> + {permissions} + </div> + )} </div> </div> ); }); - // don't display the group list if all groups are null - const displayGroupList = !groupListContents?.every(group => group === null); - return ( <div className={"sharing-interface"}> {GroupManager.Instance?.currentGroup ? @@ -519,7 +567,7 @@ export default class SharingManager extends React.Component<{}> { style={{ backgroundColor: this.copied ? "lawngreen" : "gainsboro" }} onClick={this.copy} > - <FontAwesomeIcon icon={fa.faCopy} /> + <FontAwesomeIcon icon={"copy"} /> </div> </div> } @@ -540,7 +588,7 @@ export default class SharingManager extends React.Component<{}> { </div> <div className={"hr-substitute"} /> */} <div className="sharing-contents"> - <p className={"share-title"}><b>Share </b>{this.focusOn(StrCast(this.targetDoc?.title, "this document"))}</p> + <p className={"share-title"}><b>Share </b>{this.focusOn(docs.length < 2 ? StrCast(targetDoc?.title, "this document") : "-multiple-")}</p> <div className={"close-button"} onClick={this.close}> <FontAwesomeIcon icon={"times"} color={"black"} size={"lg"} /> </div> @@ -561,7 +609,7 @@ export default class SharingManager extends React.Component<{}> { }} /> <select className="permissions-select" onChange={this.handlePermissionsChange} value={this.permissions}> - {this.sharingOptions} + {this.sharingOptions(true)} </select> <button ref={this.shareDocumentButtonRef} className="share-button" onClick={this.share}> Share @@ -571,6 +619,9 @@ export default class SharingManager extends React.Component<{}> { <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="layoutDoc-acls"> + <input type="checkbox" onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} checked={this.layoutDocAcls} /> <label>Layout</label> + </div> </div> } <div className="main-container"> @@ -578,11 +629,11 @@ export default class SharingManager extends React.Component<{}> { <div className="user-sort" onClick={action(() => this.individualSort = this.individualSort === "ascending" ? "descending" : this.individualSort === "descending" ? "none" : "ascending")}> - Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} /> - : this.individualSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} /> - : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />} + Individuals {this.individualSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> + : this.individualSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> + : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />} </div> - <div className={"users-list"} style={{ display: "block" }}>{/*200*/} + <div className={"users-list"}> {userListContents} </div> </div> @@ -590,23 +641,13 @@ export default class SharingManager extends React.Component<{}> { <div className="user-sort" onClick={action(() => this.groupSort = this.groupSort === "ascending" ? "descending" : this.groupSort === "descending" ? "none" : "ascending")}> - Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={fa.faCaretUp} size={"xs"} /> - : this.groupSort === "descending" ? <FontAwesomeIcon icon={fa.faCaretDown} size={"xs"} /> - : <FontAwesomeIcon icon={fa.faCaretRight} size={"xs"} />} + Groups {this.groupSort === "ascending" ? <FontAwesomeIcon icon={"caret-up"} size={"xs"} /> + : this.groupSort === "descending" ? <FontAwesomeIcon icon={"caret-down"} size={"xs"} /> + : <FontAwesomeIcon icon={"caret-right"} size={"xs"} />} </div> - <div className={"groups-list"} style={{ display: !displayGroupList ? "flex" : "block" }}>{/*200*/} - { - !displayGroupList ? - <div - className={"none"} - > - There are no groups this document has been shared with. - </div> - : - groupListContents - } - + <div className={"groups-list"}> + {groupListContents} </div> </div> </div> |