aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts3
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx26
-rw-r--r--src/client/views/nodes/DocumentView.tsx16
-rw-r--r--src/client/views/nodes/FieldView.tsx45
-rw-r--r--src/client/views/nodes/ImageBox.tsx41
-rw-r--r--src/debug/Test.tsx4
-rw-r--r--src/new_fields/Doc.ts5
-rw-r--r--src/new_fields/HtmlField.ts2
-rw-r--r--src/new_fields/Schema.ts24
-rw-r--r--src/new_fields/Types.ts19
10 files changed, 98 insertions, 87 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 98f75d3b9..066d653f5 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -2,7 +2,6 @@ import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
import { Socket } from 'socket.io';
import { Message } from './server/Message';
-import { Document } from './fields/Document';
export class Utils {
@@ -108,6 +107,4 @@ export function returnZero() { return 0; }
export function emptyFunction() { }
-export function emptyDocFunction(doc: Document) { }
-
export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>; \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 07599c345..273401111 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -1,9 +1,5 @@
import { computed } from "mobx";
import { observer } from "mobx-react";
-import { FieldWaiting, Field } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
@@ -22,46 +18,30 @@ import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
import React = require("react");
-import { Document } from "../../../fields/Document";
import { FieldViewProps } from "./FieldView";
import { Without, OmitKeys } from "../../../Utils";
+import { Cast } from "../../../new_fields/Types";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
type BindingProps = Without<FieldViewProps, 'fieldKey'>;
export interface JsxBindings {
props: BindingProps;
- [keyName: string]: BindingProps | Field;
}
@observer
export class DocumentContentsView extends React.Component<DocumentViewProps & {
isSelected: () => boolean,
select: (ctrl: boolean) => void,
- layoutKey: Key
+ layoutKey: string
}> {
- @computed get layout(): string { return this.props.Document.GetText(this.props.layoutKey, "<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 layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", "<p>Error loading layout data</p>"); }
CreateBindings(): JsxBindings {
let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
-
- for (const key of this.layoutKeys) {
- bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
- }
- for (const key of this.layoutFields) {
- let field = this.props.Document.Get(key);
- bindings[key.Name] = field && field !== FieldWaiting ? field.GetValue() : field;
- }
return bindings;
}
render() {
- let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
- if (!lkeys || lkeys === FieldWaiting) {
- return <p>Error loading layout keys</p>;
- }
return <JsxParser
components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
bindings={this.CreateBindings()}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index c06fe4c8d..f1a12e6db 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -46,8 +46,17 @@ const schema = createSchema({
backgroundColor: "string"
});
-type Document = makeInterface<typeof schema>;
-const Document = makeInterface(schema);
+export const positionSchema = createSchema({
+ nativeWidth: "number",
+ nativeHeight: "number",
+ width: "number",
+ height: "number",
+ x: "number",
+ y: "number",
+});
+
+type Document = makeInterface<[typeof schema]>;
+const Document = makeInterface([schema]);
@observer
export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
@@ -146,7 +155,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
- if (!SelectionManager.IsSelected(this) && e.button !== 2)
+ if (!SelectionManager.IsSelected(this) && e.button !== 2) {
if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
if (this.props.Document.Get(KeyStore.MaximizedDoc) instanceof Document) {
this.props.Document.GetAsync(KeyStore.MaximizedDoc, maxdoc => {
@@ -159,6 +168,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
SelectionManager.SelectDoc(this, e.ctrlKey);
}
}
+ }
}
stopPropagation = (e: React.SyntheticEvent) => {
e.stopPropagation();
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 93e385821..d8de5afec 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,30 +1,21 @@
import React = require("react");
import { observer } from "mobx-react";
import { computed } from "mobx";
-import { Field, FieldWaiting, FieldValue, Opt } from "../../../fields/Field";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
-import { NumberField } from "../../../fields/NumberField";
-import { RichTextField } from "../../../fields/RichTextField";
-import { ImageField } from "../../../fields/ImageField";
-import { VideoField } from "../../../fields/VideoField";
-import { Key } from "../../../fields/Key";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
import { WebBox } from "./WebBox";
import { VideoBox } from "./VideoBox";
import { AudioBox } from "./AudioBox";
-import { AudioField } from "../../../fields/AudioField";
-import { ListField } from "../../../fields/ListField";
import { DocumentContentsView } from "./DocumentContentsView";
import { Transform } from "../../util/Transform";
-import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../../Utils";
+import { returnFalse, emptyFunction, returnOne } from "../../../Utils";
import { CollectionView } from "../collections/CollectionView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { IconField } from "../../../fields/IconFIeld";
import { IconBox } from "./IconBox";
+import { Opt, Doc, Field, FieldValue, FieldWaiting } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
//
@@ -33,9 +24,9 @@ import { IconBox } from "./IconBox";
// See the LayoutString method on each field view : ImageBox, FormattedTextBox, etc.
//
export interface FieldViewProps {
- fieldKey: Key;
+ fieldKey: string;
ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
- Document: Document;
+ Document: Doc;
isSelected: () => boolean;
select: (isCtrlPressed: boolean) => void;
isTopMost: boolean;
@@ -56,17 +47,17 @@ export class FieldView extends React.Component<FieldViewProps> {
}
@computed
- get field(): FieldValue<Field> {
- const { Document: doc, fieldKey } = this.props;
- return doc.Get(fieldKey);
+ get field(): FieldValue {
+ const { Document, fieldKey } = this.props;
+ return Document[fieldKey];
}
render() {
const field = this.field;
if (!field) {
return <p>{'<null>'}</p>;
}
- if (field instanceof TextField) {
- return <p>{field.Data}</p>;
+ if (typeof field === "string") {
+ return <p>{field}</p>;
}
else if (field instanceof RichTextField) {
return <FormattedTextBox {...this.props} />;
@@ -83,7 +74,7 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof AudioField) {
return <AudioBox {...this.props} />;
}
- else if (field instanceof Document) {
+ else if (field instanceof Doc) {
return (
<DocumentContentsView Document={field}
addDocument={undefined}
@@ -94,29 +85,29 @@ export class FieldView extends React.Component<FieldViewProps> {
PanelHeight={() => 100}
isTopMost={true} //TODO Why is this top most?
selectOnLoad={false}
- focus={emptyDocFunction}
+ focus={emptyFunction}
isSelected={returnFalse}
select={returnFalse}
- layoutKey={KeyStore.Layout}
+ layoutKey={"layout"}
ContainingCollectionView={this.props.ContainingCollectionView}
parentActive={this.props.active}
whenActiveChanged={this.props.whenActiveChanged} />
);
}
- else if (field instanceof ListField) {
+ else if (field instanceof List) {
return (<div>
- {(field as ListField<Field>).Data.map(f => f instanceof Document ? f.Title : f.GetValue().toString()).join(", ")}
+ {field.map(f => f instanceof Doc ? f.title : f.toString()).join(", ")}
</div>);
}
// bcz: this belongs here, but it doesn't render well so taking it out for now
// else if (field instanceof HtmlField) {
// return <WebBox {...this.props} />
// }
- else if (field instanceof NumberField) {
- return <p>{field.Data}</p>;
+ else if (typeof field === "number") {
+ return <p>{field}</p>;
}
else if (field !== FieldWaiting) {
- return <p>{JSON.stringify(field.GetValue())}</p>;
+ return <p>{JSON.stringify(field)}</p>;
}
else {
return <p> {"Waiting for server..."} </p>;
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index fd5381b77..3b72e9f39 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -3,11 +3,6 @@ import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { FieldWaiting } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from '../../../fields/ListField';
import { Utils } from '../../../Utils';
import { DragManager } from '../../util/DragManager';
import { undoBatch } from '../../util/UndoManager';
@@ -15,9 +10,23 @@ import { ContextMenu } from "../../views/ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
+import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { DocComponent } from '../DocComponent';
+import { positionSchema } from './DocumentView';
+import { FieldValue, Cast } from '../../../new_fields/Types';
+import { URLField } from '../../../new_fields/URLField';
+import { Doc, FieldWaiting } from '../../../new_fields/Doc';
+import { List } from '../../../new_fields/List';
+
+const schema = createSchema({
+ curPage: "number"
+});
+
+type ImageDocument = makeInterface<[typeof schema, typeof positionSchema]>;
+const ImageDocument = makeInterface([schema, positionSchema]);
@observer
-export class ImageBox extends React.Component<FieldViewProps> {
+export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
public static LayoutString() { return FieldView.LayoutString(ImageBox); }
private _imgRef: React.RefObject<HTMLImageElement>;
@@ -39,8 +48,8 @@ export class ImageBox extends React.Component<FieldViewProps> {
var h = this._imgRef.current!.naturalHeight;
var w = this._imgRef.current!.naturalWidth;
if (this._photoIndex === 0) {
- this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
- this.props.Document.SetNumber(KeyStore.Height, this.props.Document.Width() * h / w);
+ this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w;
+ this.Document.height = FieldValue(this.Document.width, 0) * h / w;
}
}
@@ -126,9 +135,9 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
specificContextMenu = (e: React.MouseEvent): void => {
- let field = this.props.Document.GetT(this.props.fieldKey, ImageField);
+ let field = Cast(this.Document[this.props.fieldKey], URLField);
if (field && field !== FieldWaiting) {
- let url = field.Data.href;
+ let url = field.url.href;
ContextMenu.Instance.addItem({
description: "Copy path", event: () => {
Utils.CopyText(url);
@@ -140,11 +149,11 @@ export class ImageBox extends React.Component<FieldViewProps> {
@action
onDotDown(index: number) {
this._photoIndex = index;
- this.props.Document.SetNumber(KeyStore.CurPage, index);
+ this.Document.curPage = index;
}
dots(paths: string[]) {
- let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+ let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
let dist = Math.min(nativeWidth / paths.length, 40);
let left = (nativeWidth - paths.length * dist) / 2;
return paths.map((p, i) =>
@@ -155,12 +164,12 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
render() {
- let field = this.props.Document.Get(this.props.fieldKey);
+ let field = this.Document[this.props.fieldKey];
let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"];
- else if (field instanceof ImageField) paths = [field.Data.href];
- else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href);
- let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+ else if (field instanceof URLField) paths = [field.url.href];
+ else if (field instanceof List) paths = field.filter(val => val instanceof URLField).map(p => (p as URLField).url.href);
+ let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
return (
<div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
<img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 8b9c9fa0b..81c69ca8e 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -16,8 +16,8 @@ const schema1 = createSchema({
testDoc: Doc
});
-const TestDoc = makeInterface(schema1);
-type TestDoc = makeInterface<typeof schema1>;
+const TestDoc = makeInterface([schema1]);
+type TestDoc = makeInterface<[typeof schema1]>;
const schema2 = createSchema({
hello: URLField,
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index 23a8c05cc..e0eb44ee9 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -33,6 +33,7 @@ export type Field = number | string | boolean | ObjectField | RefField;
export type Opt<T> = T | undefined;
export type FieldWaiting = null;
export const FieldWaiting: FieldWaiting = null;
+export type FieldValue<T extends Field = Field> = Opt<T> | FieldWaiting;
export const Self = Symbol("Self");
@@ -81,8 +82,8 @@ export namespace Doc {
const self = doc[Self];
return getField(self, key, ignoreProto);
}
- export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): Field | null | undefined {
- return Cast(Get(doc, key, ignoreProto), ctor);
+ export function GetT<T extends Field>(doc: Doc, key: string, ctor: FieldCtor<T>, ignoreProto: boolean = false): T | null | undefined {
+ return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined;
}
export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
if (!doc) {
diff --git a/src/new_fields/HtmlField.ts b/src/new_fields/HtmlField.ts
index f8e54ade5..76fdb1f62 100644
--- a/src/new_fields/HtmlField.ts
+++ b/src/new_fields/HtmlField.ts
@@ -3,7 +3,7 @@ import { serializable, primitive } from "serializr";
import { ObjectField } from "./Doc";
@Deserializable("html")
-export class URLField extends ObjectField {
+export class HtmlField extends ObjectField {
@serializable(primitive())
readonly html: string;
diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts
index 1607d4c15..1d1f56844 100644
--- a/src/new_fields/Schema.ts
+++ b/src/new_fields/Schema.ts
@@ -1,8 +1,26 @@
-import { Interface, ToInterface, Cast, FieldCtor, ToConstructor } from "./Types";
+import { Interface, ToInterface, Cast, FieldCtor, ToConstructor, HasTail, Head, Tail } from "./Types";
import { Doc } from "./Doc";
-export type makeInterface<T extends Interface, U extends Doc = Doc> = Partial<ToInterface<T>> & U;
-export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U> {
+type All<T extends any[], U extends Doc> = {
+ 1: makeInterface<Head<T>, U> & All<Tail<T>, U>,
+ 0: makeInterface<Head<T>, U>
+}[HasTail<T> extends true ? 1 : 0];
+
+type AllToInterface<T extends any[]> = {
+ 1: ToInterface<Head<T>> & AllToInterface<Tail<T>>,
+ 0: ToInterface<Head<T>>
+}[HasTail<T> extends true ? 1 : 0];
+
+export type makeInterface<T extends Interface[], U extends Doc = Doc> = Partial<AllToInterface<T>> & U;
+// export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U>;
+// export function makeInterface<T extends Interface, U extends Doc>(schema: T): (doc: U) => makeInterface<T, U>;
+export function makeInterface<T extends Interface[], U extends Doc>(schemas: T): (doc: U) => All<T, U> {
+ let schema: Interface = {};
+ for (const s of schemas) {
+ for (const key in s) {
+ schema[key] = s[key];
+ }
+ }
return function (doc: any) {
return new Proxy(doc, {
get(target, prop) {
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index cafb208ce..6ffb3e02f 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -27,14 +27,17 @@ export type Tail<T extends any[]> =
((...t: T) => any) extends ((_: any, ...tail: infer TT) => any) ? TT : [];
export type HasTail<T extends any[]> = T extends ([] | [any]) ? false : true;
+//TODO Allow you to optionally specify default values for schemas, which should then make that field not be partial
export interface Interface {
[key: string]: ToConstructor<Field> | ListSpec<Field>;
// [key: string]: ToConstructor<Field> | ListSpec<Field[]>;
}
-export type FieldCtor<T extends Field> = ToConstructor<T> | ListSpec<T>;
+export type FieldCtor<T extends Field> = T extends List<infer R> ? ListSpec<R> : ToConstructor<T>;
-export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined {
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T): ToType<T> | null | undefined;
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T, defaultVal: ToType<T>): ToType<T>;
+export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined, ctor: T, defaultVal?: ToType<T>): ToType<T> | null | undefined {
if (field !== undefined && field !== null) {
if (typeof ctor === "string") {
if (typeof field === ctor) {
@@ -42,17 +45,19 @@ export function Cast<T extends FieldCtor<Field>>(field: Field | null | undefined
}
} else if (typeof ctor === "object") {
if (field instanceof List) {
- return field as ToType<T>;
+ return field as any;
}
} else if (field instanceof (ctor as any)) {
return field as ToType<T>;
}
} else {
- return field;
+ return defaultVal;
}
- return undefined;
+ return defaultVal;
}
-export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>): Opt<T> {
- return field instanceof Promise ? undefined : field;
+export function FieldValue<T extends Field, U extends T>(field: Opt<T> | Promise<Opt<T>>, defaultValue: U): T;
+export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>): Opt<T>;
+export function FieldValue<T extends Field>(field: Opt<T> | Promise<Opt<T>>, defaultValue?: T): Opt<T> {
+ return field instanceof Promise ? defaultValue : field;
}