aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/views/Main.scss19
-rw-r--r--src/client/views/Main.tsx57
-rw-r--r--src/client/views/PresentationView.scss71
-rw-r--r--src/client/views/PresentationView.tsx166
-rw-r--r--src/client/views/TemplateMenu.tsx1
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx13
-rw-r--r--src/client/views/nodes/LinkMenu.tsx2
-rw-r--r--src/new_fields/Doc.ts10
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"])