diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client/util/UndoManager.ts | 4 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 27 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 190 | ||||
-rw-r--r-- | src/client/views/nodes/FormattedTextBox.tsx | 28 | ||||
-rw-r--r-- | src/new_fields/Doc.ts | 11 | ||||
-rw-r--r-- | src/new_fields/Schema.ts | 13 | ||||
-rw-r--r-- | src/new_fields/Types.ts | 12 |
7 files changed, 133 insertions, 152 deletions
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 27aed4bac..f91ca2e06 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,4 +1,4 @@ -import { observable, action } from "mobx"; +import { observable, action, runInAction } from "mobx"; import 'source-map-support/register'; import { Without } from "../../Utils"; import { string } from "prop-types"; @@ -143,7 +143,7 @@ export namespace UndoManager { export function RunInBatch(fn: () => void, batchName: string) { let batch = StartBatch(batchName); try { - fn(); + runInAction(fn); } finally { batch.end(); } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 1493ff25b..be12dced3 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -1,36 +1,19 @@ import React = require("react"); import { FieldViewProps, FieldView } from './FieldView'; -import { FieldWaiting } from '../../../fields/Field'; import { observer } from "mobx-react"; -import { ContextMenu } from "../../views/ContextMenu"; -import { observable, action } from 'mobx'; -import { KeyStore } from '../../../fields/KeyStore'; -import { AudioField } from "../../../fields/AudioField"; import "./AudioBox.scss"; -import { NumberField } from "../../../fields/NumberField"; +import { Cast } from "../../../new_fields/Types"; +import { AudioField } from "../../../new_fields/URLField"; +const defaultField: AudioField = new AudioField(new URL("http://techslides.com/demos/samples/sample.mp3")); @observer export class AudioBox extends React.Component<FieldViewProps> { public static LayoutString() { return FieldView.LayoutString(AudioBox); } - constructor(props: FieldViewProps) { - super(props); - } - - - - componentDidMount() { - } - - componentWillUnmount() { - } - - render() { - let field = this.props.Document.Get(this.props.fieldKey); - let path = field === FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" : - field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3"; + let field = Cast(this.props.Document[this.props.fieldKey], AudioField, defaultField); + let path = field.url.href; return ( <div> diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c0afc192e..e00f56d53 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -18,9 +18,20 @@ import "./DocumentView.scss"; import React = require("react"); import { Field, Opt, Doc, Id } from "../../../new_fields/Doc"; import { DocComponent } from "../DocComponent"; -import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { FieldValue } from "../../../new_fields/Types"; +import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema"; +import { FieldValue, Cast, PromiseValue } from "../../../new_fields/Types"; +import { List } from "../../../new_fields/List"; + +const linkSchema = createSchema({ + title: "string", + linkDescription: "string", + linkTags: "string", + linkedTo: Doc, + linkedFrom: Doc +}); +type LinkDoc = makeInterface<[typeof linkSchema]>; +const LinkDoc = makeInterface(linkSchema); export interface DocumentViewProps { ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>; @@ -157,16 +168,14 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu e.stopPropagation(); 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 => { - if (maxdoc instanceof Document) { - this.props.addDocument && this.props.addDocument(maxdoc, false); - this.toggleMinimize(maxdoc, this.props.Document); - } - }); - } else { - SelectionManager.SelectDoc(this, e.ctrlKey); - } + PromiseValue(Cast(this.props.Document.maximizedDoc, Doc)).then(maxdoc => { + if (maxdoc instanceof Doc) { + this.props.addDocument && this.props.addDocument(maxdoc, false); + this.toggleMinimize(maxdoc, this.props.Document); + } else { + SelectionManager.SelectDoc(this, e.ctrlKey); + } + }); } } } @@ -184,7 +193,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu } } fullScreenClicked = (e: React.MouseEvent): void => { - CollectionDockingView.Instance.OpenFullScreen(Doc.MakeDelegate(this.Document.prototype)); + const doc = Doc.MakeDelegate(FieldValue(this.Document.proto)); + if (doc) { + CollectionDockingView.Instance.OpenFullScreen(doc); + } ContextMenu.Instance.clearItems(); ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked }); ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); @@ -197,39 +209,39 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } - @action createIcon = (layoutString: string): Document => { - let iconDoc = Documents.IconDocument(layoutString); - iconDoc.SetBoolean(KeyStore.IsMinimized, false); - iconDoc.SetNumber(KeyStore.NativeWidth, 0); - iconDoc.SetNumber(KeyStore.NativeHeight, 0); - iconDoc.Set(KeyStore.Prototype, this.props.Document); - iconDoc.Set(KeyStore.MaximizedDoc, this.props.Document); - this.props.Document.Set(KeyStore.MinimizedDoc, iconDoc); + @action createIcon = (layoutString: string): Doc => { + let iconDoc: Doc = Documents.IconDocument(layoutString); + iconDoc.isMinimized = false; + iconDoc.nativeWidth = 0; + iconDoc.nativeHeight = 0; + iconDoc.proto = this.props.Document; + iconDoc.maximizedDoc = this.props.Document; + this.Document.minimizedDoc = iconDoc; this.props.addDocument && this.props.addDocument(iconDoc, false); return iconDoc; } - animateTransition(icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) { + animateTransition(icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, maximizing: boolean) { setTimeout(() => { let now = Date.now(); let progress = Math.min(1, (now - stime) / 200); let pval = maximizing ? [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] : [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress]; - target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress); - target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress); - target.SetNumber(KeyStore.X, pval[0]); - target.SetNumber(KeyStore.Y, pval[1]); + target.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress; + target.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress; + target.x = pval[0]; + target.y = pval[1]; if (now < stime + 200) { this.animateTransition(icon, targ, width, height, stime, target, maximizing); } else { if (!maximizing) { - target.SetBoolean(KeyStore.IsMinimized, true); - target.SetNumber(KeyStore.X, targ[0]); - target.SetNumber(KeyStore.Y, targ[1]); - target.SetNumber(KeyStore.Width, width); - target.SetNumber(KeyStore.Height, height); + target.isMinimized = true; + target.x = targ[0]; + target.y = targ[1]; + target.width = width; + target.height = height; } this._completed = true; } @@ -240,83 +252,71 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu _completed = true; @action - public toggleMinimize = (maximized: Document, minim: Document): void => { + public toggleMinimize = (maximized: Doc, minim: Doc): void => { SelectionManager.DeselectAll(); - if (maximized instanceof Document && this._completed) { + if (this._completed) { this._completed = false; - let minimized = maximized.GetBoolean(KeyStore.IsMinimized, false); - maximized.SetBoolean(KeyStore.IsMinimized, false); + let minimized = Cast(maximized.isMinimized, "boolean", false); + maximized.isMinimized = false; this.animateTransition( - [minim.GetNumber(KeyStore.X, 0), minim.GetNumber(KeyStore.Y, 0)], - [maximized.GetNumber(KeyStore.X, 0), maximized.GetNumber(KeyStore.Y, 0)], - maximized.GetNumber(KeyStore.Width, 0), maximized.GetNumber(KeyStore.Height, 0), + [Cast(minim.x, "number", 0), Cast(minim.y, "number", 0)], + [Cast(maximized.x, "number", 0), Cast(maximized.y, "number", 0)], + Cast(maximized.width, "number", 0), Cast(maximized.width, "number", 0), Date.now(), maximized, minimized); } } @action - public minimize = (): void => { - this.props.Document.GetAsync(KeyStore.MinimizedDoc, mindoc => { - if (mindoc === undefined) { - this.props.Document.GetAsync(KeyStore.BackgroundLayout, field => { - if (field instanceof TextField) { - this.toggleMinimize(this.props.Document, this.createIcon(field.Data)); - } - else this.props.Document.GetAsync(KeyStore.Layout, field => { - if (field instanceof TextField) { - this.createIcon(field.Data); - this.toggleMinimize(this.props.Document, this.createIcon(field.Data)); - } - }); - }); - } - else if (mindoc instanceof Document) { - this.props.addDocument && this.props.addDocument(mindoc, false); - this.toggleMinimize(this.props.Document, mindoc); + public minimize = async (): Promise<void> => { + const mindoc = await Cast(this.props.Document.minimizedDoc, Doc); + if (mindoc === undefined) { + const background = await Cast(this.props.Document.backgroundLayout, "string"); + if (background === undefined) { + const layout = await Cast(this.props.Document.layout, "string"); + if (layout) { + this.createIcon(layout); + this.toggleMinimize(this.props.Document, this.createIcon(layout)); + } + } else { + this.toggleMinimize(this.props.Document, this.createIcon(background)); } - }); + } else { + this.props.addDocument && this.props.addDocument(mindoc, false); + this.toggleMinimize(this.props.Document, mindoc); + } } @undoBatch @action - drop = (e: Event, de: DragManager.DropEvent) => { + drop = async (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc: Document = de.data.linkSourceDocument; - let destDoc: Document = this.props.Document; - let linkDoc: Document = new Document(); - - destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest => - sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc => - runInAction(() => { - let batch = UndoManager.StartBatch("document view drop"); - linkDoc.SetText(KeyStore.Title, "New Link"); - linkDoc.SetText(KeyStore.LinkDescription, ""); - linkDoc.SetText(KeyStore.LinkTags, "Default"); - - let dstTarg = protoDest ? protoDest : destDoc; - let srcTarg = protoSrc ? protoSrc : sourceDoc; - linkDoc.Set(KeyStore.LinkedToDocs, dstTarg); - linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg); - const prom1 = new Promise(resolve => dstTarg.GetOrCreateAsync( - KeyStore.LinkedFromDocs, - ListField, - field => { - (field as ListField<Document>).Data.push(linkDoc); - resolve(); - } - )); - const prom2 = new Promise(resolve => srcTarg.GetOrCreateAsync( - KeyStore.LinkedToDocs, - ListField, - field => { - (field as ListField<Document>).Data.push(linkDoc); - resolve(); - } - )); - Promise.all([prom1, prom2]).finally(() => batch.end()); - }) - ) - ); + let sourceDoc: Doc = de.data.linkSourceDocument; + let destDoc: Doc = this.props.Document; + let linkDoc = LinkDoc(); + + const protoDest = await Cast(destDoc.proto, Doc); + const protoSrc = await Cast(sourceDoc.proto, Doc); + UndoManager.RunInBatch(() => { + linkDoc.title = "New Link"; + linkDoc.linkDescription = ""; + linkDoc.linkTags = "Default"; + + let dstTarg = protoDest ? protoDest : destDoc; + let srcTarg = protoSrc ? protoSrc : sourceDoc; + linkDoc.linkedTo = dstTarg; + linkDoc.linkedFrom = srcTarg; + let linkedFrom = Cast(dstTarg.linkedFrom, listSpec(Doc)); + if (!linkedFrom) { + dstTarg.linkedFrom = linkedFrom = new List<Doc>(); + } + linkedFrom.push(linkDoc); + + let linkedTo = Cast(srcTarg.linkedTo, listSpec(Doc)); + if (!linkedTo) { + srcTarg.linkedTo = linkedTo = new List<Doc>(); + } + linkedTo.push(linkDoc); + }, "document view drop"); e.stopPropagation(); } } @@ -361,7 +361,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth) || 0; } @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight) || 0; } - @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); } + @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 639dae30a..cb082dc69 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -4,11 +4,6 @@ import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { FieldWaiting, Opt } from "../../../fields/Field"; -import { KeyStore } from "../../../fields/KeyStore"; -import { RichTextField } from "../../../fields/RichTextField"; -import { TextField } from "../../../fields/TextField"; -import { Document } from "../../../fields/Document"; import buildKeymap from "../../util/ProsemirrorKeymap"; import { inpRules } from "../../util/RichTextRules"; import { schema } from "../../util/RichTextSchema"; @@ -20,6 +15,9 @@ import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); import { SelectionManager } from "../../util/SelectionManager"; +import { DocComponent } from "../DocComponent"; +import { createSchema, makeInterface } from "../../../new_fields/Schema"; +import { Opt, Doc } from "../../../new_fields/Doc"; const { buildMenuItems } = require("prosemirror-example-setup"); const { menuBar } = require("prosemirror-menu"); @@ -44,7 +42,14 @@ export interface FormattedTextBoxOverlay { isOverlay?: boolean; } -export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> { +const richTextSchema = createSchema({ + documentText: "string" +}); + +type RichTextDocument = makeInterface<[typeof richTextSchema]>; +const RichTextDocument = makeInterface(richTextSchema); + +export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) { public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(FormattedTextBox, fieldStr); } @@ -59,7 +64,6 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte super(props); this._ref = React.createRef(); - this.onChange = this.onChange.bind(this); } _applyingChange: boolean = false; @@ -74,7 +78,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte JSON.stringify(state.toJSON()), RichTextField ); - this.props.Document.SetDataOnPrototype(KeyStore.DocumentText, state.doc.textBetween(0, state.doc.content.size, "\n\n"), TextField); + Doc.SetOnPrototype(this.props.Document, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n")); this._applyingChange = false; // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); } @@ -166,12 +170,6 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } } - @action - onChange(e: React.ChangeEvent<HTMLInputElement>) { - const { fieldKey, Document } = this.props; - Document.SetOnPrototype(fieldKey, new RichTextField(e.target.value)); - // doc.SetData(fieldKey, e.target.value, RichTextField); - } onPointerDown = (e: React.PointerEvent): void => { if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { console.log("first"); @@ -252,7 +250,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } onKeyPress(e: React.KeyboardEvent) { - if (e.key == "Escape") { + if (e.key === "Escape") { SelectionManager.DeselectAll(); } e.stopPropagation(); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 60abccce6..5ae095e68 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -33,8 +33,8 @@ export class ObjectField { export type Field = number | string | boolean | ObjectField | RefField; export type Opt<T> = T | undefined; -export type FieldWaiting<T extends Field = Field> = Promise<T | undefined>; -export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<T>; +export type FieldWaiting<T extends RefField = RefField> = T extends undefined ? never : Promise<T | undefined>; +export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract<T, RefField>>; export const Self = Symbol("Self"); @@ -58,7 +58,8 @@ export class Doc extends RefField { @serializable(alias("fields", map(autoObject()))) @observable - private __fields: { [key: string]: Field | FieldWaiting | undefined } = {}; + //{ [key: string]: Field | FieldWaiting | undefined } + private __fields: any = {}; private [Update] = (diff: any) => { DocServer.UpdateField(this[Id], diff); @@ -86,7 +87,7 @@ export namespace Doc { return Cast(Get(doc, key, ignoreProto), ctor) as T | null | undefined; } export async function SetOnPrototype(doc: Doc, key: string, value: Field) { - const proto = await Cast(doc.prototype, Doc); + const proto = await Cast(doc.proto, Doc); if (proto) { proto[key] = value; } @@ -97,7 +98,7 @@ export namespace Doc { } const delegate = new Doc(); //TODO Does this need to be doc[Self]? - delegate.prototype = doc; + delegate.proto = doc; return delegate; } export const Prototype = Symbol("Prototype"); diff --git a/src/new_fields/Schema.ts b/src/new_fields/Schema.ts index 59c6db0bd..5081521c7 100644 --- a/src/new_fields/Schema.ts +++ b/src/new_fields/Schema.ts @@ -1,7 +1,5 @@ import { Interface, ToInterface, Cast, ToConstructor, HasTail, Head, Tail, ListSpec, ToType } from "./Types"; -import { Doc, Field, ObjectField } from "./Doc"; -import { URLField } from "./URLField"; -import { List } from "./List"; +import { Doc, Field } from "./Doc"; type AllToInterface<T extends Interface[]> = { 1: ToInterface<Head<T>> & AllToInterface<Tail<T>>, @@ -15,7 +13,7 @@ export type Document = makeInterface<[typeof emptySchema]>; export type makeInterface<T extends Interface[]> = Partial<AllToInterface<T>> & Doc; // 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[]>(...schemas: T): (doc: Doc) => makeInterface<T> { +export function makeInterface<T extends Interface[]>(...schemas: T): (doc?: Doc) => makeInterface<T> { let schema: Interface = {}; for (const s of schemas) { for (const key in s) { @@ -35,7 +33,8 @@ export function makeInterface<T extends Interface[]>(...schemas: T): (doc: Doc) return true; } }); - return function (doc: Doc) { + return function (doc?: Doc) { + doc = doc || new Doc; if (!(doc instanceof Doc)) { throw new Error("Currently wrapping a schema in another schema isn't supported"); } @@ -73,8 +72,8 @@ export function makeStrictInterface<T extends Interface>(schema: T): (doc: Doc) }; } -export function createSchema<T extends Interface>(schema: T): T & { prototype: ToConstructor<Doc> } { - schema.prototype = Doc; +export function createSchema<T extends Interface>(schema: T): T & { proto: ToConstructor<Doc> } { + schema.proto = Doc; return schema as any; } diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts index fbf002c84..246b0624e 100644 --- a/src/new_fields/Types.ts +++ b/src/new_fields/Types.ts @@ -1,4 +1,4 @@ -import { Field, Opt, FieldWaiting, FieldResult } from "./Doc"; +import { Field, Opt, FieldWaiting, FieldResult, RefField } from "./Doc"; import { List } from "./List"; export type ToType<T extends ToConstructor<Field> | ListSpec<Field>> = @@ -18,7 +18,7 @@ export type ToConstructor<T extends Field> = new (...args: any[]) => T; export type ToInterface<T extends Interface> = { - [P in keyof T]: ToType<T[P]>; + [P in keyof T]: FieldResult<ToType<T[P]>>; }; // type ListSpec<T extends Field[]> = { List: ToContructor<Head<T>> | ListSpec<Tail<T>> }; @@ -37,11 +37,11 @@ export interface Interface { // [key: string]: ToConstructor<Field> | ListSpec<Field[]>; } -export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: Field | FieldWaiting | undefined, ctor: T): FieldResult<ToType<T>>; -export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal: ToType<T>): ToType<T>; -export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: Field | FieldWaiting | undefined, ctor: T, defaultVal?: ToType<T>): FieldResult<ToType<T>> | undefined { +export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T): FieldResult<ToType<T>>; +export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal: ToType<T>): ToType<T>; +export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: FieldResult, ctor: T, defaultVal?: ToType<T>): FieldResult<ToType<T>> | undefined { if (field instanceof Promise) { - return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) : defaultVal; + return defaultVal === undefined ? field.then(f => Cast(f, ctor) as any) as any : defaultVal; } if (field !== undefined && !(field instanceof Promise)) { if (typeof ctor === "string") { |