From c6ecd219f61811c3af9776ad8c08c2232db20dfd Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Mon, 15 Apr 2019 20:04:01 -0400
Subject: template menu works when switching between diff docs

---
 src/client/views/TemplateMenu.tsx | 88 +++++++++++++++++++++++++++++++++++++++
 1 file changed, 88 insertions(+)
 create mode 100644 src/client/views/TemplateMenu.tsx

(limited to 'src/client/views/TemplateMenu.tsx')

diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
new file mode 100644
index 000000000..7f670aec0
--- /dev/null
+++ b/src/client/views/TemplateMenu.tsx
@@ -0,0 +1,88 @@
+import { observable, computed, action } from "mobx";
+import React = require("react");
+import { SelectionManager } from "../util/SelectionManager";
+import { observer } from "mobx-react";
+import './DocumentDecorations.scss';
+import { Templates, Template } from "./Templates";
+import { DocumentView } from "./nodes/DocumentView";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+@observer
+class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
+    render() {
+        if (this.props.template) {
+            return (
+                <li>
+                    <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event, this.props.template)} />
+                    {this.props.template.Name}
+                </li>
+            );
+        } else {
+            return (null);
+        }
+    }
+}
+
+export interface TemplateMenuProps {
+    doc: DocumentView;
+    templates: Map<Template, boolean>;
+}
+
+@observer
+export class TemplateMenu extends React.Component<TemplateMenuProps> {
+
+    @observable private _hidden: boolean = true;
+    @observable private _showBase: boolean = true;
+    @observable private _templates: Map<Template, boolean> = this.props.templates;
+
+
+    @action
+    toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
+        if (event.target.checked) {
+            this.props.doc.addTemplate(template);
+            this._templates.set(template, true);
+        } else {
+            this.props.doc.removeTemplate(template);
+            this._templates.set(template, false);
+        }
+
+        // const docs = view.props.ContainingCollectionView;
+        // const docs = view.props.Document.GetList<Document>(view.props.fieldKey, []);
+
+    }
+
+    @action
+    componentWillReceiveProps(nextProps: TemplateMenuProps) {
+        this._templates = nextProps.templates;
+    }
+
+    @action
+    toggleBase = (event: React.ChangeEvent<HTMLInputElement>): void => {
+        this.props.doc.toggleBase(event.target.checked);
+        this._showBase = !this._showBase;
+    }
+
+    @action
+    toggleTemplateActivity = (): void => {
+        this._hidden = !this._hidden;
+    }
+
+    render() {
+        let templateMenu: Array<JSX.Element> = [];
+        this._templates.forEach((checked, template) => {
+            templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />);
+        });
+
+        return (
+            <div className="templating-menu" >
+                <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>T</div>
+                <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+                    <li><input type="checkbox" onChange={(event) => this.toggleBase(event)} defaultChecked={true} />Base layout</li>
+                    {templateMenu}
+                </ul>
+            </div>
+        );
+    }
+}
\ No newline at end of file
-- 
cgit v1.2.3-70-g09d2


From fbf1662304e0b3f744827710d728602af4493f25 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Thu, 18 Apr 2019 22:13:08 -0400
Subject: changed menu to work like radio buttons:

---
 src/client/views/DocumentDecorations.tsx |  2 +-
 src/client/views/TemplateMenu.tsx        | 30 ++++++++++++-----------
 src/client/views/Templates.tsx           |  4 ++--
 src/client/views/nodes/DocumentView.tsx  | 41 +++++++++++++-------------------
 4 files changed, 35 insertions(+), 42 deletions(-)

(limited to 'src/client/views/TemplateMenu.tsx')

diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 1e936a881..3dcdc596d 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -409,7 +409,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let templates: Map<Template, boolean> = new Map();
         let doc = SelectionManager.SelectedDocuments()[0];
         Array.from(Object.values(Templates)).map(template => {
-            templates.set(template, doc.hasTemplate(template));
+            templates.set(template, doc.Template === template);
         });
 
         return (<div className="documentDecorations">
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 7f670aec0..0c8ae7055 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -1,9 +1,8 @@
 import { observable, computed, action } from "mobx";
 import React = require("react");
-import { SelectionManager } from "../util/SelectionManager";
 import { observer } from "mobx-react";
 import './DocumentDecorations.scss';
-import { Templates, Template } from "./Templates";
+import { Template } from "./Templates";
 import { DocumentView } from "./nodes/DocumentView";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
@@ -34,19 +33,19 @@ export interface TemplateMenuProps {
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
 
     @observable private _hidden: boolean = true;
-    @observable private _showBase: boolean = true;
+    @observable private _useBase: boolean = true;
     @observable private _templates: Map<Template, boolean> = this.props.templates;
 
 
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
-        if (event.target.checked) {
-            this.props.doc.addTemplate(template);
-            this._templates.set(template, true);
-        } else {
-            this.props.doc.removeTemplate(template);
-            this._templates.set(template, false);
-        }
+        this._useBase = false;
+        this.props.doc.toggleBase(false);
+        this.props.doc.changeTemplate(template);
+        this._templates.forEach((checked, temp) => {
+            this._templates.set(temp, false);
+        });
+        this._templates.set(template, true);
 
         // const docs = view.props.ContainingCollectionView;
         // const docs = view.props.Document.GetList<Document>(view.props.fieldKey, []);
@@ -59,9 +58,12 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
     }
 
     @action
-    toggleBase = (event: React.ChangeEvent<HTMLInputElement>): void => {
-        this.props.doc.toggleBase(event.target.checked);
-        this._showBase = !this._showBase;
+    toggleBase = (event: React.MouseEvent): void => {
+        this._useBase = true;
+        this.props.doc.toggleBase(true);
+        this._templates.forEach((checked, temp) => {
+            this._templates.set(temp, false);
+        });
     }
 
     @action
@@ -79,7 +81,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             <div className="templating-menu" >
                 <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>T</div>
                 <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
-                    <li><input type="checkbox" onChange={(event) => this.toggleBase(event)} defaultChecked={true} />Base layout</li>
+                    <li><input type="checkbox" onClick={(event) => this.toggleBase(event)} checked={this._useBase} />Base layout</li>
                     {templateMenu}
                 </ul>
             </div>
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index b82b383bb..c22b22286 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -31,7 +31,7 @@ export namespace Templates {
             {layout}
         </div>
         <div style="height:(100% + 25px); width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
             `);
@@ -42,7 +42,7 @@ export namespace Templates {
             {layout}
         </div>
         <div style="height:25px; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
         </div>
     </div>       
             `);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ef1420027..12332348b 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -92,9 +92,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
     private _downX: number = 0;
     private _downY: number = 0;
-    @observable private _templates: Set<Template> = new Set<Template>();
-    private _useBase: boolean = true;
-    private _baseLayout: string = this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>");
+    private _base: string = this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>");
+    @observable private _template: Template = new Template("", "");
     @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
     @computed get topMost(): boolean { return this.props.isTopMost; }
     @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
@@ -304,39 +303,31 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     updateLayout = (): void => {
-        let base = this._useBase ? this._baseLayout : "<div style='margin: auto; width: 100%; height: 100%; border: 1px solid black'></div>";
-        // this.props.Document.SetText(KeyStore.Layout, base);
-        this._templates.forEach(temp => {
-            let text = temp.Layout;
-            base = text.replace("{layout}", base);
-            // let oldLayout = this.props.Document.GetText(KeyStore.Layout, "");
-            // let layout = text.replace("{layout}", oldLayout);
-            // this.props.Document.SetText(KeyStore.Layout, layout);
-        });
-        this.props.Document.SetText(KeyStore.Layout, base);
+        if (this._template.Name === "") {
+            this.props.Document.SetText(KeyStore.Layout, this._base);
+        } else {
+            let temp = this._template.Layout;
+            let layout = temp.replace("{layout}", this._base);
+            this.props.Document.SetText(KeyStore.Layout, layout);
+        }
     }
 
     @action
     toggleBase = (useBase: boolean) => {
-        this._useBase = useBase;
-        this.updateLayout();
-    }
-
-    @action
-    addTemplate = (template: Template) => {
-        this._templates.add(template);
+        if (useBase) {
+            this._template = new Template("", "");
+        }
         this.updateLayout();
     }
 
     @action
-    removeTemplate = (template: Template) => {
-        this._templates.delete(template);
+    changeTemplate = (template: Template) => {
+        this._template = template;
         this.updateLayout();
     }
 
-    @action
-    hasTemplate = (template: Template) => {
-        return this._templates.has(template);
+    get Template() {
+        return this._template;
     }
 
     @action
-- 
cgit v1.2.3-70-g09d2


From 7ceac5f7f4cc8172bde90c2d495da3779901ef84 Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Fri, 19 Apr 2019 18:31:38 -0400
Subject: templating saves

---
 src/client/documents/Documents.ts        |  8 ++++-
 src/client/views/DocumentDecorations.tsx | 20 +++---------
 src/client/views/TemplateMenu.tsx        | 13 --------
 src/client/views/Templates.tsx           | 55 +++++++++++++++-----------------
 src/client/views/nodes/DocumentView.tsx  | 37 ++++++++-------------
 src/fields/KeyStore.ts                   |  4 ++-
 src/fields/TemplateField.ts              | 35 ++++++++++++++++++++
 src/server/Message.ts                    |  2 +-
 src/server/ServerUtil.ts                 |  3 ++
 9 files changed, 92 insertions(+), 85 deletions(-)
 create mode 100644 src/fields/TemplateField.ts

(limited to 'src/client/views/TemplateMenu.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 8db1a1c6d..eba53273f 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -32,6 +32,8 @@ import { action } from "mobx";
 import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
 import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
 import { AggregateFunction } from "../northstar/model/idea/idea";
+import { Template } from "../views/Templates";
+import { TemplateField } from "../../fields/TemplateField";
 
 export interface DocumentOptions {
     x?: number;
@@ -46,7 +48,9 @@ export interface DocumentOptions {
     pany?: number;
     page?: number;
     scale?: number;
+    baseLayout?: string;
     layout?: string;
+    template?: Template;
     layoutKeys?: Key[];
     viewType?: number;
     backgroundColor?: string;
@@ -95,7 +99,9 @@ export namespace Documents {
         if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
         if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); }
         if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
+        if (options.baseLayout !== undefined) { doc.SetText(KeyStore.BaseLayout, options.baseLayout); }
         if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
+        if (options.template !== undefined) { doc.Set(KeyStore.Template, new TemplateField(options.template)); }
         if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
         if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
         return doc;
@@ -112,7 +118,7 @@ export namespace Documents {
     }
 
     function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
-        return assignOptions(new Document(protoId), { ...options, title: title, layout: layout });
+        return assignOptions(new Document(protoId), { ...options, title: title, layout: layout , baseLayout: layout});
     }
     function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
         var deleg = doc.MakeDelegate(id);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3dcdc596d..2a40d7347 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -17,25 +17,11 @@ import { LinkMenu } from "./nodes/LinkMenu";
 import { TemplateMenu } from "./TemplateMenu";
 import React = require("react");
 import { Template, Templates } from "./Templates";
+import { TemplateField } from "../../fields/TemplateField";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
 
-// @observer
-// class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
-//     render() {
-//         if (this.props.template) {
-//             return (
-//                 <li>
-//                     <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event, this.props.template)} />
-//                     {this.props.template.Name}
-//                 </li>
-//             )
-//         }
-//         return (null);
-//     }
-// }
-
 @observer
 export class DocumentDecorations extends React.Component<{}, { value: string }> {
     static Instance: DocumentDecorations;
@@ -409,8 +395,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
         let templates: Map<Template, boolean> = new Map();
         let doc = SelectionManager.SelectedDocuments()[0];
         Array.from(Object.values(Templates)).map(template => {
-            templates.set(template, doc.Template === template);
+            templates.set(template, doc.template.Name === template.Name);
         });
+        // let docSrc = doc.props.Document.GetT(KeyStore.Prototype, TemplateField);
+        // console.log(docSrc);
 
         return (<div className="documentDecorations">
             <div className="documentDecorations-background" style={{
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 0c8ae7055..1e83463c8 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -33,14 +33,11 @@ export interface TemplateMenuProps {
 export class TemplateMenu extends React.Component<TemplateMenuProps> {
 
     @observable private _hidden: boolean = true;
-    @observable private _useBase: boolean = true;
     @observable private _templates: Map<Template, boolean> = this.props.templates;
 
 
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
-        this._useBase = false;
-        this.props.doc.toggleBase(false);
         this.props.doc.changeTemplate(template);
         this._templates.forEach((checked, temp) => {
             this._templates.set(temp, false);
@@ -57,15 +54,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
         this._templates = nextProps.templates;
     }
 
-    @action
-    toggleBase = (event: React.MouseEvent): void => {
-        this._useBase = true;
-        this.props.doc.toggleBase(true);
-        this._templates.forEach((checked, temp) => {
-            this._templates.set(temp, false);
-        });
-    }
-
     @action
     toggleTemplateActivity = (): void => {
         this._hidden = !this._hidden;
@@ -81,7 +69,6 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
             <div className="templating-menu" >
                 <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>T</div>
                 <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
-                    <li><input type="checkbox" onClick={(event) => this.toggleBase(event)} checked={this._useBase} />Base layout</li>
                     {templateMenu}
                 </ul>
             </div>
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index c22b22286..3afdc711c 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -24,39 +24,34 @@ export class Template {
 }
 
 export namespace Templates {
+    export const BasicLayout = new Template("Basic layout", "{layout}");
+
     export const OuterCaption = new Template("Outer caption",
-        `
-    <div>
-        <div style="margin:auto; height:calc(100%); width:100%;">
-            {layout}
-        </div>
-        <div style="height:(100% + 25px); width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
-        </div>
-    </div>       
-            `);
+        `<div><div style="margin:auto; height:calc(100%); width:100%;">{layout}</div><div style="height:(100% + 50px); width:100%; position:absolute"><FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/></div></div>`
+    );
+
     export const InnerCaption = new Template("Inner caption",
-        `
-    <div>
-        <div style="margin:auto; height:calc(100% - 25px); width:100%;">
-            {layout}
-        </div>
-        <div style="height:25px; width:100%; position:absolute">
-            <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
-        </div>
-    </div>       
-            `);
+        `<div>
+            <div style="margin:auto; height:calc(100% - 50px); width:100%;">
+                {layout}
+            </div>
+            <div style="height:50px; width:100%; position:absolute">
+                <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+            </div>
+        </div>`
+    );
 
     export const Title = new Template("Title",
-        `
-    <div>
-        <div style="margin:auto; height:calc(100% - 50px); width:100%;">
-            {layout}
-        </div>
-        <div style="height:50px; width:100%; position:absolute">
-            {Title}
-        </div>
-    </div>       
-            `);
+        `<div>
+            <div style="margin:auto; height:calc(100% - 50px); width:100%;">
+                {layout}
+            </div>
+            <div style="height:50px; width:100%; position:absolute; background-color: rgba(0, 0, 0, .6); color: white; padding:12px">
+                {Title}
+            </div>
+        </div>`
+    );
+
+
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 12332348b..440269e36 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -21,9 +21,10 @@ import { CollectionVideoView } from "../collections/CollectionVideoView";
 import { CollectionView } from "../collections/CollectionView";
 import { ContextMenu } from "../ContextMenu";
 import { DocumentContentsView } from "./DocumentContentsView";
-import { Template } from "./../Templates";
+import { Template, Templates } from "./../Templates";
 import "./DocumentView.scss";
 import React = require("react");
+import { TemplateField } from "../../../fields/TemplateField";
 
 
 export interface DocumentViewProps {
@@ -92,13 +93,19 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
     private _downX: number = 0;
     private _downY: number = 0;
-    private _base: string = this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>");
-    @observable private _template: Template = new Template("", "");
+    @computed get base(): string { return this.props.Document.GetText(KeyStore.BaseLayout, "<p>Error loading base layout data</p>"); }
     @computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
     @computed get topMost(): boolean { return this.props.isTopMost; }
     @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
     @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
     @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
+    @computed get template(): Template {
+        let field = this.props.Document.GetT(KeyStore.Template, TemplateField);
+        return !field || field === FieldWaiting ? Templates.BasicLayout : field.Data;
+    }
+    set template(template: Template) {
+        this.props.Document.SetData(KeyStore.Template, template, TemplateField);
+    }
     screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
@@ -303,33 +310,17 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     updateLayout = (): void => {
-        if (this._template.Name === "") {
-            this.props.Document.SetText(KeyStore.Layout, this._base);
-        } else {
-            let temp = this._template.Layout;
-            let layout = temp.replace("{layout}", this._base);
-            this.props.Document.SetText(KeyStore.Layout, layout);
-        }
-    }
-
-    @action
-    toggleBase = (useBase: boolean) => {
-        if (useBase) {
-            this._template = new Template("", "");
-        }
-        this.updateLayout();
+        let temp = this.template.Layout;
+        let layout = temp.replace("{layout}", this.base);
+        this.props.Document.SetText(KeyStore.Layout, layout);
     }
 
     @action
     changeTemplate = (template: Template) => {
-        this._template = template;
+        this.template = template;
         this.updateLayout();
     }
 
-    get Template() {
-        return this._template;
-    }
-
     @action
     onContextMenu = (e: React.MouseEvent): void => {
         e.stopPropagation();
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 16a909eb8..1602c3776 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -19,7 +19,9 @@ export namespace KeyStore {
     export const Data = new Key("Data");
     export const Annotations = new Key("Annotations");
     export const ViewType = new Key("ViewType");
+    export const BaseLayout = new Key("BaseLayout");
     export const Layout = new Key("Layout");
+    export const Template = new Key("Template");
     export const BackgroundColor = new Key("BackgroundColor");
     export const BackgroundLayout = new Key("BackgroundLayout");
     export const OverlayLayout = new Key("OverlayLayout");
@@ -49,7 +51,7 @@ export namespace KeyStore {
     export const CopyDraggedItems = new Key("CopyDraggedItems");
 
     export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
-        Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
+        Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, BaseLayout, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
         LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
         LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
         Archives, Workspaces, Minimized, CopyDraggedItems
diff --git a/src/fields/TemplateField.ts b/src/fields/TemplateField.ts
new file mode 100644
index 000000000..bc1e67d7b
--- /dev/null
+++ b/src/fields/TemplateField.ts
@@ -0,0 +1,35 @@
+import { BasicField } from "./BasicField";
+import { Types } from "../server/Message";
+import { FieldId } from "./Field";
+import { Template, Templates } from "../client/views/Templates";
+
+
+export class TemplateField extends BasicField<Template> {
+    constructor(data: Template = Templates.BasicLayout, id?: FieldId, save: boolean = true) {
+        super(data, save, id);
+    }
+
+    ToScriptString(): string {
+        return `new TemplateField("${this.Data}")`;
+    }
+
+    Copy() {
+        return new TemplateField(this.Data);
+    }
+
+    ToJson() {
+        return {
+            type: Types.Template,
+            data: this.Data,
+            id: this.Id,
+        };
+    }
+
+    UpdateFromServer(data: any) {
+        this.data = new Template(data.Name, data.layout);
+    }
+
+    static FromJson(id: string, data: any): TemplateField {
+        return new TemplateField(new Template(data._name, data._layout), id, false);
+    }
+}
\ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index bbe4ffcad..4a8812956 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -15,7 +15,7 @@ export class Message<T> {
 
 export enum Types {
     Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference,
-    Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script,
+    Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Template
 }
 
 export interface Transferable {
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 818230c1a..a2256bca7 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -18,6 +18,8 @@ import { NumberField } from "./../fields/NumberField";
 import { RichTextField } from "./../fields/RichTextField";
 import { TextField } from "./../fields/TextField";
 import { Transferable, Types } from "./Message";
+import { Template } from "../client/views/Templates";
+import { TemplateField } from "../fields/TemplateField";
 
 export class ServerUtils {
     public static prepend(extension: string): string {
@@ -50,6 +52,7 @@ export class ServerUtils {
             case Types.Video: return new VideoField(new URL(json.data), json.id, false);
             case Types.Tuple: return new TupleField(json.data, json.id, false);
             case Types.Ink: return InkField.FromJson(json.id, json.data);
+            case Types.Template: return TemplateField.FromJson(json.id, json.data);
             case Types.Document: return Document.FromJson(json.data, json.id, false);
             default:
                 throw Error(
-- 
cgit v1.2.3-70-g09d2


From e794b4b38e8ab2f4e7a79f223f9488cc845c724f Mon Sep 17 00:00:00 2001
From: Fawn <fangrui_tong@brown.edu>
Date: Mon, 22 Apr 2019 00:05:35 -0400
Subject: mutiple templates can be used

---
 src/client/documents/Documents.ts        |  6 ++--
 src/client/views/DocumentDecorations.tsx | 14 +++++---
 src/client/views/TemplateMenu.tsx        | 18 +++++-----
 src/client/views/Templates.tsx           | 61 ++++++++++++++++++--------------
 src/client/views/nodes/DocumentView.tsx  | 43 ++++++++++++++++------
 src/fields/KeyStore.ts                   |  2 +-
 src/fields/TemplateField.ts              | 22 ++++++++----
 src/server/Message.ts                    |  2 +-
 src/server/ServerUtil.ts                 |  3 +-
 9 files changed, 106 insertions(+), 65 deletions(-)

(limited to 'src/client/views/TemplateMenu.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index eba53273f..c93e65edb 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -50,7 +50,7 @@ export interface DocumentOptions {
     scale?: number;
     baseLayout?: string;
     layout?: string;
-    template?: Template;
+    templates?: Array<Template>;
     layoutKeys?: Key[];
     viewType?: number;
     backgroundColor?: string;
@@ -101,7 +101,7 @@ export namespace Documents {
         if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
         if (options.baseLayout !== undefined) { doc.SetText(KeyStore.BaseLayout, options.baseLayout); }
         if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
-        if (options.template !== undefined) { doc.Set(KeyStore.Template, new TemplateField(options.template)); }
+        if (options.templates !== undefined) { doc.Set(KeyStore.Templates, new TemplateField(options.templates)); }
         if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
         if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
         return doc;
@@ -118,7 +118,7 @@ export namespace Documents {
     }
 
     function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
-        return assignOptions(new Document(protoId), { ...options, title: title, layout: layout , baseLayout: layout});
+        return assignOptions(new Document(protoId), { ...options, title: title, layout: layout, baseLayout: layout });
     }
     function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
         var deleg = doc.MakeDelegate(id);
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2a40d7347..2b22d9e19 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -17,7 +17,6 @@ import { LinkMenu } from "./nodes/LinkMenu";
 import { TemplateMenu } from "./TemplateMenu";
 import React = require("react");
 import { Template, Templates } from "./Templates";
-import { TemplateField } from "../../fields/TemplateField";
 const higflyout = require("@hig/flyout");
 export const { anchorPoints } = higflyout;
 export const Flyout = higflyout.default;
@@ -394,11 +393,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
 
         let templates: Map<Template, boolean> = new Map();
         let doc = SelectionManager.SelectedDocuments()[0];
-        Array.from(Object.values(Templates)).map(template => {
-            templates.set(template, doc.template.Name === template.Name);
+        Array.from(Object.values(Templates.TemplateList)).map(template => {
+            let docTemps = doc.templates;
+            let checked = false;
+            docTemps.forEach(temp => {
+                if (template.Name === temp.Name) {
+                    checked = true;
+                }
+            });
+            templates.set(template, checked);
         });
-        // let docSrc = doc.props.Document.GetT(KeyStore.Prototype, TemplateField);
-        // console.log(docSrc);
 
         return (<div className="documentDecorations">
             <div className="documentDecorations-background" style={{
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index 1e83463c8..8eb2fc6c6 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -38,15 +38,13 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
 
     @action
     toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
-        this.props.doc.changeTemplate(template);
-        this._templates.forEach((checked, temp) => {
-            this._templates.set(temp, false);
-        });
-        this._templates.set(template, true);
-
-        // const docs = view.props.ContainingCollectionView;
-        // const docs = view.props.Document.GetList<Document>(view.props.fieldKey, []);
-
+        if (event.target.checked) {
+            this.props.doc.addTemplate(template);
+            this._templates.set(template, true);
+        } else {
+            this.props.doc.removeTemplate(template);
+            this._templates.set(template, false);
+        }
     }
 
     @action
@@ -67,7 +65,7 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
 
         return (
             <div className="templating-menu" >
-                <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>T</div>
+                <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
                 <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
                     {templateMenu}
                 </ul>
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index 3afdc711c..616bc2566 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -1,57 +1,66 @@
-import { observer } from "mobx-react";
-import { observable } from "mobx";
-import { action, computed } from "mobx";
 import React = require("react");
-import { StringLiteral } from "babel-types";
+
+export enum TemplatePosition {
+    InnerTop,
+    InnerBottom,
+    InnerRight,
+    InnerLeft,
+    OutterTop,
+    OutterBottom,
+    OutterRight,
+    OutterLeft,
+}
 
 export class Template {
-    constructor(name: string, layout: string) {
+    constructor(name: string, position: TemplatePosition, layout: string) {
         this._name = name;
+        this._position = position;
         this._layout = layout;
     }
 
     private _name: string;
+    private _position: TemplatePosition;
     private _layout: string;
 
     get Name(): string {
         return this._name;
     }
 
+    get Position(): TemplatePosition {
+        return this._position;
+    }
+
     get Layout(): string {
         return this._layout;
     }
-
 }
 
 export namespace Templates {
-    export const BasicLayout = new Template("Basic layout", "{layout}");
+    // export const BasicLayout = new Template("Basic layout", "{layout}");
 
-    export const OuterCaption = new Template("Outer caption",
+    export const OuterCaption = new Template("Outer caption", TemplatePosition.OutterBottom,
         `<div><div style="margin:auto; height:calc(100%); width:100%;">{layout}</div><div style="height:(100% + 50px); width:100%; position:absolute"><FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/></div></div>`
     );
 
-    export const InnerCaption = new Template("Inner caption",
-        `<div>
-            <div style="margin:auto; height:calc(100% - 50px); width:100%;">
-                {layout}
-            </div>
-            <div style="height:50px; width:100%; position:absolute">
-                <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
-            </div>
-        </div>`
+    export const InnerCaption = new Template("Inner caption", TemplatePosition.InnerBottom,
+        `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute"><FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/></div></div>`
     );
 
-    export const Title = new Template("Title",
-        `<div>
-            <div style="margin:auto; height:calc(100% - 50px); width:100%;">
-                {layout}
-            </div>
-            <div style="height:50px; width:100%; position:absolute; background-color: rgba(0, 0, 0, .6); color: white; padding:12px">
-                {Title}
-            </div>
-        </div>`
+    export const SideCaption = new Template("Side caption", TemplatePosition.OutterRight,
+        `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={CaptionKey} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/></div> </div>`
     );
 
+    export const Title = new Template("Title", TemplatePosition.InnerTop,
+        `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .6); color: white; padding:12px">{Title}</div></div>`
+    );
+
+    export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption];
+
+    export function sortTemplates(a: Template, b: Template) {
+        if (a.Position < b.Position) { return -1; }
+        if (a.Position > b.Position) { return 1; }
+        return 0;
+    }
 
 }
 
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 440269e36..e851bc9c5 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -99,13 +99,11 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     @computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
     @computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
     @computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-    @computed get template(): Template {
-        let field = this.props.Document.GetT(KeyStore.Template, TemplateField);
-        return !field || field === FieldWaiting ? Templates.BasicLayout : field.Data;
-    }
-    set template(template: Template) {
-        this.props.Document.SetData(KeyStore.Template, template, TemplateField);
+    @computed get templates(): Array<Template> {
+        let field = this.props.Document.GetT(KeyStore.Templates, TemplateField);
+        return !field || field === FieldWaiting ? [] : field.Data;
     }
+    set templates(templates: Array<Template>) { this.props.Document.SetData(KeyStore.Templates, templates, TemplateField); }
     screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
     onPointerDown = (e: React.PointerEvent): void => {
         this._downX = e.clientX;
@@ -310,14 +308,39 @@ export class DocumentView extends React.Component<DocumentViewProps> {
     }
 
     updateLayout = (): void => {
-        let temp = this.template.Layout;
-        let layout = temp.replace("{layout}", this.base);
+        let base = this.base;
+        let layout = this.base;
+
+        this.templates.forEach(template => {
+            let temp = template.Layout;
+            layout = temp.replace("{layout}", base);
+            base = layout;
+        });
+
         this.props.Document.SetText(KeyStore.Layout, layout);
     }
 
     @action
-    changeTemplate = (template: Template) => {
-        this.template = template;
+    addTemplate = (template: Template) => {
+        let templates = this.templates;
+        templates.push(template);
+        templates = templates.splice(0, templates.length).sort(Templates.sortTemplates);
+        this.templates = templates;
+        this.updateLayout();
+    }
+
+    @action
+    removeTemplate = (template: Template) => {
+        let templates = this.templates;
+        for (let i = 0; i < templates.length; i++) {
+            let temp = templates[i];
+            if (temp.Name === template.Name) {
+                templates.splice(i, 1);
+                break;
+            }
+        }
+        templates = templates.splice(0, templates.length).sort(Templates.sortTemplates);
+        this.templates = templates;
         this.updateLayout();
     }
 
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 1602c3776..5bd402890 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -21,7 +21,7 @@ export namespace KeyStore {
     export const ViewType = new Key("ViewType");
     export const BaseLayout = new Key("BaseLayout");
     export const Layout = new Key("Layout");
-    export const Template = new Key("Template");
+    export const Templates = new Key("Templates");
     export const BackgroundColor = new Key("BackgroundColor");
     export const BackgroundLayout = new Key("BackgroundLayout");
     export const OverlayLayout = new Key("OverlayLayout");
diff --git a/src/fields/TemplateField.ts b/src/fields/TemplateField.ts
index bc1e67d7b..72ae13c2e 100644
--- a/src/fields/TemplateField.ts
+++ b/src/fields/TemplateField.ts
@@ -1,11 +1,11 @@
 import { BasicField } from "./BasicField";
 import { Types } from "../server/Message";
 import { FieldId } from "./Field";
-import { Template, Templates } from "../client/views/Templates";
+import { Template, TemplatePosition } from "../client/views/Templates";
 
 
-export class TemplateField extends BasicField<Template> {
-    constructor(data: Template = Templates.BasicLayout, id?: FieldId, save: boolean = true) {
+export class TemplateField extends BasicField<Array<Template>> {
+    constructor(data: Array<Template> = [], id?: FieldId, save: boolean = true) {
         super(data, save, id);
     }
 
@@ -18,18 +18,26 @@ export class TemplateField extends BasicField<Template> {
     }
 
     ToJson() {
+        let templates: Array<{ name: string, position: TemplatePosition, layout: string }> = [];
+        this.Data.forEach(template => {
+            templates.push({ name: template.Name, layout: template.Layout, position: template.Position });
+        });
         return {
-            type: Types.Template,
-            data: this.Data,
+            type: Types.Templates,
+            data: templates,
             id: this.Id,
         };
     }
 
     UpdateFromServer(data: any) {
-        this.data = new Template(data.Name, data.layout);
+        this.data = new Array(data);
     }
 
     static FromJson(id: string, data: any): TemplateField {
-        return new TemplateField(new Template(data._name, data._layout), id, false);
+        let templates: Array<Template> = [];
+        data.forEach((template: { name: string, position: number, layout: string }) => {
+            templates.push(new Template(template.name, template.position, template.layout));
+        });
+        return new TemplateField(templates, id, false);
     }
 }
\ No newline at end of file
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 4a8812956..00a30b329 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -15,7 +15,7 @@ export class Message<T> {
 
 export enum Types {
     Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference,
-    Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Template
+    Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
 }
 
 export interface Transferable {
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index a2256bca7..a10bdd5e6 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -18,7 +18,6 @@ import { NumberField } from "./../fields/NumberField";
 import { RichTextField } from "./../fields/RichTextField";
 import { TextField } from "./../fields/TextField";
 import { Transferable, Types } from "./Message";
-import { Template } from "../client/views/Templates";
 import { TemplateField } from "../fields/TemplateField";
 
 export class ServerUtils {
@@ -52,7 +51,7 @@ export class ServerUtils {
             case Types.Video: return new VideoField(new URL(json.data), json.id, false);
             case Types.Tuple: return new TupleField(json.data, json.id, false);
             case Types.Ink: return InkField.FromJson(json.id, json.data);
-            case Types.Template: return TemplateField.FromJson(json.id, json.data);
+            case Types.Templates: return TemplateField.FromJson(json.id, json.data);
             case Types.Document: return Document.FromJson(json.data, json.id, false);
             default:
                 throw Error(
-- 
cgit v1.2.3-70-g09d2

