diff options
-rw-r--r-- | src/client/documents/Documents.ts | 3 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 19 | ||||
-rw-r--r-- | src/client/views/DashboardView.scss | 18 | ||||
-rw-r--r-- | src/client/views/DashboardView.tsx | 152 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 10 | ||||
-rw-r--r-- | src/client/views/nodes/button/FontIconBadge.tsx | 2 | ||||
-rw-r--r-- | src/client/views/topbar/TopBar.scss | 2 | ||||
-rw-r--r-- | src/client/views/topbar/TopBar.tsx | 34 |
8 files changed, 183 insertions, 57 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index f96bffb3c..012a8b8a3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -259,7 +259,7 @@ export class DocumentOptions { numBtnType?: string; numBtnMax?: number; numBtnMin?: number; - switchToggle?: boolean; + switchToggle?: boolean; badgeValue?: ScriptField; //LINEAR VIEW @@ -306,7 +306,6 @@ export class DocumentOptions { treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. treeViewGrowsHorizontally?: boolean; // whether an embedded tree view of the document can grow horizontally without growing vertically treeViewChildDoubleClick?: ScriptField; // - // Action Button buttonMenu?: boolean; // whether a action button should be displayed buttonMenuDoc?: Doc; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7a38886fe..602867948 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -24,6 +24,7 @@ import { Colors } from "../views/global/globalEnums"; import { MainView } from "../views/MainView"; import { ButtonType, NumButtonType } from "../views/nodes/button/FontIconBox"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; +import { DocumentView } from "../views/nodes/DocumentView"; import { OverlayView } from "../views/OverlayView"; import { DocumentManager } from "./DocumentManager"; import { DragManager } from "./DragManager"; @@ -992,6 +993,7 @@ export class CurrentUserUtils { // undefined means ColorScheme.Light until all CSS is updated with values for each color scheme (e.g., see MainView.scss, DocumentDecorations.scss) doc.activeDashboard.colorScheme = doc.activeDashboard.colorScheme === ColorScheme.Light ? undefined : doc.activeDashboard.colorScheme; } + return doc; } @@ -1148,7 +1150,15 @@ export class CurrentUserUtils { CurrentUserUtils.ActiveDashboard = undefined; } - public static createNewDashboard = async (userDoc: Doc, id?: string) => { + public static async removeDashboard(dashboard: Doc) { + const dashboards = await DocListCastAsync(CurrentUserUtils.MyDashboards.data); + if (dashboards && dashboards.length > 0) { + Doc.RemoveDocFromList(CurrentUserUtils.MyDashboards, "data", dashboard); + } + } + + public static createNewDashboard = async (userDoc: Doc, id?: string, name?: string) => { + console.log(name) const presentation = Doc.MakeCopy(userDoc.emptyPresentation as Doc, true); const dashboards = await Cast(userDoc.myDashboards, Doc) as Doc; const dashboardCount = DocListCast(dashboards.data).length + 1; @@ -1161,8 +1171,9 @@ export class CurrentUserUtils { _backgroundGridShow: true, title: `Untitled Tab 1`, }; + const title = name ? name : `Dashboard ${dashboardCount}` const freeformDoc = CurrentUserUtils.GuestTarget || Docs.Create.FreeformDocument([], freeformOptions); - const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: `Dashboard ${dashboardCount}` }, id, "row"); + const dashboardDoc = Docs.Create.StandardCollectionDockingDocument([{ doc: freeformDoc, initialWidth: 600 }], { title: title }, id, "row"); freeformDoc.context = dashboardDoc; // switching the tabs from the datadoc to the regular doc @@ -1173,7 +1184,9 @@ export class CurrentUserUtils { userDoc.activePresentation = presentation; Doc.AddDocToList(dashboards, "data", dashboardDoc); - // CurrentUserUtils.openDashboard(userDoc, dashboardDoc); + // open this new dashboard + Doc.UserDoc().activeDashboard = dashboardDoc; + Doc.UserDoc().activePage = "dashboard"; } public static GetNewTextDoc(title: string, x: number, y: number, width?: number, height?: number, noMargins?: boolean, annotationOn?: Doc, maxHeight?: number, backgroundColor?: string) { diff --git a/src/client/views/DashboardView.scss b/src/client/views/DashboardView.scss index 67587dd2b..3db23b86f 100644 --- a/src/client/views/DashboardView.scss +++ b/src/client/views/DashboardView.scss @@ -2,6 +2,8 @@ padding: 50px; display: flex; flex-direction: row; + width: 100%; + position: absolute; .left-menu { display: flex; @@ -45,4 +47,20 @@ margin: 10px; font-weight: 500; } + + img { + width: auto; + height: 80%; + } + + .info { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + } + + .more { + z-index: 100; + } }
\ No newline at end of file diff --git a/src/client/views/DashboardView.tsx b/src/client/views/DashboardView.tsx index 9a0f25fe3..ebe73ffea 100644 --- a/src/client/views/DashboardView.tsx +++ b/src/client/views/DashboardView.tsx @@ -1,4 +1,4 @@ -import { action, observable } from "mobx"; +import { action, computed, observable } from "mobx"; import { extname } from 'path'; import { observer } from "mobx-react"; import * as React from 'react'; @@ -8,62 +8,158 @@ import { Cast, ImageCast, StrCast } from "../../fields/Types"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { UndoManager } from "../util/UndoManager"; import "./DashboardView.scss" +import { MainViewModal } from "./MainViewModal"; +import { ContextMenu } from "./ContextMenu"; +import { DocumentManager } from "../util/DocumentManager"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ContextMenuProps } from "./ContextMenuItem"; +import { simulateMouseClick } from "../../Utils"; +import { SharingManager } from "../util/SharingManager"; +import { CollectionViewType } from "./collections/CollectionView"; enum DashboardGroup { MyDashboards, SharedDashboards } +// DashboardView is the view with the dashboard previews, rendered when the app first loads + @observer export class DashboardView extends React.Component { //TODO: delete dashboard, share dashboard, etc. - @observable - private selectedDashboardGroup = DashboardGroup.MyDashboards; + @observable private selectedDashboardGroup = DashboardGroup.MyDashboards; + + @observable private newDashboardName: string | undefined = undefined; + @action abortCreateNewDashboard = () => { this.newDashboardName = undefined } + @action setNewDashboardName(name: string) { this.newDashboardName = name } @action selectDashboardGroup = (group: DashboardGroup) => { this.selectedDashboardGroup = group } - newDashboard = async () => { - const batch = UndoManager.StartBatch("new dash"); - await CurrentUserUtils.createNewDashboard(Doc.UserDoc()); - batch.end(); - } - clickDashboard = async (e: React.MouseEvent, dashboard: Doc) => { if (e.detail === 2) { + Doc.AddDocToList(CurrentUserUtils.MySharedDocs, "viewed", dashboard) CurrentUserUtils.ActiveDashboard = dashboard; CurrentUserUtils.ActivePage = "dashboard"; } } getDashboards = () => { - const allDashbaords = DocListCast(CurrentUserUtils.MyDashboards.data); - // TODO: filter the dashboards - // return allDashbaords.filter(...) - return allDashbaords + const allDashboards = DocListCast(CurrentUserUtils.MyDashboards.data); + if (this.selectedDashboardGroup === DashboardGroup.MyDashboards) { + return allDashboards.filter((dashboard) => Doc.GetProto(dashboard).author === Doc.CurrentUserEmail) + } else { + const sharedDashboards = DocListCast(CurrentUserUtils.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); + return sharedDashboards + } + } + + isUnviewedSharedDashboard = (dashboard: Doc): boolean => { + // const sharedDashboards = DocListCast(CurrentUserUtils.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); + return !DocListCast(CurrentUserUtils.MySharedDocs.viewed).includes(dashboard) + } + + getSharedDashboards = () => { + const sharedDashs = DocListCast(CurrentUserUtils.MySharedDocs.data).filter(doc => doc._viewType === CollectionViewType.Docking); + return sharedDashs.filter((dashboard) => !DocListCast(CurrentUserUtils.MySharedDocs.viewed).includes(dashboard)) + } + + createNewDashboard = async (name: string) => { + const batch = UndoManager.StartBatch("new dash"); + await CurrentUserUtils.createNewDashboard(Doc.UserDoc(), undefined, name); + batch.end(); + this.abortCreateNewDashboard(); } + @computed + get namingInterface() { + return <div> + <input className="password-inputs" placeholder="Untitled Dashboard" onChange={e => this.setNewDashboardName((e.target as any).value)} /> + <button className="password-submit" onClick={this.abortCreateNewDashboard}>Cancel</button> + <button className="password-submit" onClick={() => { this.createNewDashboard(this.newDashboardName!) }}>Create</button> + </div>; + } + + _downX: number = 0; + _downY: number = 0; + @action + onContextMenu = (dashboard: Doc, e?: React.MouseEvent, pageX?: number, pageY?: number) => { + // the touch onContextMenu is button 0, the pointer onContextMenu is button 2 + if (e) { + e.preventDefault(); + e.stopPropagation(); + e.persist(); + + if (!navigator.userAgent.includes("Mozilla") && (Math.abs(this._downX - e?.clientX) > 3 || Math.abs(this._downY - e?.clientY) > 3)) { + return; + } + const cm = ContextMenu.Instance; + cm.addItem({ + description: "Share Dashboard", event: async () => { + SharingManager.Instance.open(undefined, dashboard) + }, icon: "edit" + }); + cm.addItem({ + description: "Delete Dashboard", event: async () => { + CurrentUserUtils.removeDashboard(dashboard) + }, icon: "trash" + }); + cm.displayMenu((e?.pageX || pageX || 0) - 15, (e?.pageY || pageY || 0) - 15); + } + } + + render() { - return <div className="dashboard-view"> - <div className="left-menu"> - <div className="text-button" onClick={this.newDashboard}>New</div> - <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.MyDashboards && 'selected'}`}onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards) }>My Dashboards</div> - <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.SharedDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.SharedDashboards) }>Shared Dashboards</div> - </div> - <div className="all-dashboards"> - {this.getDashboards().map((dashboard) => { - const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; - return <div className="dashboard-container" key={dashboard[Id]} onClick={e => this.clickDashboard(e, dashboard)}> - <img src={href ?? "https://media.istockphoto.com/photos/hot-air-balloons-flying-over-the-botan-canyon-in-turkey-picture-id1297349747?b=1&k=20&m=1297349747&s=170667a&w=0&h=oH31fJty_4xWl_JQ4OIQWZKP8C6ji9Mz7L4XmEnbqRU="}></img> - <div className="title"> {StrCast(dashboard.title)} </div> - </div> + return <> + <div className="dashboard-view"> + <div className="left-menu"> + <div className="text-button" onClick={() => { this.setNewDashboardName("") }}>New</div> + <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.MyDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.MyDashboards)}>My Dashboards</div> + <div className={`text-button ${this.selectedDashboardGroup === DashboardGroup.SharedDashboards && 'selected'}`} onClick={() => this.selectDashboardGroup(DashboardGroup.SharedDashboards)}>Shared Dashboards</div> + </div> + <div className="all-dashboards"> + {this.getDashboards().map((dashboard) => { + const href = ImageCast((dashboard.thumb as Doc)?.data)?.url.href; + return <div className="dashboard-container" key={dashboard[Id]} + onContextMenu={(e) => {this.onContextMenu(dashboard, e)}} + onClick={e => this.clickDashboard(e, dashboard)}> + <img src={href ?? "https://media.istockphoto.com/photos/hot-air-balloons-flying-over-the-botan-canyon-in-turkey-picture-id1297349747?b=1&k=20&m=1297349747&s=170667a&w=0&h=oH31fJty_4xWl_JQ4OIQWZKP8C6ji9Mz7L4XmEnbqRU="}></img> + <div className="info"> + <div className="title"> {StrCast(dashboard.title)} </div> + {this.selectedDashboardGroup === DashboardGroup.SharedDashboards && this.isUnviewedSharedDashboard(dashboard) ? + <div>unviewed</div> : <div></div> + } + <div className="more" onPointerDown={e => { + this._downX = e.clientX; + this._downY = e.clientY; + }} + onClick={(e) => {this.onContextMenu(dashboard, e)}} + > + <FontAwesomeIcon color="black" size="lg" icon="bars" /> + </div> + </div> + + </div> - })} + })} + </div> </div> - </div> + <MainViewModal + contents={this.namingInterface} + isDisplayed={this.newDashboardName !== undefined} + interactive={true} + closeOnExternalClick={this.abortCreateNewDashboard} + dialogueBoxStyle={{ width: "500px", height: "300px", background: Cast(Doc.SharingDoc().userColor, "string", null) }} />; + </> + } } + +export function AddToList(MySharedDocs: Doc, arg1: string, dash: any) { + throw new Error("Function not implemented."); +} + diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 791c64630..895687f2c 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -222,16 +222,10 @@ export class MainView extends React.Component { } initAuthenticationRouters = async () => { - // Load the user's active dashboard, or create a new one if initial session after signup const received = CurrentUserUtils.MainDocId; if (received && !this.userDoc) { - reaction(() => CurrentUserUtils.GuestTarget, target => target && CurrentUserUtils.createNewDashboard(Doc.UserDoc()), { fireImmediately: true }); - } else { - PromiseValue(this.userDoc.activeDashboard).then(dash => { - if (dash instanceof Doc) CurrentUserUtils.openDashboard(this.userDoc, dash); - else CurrentUserUtils.createNewDashboard(this.userDoc); - }); - } + reaction(() => CurrentUserUtils.GuestTarget, target => target); + } } @action diff --git a/src/client/views/nodes/button/FontIconBadge.tsx b/src/client/views/nodes/button/FontIconBadge.tsx index df17d603f..3b5aac221 100644 --- a/src/client/views/nodes/button/FontIconBadge.tsx +++ b/src/client/views/nodes/button/FontIconBadge.tsx @@ -6,7 +6,7 @@ import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../../Utils import { DragManager } from "../../../util/DragManager"; import "./FontIconBadge.scss"; -interface FontIconBadgeProps { +interface FontIconBadgeProps { value: string | undefined; } diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss index c5b340514..214b20193 100644 --- a/src/client/views/topbar/TopBar.scss +++ b/src/client/views/topbar/TopBar.scss @@ -2,7 +2,6 @@ .topbar-container { - display: flex; flex-direction: column; font-size: 10px; line-height: 1; @@ -11,6 +10,7 @@ background: $dark-gray; overflow: visible; z-index: 1000; + align-items: center; height: $topbar-height; background-color: $dark-gray; diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 1de0c0b10..13c8e907a 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -38,10 +38,18 @@ export class TopBar extends React.Component { <div style={{ pointerEvents: "all", background: Colors.DARK_GRAY, borderBottom: Borders.STANDARD }} className="topbar-container"> <div className="topbar-inner-container"> <div className="topbar-left"> - {activeDashboard ? <div className="topbar-button-text" onClick={e => { - ContextMenu.Instance.addItem({ description: "Logout", event: () => window.location.assign(Utils.prepend("/logout")), icon: "edit" }); - ContextMenu.Instance.displayMenu(e.clientX +5, e.clientY + 10); - }}>{Doc.CurrentUserEmail}</div> : (null)} + {activeDashboard ? + <> + <div className="topbar-button-text" onClick={e => { + ContextMenu.Instance.addItem({ description: "Logout", event: () => window.location.assign(Utils.prepend("/logout")), icon: "edit" }); + ContextMenu.Instance.displayMenu(e.clientX + 5, e.clientY + 10); + }}>{Doc.CurrentUserEmail}</div> + <div className="topbar-button-icon" onClick={this.navigateToHome}> + <FontAwesomeIcon icon="home" /> + </div> + </> + : (null)} + </div> <div className="topbar-center" > <div className="topbar-title" onClick={() => activeDashboard && SelectionManager.SelectView(DocumentManager.Instance.getDocumentView(activeDashboard)!, false)}> @@ -57,20 +65,18 @@ export class TopBar extends React.Component { }, icon: "edit" }); dashView?.showContextMenu(e.clientX+20, e.clientY+30); }}> - <FontAwesomeIcon color="white" size="lg" icon="bars" /> + <FontAwesomeIcon color="white" size="lg" icon="bars" /> </div> <Tooltip title={<div className="dash-tooltip">Browsing mode for directly navigating to documents</div>} placement="bottom"> - <div className="topbar-icon" style={{ background: MainView.Instance._exploreMode ? Colors.LIGHT_BLUE : undefined }} onClick={action(() => MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> - <FontAwesomeIcon color={MainView.Instance._exploreMode ? "red":"white"} icon="eye" size="lg" /> - </div> + <div className="topbar-icon" style={{ background: MainView.Instance._exploreMode ? Colors.LIGHT_BLUE : undefined }} onClick={action(() => MainView.Instance._exploreMode = !MainView.Instance._exploreMode)}> + <FontAwesomeIcon color={MainView.Instance._exploreMode ? "red" : "white"} icon="eye" size="lg" /> + </div> </Tooltip> </div> <div className="topbar-right" > - <div className="topbar-button-text" onClick={() => {SharingManager.Instance.open(undefined, activeDashboard)}}> - {/* TODO: if this is my dashboard, display share - if this is a shared dashboard, display "view original or view annotated" */} - { CurrentUserUtils.ActiveDashboard && (Doc.GetProto(CurrentUserUtils.ActiveDashboard)?.author === Doc.CurrentUserEmail ? "Share": "view original") } - </div> + {CurrentUserUtils.ActiveDashboard ? <div className="topbar-button-text" onClick={() => { SharingManager.Instance.open(undefined, activeDashboard) }}> + {GetEffectiveAcl(Doc.GetProto(CurrentUserUtils.ActiveDashboard)) === AclAdmin ? "Share" : "view original"} + </div> : (null)} <div className="topbar-button-icon" onClick={() => window.open( "https://brown-dash.github.io/Dash-Documentation/", "_blank")}> <FontAwesomeIcon icon="question-circle" /> @@ -81,7 +87,7 @@ export class TopBar extends React.Component { </div> </div> </div> - + ); } }
\ No newline at end of file |