diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/views/Main.scss | 19 | ||||
-rw-r--r-- | src/client/views/Main.tsx | 57 | ||||
-rw-r--r-- | src/client/views/PresentationView.scss | 71 | ||||
-rw-r--r-- | src/client/views/PresentationView.tsx | 166 | ||||
-rw-r--r-- | src/client/views/TemplateMenu.tsx | 1 | ||||
-rw-r--r-- | src/client/views/collections/CollectionDockingView.tsx | 13 | ||||
-rw-r--r-- | src/client/views/nodes/LinkMenu.tsx | 2 | ||||
-rw-r--r-- | src/new_fields/Doc.ts | 10 |
8 files changed, 155 insertions, 184 deletions
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 2430e8f6c..d63b0147b 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -27,25 +27,6 @@ div { z-index: 9999; } -h1 { - font-size: 50px; - position: fixed; - top: 30px; - left: 50%; - transform: translateX(-50%); - color: $dark-color; - text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff; - z-index: 9999; - font-family: $sans-serif; - font-weight: 700; - -webkit-touch-callout: none; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -ms-user-select: none; - user-select: none; -} - .jsx-parser { width: 100%; pointer-events: none; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 158de31f5..66205f8ca 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,7 +1,7 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, configure, observable, runInAction } from 'mobx'; +import { action, computed, configure, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; @@ -51,6 +51,9 @@ export class Main extends React.Component { } private set mainContainer(doc: Opt<Doc>) { if (doc) { + if (!("presentationView" in doc)) { + doc.presentationView = new Doc(); + } CurrentUserUtils.UserDocument.activeWorkspace = doc; } } @@ -174,32 +177,42 @@ export class Main extends React.Component { } }, 100); } + @action + onResize = (r: any) => { + this.pwidth = r.offset.width; + this.pheight = r.offset.height; + } + getPWidth = () => { + return this.pwidth; + } + getPHeight = () => { + return this.pheight; + } @computed get mainContent() { - let pwidthFunc = () => this.pwidth; - let pheightFunc = () => this.pheight; - let noScaling = () => 1; let mainCont = this.mainContainer; - return <Measure offset onResize={action((r: any) => { this.pwidth = r.offset.width; this.pheight = r.offset.height; })}> + let content = !mainCont ? (null) : + <DocumentView Document={mainCont} + toggleMinimized={emptyFunction} + addDocument={undefined} + removeDocument={undefined} + ScreenToLocalTransform={Transform.Identity} + ContentScaling={returnOne} + PanelWidth={this.getPWidth} + PanelHeight={this.getPHeight} + isTopMost={true} + selectOnLoad={false} + focus={emptyFunction} + parentActive={returnTrue} + whenActiveChanged={emptyFunction} + bringToFront={emptyFunction} + ContainingCollectionView={undefined} />; + const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined; + return <Measure offset onResize={this.onResize}> {({ measureRef }) => <div ref={measureRef} id="mainContent-div"> - {!mainCont ? (null) : - <DocumentView Document={mainCont} - toggleMinimized={emptyFunction} - addDocument={undefined} - removeDocument={undefined} - ScreenToLocalTransform={Transform.Identity} - ContentScaling={noScaling} - PanelWidth={pwidthFunc} - PanelHeight={pheightFunc} - isTopMost={true} - selectOnLoad={false} - focus={emptyFunction} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} - bringToFront={emptyFunction} - ContainingCollectionView={undefined} />} - <PresentationView key="presentation" /> + {content} + {pres ? <PresentationView Document={pres} key="presentation" /> : null} </div> } </Measure>; diff --git a/src/client/views/PresentationView.scss b/src/client/views/PresentationView.scss index 7c5677f0d..fb4a851c4 100644 --- a/src/client/views/PresentationView.scss +++ b/src/client/views/PresentationView.scss @@ -4,15 +4,14 @@ z-index: 1; box-shadow: #AAAAAA .2vw .2vw .4vw; right: 0; - top:0; - bottom:0; + top: 0; + bottom: 0; } .presentationView-item { - width: 220px; - height: 40px; - vertical-align: center; - padding-top: 15px; + padding: 10px; + display: inline-block; + width: 100%; -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; @@ -22,47 +21,59 @@ transition: all .1s; } +.presentationView-listCont { + padding-left: 10px; + padding-right: 10px; +} + .presentationView-item:hover { transition: all .1s; background: #AAAAAA } +.presentationView-selected { + background: gray; +} + .presentationView-heading { - margin-top: 0px; - height: 40px; background: lightseagreen; - padding: 30px; + padding: 10px; + display: inline-block; + width: 100%; } + .presentationView-title { - padding-top: 3px; - padding-bottom: 3px; - font-size: 25px; - float:left; + padding-top: 3px; + padding-bottom: 3px; + font-size: 25px; + display: inline-block; } -.presentation-icon{ + +.presentation-icon { float: right; - display: inline; - width: 10px; - margin-top: 7px; } -.presentationView-header { - padding-top: 1px; - padding-bottom: 1px; + +.presentationView-name { font-size: 15px; - float:left; - } + display: inline-block; +} + +.presentation-button { + margin-right: 12.5%; + margin-left: 12.5%; + width: 25%; +} - .presentation-next{ - float: right; - } - .presentation-back{ - float: left; - } - .presentation-next:hover{ +.presentation-buttons { + padding: 10px; +} + +.presentation-next:hover { transition: all .1s; background: #AAAAAA } -.presentation-back:hover{ + +.presentation-back:hover { transition: all .1s; background: #AAAAAA }
\ No newline at end of file diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx index 3fb24a339..098e725c7 100644 --- a/src/client/views/PresentationView.tsx +++ b/src/client/views/PresentationView.tsx @@ -5,15 +5,20 @@ import "./PresentationView.scss" import "./Main.tsx"; import { DocumentManager } from "../util/DocumentManager"; import { Utils } from "../../Utils"; -import { Doc } from "../../new_fields/Doc"; +import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc"; import { listSpec } from "../../new_fields/Schema"; -import { Cast, NumCast, FieldValue, PromiseValue } from "../../new_fields/Types"; +import { Cast, NumCast, FieldValue, PromiseValue, StrCast } from "../../new_fields/Types"; import { Id } from "../../new_fields/RefField"; import { List } from "../../new_fields/List"; import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; export interface PresViewProps { - //Document: Doc; + Document: Doc; +} + +interface PresListProps extends PresViewProps { + deleteDocument(index: number): void; + gotoDocument(index: number): void; } @@ -21,72 +26,40 @@ export interface PresViewProps { /** * Component that takes in a document prop and a boolean whether it's collapsed or not. */ -class PresentationViewItem extends React.Component<PresViewProps> { - - @observable Document: Doc; - constructor(props: PresViewProps) { - super(props); - this.Document = FieldValue(Cast(FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc))!.presentationView, Doc))!; - } - //look at CollectionFreeformView.focusDocument(d) - @action - openDoc = (doc: Doc) => { - let docView = DocumentManager.Instance.getDocumentView(doc); - if (docView) { - docView.props.focus(docView.props.Document); - } - } - - /** - * Removes a document from the presentation view - **/ - @action - public RemoveDoc(doc: Doc) { - const value = Cast(this.Document.data, listSpec(Doc), []); - let index = -1; - for (let i = 0; i < value.length; i++) { - if (value[i][Id] === doc[Id]) { - index = i; - break; - } - } - if (index !== -1) { - value.splice(index, 1); - } - } +class PresentationViewList extends React.Component<PresListProps> { /** * Renders a single child document. It will just append a list element. * @param document The document to render. */ - renderChild(document: Doc) { + renderChild = (document: Doc, index: number) => { let title = document.title; //to get currently selected presentation doc - let selected = NumCast(this.Document.selectedDoc, 0); + let selected = NumCast(this.props.Document.selectedDoc, 0); - // finally, if it's a normal document, then render it as such. - const children = Cast(this.Document.data, listSpec(Doc)); - const styles: any = {}; - if (children && children[selected] === document) { + let className = "presentationView-item"; + if (selected === index) { //this doc is selected - styles.background = "gray"; + className += " presentationView-selected"; } return ( - <li className="presentationView-item" style={styles} key={Utils.GenerateGuid()}> - <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title}</div> - <div className="presentation-icon" onClick={() => this.RemoveDoc(document)}>X</div> - </li> + <div className={className} key={document[Id] + index} onClick={e => { this.props.gotoDocument(index); e.stopPropagation(); }}> + <strong className="presentationView-name"> + {`${index + 1}. ${title}`} + </strong> + <button className="presentation-icon" onClick={e => { this.props.deleteDocument(index); e.stopPropagation(); }}>X</button> + </div> ); } render() { - const children = Cast(this.Document.data, listSpec(Doc), []); + const children = DocListCast(this.props.Document.data); return ( - <div> - {children.map(value => this.renderChild(value))} + <div className="presentationView-listCont"> + {children.map(this.renderChild)} </div> ); } @@ -100,59 +73,42 @@ export class PresentationView extends React.Component<PresViewProps> { //observable means render is re-called every time variable is changed @observable collapsed: boolean = false; - closePresentation = action(() => this.Document!.width = 0); + closePresentation = action(() => this.props.Document.width = 0); next = () => { - const current = NumCast(this.Document!.selectedDoc); - const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc))); - if (allDocs && current < allDocs.length + 1) { - //can move forwards - this.Document!.selectedDoc = current + 1; - const doc = allDocs[current + 1]; - let docView = DocumentManager.Instance.getDocumentView(doc); - if (docView) { - docView.props.focus(docView.props.Document); - } - } + const current = NumCast(this.props.Document.selectedDoc); + this.gotoDocument(current + 1); } back = () => { - const current = NumCast(this.Document!.selectedDoc); - const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc))); - if (allDocs && current - 1 >= 0) { - //can move forwards - this.Document!.selectedDoc = current - 1; - const doc = allDocs[current - 1]; - let docView = DocumentManager.Instance.getDocumentView(doc); - if (docView) { - docView.props.focus(docView.props.Document); - } + const current = NumCast(this.props.Document.selectedDoc); + this.gotoDocument(current - 1); + } + + @action + public RemoveDoc = (index: number) => { + const value = FieldValue(Cast(this.props.Document.data, listSpec(Doc))); + if (value) { + value.splice(index, 1); } } - private ref = React.createRef<HTMLDivElement>(); + public gotoDocument = async (index: number) => { + const list = FieldValue(Cast(this.props.Document.data, listSpec(Doc))); + if (!list) { + return; + } + if (index < 0 || index >= list.length) { + return; + } + + this.props.Document.selectedDoc = index; + const doc = await list[index]; + DocumentManager.Instance.jumpToDocument(doc); + } - @observable Document?: Doc; //initilize class variables constructor(props: PresViewProps) { super(props); - let self = this; - reaction(() => - CurrentUserUtils.UserDocument.activeWorkspace, - (activeW) => { - if (activeW && activeW instanceof Doc) { - PromiseValue(Cast(activeW.presentationView, Doc)). - then(pv => runInAction(() => { - if (pv) self.Document = pv; - else { - pv = new Doc(); - pv.title = "Presentation Doc"; - activeW.presentationView = pv; - self.Document = pv; - } - })) - } - }, - { fireImmediately: true }); PresentationView.Instance = this; } @@ -162,36 +118,32 @@ export class PresentationView extends React.Component<PresViewProps> { @action public PinDoc(doc: Doc) { //add this new doc to props.Document - const data = Cast(this.Document!.data, listSpec(Doc)); + const data = Cast(this.props.Document.data, listSpec(Doc)); if (data) { data.push(doc); } else { - this.Document!.data = new List([doc]); + this.props.Document.data = new List([doc]); } - this.Document!.width = 300; + this.props.Document.width = 300; } render() { - if (!this.Document) - return (null); - let titleStr = this.Document.Title; - let width = NumCast(this.Document.width); + let titleStr = StrCast(this.props.Document.title); + let width = NumCast(this.props.Document.width); //TODO: next and back should be icons return ( <div className="presentationView-cont" style={{ width: width, overflow: "hidden" }}> <div className="presentationView-heading"> <div className="presentationView-title">{titleStr}</div> - <div className='presentation-icon' onClick={this.closePresentation}>X</div></div> - <div> - <div className="presentation-back" onClick={this.back}>back</div> - <div className="presentation-next" onClick={this.next}>next</div> - + <button className='presentation-icon' onClick={this.closePresentation}>X</button> + </div> + <div className="presentation-buttons"> + <button className="presentation-button" onClick={this.back}>back</button> + <button className="presentation-button" onClick={this.next}>next</button> </div> - <ul> - <PresentationViewItem /> - </ul> + <PresentationViewList Document={this.props.Document} deleteDocument={this.RemoveDoc} gotoDocument={this.gotoDocument} /> </div> ); } diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index e2b3bd07a..22c4edc25 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -38,7 +38,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { constructor(props: TemplateMenuProps) { super(props); - console.log(""); } @action diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a755e0f91..6651a834d 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -1,6 +1,6 @@ import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; -import { action, observable, reaction } from "mobx"; +import { action, observable, reaction, Lambda } from "mobx"; import { observer } from "mobx-react"; import * as ReactDOM from 'react-dom'; import Measure from "react-measure"; @@ -188,12 +188,16 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this._goldenLayout.init(); } } + reactionDisposer?: Lambda; componentDidMount: () => void = () => { + console.log("Docking mount"); if (this._containerRef.current) { - reaction( + this.reactionDisposer = reaction( () => StrCast(this.props.Document.dockingConfig), () => { if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) { + // Because this is in a set timeout, if this component unmounts right after mounting, + // we will leak a GoldenLayout, because we try to destroy it before we ever create it setTimeout(() => this.setupGoldenLayout(), 1); } this._ignoreStateChange = ""; @@ -203,6 +207,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp } } componentWillUnmount: () => void = () => { + console.log("Docking unmount"); try { this._goldenLayout.unbind('itemDropped', this.itemDropped); this._goldenLayout.unbind('tabCreated', this.tabCreated); @@ -214,6 +219,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp if (this._goldenLayout) this._goldenLayout.destroy(); this._goldenLayout = null; window.removeEventListener('resize', this.onResize); + + if (this.reactionDisposer) { + this.reactionDisposer(); + } } @action onResize = (event: any) => { diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx index 5dabfc30d..11117122d 100644 --- a/src/client/views/nodes/LinkMenu.tsx +++ b/src/client/views/nodes/LinkMenu.tsx @@ -36,7 +36,7 @@ export class LinkMenu extends React.Component<Props> { if (this._editingLink === undefined) { return ( <div id="linkMenu-container"> - <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> + {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */} <div id="linkMenu-list"> {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")} {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")} diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 4c837fcbd..89901490d 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -29,15 +29,21 @@ export const SelfProxy = Symbol("SelfProxy"); export const WidthSym = Symbol("Width"); export const HeightSym = Symbol("Height"); +/** + * Cast any field to either a List of Docs or undefined if the given field isn't a List of Docs. + * If a default value is given, that will be returned instead of undefined. + * If a default value is given, the returned value should not be modified as it might be a temporary value. + * If no default value is given, and the returned value is not undefined, it can be safely modified. + */ export function DocListCastAsync(field: FieldResult): Promise<Doc[] | undefined>; export function DocListCastAsync(field: FieldResult, defaultValue: Doc[]): Promise<Doc[]>; export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { const list = Cast(field, listSpec(Doc)); - return list ? Promise.all(list) : Promise.resolve(defaultValue); + return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); } export function DocListCast(field: FieldResult) { - return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc).map(d => d as Doc) + return Cast(field, listSpec(Doc), []).filter(d => d && d instanceof Doc).map(d => d as Doc); } @Deserializable("doc").withFields(["id"]) |