aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorBob Zeleznik <zzzman@gmail.com>2019-04-28 22:26:03 -0400
committerBob Zeleznik <zzzman@gmail.com>2019-04-28 22:26:03 -0400
commitbf4a6e0962264e6ce934dcc727cccef6437f7713 (patch)
tree9586f47ef674407e1dea311f9b1e22013b995ede /src
parenta5ab5caa244da261814bc10f0ae88b302da93951 (diff)
parent227e8443a9fc9ca0c3f2e02e85dbd80b26a8b7b2 (diff)
Merge remote-tracking branch 'origin/templating'
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts9
-rw-r--r--src/client/views/.DS_Storebin8196 -> 6148 bytes
-rw-r--r--src/client/views/DocumentDecorations.scss96
-rw-r--r--src/client/views/DocumentDecorations.tsx20
-rw-r--r--src/client/views/TemplateMenu.tsx75
-rw-r--r--src/client/views/Templates.tsx66
-rw-r--r--src/client/views/nodes/DocumentView.tsx48
-rw-r--r--src/fields/KeyStore.ts2
-rw-r--r--src/fields/TemplateField.ts43
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/ServerUtil.ts2
11 files changed, 321 insertions, 42 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 879b114b6..a7514f1d6 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";
import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
import { IconBox } from "../views/nodes/IconBox";
import { IconField } from "../../fields/IconFIeld";
@@ -49,7 +51,9 @@ export interface DocumentOptions {
pany?: number;
page?: number;
scale?: number;
+ baseLayout?: string;
layout?: string;
+ templates?: Array<Template>;
layoutKeys?: Key[];
viewType?: number;
backgroundColor?: string;
@@ -106,7 +110,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.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); }
if (options.borderRounding !== undefined) { doc.SetNumber(KeyStore.BorderRounding, options.borderRounding); }
@@ -124,7 +130,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);
@@ -142,6 +148,7 @@ export namespace Documents {
{ x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
imageProto.SetNumber(KeyStore.CurPage, 0);
+ imageProto.SetData(KeyStore.LayoutFields, [KeyStore.Title], ListField);
return imageProto;
}
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
index 0964d5ff3..5008ddfcf 100644
--- a/src/client/views/.DS_Store
+++ b/src/client/views/.DS_Store
Binary files differ
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index dd646e77c..27b94e6d2 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -3,18 +3,19 @@
.documentDecorations {
position: absolute;
}
+
.documentDecorations-container {
z-index: $docDecorations-zindex;
position: absolute;
top: 0;
- left:0;
+ left: 0;
display: grid;
grid-template-rows: 20px 8px 1fr 8px;
grid-template-columns: 8px 16px 1fr 8px 8px;
pointer-events: none;
#documentDecorations-centerCont {
- grid-column:3;
+ grid-column: 3;
background: none;
}
@@ -39,8 +40,8 @@
#documentDecorations-bottomRightResizer,
#documentDecorations-topRightResizer,
#documentDecorations-rightResizer {
- grid-column-start:5;
- grid-column-end:7;
+ grid-column-start: 5;
+ grid-column-end: 7;
}
#documentDecorations-topLeftResizer,
@@ -64,7 +65,7 @@
}
.title{
background: lightblue;
- grid-column-start:3;
+ grid-column-start: 3;
grid-column-end: 4;
pointer-events: auto;
overflow: hidden;
@@ -72,7 +73,7 @@
}
.documentDecorations-closeButton {
- background:$alt-accent;
+ background: $alt-accent;
opacity: 0.8;
grid-column-start: 4;
grid-column-end: 6;
@@ -80,8 +81,9 @@
text-align: center;
cursor: pointer;
}
+
.documentDecorations-minimizeButton {
- background:$alt-accent;
+ background: $alt-accent;
opacity: 0.8;
grid-column-start: 1;
grid-column-end: 3;
@@ -94,6 +96,7 @@
width: $MINIMIZED_ICON_SIZE;
height: $MINIMIZED_ICON_SIZE;
}
+
.documentDecorations-background {
background: lightblue;
position: absolute;
@@ -105,21 +108,9 @@
margin-left: 25px;
}
-.linkButton-empty:hover {
- background: $main-accent;
- transform: scale(1.05);
- cursor: pointer;
-}
-
-.linkButton-nonempty:hover {
- background: $main-accent;
- transform: scale(1.05);
- cursor: pointer;
-}
-
.linkButton-linker {
- position:absolute;
- bottom:0px;
+ position: absolute;
+ bottom: 0px;
left: 0px;
height: 20px;
width: 20px;
@@ -139,13 +130,14 @@
justify-content: center;
align-items: center;
}
+
.linkButton-tray {
grid-column: 1/4;
}
+
.linkButton-empty {
height: 20px;
width: 20px;
- margin-top: 10px;
border-radius: 50%;
opacity: 0.9;
pointer-events: auto;
@@ -159,23 +151,51 @@
display: flex;
justify-content: center;
align-items: center;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
}
-.linkButton-nonempty {
- height: 20px;
- width: 20px;
- margin-top: 10px;
- border-radius: 50%;
- opacity: 0.9;
+.templating-menu {
+ position: absolute;
+ bottom: 0;
+ left: 50px;
pointer-events: auto;
- background-color: $dark-color;
- color: $light-color;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
+
+ .templating-button {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ background-color: $dark-color;
+ color: $light-color;
+ text-align: center;
+ cursor: pointer;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ }
+ }
+
+ #template-list {
+ position: absolute;
+ top: 0;
+ left: 30px;
+ width: 150px;
+ line-height: 25px;
+ max-height: 175px;
+ font-family: $sans-serif;
+ font-size: 12px;
+ background-color: $light-color-secondary;
+ padding: 2px 12px;
+ list-style: none;
+
+ input {
+ margin-right: 10px;
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index a1cf0087f..bbd92c6c5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -14,7 +14,9 @@ import './DocumentDecorations.scss';
import { MainOverlayTextBox } from "./MainOverlayTextBox";
import { DocumentView } from "./nodes/DocumentView";
import { LinkMenu } from "./nodes/LinkMenu";
+import { TemplateMenu } from "./TemplateMenu";
import React = require("react");
+import { Template, Templates } from "./Templates";
import { CompileScript } from "../util/Scripting";
import { IconBox } from "./nodes/IconBox";
import { FieldValue, Field } from "../../fields/Field";
@@ -31,7 +33,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private keyinput: React.RefObject<HTMLInputElement>;
private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
private _resizeBorderWidth = 16;
- private _linkBoxHeight = 30;
+ private _linkBoxHeight = 20;
private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
private _linkerButton = React.createRef<HTMLDivElement>();
@@ -470,6 +472,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
// buttonOnPointerUp = (e: React.PointerEvent): void => {
// e.stopPropagation();
// }
+
render() {
var bounds = this.Bounds;
let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
@@ -498,6 +501,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
</Flyout>);
}
+
+ let templates: Map<Template, boolean> = new Map();
+ let doc = SelectionManager.SelectedDocuments()[0];
+ 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);
+ });
+
return (<div className="documentDecorations">
<div className="documentDecorations-background" style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
@@ -533,6 +550,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div>
<div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
+ <TemplateMenu doc={doc} templates={templates} />
</div >
</div>
);
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
new file mode 100644
index 000000000..8eb2fc6c6
--- /dev/null
+++ b/src/client/views/TemplateMenu.tsx
@@ -0,0 +1,75 @@
+import { observable, computed, action } from "mobx";
+import React = require("react");
+import { observer } from "mobx-react";
+import './DocumentDecorations.scss';
+import { 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 _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);
+ }
+ }
+
+ @action
+ componentWillReceiveProps(nextProps: TemplateMenuProps) {
+ this._templates = nextProps.templates;
+ }
+
+ @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()}>+</div>
+ <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+ {templateMenu}
+ </ul>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
new file mode 100644
index 000000000..616bc2566
--- /dev/null
+++ b/src/client/views/Templates.tsx
@@ -0,0 +1,66 @@
+import React = require("react");
+
+export enum TemplatePosition {
+ InnerTop,
+ InnerBottom,
+ InnerRight,
+ InnerLeft,
+ OutterTop,
+ OutterBottom,
+ OutterRight,
+ OutterLeft,
+}
+
+export class Template {
+ 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 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", 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 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 03d9a4322..bae14193e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,10 +1,11 @@
import { action, computed, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
-import { Field, Opt } from "../../../fields/Field";
+import { Field, FieldWaiting, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
+import { TemplateField } from "../../../fields/TemplateField";
import { ServerUtils } from "../../../server/ServerUtil";
import { emptyFunction, Utils } from "../../../Utils";
import { Documents } from "../../documents/Documents";
@@ -18,6 +19,7 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
+import { Template, Templates } from "./../Templates";
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import React = require("react");
@@ -86,6 +88,7 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
export class DocumentView extends React.Component<DocumentViewProps> {
private _downX: number = 0;
private _downY: number = 0;
+ @computed get base(): string { return this.props.Document.GetText(KeyStore.BaseLayout, "<p>Error loading base layout data</p>"); }
private _mainCont = React.createRef<HTMLDivElement>();
private _dropDisposer?: DragManager.DragDropDisposer;
@@ -95,6 +98,12 @@ 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 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();
@action
componentDidMount() {
@@ -240,6 +249,43 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
+ updateLayout = (): void => {
+ 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
+ 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();
+ }
+
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index aa18a9f33..a7652f1bf 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 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
new file mode 100644
index 000000000..72ae13c2e
--- /dev/null
+++ b/src/fields/TemplateField.ts
@@ -0,0 +1,43 @@
+import { BasicField } from "./BasicField";
+import { Types } from "../server/Message";
+import { FieldId } from "./Field";
+import { Template, TemplatePosition } from "../client/views/Templates";
+
+
+export class TemplateField extends BasicField<Array<Template>> {
+ constructor(data: Array<Template> = [], id?: FieldId, save: boolean = true) {
+ super(data, save, id);
+ }
+
+ ToScriptString(): string {
+ return `new TemplateField("${this.Data}")`;
+ }
+
+ Copy() {
+ return new TemplateField(this.Data);
+ }
+
+ 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.Templates,
+ data: templates,
+ id: this.Id,
+ };
+ }
+
+ UpdateFromServer(data: any) {
+ this.data = new Array(data);
+ }
+
+ static FromJson(id: string, data: any): TemplateField {
+ 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 15916ef12..854ae0168 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, Icon, RichText, DocumentReference,
- Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script,
+ 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 79ca5e55d..eb5749dff 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -18,6 +18,7 @@ import { NumberField } from "./../fields/NumberField";
import { RichTextField } from "./../fields/RichTextField";
import { TextField } from "./../fields/TextField";
import { Transferable, Types } from "./Message";
+import { TemplateField } from "../fields/TemplateField";
import { IconField } from "../fields/IconFIeld";
export class ServerUtils {
@@ -52,6 +53,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.Templates: return TemplateField.FromJson(json.id, json.data);
case Types.Document: return Document.FromJson(json.data, json.id, false);
default:
throw Error(