diff options
Diffstat (limited to 'src/client/views')
20 files changed, 298 insertions, 192 deletions
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index e2f98de1e..cffcd0f17 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,10 +1,10 @@ import React = require("react"); -import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem"; -import { observable, action, computed, runInAction, IReactionDisposer, reaction } from "mobx"; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, IReactionDisposer, observable } from "mobx"; import { observer } from "mobx-react"; import "./ContextMenu.scss"; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import Measure from "react-measure"; +import { ContextMenuItem, ContextMenuProps, OriginalMenuProps } from "./ContextMenuItem"; +import { Utils } from "../../Utils"; @observer export class ContextMenu extends React.Component { @@ -74,7 +74,6 @@ export class ContextMenu extends React.Component { componentDidMount = () => { document.addEventListener("pointerdown", this.onPointerDown); document.addEventListener("pointerup", this.onPointerUp); - } @action @@ -116,10 +115,6 @@ export class ContextMenu extends React.Component { this._defaultItem = item; } - getItems() { - return this._items; - } - static readonly buffer = 20; get pageX() { const x = this._pageX; @@ -199,37 +194,25 @@ export class ContextMenu extends React.Component { return eles; }; - return flattenItems(this._items, name => [name]); + return flattenItems(this._items.slice(), name => [name]); } @computed get flatItems(): OriginalMenuProps[] { return this.filteredItems.filter(item => !Array.isArray(item)) as OriginalMenuProps[]; } - @computed get filteredViews() { - const createGroupHeader = (contents: any) => { - return ( - <div className="contextMenu-group"> - <div className="contextMenu-description">{contents}</div> - </div> - ); - }; - const createItem = (item: ContextMenuProps, selected: boolean) => <ContextMenuItem {...item} key={item.description} closeMenu={this.closeMenu} selected={selected} />; - let itemIndex = 0; - return this.filteredItems.map(value => { - if (Array.isArray(value)) { - return createGroupHeader(value.join(" -> ")); - } else { - return createItem(value, itemIndex++ === this.selectedIndex); - } - }); - } - @computed get menuItems() { if (!this._searchString) { return this._items.map((item, ind) => <ContextMenuItem {...item} noexpand={this.itemsNeedSearch ? true : (item as any).noexpand} key={ind + item.description} closeMenu={this.closeMenu} />); } - return this.filteredViews; + return this.filteredItems.map((value, index) => + Array.isArray(value) ? + <div className="contextMenu-group"> + <div className="contextMenu-description">{value.join(" -> ")}</div> + </div> + : + <ContextMenuItem {...value} key={index+value.description} closeMenu={this.closeMenu} selected={index === this.selectedIndex} /> + ); } @computed get itemsNeedSearch() { @@ -237,14 +220,8 @@ export class ContextMenu extends React.Component { } render() { - if (!this._display) { - return null; - } - const style = this._yRelativeToTop ? { left: this.pageX, top: this.pageY } : - { left: this.pageX, bottom: this.pageY }; - - const contents = ( - <> + return !this._display ? (null) : + <div className="contextMenu-cont" style={{left: this.pageX, ...(this._yRelativeToTop ? { top: this.pageY } : { bottom: this.pageY })}}> {!this.itemsNeedSearch ? (null) : <span className={"search-icon"}> <span className="icon-background"> @@ -253,17 +230,7 @@ export class ContextMenu extends React.Component { <input className="contextMenu-item contextMenu-description search" type="text" placeholder="Filter Menu..." value={this._searchString} onKeyDown={this.onKeyDown} onChange={this.onChange} autoFocus /> </span>} {this.menuItems} - </> - ); - return ( - <Measure offset onResize={action((r: any) => { this._width = r.offset.width; this._height = r.offset.height; })}> - {({ measureRef }) => ( - <div className="contextMenu-cont" style={style} ref={measureRef}> - {contents} - </div> - )} - </Measure> - ); + </div>; } @action diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index 25d00f701..30073e21f 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { observable, action } from "mobx"; +import { observable, action, runInAction } from "mobx"; import { observer } from "mobx-react"; import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -28,12 +28,11 @@ export type ContextMenuProps = OriginalMenuProps | SubmenuProps; export class ContextMenuItem extends React.Component<ContextMenuProps & { selected?: boolean }> { @observable private _items: Array<ContextMenuProps> = []; @observable private overItem = false; - @observable private subRef = React.createRef<HTMLDivElement>(); - constructor(props: ContextMenuProps | SubmenuProps) { - super(props); - if ((this.props as SubmenuProps).subitems) { - (this.props as SubmenuProps).subitems?.forEach(i => this._items.push(i)); + componentDidMount() { + this._items.length = 0; + if ((this.props as SubmenuProps)?.subitems) { + (this.props as SubmenuProps).subitems?.forEach(i => runInAction(() => this._items.push(i))); } } @@ -78,9 +77,6 @@ export class ContextMenuItem extends React.Component<ContextMenuProps & { select } render() { - - - if ("event" in this.props) { return ( <div className={"contextMenu-item" + (this.props.selected ? " contextMenu-itemSelected" : "")} onPointerDown={this.handleEvent}> 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 a126218c4..868d63a90 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'; @@ -6,64 +6,159 @@ import { Doc, DocListCast } from "../../fields/Doc"; import { Id } from "../../fields/FieldSymbols"; import { Cast, ImageCast, StrCast } from "../../fields/Types"; import { CurrentUserUtils } from "../util/CurrentUserUtils"; -import { UndoManager } from "../util/UndoManager"; +import { undoBatch, 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(); - 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)) + } + + @undoBatch + createNewDashboard = async (name: string) => { + CurrentUserUtils.createNewDashboard(undefined, name); + 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/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4247501bb..669718e81 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -311,7 +311,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P move[1] = thisPt.y - this._snapY; this._snapX = thisPt.x; this._snapY = thisPt.y; - let dragBottom = false, dragRight = false, dragBotRight = false; + let dragBottom = false, dragRight = false, dragBotRight = false, dragTop = false; let dX = 0, dY = 0, dW = 0, dH = 0; switch (this._resizeHdlId.split(" ")[0]) { case "": break; @@ -329,7 +329,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P case "documentDecorations-topResizer": dY = -1; dH = -move[1]; - dragBottom = true; + dragTop = true; break; case "documentDecorations-bottomLeftResizer": dX = -1; @@ -361,27 +361,28 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P const doc = Document(docView.rootDoc); const nwidth = docView.nativeWidth; const nheight = docView.nativeHeight; - const docheight = doc._height || 0; - const docwidth = doc._width || 0; + let docheight = doc._height || 0; + let docwidth = doc._width || 0; const width = docwidth; let height = (docheight || (nheight / nwidth * width)); height = !height || isNaN(height) ? 20 : height; const scale = docView.props.ScreenToLocalTransform().Scale; - const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable; + const modifyNativeDim = (e.ctrlKey || doc.forceReflow) && doc.nativeDimModifiable && ((!dragBottom && !dragTop) || e.ctrlKey || doc.nativeHeightUnfrozen); if (nwidth && nheight) { - if (nwidth / nheight !== width / height && !dragBottom) { + if (nwidth / nheight !== width / height && !dragBottom && !dragTop) { height = nheight / nwidth * width; } - if (modifyNativeDim && !dragBottom) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction + if (modifyNativeDim && !dragBottom && !dragTop) { // ctrl key enables modification of the nativeWidth or nativeHeight durin the interaction if (Math.abs(dW) > Math.abs(dH)) dH = dW * nheight / nwidth; else dW = dH * nwidth / nheight; } } let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); - const fixedAspect = (nwidth && nheight && !doc._fitWidth); + const fixedAspect = (nwidth && nheight && (!doc._fitWidth || e.ctrlKey || doc.nativeHeightUnfrozen)); + console.log(fixedAspect); if (fixedAspect) { - if ((Math.abs(dW) > Math.abs(dH) && (!dragBottom || !modifyNativeDim)) || dragRight) { + if ((Math.abs(dW) > Math.abs(dH) && ((!dragBottom && !dragTop)|| !modifyNativeDim)) || dragRight) { if (dragRight && modifyNativeDim) { doc._nativeWidth = actualdW / (doc._width || 1) * Doc.NativeWidth(doc); } else { @@ -394,7 +395,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P doc._width = actualdW; } else { - if (dragBottom && (modifyNativeDim || + if ((dragBottom|| dragTop) && (modifyNativeDim || (docView.layoutDoc.nativeHeightUnfrozen && docView.layoutDoc._fitWidth))) { // frozen web pages, PDFs, and some RTFS have frozen nativewidth/height. But they are marked to allow their nativeHeight to be explicitly modified with fitWidth and vertical resizing. (ie, with fitWidth they can't grow horizontally to match a vertical resize so it makes more sense to change their nativeheight even if the ctrl key isn't used) doc._nativeHeight = actualdH / (doc._height || 1) * Doc.NativeHeight(doc); doc._autoHeight = false; @@ -417,7 +418,7 @@ export class DocumentDecorations extends React.Component<{ PanelWidth: number, P dH && (doc._autoHeight = false); } doc.x = (doc.x || 0) + dX * (actualdW - docwidth); - doc.y = (doc.y || 0) + dY * (actualdH - docheight); + doc.y = (doc.y || 0) + (dragBottom ? 0: dY * (actualdH - docheight)); doc._lastModified = new DateField(); } const val = this._dragHeights.get(docView.layoutDoc); diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 49c2dcf34..17841c055 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -10,7 +10,6 @@ import { CurrentUserUtils } from "../util/CurrentUserUtils"; import { LinkManager } from "../util/LinkManager"; import { RecordingApi } from "../util/RecordingApi"; import { CollectionView } from "./collections/CollectionView"; -import { DashboardView } from './DashboardView'; import { MainView } from "./MainView"; AssignAllExtensions(); @@ -24,6 +23,7 @@ AssignAllExtensions(); await CurrentUserUtils.loadUserDocument(info.id); } else { await Docs.Prototypes.initialize(); + new LinkManager(); } document.getElementById('root')!.addEventListener('wheel', event => { if (event.ctrlKey) { @@ -37,7 +37,6 @@ AssignAllExtensions(); d.setTime(d.getTime() + (100 * 24 * 60 * 60 * 1000)); const expires = "expires=" + d.toUTCString(); document.cookie = `loadtime=${loading};${expires};path=/`; - new LinkManager(); new RecordingApi; ReactDOM.render(<MainView />, document.getElementById('root')); })();
\ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 6ee8065f5..4940c5f9d 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -8,9 +8,8 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; -import { PrefetchProxy } from '../../fields/Proxy'; import { ScriptField } from '../../fields/ScriptField'; -import { DocCast, PromiseValue, StrCast } from '../../fields/Types'; +import { PromiseValue, StrCast } from '../../fields/Types'; import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick, Utils } from '../../Utils'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { DocServer } from '../DocServer'; @@ -32,8 +31,7 @@ import { CollectionDockingView } from './collections/CollectionDockingView'; import { MarqueeOptionsMenu } from './collections/collectionFreeForm/MarqueeOptionsMenu'; import { CollectionLinearView } from './collections/collectionLinear'; import { CollectionMenu } from './collections/CollectionMenu'; -import { TreeViewType } from './collections/CollectionTreeView'; -import { CollectionView, CollectionViewType } from './collections/CollectionView'; +import { CollectionViewType } from './collections/CollectionView'; import "./collections/TreeView.scss"; import { ComponentDecorations } from './ComponentDecorations'; import { ContextMenu } from './ContextMenu'; @@ -49,7 +47,6 @@ import { LightboxView } from './LightboxView'; import { LinkMenu } from './linking/LinkMenu'; import "./MainView.scss"; import { AudioBox } from './nodes/AudioBox'; -import { ButtonType } from './nodes/button/FontIconBox'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { DocumentView } from './nodes/DocumentView'; import { DashFieldViewMenu } from './nodes/formattedText/DashFieldView'; @@ -222,16 +219,16 @@ 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(), { fireImmediately: true }); - } else { - PromiseValue(this.userDoc.activeDashboard).then(dash => { - if (dash instanceof Doc) CurrentUserUtils.openDashboard(dash); - else CurrentUserUtils.createNewDashboard(); - }); - } + } + // else { + // PromiseValue(this.userDoc.activeDashboard).then(dash => { + // if (dash instanceof Doc) CurrentUserUtils.openDashboard(dash); + // else CurrentUserUtils.createNewDashboard(); + // }); + // } } @action diff --git a/src/client/views/OverlayView.scss b/src/client/views/OverlayView.scss index 302e7a5e3..033cdf1f7 100644 --- a/src/client/views/OverlayView.scss +++ b/src/client/views/OverlayView.scss @@ -15,6 +15,7 @@ flex-direction: column; top: 0; left: 0; + pointer-events: all; } .overlayWindow-outerDiv, diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 07fcd6a7d..0830b6fdf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -4,12 +4,12 @@ import { action, IReactionDisposer, observable, reaction, runInAction } from "mo import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import * as GoldenLayout from "../../../client/goldenLayout"; -import { DataSym, Doc, DocListCast, DocListCastAsync, Opt } from "../../../fields/Doc"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { listSpec } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../fields/Types"; +import { ImageField } from '../../../fields/URLField'; import { inheritParentAcls } from '../../../fields/util'; import { emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from "../../DocServer"; @@ -19,14 +19,15 @@ import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DragManager } from "../../util/DragManager"; import { InteractionUtils } from '../../util/InteractionUtils'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; +import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { LightboxView } from '../LightboxView'; import "./CollectionDockingView.scss"; +import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; import { CollectionViewType } from './CollectionView'; import { TabDocView } from './TabDocView'; import React = require("react"); -import { SelectionManager } from '../../util/SelectionManager'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -373,13 +374,34 @@ export class CollectionDockingView extends CollectionSubView() { } } - public static async Copy(doc: Doc, clone = false) { + public CaptureThumbnail() { + const content = this.props.DocumentView?.()?.ContentDiv; + if (content) { + const _width = Number(getComputedStyle(content).width.replace("px","")); + const _height = Number(getComputedStyle(content).height.replace("px","")); + return CollectionFreeFormView.UpdateIcon( + this.layoutDoc[Id] + "-icon" + (new Date()).getTime(), + content, + _width, _height, + _width, _height, 0, 1, true, this.layoutDoc[Id] + "-icon", + (iconFile, _nativeWidth, _nativeHeight) => { + const img = Docs.Create.ImageDocument(new ImageField(iconFile), { title: this.rootDoc.title+"-icon", _width, _height, _nativeWidth, _nativeHeight}); + const proto = Cast(img.proto, Doc, null)!; + proto["data-nativeWidth"] = _width; + proto["data-nativeHeight"] = _height; + this.dataDoc.thumb = img; + }); + } + + } + public static async TakeSnapshot(doc: Doc|undefined, clone = false) { + if (!doc) return undefined; let json = StrCast(doc.dockingConfig); if (clone) { - const cloned = (await Doc.MakeClone(doc)); + const cloned = await Doc.MakeClone(doc); Array.from(cloned.map.entries()).map(entry => json = json.replace(entry[0], entry[1][Id])); Doc.GetProto(cloned.clone).dockingConfig = json; - return cloned.clone; + return CurrentUserUtils.openDashboard(cloned.clone); } const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); const origtabids = matches?.map(m => m.replace("\"documentId\":\"", "").replace("\"", "")) || []; @@ -395,7 +417,8 @@ export class CollectionDockingView extends CollectionSubView() { json = json.replace(origtab[Id], newtab[Id]); return newtab; }); - return Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); + const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); + return CurrentUserUtils.openDashboard(await copy); } @action diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 77ed83278..b00017453 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -458,7 +458,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack if (anchorStartTime === undefined) return rootDoc; const anchor = docAnchor ?? Docs.Create.LabelDocument({ title: ComputedField.MakeFunction( - `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])` + `self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])` ) as any, _minFontSize: 12, _maxFontSize: 24, @@ -725,12 +725,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack /> {/* {this.renderDictation} */} - { /* check time to prevent weird div overflow */ this._hoverTime < this.clipDuration && <div + <div className="collectionStackedTimeline-hover" style={{ left: `${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%`, }} - />} + /> <div className="collectionStackedTimeline-current" @@ -775,7 +775,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack </div> </div> <div className="timeline-hoverUI" style={{ left: `calc(${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%` }}> - <div className="hoverTime">{formatTime(this._hoverTime)}</div> + <div className="hoverTime">{formatTime(this._hoverTime - this.clipStart)}</div> {this._thumbnail && <img className="videoBox-thumbnail" src={this._thumbnail} />} </div> </div >); @@ -833,7 +833,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> return `#${formatTime(start)}-${formatTime(end)}`; } - componentDidMount() { this._disposer = reaction( () => this.props.currentTimecode(), @@ -924,7 +923,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // context menu contextMenuItems = () => { - const resetTitle = { script: ScriptField.MakeFunction(`self.title = "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"])`)!, icon: "folder-plus", label: "Reset Title" }; + const resetTitle = { script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${this.props.startTag}"])`)!, icon: "folder-plus", label: "Reset Title" }; return [resetTitle]; } diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 17fdba764..03450b798 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -274,7 +274,7 @@ export function CollectionSubView<X>(moreProps?: X) { if (docid) { // prosemirror text containing link to dash document DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + if (options.x || options.y) { f.x = options.x as number; f.y = options.y as number; } // should be in CollectionFreeFormView (f instanceof Doc) && addDocument(f); } }); @@ -311,7 +311,7 @@ export function CollectionSubView<X>(moreProps?: X) { const docid = text.replace(Doc.globalServerPath(), "").split("?")[0]; DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { - if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + if (options.x || options.y) { f.x = options.x as number; f.y = options.y as number; } // should be in CollectionFreeFormView (f instanceof Doc) && addDocument(f); } }); @@ -445,7 +445,7 @@ export function CollectionSubView<X>(moreProps?: X) { if (completed) completed(set); else { if (isFreeformView && generatedDocuments.length > 1) { - addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!,); + addDocument(DocUtils.pileup(generatedDocuments, options.x as number, options.y as number)!,); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 542b1fce1..b9da4faa4 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -153,6 +153,21 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return this.getContainerTransform().translate(- this.cachedCenteringShiftX, - this.cachedCenteringShiftY).transform(this.cachedGetLocalTransform); } + changeKeyFrame = (back=false) => { + const currentFrame = Cast(this.Document._currentFrame, "number", null); + if (currentFrame === undefined) { + this.Document._currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0); + } + if (back) { + CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice()); + this.Document._currentFrame = Math.max(0, (currentFrame || 0) - 1); + } else { + CollectionFreeFormDocumentView.updateKeyframe(this.childDocs, currentFrame || 0); + this.Document._currentFrame = Math.max(0, (currentFrame || 0) + 1); + this.Document.lastFrame = Math.max(NumCast(this.Document._currentFrame), NumCast(this.Document.lastFrame)); + } + } @action setKeyFrameEditing = (set: boolean) => this._keyframeEditing = set; getKeyFrameEditing = () => this._keyframeEditing; onBrowseClickHandler = () => this.props.onBrowseClick?.() || ScriptCast(this.layoutDoc.onBrowseClick); @@ -1660,8 +1675,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const mores = ContextMenu.Instance.findByDescription("More..."); const moreItems = mores && "subitems" in mores ? mores.subitems : []; if (!Doc.noviceMode) { + e.persist(); moreItems.push({ description: "Export collection", icon: "download", event: async () => Doc.Zip(this.props.Document) }); - moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) }); + moreItems.push({ description: "Import exported collection", icon: "upload", event: ({ x, y }) => this.importDocument(e.clientX, e.clientY) }); } !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" }); } @@ -1670,28 +1686,13 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const input = document.createElement("input"); input.type = "file"; input.accept = ".zip"; - input.onchange = async _e => { - const upload = Utils.prepend("/uploadDoc"); - const formData = new FormData(); - const file = input.files && input.files[0]; - if (file) { - 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) { - const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); - doc.x = xx, doc.y = yy; - this.props.addDocument?.(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. - } - } - } + input.onchange = _e => { + input.files && Doc.importDocument(input.files[0]).then(doc => { + if (doc instanceof Doc) { + const [xx, yy] = this.getTransform().transformPoint(x, y); + doc.x = xx, doc.y = yy; + this.props.addDocument?.(doc);} + }); }; input.click(); } @@ -2100,4 +2101,6 @@ export function CollectionBrowseClick(dv: DocumentView, clientX: number, clientY }); Doc.linkFollowHighlight(dv?.props.Document, false); } -ScriptingGlobals.add(CollectionBrowseClick);
\ No newline at end of file +ScriptingGlobals.add(CollectionBrowseClick); +ScriptingGlobals.add(function nextKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(); }); +ScriptingGlobals.add(function prevKeyFrame(readOnly: boolean) { !readOnly && (SelectionManager.Views()[0].ComponentView as CollectionFreeFormView)?.changeKeyFrame(true); });
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 081a1a924..ab8a34d5a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -156,7 +156,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque })); } else if (e.key === "s" && e.ctrlKey) { e.preventDefault(); - const slide = Doc.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!; + const slide = DocUtils.copyDragFactory(DocCast(Doc.UserDoc().emptySlide))!; slide.x = x; slide.y = y; FormattedTextBox.SelectOnLoad = slide[Id]; @@ -517,7 +517,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque d.y = NumCast(d.y) - this.Bounds.top; return d; }); - const summary = Docs.Create.TextDocument("", { _backgroundColor: "#e2ad32", x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: "overview" }); + const summary = Docs.Create.TextDocument("", { backgroundColor: "#e2ad32", x: this.Bounds.left, y: this.Bounds.top, isPushpin: true, _width: 200, _height: 200, _fitContentsToBox: true, _showSidebar: true, title: "overview" }); const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: "transparent" }); DocUtils.MakeLink({ doc: summary }, { doc: portal }, "summary of:summarized by", ""); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 94cef2906..c42c2306a 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -63,7 +63,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp _recorder: any; // MediaRecorder _recordStart = 0; _pauseStart = 0; // time when recording is paused (used to keep track of recording timecodes) - _pauseEnd = 0; _pausedTime = 0; _stream: MediaStream | undefined; // passed to MediaRecorder, records device input audio _play: any = null; // timeout for playback @@ -81,7 +80,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp @computed get miniPlayer() { return this.props.PanelHeight() < 50; } // used to collapse timeline when node is shrunk @computed get links() { return DocListCast(this.dataDoc.links); } - @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct recording time @computed get mediaState() { return this.dataDoc.mediaState as media_state; } @computed get path() { // returns the path of the audio file const path = Cast(this.props.Document[this.fieldKey], AudioField, null)?.url.href || ""; @@ -97,9 +95,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp this._dropDisposer?.(); Object.values(this._disposers).forEach((disposer) => disposer?.()); - // removes doc from active recordings if recording when closed - const ind = DocUtils.ActiveRecordings.indexOf(this); - ind !== -1 && DocUtils.ActiveRecordings.splice(ind, 1); + this.mediaState === media_state.Recording && this.stopRecording(); } @action @@ -220,10 +216,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp updateRecordTime = () => { if (this.mediaState === media_state.Recording) { setTimeout(this.updateRecordTime, 30); - if (this._paused) { - this._pausedTime += (new Date().getTime() - this._recordStart) / 1000; - } else { - this.layoutDoc._currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; + if (!this._paused) { + this.layoutDoc._currentTimecode = (new Date().getTime() - this._recordStart - this._pausedTime) / 1000; } } } @@ -253,7 +247,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp if (this._recorder) { this._recorder.stop(); this._recorder = undefined; - this.dataDoc[this.fieldKey + "-duration"] = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000; + const now = new Date().getTime(); + this._paused && (this._pausedTime += now - this._pauseStart); + this.dataDoc[this.fieldKey + "-duration"] = (now - this._recordStart - this._pausedTime) / 1000; this.mediaState = media_state.Paused; this._stream?.getAudioTracks()[0].stop(); const ind = DocUtils.ActiveRecordings.indexOf(this); @@ -379,8 +375,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp // continue the recording recordPlay = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => { - this._pauseEnd = new Date().getTime(); this._paused = false; + this._pausedTime += new Date().getTime() - this._pauseStart; this._recorder.resume(); }), false); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5d2adc321..2ea976813 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1243,15 +1243,16 @@ export class DocumentView extends React.Component<DocumentViewProps> { @computed get panelWidth() { return this.effectiveNativeWidth ? this.effectiveNativeWidth * this.nativeScaling : this.props.PanelWidth(); } @computed get panelHeight() { - if (this.effectiveNativeHeight) { - return Math.min(this.props.PanelHeight(), Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight), this.effectiveNativeHeight) * this.nativeScaling); + if (this.effectiveNativeHeight && !this.layoutDoc.nativeHeightUnfrozen) { + const scrollHeight = this.fitWidth ? Math.max(this.ComponentView?.getScrollHeight?.() ?? NumCast(this.layoutDoc.scrollHeight)) : 0; + return Math.min(this.props.PanelHeight(), Math.max(scrollHeight, this.effectiveNativeHeight) * this.nativeScaling); } return this.props.PanelHeight(); } @computed get Xshift() { return this.effectiveNativeWidth ? (this.props.PanelWidth() - this.effectiveNativeWidth * this.nativeScaling) / 2 : 0; } - @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 ? (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2 : 0; } + @computed get Yshift() { return this.effectiveNativeWidth && this.effectiveNativeHeight && Math.abs(this.Xshift) < 0.001 && !this.layoutDoc.nativeHeightUnfrozen ? Math.max(0, (this.props.PanelHeight() - this.effectiveNativeHeight * this.nativeScaling) / 2) : 0; } @computed get centeringX() { return this.props.dontCenter?.includes("x") ? 0 : this.Xshift; } - @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } + @computed get centeringY() { return this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); @@ -1343,7 +1344,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { transition: this.props.dataTransition, position: this.props.Document.isInkMask ? "absolute" : undefined, transform: isButton ? undefined : `translate(${this.centeringX}px, ${this.centeringY}px)`, - margin: this.fitWidth ? "auto" : undefined, width: isButton || isPresTreeElement ? "100%" : xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`, height: isButton || this.props.forceAutoHeight ? undefined : yshift() ?? (this.fitWidth ? `${this.panelHeight}px` : `${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`), diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index da29c291f..aa51714da 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -94,10 +94,10 @@ background-color: $dark-gray; color: white; border-radius: 100px; - z-index: 2001; height: 40px; padding: 0 10px 0 7px; transition: opacity 0.3s; + z-index: 100001; .timecode-controls { display: flex; 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/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 600730d87..8e1698eba 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -29,7 +29,7 @@ import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; import { DictationManager } from '../../../util/DictationManager'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager } from "../../../util/DragManager"; -import { makeTemplate } from '../../../util/DropConverter'; +import { MakeTemplate } from '../../../util/DropConverter'; import { LinkManager } from '../../../util/LinkManager'; import { SelectionManager } from "../../../util/SelectionManager"; import { SnappingManager } from '../../../util/SnappingManager'; @@ -682,7 +682,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (!this.layoutDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); this.rootDoc.title = "text"; - this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); + MakeTemplate(this.rootDoc, true, title); } else if (!this.rootDoc.isTemplateDoc) { const title = StrCast(this.rootDoc.title); this.rootDoc.title = "text"; @@ -691,7 +691,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.rootDoc.isTemplateDoc = false; this.rootDoc.isTemplateForField = ""; this.rootDoc.layoutKey = "layout"; - this.rootDoc.isTemplateDoc = makeTemplate(this.rootDoc, true, title); + MakeTemplate(this.rootDoc, true, title); setTimeout(() => { this.rootDoc._autoHeight = this.layoutDoc._autoHeight; // autoHeight, width and height this.rootDoc._width = this.layoutDoc._width || 300; // are stored on the template, since we're getting rid of the old template @@ -854,6 +854,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this._didScroll ? this._focusSpeed : undefined; // if we actually scrolled, then return some focusSpeed } + getScrollHeight = () => this.scrollHeight; // if the scroll height has changed and we're in autoHeight mode, then we need to update the textHeight component of the doc. // Since we also monitor all component height changes, this will update the document's height. resetNativeHeight = (scrollHeight: number) => { @@ -862,6 +863,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (nh) this.layoutDoc._nativeHeight = scrollHeight; } + @computed get contentScaling() { return Doc.NativeAspect(this.rootDoc, this.dataDoc, false) ? this.props.scaling?.() || 1 : 1;} componentDidMount() { !this.props.dontSelectOnLoad && this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. this._cachedLinks = DocListCast(this.Document.links); @@ -875,7 +877,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._disposers.componentHeights = reaction( // set the document height when one of the component heights changes and autoHeight is on () => ({ sidebarHeight: this.sidebarHeight, textHeight: this.textHeight, autoHeight: this.autoHeight, marginsHeight: this.autoHeightMargins }), ({ sidebarHeight, textHeight, autoHeight, marginsHeight }) => { - autoHeight && this.props.setHeight?.((this.props.scaling?.() || 1) * (marginsHeight + Math.max(sidebarHeight, textHeight))); + autoHeight && this.props.setHeight?.(this.contentScaling * (marginsHeight + Math.max(sidebarHeight, textHeight))); }, { fireImmediately: true }); this._disposers.links = reaction(() => DocListCast(this.dataDoc.links), // if a link is deleted, then remove all hyperlinks that reference it from the text's marks newLinks => { diff --git a/src/client/views/topbar/TopBar.scss b/src/client/views/topbar/TopBar.scss index c5b340514..d415e9367 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,8 +10,10 @@ background: $dark-gray; overflow: visible; z-index: 1000; + align-items: center; height: $topbar-height; background-color: $dark-gray; + cursor: default; .topbar-inner-container { display: flex; @@ -52,6 +53,7 @@ &:hover { background-color: darken($color: $light-gray, $amount: 20); + font-weight: 500; } } diff --git a/src/client/views/topbar/TopBar.tsx b/src/client/views/topbar/TopBar.tsx index 6a4deca38..b447bdc19 100644 --- a/src/client/views/topbar/TopBar.tsx +++ b/src/client/views/topbar/TopBar.tsx @@ -14,6 +14,7 @@ import { SelectionManager } from "../../util/SelectionManager"; import { SettingsManager } from "../../util/SettingsManager"; import { SharingManager } from "../../util/SharingManager"; import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; import { ContextMenu } from "../ContextMenu"; import { Borders, Colors } from "../global/globalEnums"; import { MainView } from "../MainView"; @@ -26,7 +27,7 @@ import "./TopBar.scss"; @observer export class TopBar extends React.Component { navigateToHome = () => { - CurrentUserUtils.CaptureDashboardThumbnail()?.then(() => { + CollectionDockingView.Instance.CaptureThumbnail()?.then(() => { CurrentUserUtils.ActivePage = "home"; CurrentUserUtils.closeActiveDashboard(); // bcz: if we do this, we need some other way to keep track, for user convenience, of the last dashboard in use }); @@ -38,10 +39,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-icon" 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 +66,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-button-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-icon" 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 +88,7 @@ export class TopBar extends React.Component { </div> </div> </div> - + ); } }
\ No newline at end of file |
