aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes')
-rw-r--r--src/client/views/nodes/AudioBox.tsx27
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx234
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx62
-rw-r--r--src/client/views/nodes/DocumentView.tsx419
-rw-r--r--src/client/views/nodes/FieldView.tsx73
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss15
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx144
-rw-r--r--src/client/views/nodes/IconBox.scss12
-rw-r--r--src/client/views/nodes/IconBox.tsx40
-rw-r--r--src/client/views/nodes/ImageBox.scss6
-rw-r--r--src/client/views/nodes/ImageBox.tsx78
-rw-r--r--src/client/views/nodes/KeyValueBox.scss1
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx37
-rw-r--r--src/client/views/nodes/KeyValuePair.scss1
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx59
-rw-r--r--src/client/views/nodes/LinkBox.scss23
-rw-r--r--src/client/views/nodes/LinkBox.tsx84
-rw-r--r--src/client/views/nodes/LinkEditor.scss3
-rw-r--r--src/client/views/nodes/LinkEditor.tsx16
-rw-r--r--src/client/views/nodes/LinkMenu.tsx27
-rw-r--r--src/client/views/nodes/PDFBox.scss18
-rw-r--r--src/client/views/nodes/PDFBox.tsx301
-rw-r--r--src/client/views/nodes/Sticky.tsx83
-rw-r--r--src/client/views/nodes/Timeline.tsx18
-rw-r--r--src/client/views/nodes/VideoBox.tsx80
-rw-r--r--src/client/views/nodes/WebBox.scss17
-rw-r--r--src/client/views/nodes/WebBox.tsx38
27 files changed, 910 insertions, 1006 deletions
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/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 01a9f26bf..2ba0458f5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,68 +1,77 @@
-import { computed, trace } from "mobx";
+import { computed, trace, action, reaction, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
-import { KeyStore } from "../../../fields/KeyStore";
-import { NumberField } from "../../../fields/NumberField";
import { Transform } from "../../util/Transform";
-import { DocumentView, DocumentViewProps } from "./DocumentView";
+import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
-import { OmitKeys } from "../../../Utils";
+import { DocComponent } from "../DocComponent";
+import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
+import { FieldValue, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
+import { OmitKeys, Utils } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Doc } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
}
+const schema = createSchema({
+ zoomBasis: "number",
+ zIndex: "number"
+});
+
+//TODO Types: The import order is wrong, so positionSchema is undefined
+type FreeformDocument = makeInterface<[typeof schema, typeof positionSchema]>;
+const FreeformDocument = makeInterface(schema, positionSchema);
+
@observer
-export class CollectionFreeFormDocumentView extends React.Component<CollectionFreeFormDocumentViewProps> {
+export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
private _mainCont = React.createRef<HTMLDivElement>();
+ private _downX: number = 0;
+ private _downY: number = 0;
+ _bringToFrontDisposer?: IReactionDisposer;
- @computed
- get transform(): string {
+ @computed get transform() {
return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
}
- @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); }
- @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
- @computed get width(): number { return this.props.Document.Width(); }
- @computed get height(): number { return this.props.Document.Height(); }
- @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
- @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+ @computed get X() { return FieldValue(this.Document.x, 0); }
+ @computed get Y() { return FieldValue(this.Document.y, 0); }
+ @computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
+ @computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
+ @computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
+ @computed get width(): number { return FieldValue(this.Document.width, 0); }
+ @computed get height(): number { return FieldValue(this.Document.height, 0); }
+ @computed get zIndex(): number { return FieldValue(this.Document.zIndex, 0); }
set width(w: number) {
- this.props.Document.SetData(KeyStore.Width, w, NumberField);
+ this.Document.width = w;
if (this.nativeWidth && this.nativeHeight) {
- this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w);
+ this.Document.height = this.nativeHeight / this.nativeWidth * w;
}
}
-
set height(h: number) {
- this.props.Document.SetData(KeyStore.Height, h, NumberField);
+ this.Document.height = h;
if (this.nativeWidth && this.nativeHeight) {
- this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h);
+ this.Document.width = this.nativeWidth / this.nativeHeight * h;
}
}
-
set zIndex(h: number) {
- this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
- }
-
- get X() {
- return this.props.Document.GetNumber(KeyStore.X, 0);
- }
- get Y() {
- return this.props.Document.GetNumber(KeyStore.Y, 0);
+ this.Document.zIndex = h;
}
- getTransform = (): Transform =>
- this.props.ScreenToLocalTransform()
- .translate(-this.X, -this.Y)
- .scale(1 / this.contentScaling()).scale(1 / this.zoom)
- contentScaling = () => (this.nativeWidth > 0 ? this.width / this.nativeWidth : 1);
+ contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
panelWidth = () => this.props.PanelWidth();
panelHeight = () => this.props.PanelHeight();
+ toggleMinimized = () => this.toggleIcon();
+ getTransform = (): Transform => this.props.ScreenToLocalTransform()
+ .translate(-this.X, -this.Y)
+ .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@computed
get docView() {
- return <DocumentView {...OmitKeys(this.props, ['zoomFade'])}
+ return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
+ toggleMinimized={this.toggleMinimized}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
PanelWidth={this.panelWidth}
@@ -70,30 +79,147 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
/>;
}
+ componentDidMount() {
+ this._bringToFrontDisposer = reaction(() => this.props.Document.isIconAnimating, (values) => {
+ this.props.bringToFront(this.props.Document);
+ if (values instanceof List) {
+ let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]);
+ this.animateBetweenIcon(true, scrpt, [values[2], values[3]], values[4], values[5], values[6], this.props.Document, values[7] ? true : false);
+ }
+ });
+ }
+
+ componentWillUnmount() {
+ if (this._bringToFrontDisposer) this._bringToFrontDisposer();
+ }
+
+ animateBetweenIcon(first: boolean, 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.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.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing);
+ }
+ else {
+ if (!maximizing) {
+ target.isMinimized = true;
+ target.x = targ[0];
+ target.y = targ[1];
+ target.width = width;
+ target.height = height;
+ }
+ target.isIconAnimating = undefined;
+ }
+ },
+ 2);
+ }
+ @action
+ public toggleIcon = async (): Promise<void> => {
+ SelectionManager.DeselectAll();
+ let isMinimized: boolean | undefined;
+ let maximizedDocs = await Cast(this.props.Document.maximizedDocs, listSpec(Doc));
+ let minimizedDoc: Doc | undefined = this.props.Document;
+ if (!maximizedDocs) {
+ minimizedDoc = await Cast(this.props.Document.minimizedDoc, Doc);
+ if (minimizedDoc) maximizedDocs = await Cast(minimizedDoc.maximizedDocs, listSpec(Doc));
+ }
+ if (minimizedDoc && maximizedDocs && maximizedDocs instanceof List) {
+ let minimizedTarget = minimizedDoc;
+ maximizedDocs.map(maximizedDoc => {
+ let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
+ if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
+ if (isMinimized === undefined) {
+ isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+ }
+ let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2;
+ let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2;
+ let maxx = NumCast(maximizedDoc.x, undefined);
+ let maxy = NumCast(maximizedDoc.y, undefined);
+ let maxw = NumCast(maximizedDoc.width, undefined);
+ let maxh = NumCast(maximizedDoc.height, undefined);
+ if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
+ maxw !== undefined && maxh !== undefined) {
+ let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
+ maximizedDoc.isMinimized = false;
+ maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), isMinimized ? 1 : 0])
+ }
+ }
+ });
+ }
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ e.stopPropagation();
+ }
+ onClick = async (e: React.MouseEvent) => {
+ e.stopPropagation();
+ let ctrlKey = e.ctrlKey;
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (await BoolCast(this.props.Document.isButton, false)) {
+ let maximizedDocs = await Cast(this.props.Document.maximizedDocs, listSpec(Doc));
+ if (maximizedDocs) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ if (ctrlKey)
+ this.props.addDocument && maximizedDocs.filter(d => d instanceof Doc).map(maxDoc => this.props.addDocument!(maxDoc, false));
+ this.toggleIcon();
+ }
+ }
+ }
+ }
+
+ onPointerEnter = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = true; }
+ onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }
+
+ borderRounding = () => {
+ let br = NumCast(this.props.Document.borderRounding);
+ return br >= 0 ? br :
+ NumCast(this.props.Document.nativeWidth) === 0 ?
+ Math.min(this.props.PanelWidth(), this.props.PanelHeight())
+ : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0);
+ }
+
render() {
+ let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDocs, listSpec(Doc)));
let zoomFade = 1;
- //var zoom = doc.GetNumber(KeyStore.Zoom, 1);
- // let transform = this.getTransform().scale(this.contentScaling()).inverse();
- // var [sptX, sptY] = transform.transformPoint(0, 0);
- // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
- // let w = bptX - sptX;
- // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
- // let fadeUp = .75 * 1800;
- // let fadeDown = .075 * 1800;
- // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+ //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1);
+ let transform = this.getTransform().scale(this.contentScaling()).inverse();
+ var [sptX, sptY] = transform.transformPoint(0, 0);
+ let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
+ let w = bptX - sptX;
+ //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
+ const screenWidth = Math.min(50 * NumCast(this.props.Document.nativeWidth, 0), 1800);
+ let fadeUp = .75 * screenWidth;
+ let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
+ zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
return (
- <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
- opacity: zoomFade,
- transformOrigin: "left top",
- transform: this.transform,
- pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
- width: this.width,
- height: this.height,
- position: "absolute",
- zIndex: this.zIndex,
- backgroundColor: "transparent"
- }} >
+ <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
+ onPointerDown={this.onPointerDown}
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+ onClick={this.onClick}
+ style={{
+ outlineColor: "black",
+ outlineStyle: "dashed",
+ outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ? `${0.5 / this.contentScaling()}px` : "0px",
+ opacity: zoomFade,
+ borderRadius: `${this.borderRounding()}px`,
+ transformOrigin: "left top",
+ transform: this.transform,
+ pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
+ width: this.width,
+ height: this.height,
+ position: "absolute",
+ zIndex: this.zIndex,
+ backgroundColor: "transparent"
+ }} >
{this.docView}
</div>
);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 07599c345..bbc927b5a 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";
@@ -19,53 +15,67 @@ import { IconBox } from "./IconBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { VideoBox } from "./VideoBox";
+import { FieldView } from "./FieldView";
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, StrCast } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
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;
}
+class ObserverJsxParser1 extends JsxParser {
+ constructor(props: any) {
+ super(props);
+ observer(this as any);
+ }
+}
+
+const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
+
@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) };
+ return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
+ }
- 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;
+ @computed get templates(): List<string> {
+ let field = this.props.Document.templates;
+ if (field && field instanceof List) {
+ return field;
}
- return bindings;
+ return new List<string>();
+ }
+ set templates(templates: List<string>) { this.props.Document.templates = templates; }
+ get finalLayout() {
+ const baseLayout = this.layout;
+ let base = baseLayout;
+ let layout = baseLayout;
+
+ this.templates.forEach(template => {
+ layout = template.replace("{layout}", base);
+ base = layout;
+ });
+ return layout;
}
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 }}
+ return <ObserverJsxParser
+ components={{ FormattedTextBox, ImageBox, IconBox, FieldView, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
bindings={this.CreateBindings()}
- jsx={this.layout}
+ jsx={this.finalLayout}
showWarnings={true}
onError={(test: any) => { console.log(test); }}
/>;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 0691e8c30..fd012e7ea 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,15 +1,9 @@
import { action, computed, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { Field, Opt } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from "../../../fields/KeyStore";
-import { ListField } from "../../../fields/ListField";
-import { ServerUtils } from "../../../server/ServerUtil";
import { emptyFunction, Utils } from "../../../Utils";
-import { Documents } from "../../documents/Documents";
+import { Docs } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
+import { DragManager, dropActionType } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { undoBatch, UndoManager } from "../../util/UndoManager";
@@ -18,16 +12,35 @@ 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");
-import { TextField } from "../../../fields/TextField";
-import { string } from "prop-types";
-import { NumberField } from "../../../fields/NumberField";
+import { Opt, Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { DocComponent } from "../DocComponent";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { FieldValue, StrCast, BoolCast } from "../../../new_fields/Types";
+import { List } from "../../../new_fields/List";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { DocServer } from "../../DocServer";
+import { Id } from "../../../new_fields/RefField";
+import { PresentationView } from "../PresentationView";
+
+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>;
- Document: Document;
+ Document: Doc;
addDocument?: (doc: Document, allowDuplicates?: boolean) => boolean;
removeDocument?: (doc: Document) => boolean;
moveDocument?: (doc: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
@@ -40,50 +53,34 @@ export interface DocumentViewProps {
selectOnLoad: boolean;
parentActive: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
-}
-export interface JsxArgs extends DocumentViewProps {
- Keys: { [name: string]: Key };
- Fields: { [name: string]: Field };
+ toggleMinimized: () => void;
+ bringToFront: (doc: Doc) => void;
}
-/*
-This function is pretty much a hack that lets us fill out the fields in JsxArgs with something that
-jsx-to-string can recover the jsx from
-Example usage of this function:
- public static LayoutString() {
- let args = FakeJsxArgs(["Data"]);
- return jsxToString(
- <CollectionFreeFormView
- doc={args.Document}
- fieldKey={args.Keys.Data}
- DocumentViewForField={args.DocumentView} />,
- { useFunctionCode: true, functionNameOnly: true }
- )
- }
-*/
-export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
- let Keys: { [name: string]: any } = {};
- let Fields: { [name: string]: any } = {};
- for (const key of keys) {
- Object.defineProperty(emptyFunction, "name", { value: key + "Key" });
- Keys[key] = emptyFunction;
- }
- for (const field of fields) {
- Object.defineProperty(emptyFunction, "name", { value: field });
- Fields[field] = emptyFunction;
- }
- let args: JsxArgs = {
- Document: function Document() { },
- DocumentView: function DocumentView() { },
- Keys,
- Fields
- } as any;
- return args;
-}
+const schema = createSchema({
+ layout: "string",
+ nativeWidth: "number",
+ nativeHeight: "number",
+ backgroundColor: "string"
+});
+
+export const positionSchema = createSchema({
+ nativeWidth: "number",
+ nativeHeight: "number",
+ width: "number",
+ height: "number",
+ x: "number",
+ y: "number",
+});
+
+export type PositionDocument = makeInterface<[typeof positionSchema]>;
+export const PositionDocument = makeInterface(positionSchema);
+
+type Document = makeInterface<[typeof schema]>;
+const Document = makeInterface(schema);
@observer
-export class DocumentView extends React.Component<DocumentViewProps> {
- static _incompleteAnimations: Map<string, boolean> = new Map<string, boolean>();
+export class DocumentView extends DocComponent<DocumentViewProps, Document>(Document) {
private _downX: number = 0;
private _downY: number = 0;
private _mainCont = React.createRef<HTMLDivElement>();
@@ -92,43 +89,26 @@ export class DocumentView extends React.Component<DocumentViewProps> {
public get ContentDiv() { return this._mainCont.current; }
@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>()); }
-
- onPointerDown = (e: React.PointerEvent): void => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- if (e.button === 2 && !this.isSelected()) {
- return;
- }
- if (e.shiftKey && e.buttons === 2) {
- if (this.props.isTopMost) {
- this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
- } else {
- CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
- }
- e.stopPropagation();
- } else {
- if (this.active) {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointerup", this.onPointerUp);
- }
+ @computed get templates(): List<string> {
+ let field = this.props.Document.templates;
+ if (field && field instanceof List) {
+ return field;
}
+ return new List<string>();
}
+ set templates(templates: List<string>) { this.props.Document.templates = templates; }
+ screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
+ @action
componentDidMount() {
if (this._mainCont.current) {
this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
handlers: { drop: this.drop.bind(this) }
});
}
- runInAction(() => DocumentManager.Instance.DocumentViews.push(this));
+ DocumentManager.Instance.DocumentViews.push(this);
}
-
+ @action
componentDidUpdate() {
if (this._dropDisposer) {
this._dropDisposer();
@@ -139,230 +119,147 @@ export class DocumentView extends React.Component<DocumentViewProps> {
});
}
}
-
+ @action
componentWillUnmount() {
if (this._dropDisposer) {
this._dropDisposer();
}
- runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1));
+ DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+ }
+
+ stopPropagation = (e: React.SyntheticEvent) => {
+ e.stopPropagation();
}
- startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
+ startDragging(x: number, y: number, dropAction: dropActionType) {
if (this._mainCont.current) {
- const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
let dragData = new DragManager.DocumentDragData([this.props.Document]);
- dragData.aliasOnDrop = dropAliasOfDraggedDoc;
- dragData.xOffset = x - left;
- dragData.yOffset = y - top;
+ const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
+ dragData.dropAction = dropAction;
+ dragData.xOffset = xoff;
+ dragData.yOffset = yoff;
dragData.moveDocument = this.props.moveDocument;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
dragComplete: action(emptyFunction)
},
- hideSource: !dropAliasOfDraggedDoc
+ hideSource: !dropAction
});
}
}
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) {
+ onClick = (e: React.MouseEvent): void => {
+ if (CurrentUserUtils.MainDocId !== this.props.Document[Id] &&
+ (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
return;
}
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ if (e.shiftKey && e.buttons === 1) {
+ if (this.props.isTopMost) {
+ this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined);
+ } else {
+ CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
+ }
+ e.stopPropagation();
+ } else if (this.active) {
document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- if (!e.altKey && (!this.topMost || e.buttons === 2)) {
- this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
+ onPointerMove = (e: PointerEvent): void => {
+ if (!e.cancelBubble) {
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
+ this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined);
+ }
}
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
}
- e.stopPropagation();
- e.preventDefault();
}
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
- if (!SelectionManager.IsSelected(this) && e.button !== 2 &&
- Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
- this.props.Document.GetTAsync(KeyStore.MaximizedDoc, Document).then(maxdoc => {
- if (maxdoc instanceof Document) {
- this.props.addDocument && this.props.addDocument(maxdoc, false);
- this.toggleIcon();
- } else {
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
- });
- }
- }
- stopPropagation = (e: React.SyntheticEvent) => {
- e.stopPropagation();
}
deleteClicked = (): void => {
this.props.removeDocument && this.props.removeDocument(this.props.Document);
}
-
fieldsClicked = (e: React.MouseEvent): void => {
- if (this.props.addDocument) {
- this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
+ let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 });
+ CollectionDockingView.Instance.AddRightSplit(kvp);
+ }
+ makeButton = (e: React.MouseEvent): void => {
+ this.props.Document.isButton = !BoolCast(this.props.Document.isButton, false);
+ if (this.props.Document.isButton && !this.props.Document.nativeWidth) {
+ this.props.Document.nativeWidth = this.props.Document[WidthSym]();
+ this.props.Document.nativeHeight = this.props.Document[HeightSym]();
}
}
fullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.Instance.OpenFullScreen((this.props.Document.GetPrototype() as Document).MakeDelegate());
- ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- }
-
- closeFullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.Instance.CloseFullScreen();
+ const doc = Doc.MakeDelegate(FieldValue(this.Document.proto));
+ if (doc) {
+ CollectionDockingView.Instance.OpenFullScreen(doc);
+ }
ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- }
-
- @action createIcon = (layoutString: string): Document => {
- let iconDoc = Documents.IconDocument(layoutString);
- iconDoc.SetText(KeyStore.Title, "ICON" + this.props.Document.Title);
- iconDoc.SetBoolean(KeyStore.IsMinimized, false);
- iconDoc.SetNumber(KeyStore.NativeWidth, 0);
- iconDoc.SetNumber(KeyStore.NativeHeight, 0);
- iconDoc.SetNumber(KeyStore.X, this.props.Document.GetNumber(KeyStore.X, 0));
- iconDoc.SetNumber(KeyStore.Y, this.props.Document.GetNumber(KeyStore.Y, 0) - 24);
- iconDoc.Set(KeyStore.Prototype, this.props.Document);
- iconDoc.Set(KeyStore.MaximizedDoc, this.props.Document);
- this.props.Document.Set(KeyStore.MinimizedDoc, iconDoc);
- this.props.addDocument && this.props.addDocument(iconDoc, false);
- return iconDoc;
- }
-
- animateBetweenIcon(icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, 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]);
- if (now < stime + 200) {
- this.animateBetweenIcon(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);
- }
- DocumentView._incompleteAnimations.set(target.Id, false);
- }
- },
- 2);
- }
-
- @action
- public toggleIcon = async (): Promise<void> => {
SelectionManager.DeselectAll();
- let isMinimized: boolean | undefined;
- let minDoc = await this.props.Document.GetTAsync(KeyStore.MinimizedDoc, Document);
- if (!minDoc) return;
- let minimizedDocSet = await minDoc.GetTAsync(KeyStore.LinkTags, ListField);
- if (!minimizedDocSet) return;
- minimizedDocSet.Data.map(async minimizedDoc => {
- if (minimizedDoc instanceof Document) {
- this.props.addDocument && this.props.addDocument(minimizedDoc, false);
- let maximizedDoc = await minimizedDoc.GetTAsync(KeyStore.MaximizedDoc, Document);
- if (maximizedDoc instanceof Document && !DocumentView._incompleteAnimations.get(maximizedDoc.Id)) {
- DocumentView._incompleteAnimations.set(maximizedDoc.Id, true);
- isMinimized = isMinimized === undefined ? maximizedDoc.GetBoolean(KeyStore.IsMinimized, false) : isMinimized;
- maximizedDoc.SetBoolean(KeyStore.IsMinimized, false);
- let minx = await minimizedDoc.GetTAsync(KeyStore.X, NumberField);
- let miny = await minimizedDoc.GetTAsync(KeyStore.Y, NumberField);
- let maxx = await maximizedDoc.GetTAsync(KeyStore.X, NumberField);
- let maxy = await maximizedDoc.GetTAsync(KeyStore.Y, NumberField);
- let maxw = await maximizedDoc.GetTAsync(KeyStore.Width, NumberField);
- let maxh = await maximizedDoc.GetTAsync(KeyStore.Height, NumberField);
- if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
- maxw !== undefined && maxh !== undefined) {
- this.animateBetweenIcon(
- [minx.Data, miny.Data], [maxx.Data, maxy.Data], maxw.Data, maxh.Data,
- Date.now(), maximizedDoc, isMinimized);
- }
- }
-
- }
- });
}
-
- @action
- public getIconDoc = async (): Promise<Document | undefined> => {
- return this.props.Document.GetTAsync(KeyStore.MinimizedDoc, Document).then(async mindoc =>
- mindoc ? mindoc :
- this.props.Document.GetTAsync(KeyStore.BackgroundLayout, TextField).then(async field =>
- (field instanceof TextField) ? this.createIcon(field.Data) :
- this.props.Document.GetTAsync(KeyStore.Layout, TextField).then(field =>
- (field instanceof TextField) ? this.createIcon(field.Data) : undefined)));
- }
-
@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 sourceDoc = de.data.linkSourceDocument;
+ let destDoc = this.props.Document;
- 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());
- })
- )
- );
+ const protoDest = destDoc.proto;
+ const protoSrc = sourceDoc.proto;
+ Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
e.stopPropagation();
}
}
+ @action
onDrop = (e: React.DragEvent) => {
let text = e.dataTransfer.getData("text/plain");
if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
- let oldLayout = this.props.Document.GetText(KeyStore.Layout, "");
+ let oldLayout = FieldValue(this.Document.layout) || "";
let layout = text.replace("{layout}", oldLayout);
- this.props.Document.SetText(KeyStore.Layout, layout);
+ this.Document.layout = layout;
e.stopPropagation();
e.preventDefault();
}
}
+
+ @action
+ addTemplate = (template: Template) => {
+ this.templates.push(template.Layout);
+ this.templates = this.templates;
+ }
+
+ @action
+ removeTemplate = (template: Template) => {
+ for (let i = 0; i < this.templates.length; i++) {
+ if (this.templates[i] === template.Layout) {
+ this.templates.splice(i, 1);
+ break;
+ }
+ }
+ this.templates = this.templates;
+ }
+
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
@@ -374,26 +271,32 @@ export class DocumentView extends React.Component<DocumentViewProps> {
e.preventDefault();
ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
+ ContextMenu.Instance.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
- ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)) });
- ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document.Id) });
+ ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])) });
+ ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
//ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+ ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) });
ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
+ if (!this.topMost) {
+ // DocumentViews should stop propagation of this event
+ e.stopPropagation();
+ }
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
if (!SelectionManager.IsSelected(this)) {
SelectionManager.SelectDoc(this, false);
}
}
+
isSelected = () => SelectionManager.IsSelected(this);
select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
- @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
- @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
- @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); }
-
+ @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+ @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); }
render() {
var scaling = this.props.ContentScaling();
@@ -404,14 +307,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
<div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
- background: this.props.Document.GetText(KeyStore.BackgroundColor, ""),
- width: nativeWidth, height: nativeHeight,
+ borderRadius: "inherit",
+ background: this.Document.backgroundColor || "",
+ width: nativeWidth,
+ height: nativeHeight,
transform: `scale(${scaling}, ${scaling})`
}}
- onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}
+ onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
>
{this.contents}
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 93e385821..a1e083b36 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -1,30 +1,22 @@
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 } 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, FieldResult } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField";
+import { IconField } from "../../../new_fields/IconField";
+import { RichTextField } from "../../../new_fields/RichTextField";
//
@@ -33,41 +25,44 @@ 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;
selectOnLoad: boolean;
- addDocument?: (document: Document, allowDuplicates?: boolean) => boolean;
- removeDocument?: (document: Document) => boolean;
- moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+ addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
+ removeDocument?: (document: Doc) => boolean;
+ moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
- focus: (doc: Document) => void;
+ focus: (doc: Doc) => void;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
+ setVideoBox?: (player: VideoBox) => void;
}
@observer
export class FieldView extends React.Component<FieldViewProps> {
- public static LayoutString(fieldType: { name: string }, fieldStr: string = "DataKey") {
- return `<${fieldType.name} {...props} fieldKey={${fieldStr}} />`;
+ public static LayoutString(fieldType: { name: string }, fieldStr: string = "data") {
+ return `<${fieldType.name} {...props} fieldKey={"${fieldStr}"} />`;
}
@computed
- get field(): FieldValue<Field> {
- const { Document: doc, fieldKey } = this.props;
- return doc.Get(fieldKey);
+ get field(): FieldResult {
+ const { Document, fieldKey } = this.props;
+ return Document[fieldKey];
}
render() {
const field = this.field;
- if (!field) {
+ if (field === undefined) {
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 +78,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 +89,27 @@ export class FieldView extends React.Component<FieldViewProps> {
PanelHeight={() => 100}
isTopMost={true} //TODO Why is this top most?
selectOnLoad={false}
- focus={emptyDocFunction}
- isSelected={returnFalse}
+ focus={emptyFunction}
+ isSelected={this.props.isSelected}
select={returnFalse}
- layoutKey={KeyStore.Layout}
+ layoutKey={"layout"}
ContainingCollectionView={this.props.ContainingCollectionView}
parentActive={this.props.active}
+ toggleMinimized={emptyFunction}
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 (field !== FieldWaiting) {
- return <p>{JSON.stringify(field.GetValue())}</p>;
+ else if (!(field instanceof Promise)) {
+ return <p>{JSON.stringify(field)}</p>;
}
else {
return <p> {"Waiting for server..."} </p>;
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 5eb2bf7ce..d43aa4e02 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -10,11 +10,11 @@
outline: none !important;
}
-.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
+.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
background: $light-color-secondary;
- padding: 0.9em;
+ padding: 0;
border-width: 0px;
- border-radius: $border-radius;
+ border-radius: inherit;
border-color: $intermediate-color;
box-sizing: border-box;
border-style: solid;
@@ -24,10 +24,19 @@
height: 100%;
pointer-events: all;
}
+
.formattedTextBox-cont-hidden {
overflow: hidden;
pointer-events: none;
}
+.formattedTextBox-inner-rounded {
+ height: calc(100% - 25px);
+ width: calc(100% - 40px);
+ position: absolute;
+ overflow: auto;
+ top: 15;
+ left: 20;
+}
.menuicon {
display: inline-block;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ae05c2dad..eeb60cb3d 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,14 +1,9 @@
-import { action, IReactionDisposer, reaction, trace, computed } from "mobx";
+import { action, IReactionDisposer, reaction, trace, computed, _allowStateChangesInsideComputed } from "mobx";
import { baseKeymap } from "prosemirror-commands";
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,13 +15,20 @@ 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, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { observer } from "mobx-react";
+import { InkingControl } from "../InkingControl";
+import { StrCast, Cast, NumCast, BoolCast } from "../../../new_fields/Types";
+import { RichTextField } from "../../../new_fields/RichTextField";
+import { Id } from "../../../new_fields/RefField";
const { buildMenuItems } = require("prosemirror-example-setup");
const { menuBar } = require("prosemirror-menu");
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
-// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name + "Key"}
+// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name}
//
// In Code, the node's HTML is specified in the document's parameterized structure as:
// document.SetField(KeyStore.Layout, "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />");
@@ -45,12 +47,20 @@ export interface FormattedTextBoxOverlay {
isOverlay?: boolean;
}
+const richTextSchema = createSchema({
+ documentText: "string"
+});
+
+type RichTextDocument = makeInterface<[typeof richTextSchema]>;
+const RichTextDocument = makeInterface(richTextSchema);
+
@observer
-export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> {
- public static LayoutString(fieldStr: string = "DataKey") {
+export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) {
+ public static LayoutString(fieldStr: string = "data") {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
private _ref: React.RefObject<HTMLDivElement>;
+ private _proseRef: React.RefObject<HTMLDivElement>;
private _editorView: Opt<EditorView>;
private _gotDown: boolean = false;
private _reactionDisposer: Opt<IReactionDisposer>;
@@ -61,24 +71,27 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
super(props);
this._ref = React.createRef();
- this.onChange = this.onChange.bind(this);
+ this._proseRef = React.createRef();
}
_applyingChange: boolean = false;
+ _lastState: any = undefined;
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
- const state = this._editorView.state.apply(tx);
+ const state = this._lastState = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this._applyingChange = true;
- this.props.Document.SetDataOnPrototype(
- this.props.fieldKey,
- 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, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
+ 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);
+ let title = StrCast(this.props.Document.title);
+ if (title && title.startsWith("-") && this._editorView) {
+ let str = this._editorView.state.doc.textContent;
+ let titlestr = str.substr(0, Math.min(40, str.length));
+ let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+ };
}
}
@@ -87,10 +100,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
schema,
inpRules, //these currently don't do anything, but could eventually be helpful
plugins: this.props.isOverlay ? [
+ this.tooltipTextMenuPlugin(),
history(),
keymap(buildKeymap(schema)),
keymap(baseKeymap),
- this.tooltipTextMenuPlugin(),
// this.tooltipLinkingMenuPlugin(),
new Plugin({
props: {
@@ -105,7 +118,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
};
if (this.props.isOverlay) {
- this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc.Id,
+ this._inputReactionDisposer = reaction(() => MainOverlayTextBox.Instance.TextDoc && MainOverlayTextBox.Instance.TextDoc[Id],
() => {
if (this._editorView) {
this._editorView.destroy();
@@ -115,14 +128,14 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
);
} else {
this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
- () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()));
+ () => this.props.isSelected() && !BoolCast(this.props.Document.isButton, false) && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
}
this._reactionDisposer = reaction(
() => {
- const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
- return field && field !== FieldWaiting ? field.Data : undefined;
+ const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined;
+ return field ? field.Data : undefined;
},
field => field && this._editorView && !this._applyingChange &&
this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
@@ -130,10 +143,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
this.setupEditor(config, this.props.Document);
}
- private setupEditor(config: any, doc?: Document) {
- let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined;
- if (this._ref.current) {
- this._editorView = new EditorView(this._ref.current, {
+ private setupEditor(config: any, doc?: Doc) {
+ let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined;
+ if (this._proseRef.current) {
+ this._editorView = new EditorView(this._proseRef.current, {
state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
dispatchTransaction: this.dispatchTransaction
});
@@ -160,25 +173,20 @@ 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");
+ if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
+ if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
+ this._toolTipTextMenu.tooltip.style.opacity = "0";
}
- if (e.button === 2) {
+ if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
this._gotDown = true;
- console.log("second");
e.preventDefault();
}
}
onPointerUp = (e: React.PointerEvent): void => {
- console.log("pointer up");
+ if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
+ this._toolTipTextMenu.tooltip.style.opacity = "1";
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
@@ -186,24 +194,32 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
onFocused = (e: React.FocusEvent): void => {
if (!this.props.isOverlay) {
- MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform());
+ MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
} else {
- if (this._ref.current) {
- this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
+ if (this._proseRef.current) {
+ this._proseRef.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
}
}
}
//REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
- textCapability = (e: React.MouseEvent): void => { };
+ textCapability = (e: React.MouseEvent): void => {
+ if (NumCast(this.props.Document.nativeWidth)) {
+ this.props.Document.nativeWidth = undefined;
+ this.props.Document.nativeHeight = undefined;
+ } else {
+ this.props.Document.nativeWidth = this.props.Document[WidthSym]();
+ this.props.Document.nativeHeight = this.props.Document[HeightSym]();
+ }
+ }
specificContextMenu = (e: React.MouseEvent): void => {
if (!this._gotDown) {
e.preventDefault();
return;
}
ContextMenu.Instance.addItem({
- description: "Text Capability",
+ description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze",
event: this.textCapability
});
@@ -227,15 +243,26 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
}
}
+ onClick = (e: React.MouseEvent): void => {
+ this._proseRef.current!.focus();
+ }
+ onMouseDown = (e: React.MouseEvent): void => {
+ if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself
+ e.preventDefault(); // bcz: this would normally be in OnPointerDown - however, if done there, no mouse move events will be generated which makes transititioning to GoldenLayout's drag interactions impossible
+ }
+ }
+
tooltipTextMenuPlugin() {
let myprops = this.props;
+ let self = this;
return new Plugin({
view(_editorView) {
- return new TooltipTextMenu(_editorView, myprops);
+ return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
}
});
}
+ _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
tooltipLinkingMenuPlugin() {
let myprops = this.props;
return new Plugin({
@@ -245,29 +272,46 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
});
}
- onKeyPress(e: React.KeyboardEvent) {
- if (e.key == "Escape") {
+ onKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key === "Escape") {
SelectionManager.DeselectAll();
}
e.stopPropagation();
- if (e.keyCode === 9) e.preventDefault();
+ if (e.key === "Tab") e.preventDefault();
// stop propagation doesn't seem to stop propagation of native keyboard events.
// so we set a flag on the native event that marks that the event's been handled.
- // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+ (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+ if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
+ let str = this._editorView.state.doc.textContent;
+ let titlestr = str.substr(0, Math.min(40, str.length));
+ let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
+ target.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+ }
}
render() {
let style = this.props.isOverlay ? "scroll" : "hidden";
+ let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
+ let color = StrCast(this.props.Document.backgroundColor);
+ let interactive = InkingControl.Instance.selectedTool ? "" : "interactive";
return (
- <div className={`formattedTextBox-cont-${style}`}
+ <div className={`formattedTextBox-cont-${style}`} ref={this._ref}
+ style={{
+ pointerEvents: interactive ? "all" : "none",
+ background: color,
+ }}
onKeyDown={this.onKeyPress}
onKeyPress={this.onKeyPress}
onFocus={this.onFocused}
+ onClick={this.onClick}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
+ onMouseDown={this.onMouseDown}
onContextMenu={this.specificContextMenu}
// tfs: do we need this event handler
onWheel={this.onPointerWheel}
- ref={this._ref} />
+ >
+ <div className={`formattedTextBox-inner${rounded}`} style={{ pointerEvents: this.props.Document.isButton ? "none" : "all" }} ref={this._proseRef} />
+ </div>
);
}
}
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
index ce0ee2e09..85bbdeb59 100644
--- a/src/client/views/nodes/IconBox.scss
+++ b/src/client/views/nodes/IconBox.scss
@@ -4,9 +4,19 @@
position: absolute;
left:0;
top:0;
+ height: 100%;
+ width: max-content;
+ // overflow: hidden;
+ pointer-events: all;
svg {
- width: 100% !important;
+ width: $MINIMIZED_ICON_SIZE !important;
height: 100%;
background: white;
}
+ .iconBox-label {
+ position: inherit;
+ width:max-content;
+ font-size: 14px;
+ margin-top: 3px;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 9c90c0a0e..3fab10df4 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -2,14 +2,18 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Document } from '../../../fields/Document';
-import { IconField } from "../../../fields/IconFIeld";
-import { KeyStore } from "../../../fields/KeyStore";
import { SelectionManager } from "../../util/SelectionManager";
import { FieldView, FieldViewProps } from './FieldView';
import "./IconBox.scss";
+import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc";
+import { IconField } from "../../../new_fields/IconField";
+import { ContextMenu } from "../ContextMenu";
+import Measure from "react-measure";
+import { MINIMIZED_ICON_SIZE } from "../../views/globalCssVariables.scss";
+import { listSpec } from "../../../new_fields/Schema";
library.add(faCaretUp);
@@ -22,8 +26,7 @@ library.add(faFilm);
export class IconBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(IconBox); }
- @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); }
- @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "<p>Error loading layout data</p>" as string); }
+ @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
@computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
public static DocumentIcon(layout: string) {
@@ -33,13 +36,34 @@ export class IconBox extends React.Component<FieldViewProps> {
layout.indexOf("Video") !== -1 ? faFilm :
layout.indexOf("Collection") !== -1 ? faObjectGroup :
faCaretUp;
- return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
}
+ setLabelField = (e: React.MouseEvent): void => {
+ this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
+ }
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ ContextMenu.Instance.addItem({
+ description: BoolCast(this.props.Document.hideLabel) ? "show label" : "hide label",
+ event: this.setLabelField
+ });
+ }
+ @observable _panelWidth: number = 0;
+ @observable _panelHeight: number = 0;
render() {
+ let labelField = StrCast(this.props.Document.labelField);
+ let hideLabel = BoolCast(this.props.Document.hideLabel);
+ let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []);
+ let label = !hideLabel && maxDoc && labelField ? (maxDoc.length === 1 ? maxDoc[0][labelField] : this.props.Document[labelField]) : "";
return (
- <div className="iconBox-container">
+ <div className="iconBox-container" onContextMenu={this.specificContextMenu}>
{this.minimizedIcon}
+ <Measure onResize={(r) => runInAction(() => { if (r.entry.width || BoolCast(this.props.Document.hideLabel)) this.props.Document.nativeWidth = this.props.Document.width = (r.entry.width + Number(MINIMIZED_ICON_SIZE)); })}>
+ {({ measureRef }) =>
+ <span ref={measureRef} className="iconBox-label">{label}</span>
+ }
+ </Measure>
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index f4b3837ff..2316a050e 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -6,6 +6,12 @@
height: auto;
max-width: 100%;
max-height: 100%;
+ pointer-events: none;
+}
+.imageBox-cont-interactive {
+ pointer-events: all;
+ width:100%;
+ height:auto;
}
.imageBox-dot {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index ce855384c..0e9e904a8 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,12 +10,27 @@ import { ContextMenu } from "../../views/ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
+import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
+import { DocComponent } from '../DocComponent';
+import { positionSchema } from './DocumentView';
+import { FieldValue, Cast, StrCast } from '../../../new_fields/Types';
+import { ImageField } from '../../../new_fields/URLField';
+import { List } from '../../../new_fields/List';
+import { InkingControl } from '../InkingControl';
+import { Doc } from '../../../new_fields/Doc';
+
+export const pageSchema = createSchema({
+ curPage: "number"
+});
+
+type ImageDocument = makeInterface<[typeof pageSchema, typeof positionSchema]>;
+const ImageDocument = makeInterface(pageSchema, 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>;
+ private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
@@ -28,19 +38,13 @@ export class ImageBox extends React.Component<FieldViewProps> {
@observable private _isOpen: boolean = false;
private dropDisposer?: DragManager.DragDropDisposer;
- constructor(props: FieldViewProps) {
- super(props);
-
- this._imgRef = React.createRef();
- }
-
@action
onLoad = (target: any) => {
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;
}
}
@@ -63,19 +67,18 @@ export class ImageBox extends React.Component<FieldViewProps> {
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.DocumentDragData) {
- de.data.droppedDocuments.map(action((drop: Document) => {
- let layout = drop.GetText(KeyStore.BackgroundLayout, "");
+ de.data.droppedDocuments.forEach(action((drop: Doc) => {
+ let layout = StrCast(drop.backgroundLayout);
if (layout.indexOf(ImageBox.name) !== -1) {
- let imgData = this.props.Document.Get(KeyStore.Data);
- if (imgData instanceof ImageField && imgData) {
- this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData]));
+ let imgData = this.props.Document[this.props.fieldKey];
+ if (imgData instanceof ImageField) {
+ Doc.SetOnPrototype(this.props.Document, "data", new List([imgData]));
}
- let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]);
+ let imgList = Cast(this.props.Document[this.props.fieldKey], listSpec(ImageField), [] as any[]);
if (imgList) {
- let field = drop.Get(KeyStore.Data);
- if (field === FieldWaiting) { }
- else if (field instanceof ImageField) imgList.push(field);
- else if (field instanceof ListField) imgList.push(field.Data);
+ let field = drop.data;
+ if (field instanceof ImageField) imgList.push(field);
+ else if (field instanceof List) imgList.concat(field);
}
e.stopPropagation();
}
@@ -87,7 +90,6 @@ export class ImageBox extends React.Component<FieldViewProps> {
onPointerDown = (e: React.PointerEvent): void => {
if (Date.now() - this._lastTap < 300) {
if (e.buttons === 1) {
- e.stopPropagation();
this._downX = e.clientX;
this._downY = e.clientY;
document.removeEventListener("pointerup", this.onPointerUp);
@@ -126,9 +128,9 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
specificContextMenu = (e: React.MouseEvent): void => {
- let field = this.props.Document.GetT(this.props.fieldKey, ImageField);
- if (field && field !== FieldWaiting) {
- let url = field.Data.href;
+ let field = Cast(this.Document[this.props.fieldKey], ImageField);
+ if (field) {
+ let url = field.url.href;
ContextMenu.Instance.addItem({
description: "Copy path", event: () => {
Utils.CopyText(url);
@@ -140,29 +142,29 @@ 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) =>
<div className="imageBox-placer" key={i} >
- <div className="imageBox-dot" style={{ background: (i == this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
+ <div className="imageBox-dot" style={{ background: (i === this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
</div>
);
}
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);
+ if (field instanceof ImageField) paths = [field.url.href];
+ else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href);
+ let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
+ let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
return (
- <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <div className={`imageBox-cont${interactive}`} 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} />
{paths.length > 1 ? this.dots(paths) : (null)}
{this.lightbox(paths)}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 6ebd73f2c..20cae03d4 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -8,6 +8,7 @@
border-radius: $border-radius;
box-sizing: border-box;
display: inline-block;
+ pointer-events: all;
.imageBox-cont img {
width: auto;
}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index ddbec014b..876a3c173 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -2,24 +2,22 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { Field, FieldWaiting } from '../../../fields/Field';
-import { Key } from '../../../fields/Key';
-import { KeyStore } from '../../../fields/KeyStore';
-import { CompileScript, ToField } from "../../util/Scripting";
+import { CompileScript } from "../../util/Scripting";
import { FieldView, FieldViewProps } from './FieldView';
import "./KeyValueBox.scss";
import { KeyValuePair } from "./KeyValuePair";
import React = require("react");
+import { NumCast, Cast, FieldValue } from "../../../new_fields/Types";
+import { Doc, IsField } from "../../../new_fields/Doc";
@observer
export class KeyValueBox extends React.Component<FieldViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
- public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
+ public static LayoutString(fieldStr: string = "data") { return FieldView.LayoutString(KeyValueBox, fieldStr); }
@observable private _keyInput: string = "";
@observable private _valueInput: string = "";
- @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 50); }
+ @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
constructor(props: FieldViewProps) {
@@ -30,8 +28,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
onEnterKey = (e: React.KeyboardEvent): void => {
if (e.key === 'Enter') {
if (this._keyInput && this._valueInput) {
- let doc = this.props.Document.GetT(KeyStore.Data, Document);
- if (!doc || doc === FieldWaiting) {
+ let doc = FieldValue(Cast(this.props.Document.data, Doc));
+ if (!doc) {
return;
}
let realDoc = doc;
@@ -43,13 +41,8 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
let res = script.run();
if (!res.success) return;
const field = res.result;
- if (field instanceof Field) {
- realDoc.Set(new Key(this._keyInput), field);
- } else {
- let dataField = ToField(field);
- if (dataField) {
- realDoc.Set(new Key(this._keyInput), dataField);
- }
+ if (IsField(field)) {
+ realDoc[this._keyInput] = field;
}
this._keyInput = "";
this._valueInput = "";
@@ -67,16 +60,16 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
createTable = () => {
- let doc = this.props.Document.GetT(KeyStore.Data, Document);
- if (!doc || doc === FieldWaiting) {
+ let doc = FieldValue(Cast(this.props.Document.data, Doc));
+ if (!doc) {
return <tr><td>Loading...</td></tr>;
}
let realDoc = doc;
let ids: { [key: string]: string } = {};
- let protos = doc.GetAllPrototypes();
+ let protos = Doc.GetAllPrototypes(doc);
for (const proto of protos) {
- proto._proxies.forEach((val: any, key: string) => {
+ Object.keys(proto).forEach(key => {
if (!(key in ids)) {
ids[key] = key;
}
@@ -86,7 +79,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
let rows: JSX.Element[] = [];
let i = 0;
for (let key in ids) {
- rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />);
+ rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} key={key} keyName={key} />);
}
return rows;
}
@@ -116,7 +109,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@action
onDividerMove = (e: PointerEvent): void => {
let nativeWidth = this._mainCont.current!.getBoundingClientRect();
- this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));
+ this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
}
@action
onDividerUp = (e: PointerEvent): void => {
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 01701e02c..ff6885965 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -25,4 +25,5 @@
}
.keyValuePair-td-value {
display:inline-block;
+ overflow: scroll;
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index d480eb5af..235792cbe 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -1,48 +1,33 @@
import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
-import { Document } from '../../../fields/Document';
-import { Field, Opt } from '../../../fields/Field';
-import { Key } from '../../../fields/Key';
-import { emptyDocFunction, emptyFunction, returnFalse } from '../../../Utils';
-import { Server } from "../../Server";
-import { CompileScript, ToField } from "../../util/Scripting";
+import { emptyFunction, returnFalse, returnZero } from '../../../Utils';
+import { CompileScript } from "../../util/Scripting";
import { Transform } from '../../util/Transform';
import { EditableView } from "../EditableView";
import { FieldView, FieldViewProps } from './FieldView';
import "./KeyValueBox.scss";
import "./KeyValuePair.scss";
import React = require("react");
+import { Doc, Opt, IsField } from '../../../new_fields/Doc';
+import { FieldValue } from '../../../new_fields/Types';
// Represents one row in a key value plane
export interface KeyValuePairProps {
rowStyle: string;
- fieldId: string;
- doc: Document;
+ keyName: string;
+ doc: Doc;
keyWidth: number;
}
@observer
export class KeyValuePair extends React.Component<KeyValuePairProps> {
- @observable private key: Opt<Key>;
-
- constructor(props: KeyValuePairProps) {
- super(props);
- Server.GetField(this.props.fieldId,
- action((field: Opt<Field>) => field instanceof Key && (this.key = field)));
-
- }
-
-
render() {
- if (!this.key) {
- return <tr><td>error</td><td /></tr>;
- }
let props: FieldViewProps = {
Document: this.props.doc,
ContainingCollectionView: undefined,
- fieldKey: this.key,
+ fieldKey: this.props.keyName,
isSelected: returnFalse,
select: emptyFunction,
isTopMost: false,
@@ -50,7 +35,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
active: returnFalse,
whenActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
- focus: emptyDocFunction,
+ focus: emptyFunction,
+ PanelWidth: returnZero,
+ PanelHeight: returnZero,
};
let contents = <FieldView {...props} />;
return (
@@ -58,21 +45,23 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
<td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
<div className="keyValuePair-td-key-container">
<button className="keyValuePair-td-key-delete" onClick={() => {
- let field = props.Document.Get(props.fieldKey);
- field && field instanceof Field && props.Document.Set(props.fieldKey, undefined);
+ let field = FieldValue(props.Document[props.fieldKey]);
+ field && (props.Document[props.fieldKey] = undefined);
}}>
X
</button>
- <div className="keyValuePair-keyField">{this.key.Name}</div>
+ <div className="keyValuePair-keyField">{this.props.keyName}</div>
</div>
</td>
<td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }}>
<EditableView contents={contents} height={36} GetValue={() => {
- let field = props.Document.Get(props.fieldKey);
- if (field && field instanceof Field) {
- return field.ToScriptString();
+ let field = FieldValue(props.Document[props.fieldKey]);
+ if (field) {
+ //TODO Types
+ return String(field);
+ // return field.ToScriptString();
}
- return field || "";
+ return "";
}}
SetValue={(value: string) => {
let script = CompileScript(value, { addReturn: true });
@@ -82,15 +71,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
let res = script.run();
if (!res.success) return false;
const field = res.result;
- if (field instanceof Field) {
- props.Document.Set(props.fieldKey, field);
+ if (IsField(field)) {
+ props.Document[props.fieldKey] = field;
return true;
- } else {
- let dataField = ToField(field);
- if (dataField) {
- props.Document.Set(props.fieldKey, dataField);
- return true;
- }
}
return false;
}}>
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 8bc70b48f..639f83b38 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,14 +1,14 @@
@import "../globalCssVariables";
.link-container {
width: 100%;
- height: 35px;
+ height: 50px;
display: flex;
flex-direction: row;
border-top: 0.5px solid #bababa;
}
.info-container {
- width: 55%;
+ width: 65%;
padding-top: 5px;
padding-left: 5px;
display: flex;
@@ -24,7 +24,8 @@
}
.button-container {
- width: 45%;
+ width: 35%;
+ padding-top: 8px;
display: flex;
flex-direction: row;
}
@@ -49,17 +50,17 @@
cursor: pointer;
}
-.fa-icon-view {
- margin-left: 3px;
- margin-top: 5px;
-}
+// .fa-icon-view {
+// margin-left: 3px;
+// margin-top: 5px;
+// }
.fa-icon-edit {
- margin-left: 5px;
- margin-top: 5px;
+ margin-left: 6px;
+ margin-top: 6px;
}
.fa-icon-delete {
- margin-left: 6px;
- margin-top: 5px;
+ margin-left: 7px;
+ margin-top: 6px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 1c0e316e8..08cfa590b 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -2,15 +2,15 @@ import { library } from '@fortawesome/fontawesome-svg-core';
import { faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from "../../../fields/ListField";
-import { NumberField } from "../../../fields/NumberField";
import { DocumentManager } from "../../util/DocumentManager";
import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import './LinkBox.scss';
import React = require("react");
+import { Doc } from '../../../new_fields/Doc';
+import { Cast, NumCast } from '../../../new_fields/Types';
+import { listSpec } from '../../../new_fields/Schema';
+import { action } from 'mobx';
library.add(faEye);
@@ -18,9 +18,9 @@ library.add(faEdit);
library.add(faTimes);
interface Props {
- linkDoc: Document;
+ linkDoc: Doc;
linkName: String;
- pairedDoc: Document;
+ pairedDoc: Doc;
type: String;
showEditor: () => void;
}
@@ -29,62 +29,54 @@ interface Props {
export class LinkBox extends React.Component<Props> {
@undoBatch
- onViewButtonPressed = (e: React.PointerEvent): void => {
+ onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
e.stopPropagation();
let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc);
if (docView) {
docView.props.focus(docView.props.Document);
} else {
- this.props.pairedDoc.GetAsync(KeyStore.AnnotationOn, (contextDoc: any) => {
- if (!contextDoc) {
- CollectionDockingView.Instance.AddRightSplit(this.props.pairedDoc.MakeDelegate());
- } else if (contextDoc instanceof Document) {
- this.props.pairedDoc.GetTAsync(KeyStore.Page, NumberField).then((pfield: any) => {
- contextDoc.GetTAsync(KeyStore.CurPage, NumberField).then((cfield: any) => {
- if (pfield !== cfield) {
- contextDoc.SetNumber(KeyStore.CurPage, pfield.Data);
- }
- let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
- if (contextView) {
- contextView.props.focus(contextDoc);
- } else {
- CollectionDockingView.Instance.AddRightSplit(contextDoc);
- }
- });
- });
+ const contextDoc = await Cast(this.props.pairedDoc.annotationOn, Doc);
+ if (!contextDoc) {
+ CollectionDockingView.Instance.AddRightSplit(Doc.MakeDelegate(this.props.pairedDoc));
+ } else {
+ const page = NumCast(this.props.pairedDoc.page, undefined);
+ const curPage = NumCast(contextDoc.curPage, undefined);
+ if (page !== curPage) {
+ contextDoc.curPage = page;
}
- });
+ let contextView = DocumentManager.Instance.getDocumentView(contextDoc);
+ if (contextView) {
+ contextDoc.panTransformType = "Ease";
+ contextView.props.focus(contextDoc);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(contextDoc);
+ }
+ }
}
}
onEditButtonPressed = (e: React.PointerEvent): void => {
- console.log("edit down");
e.stopPropagation();
this.props.showEditor();
}
- onDeleteButtonPressed = (e: React.PointerEvent): void => {
- console.log("delete down");
+ @action
+ onDeleteButtonPressed = async (e: React.PointerEvent): Promise<void> => {
e.stopPropagation();
- this.props.linkDoc.GetTAsync(KeyStore.LinkedFromDocs, Document, field => {
- if (field) {
- field.GetTAsync<ListField<Document>>(KeyStore.LinkedToDocs, ListField, field => {
- if (field) {
- field.Data.splice(field.Data.indexOf(this.props.linkDoc));
- }
- });
+ const [linkedFrom, linkedTo] = await Promise.all([Cast(this.props.linkDoc.linkedFrom, Doc), Cast(this.props.linkDoc.linkedTo, Doc)]);
+ if (linkedFrom) {
+ const linkedToDocs = Cast(linkedFrom.linkedToDocs, listSpec(Doc));
+ if (linkedToDocs) {
+ linkedToDocs.splice(linkedToDocs.indexOf(this.props.linkDoc), 1);
}
- });
- this.props.linkDoc.GetTAsync(KeyStore.LinkedToDocs, Document, field => {
- if (field) {
- field.GetTAsync<ListField<Document>>(KeyStore.LinkedFromDocs, ListField, field => {
- if (field) {
- field.Data.splice(field.Data.indexOf(this.props.linkDoc));
- }
- });
+ }
+ if (linkedTo) {
+ const linkedFromDocs = Cast(linkedTo.linkedFromDocs, listSpec(Doc));
+ if (linkedFromDocs) {
+ linkedFromDocs.splice(linkedFromDocs.indexOf(this.props.linkDoc), 1);
}
- });
+ }
}
render() {
@@ -102,8 +94,8 @@ export class LinkBox extends React.Component<Props> {
</div>
<div className="button-container">
- <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}>
- <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div>
+ {/* <div title="Follow Link" className="button" onPointerDown={this.onViewButtonPressed}>
+ <FontAwesomeIcon className="fa-icon-view" icon="eye" size="sm" /></div> */}
<div title="Edit Link" className="button" onPointerDown={this.onEditButtonPressed}>
<FontAwesomeIcon className="fa-icon-edit" icon="edit" size="sm" /></div>
<div title="Delete Link" className="button" onPointerDown={this.onDeleteButtonPressed}>
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index ea2e7289c..9629585d7 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -22,7 +22,7 @@
.save-button {
width: 50px;
- height: 20px;
+ height: 22px;
pointer-events: auto;
background-color: $dark-color;
color: $light-color;
@@ -38,6 +38,5 @@
.save-button:hover {
background: $main-accent;
- transform: scale(1.05);
cursor: pointer;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkEditor.tsx b/src/client/views/nodes/LinkEditor.tsx
index bde50fed8..f82c6e9cb 100644
--- a/src/client/views/nodes/LinkEditor.tsx
+++ b/src/client/views/nodes/LinkEditor.tsx
@@ -3,31 +3,29 @@ import React = require("react");
import { SelectionManager } from "../../util/SelectionManager";
import { observer } from "mobx-react";
import './LinkEditor.scss';
-import { KeyStore } from '../../../fields/KeyStore';
import { props } from "bluebird";
import { DocumentView } from "./DocumentView";
-import { Document } from "../../../fields/Document";
-import { TextField } from "../../../fields/TextField";
import { link } from "fs";
+import { StrCast } from "../../../new_fields/Types";
+import { Doc } from "../../../new_fields/Doc";
interface Props {
- linkDoc: Document;
+ linkDoc: Doc;
showLinks: () => void;
}
@observer
export class LinkEditor extends React.Component<Props> {
- @observable private _nameInput: string = this.props.linkDoc.GetText(KeyStore.Title, "");
- @observable private _descriptionInput: string = this.props.linkDoc.GetText(KeyStore.LinkDescription, "");
+ @observable private _nameInput: string = StrCast(this.props.linkDoc.title);
+ @observable private _descriptionInput: string = StrCast(this.props.linkDoc.linkDescription);
onSaveButtonPressed = (e: React.PointerEvent): void => {
- console.log("view down");
e.stopPropagation();
- this.props.linkDoc.SetData(KeyStore.Title, this._nameInput, TextField);
- this.props.linkDoc.SetData(KeyStore.LinkDescription, this._descriptionInput, TextField);
+ this.props.linkDoc.title = this._nameInput;
+ this.props.linkDoc.linkDescription = this._descriptionInput;
this.props.showLinks();
}
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index ac09da305..e21adebbc 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -1,15 +1,14 @@
import { action, observable } from "mobx";
import { observer } from "mobx-react";
-import { Document } from "../../../fields/Document";
-import { FieldWaiting } from "../../../fields/Field";
-import { Key } from "../../../fields/Key";
-import { KeyStore } from '../../../fields/KeyStore';
-import { ListField } from "../../../fields/ListField";
import { DocumentView } from "./DocumentView";
import { LinkBox } from "./LinkBox";
import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
+import { Doc } from "../../../new_fields/Doc";
+import { Cast, FieldValue } from "../../../new_fields/Types";
+import { listSpec } from "../../../new_fields/Schema";
+import { Id } from "../../../new_fields/RefField";
interface Props {
docView: DocumentView;
@@ -19,28 +18,28 @@ interface Props {
@observer
export class LinkMenu extends React.Component<Props> {
- @observable private _editingLink?: Document;
+ @observable private _editingLink?: Doc;
- renderLinkItems(links: Document[], key: Key, type: string) {
+ renderLinkItems(links: Doc[], key: string, type: string) {
return links.map(link => {
- let doc = link.GetT(key, Document);
- if (doc && doc !== FieldWaiting) {
- return <LinkBox key={doc.Id} linkDoc={link} linkName={link.Title} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
+ let doc = FieldValue(Cast(link[key], Doc));
+ if (doc) {
+ return <LinkBox key={doc[Id]} linkDoc={link} linkName={Cast(link.title, "string", "")} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
}
});
}
render() {
//get list of links from document
- let linkFrom: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []);
- let linkTo: Document[] = this.props.docView.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []);
+ let linkFrom: Doc[] = Cast(this.props.docView.props.Document.linkedFromDocs, listSpec(Doc), []);
+ let linkTo: Doc[] = Cast(this.props.docView.props.Document.linkedToDocs, listSpec(Doc), []);
if (this._editingLink === undefined) {
return (
<div id="linkMenu-container">
<input id="linkMenu-searchBar" type="text" placeholder="Search..."></input>
<div id="linkMenu-list">
- {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Destination: ")}
- {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")}
+ {this.renderLinkItems(linkTo, "linkedTo", "Destination: ")}
+ {this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")}
</div>
</div>
);
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 830dfe6c6..3760e378a 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -4,6 +4,9 @@
top: 0;
left:0;
}
+.react-pdf__Page__textContent span {
+ user-select: text;
+}
.react-pdf__Document {
position: absolute;
}
@@ -12,6 +15,21 @@
top: 0;
left:0;
z-index: 25;
+ pointer-events: all;
+}
+.pdfButton {
+ pointer-events: all;
+ width: 100px;
+ height:100px;
+}
+.pdfBox-cont {
+ pointer-events: none ;
+ span {
+ pointer-events: none !important;
+ }
+}
+.pdfBox-cont-interactive {
+ pointer-events: all;
}
.pdfBox-contentContainer {
position: absolute;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index c73ee4f21..14cbed04c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,24 +1,26 @@
import * as htmlToImage from "html-to-image";
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import Measure from "react-measure";
//@ts-ignore
import { Document, Page } from "react-pdf";
import 'react-pdf/dist/Page/AnnotationLayer.css';
-import { FieldWaiting, Opt } from '../../../fields/Field';
-import { ImageField } from '../../../fields/ImageField';
-import { KeyStore } from '../../../fields/KeyStore';
-import { PDFField } from '../../../fields/PDFField';
import { RouteStore } from "../../../server/RouteStore";
import { Utils } from '../../../Utils';
import { Annotation } from './Annotation';
import { FieldView, FieldViewProps } from './FieldView';
-import "./ImageBox.scss";
import "./PDFBox.scss";
-import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
import React = require("react");
import { SelectionManager } from "../../util/SelectionManager";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Opt } from "../../../new_fields/Doc";
+import { DocComponent } from "../DocComponent";
+import { makeInterface } from "../../../new_fields/Schema";
+import { positionSchema } from "./DocumentView";
+import { pageSchema } from "./ImageBox";
+import { ImageField, PdfField } from "../../../new_fields/URLField";
+import { InkingControl } from "../InkingControl";
/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -40,46 +42,20 @@ import { SelectionManager } from "../../util/SelectionManager";
* 4) another method: click on highlight first and then drag on your desired text
* 5) To make another highlight, you need to reclick on the button
*
- * Draw:
- * 1) click draw and select color. then just draw like there's no tomorrow.
- * 2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session.
- *
- * Pagination:
- * 1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't.
- * 2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved.
- *
- *
* written by: Andrew Kim
*/
+
+type PdfDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
+const PdfDocument = makeInterface(positionSchema, pageSchema);
+
@observer
-export class PDFBox extends React.Component<FieldViewProps> {
+export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocument) {
public static LayoutString() { return FieldView.LayoutString(PDFBox); }
private _mainDiv = React.createRef<HTMLDivElement>();
- private _pdf = React.createRef<HTMLCanvasElement>();
@observable private _renderAsSvg = true;
- //very useful for keeping track of X and y position throughout the PDF Canvas
- private initX: number = 0;
- private initY: number = 0;
-
- //checks if tool is on
- private _toolOn: boolean = false; //checks if tool is on
- private _pdfContext: any = null; //gets pdf context
- private bool: Boolean = false; //general boolean debounce
- private currSpan: any;//keeps track of current span (for highlighting)
-
- private _currTool: any; //keeps track of current tool button reference
- private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool
- private _drawTool = React.createRef<HTMLButtonElement>();//drawing tool button reference
-
- private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
- private _currColor: string = "black"; //current color that user selected (for ink/pen)
-
- private _highlightTool = React.createRef<HTMLButtonElement>(); //highlighter button reference
- private _highlightToolOn: boolean = false;
- private _pdfCanvas: any;
private _reactionDisposer: Opt<IReactionDisposer>;
@observable private _perPageInfo: Object[] = []; //stores pageInfo
@@ -89,8 +65,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
@observable private _interactive: boolean = false;
@observable private _loaded: boolean = false;
- @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 1); }
- @computed private get thumbnailPage() { return this.props.Document.GetNumber(KeyStore.ThumbnailPage, -1); }
+ @computed private get curPage() { return FieldValue(this.Document.curPage, 1); }
+ @computed private get thumbnailPage() { return Cast(this.props.Document.thumbnailPage, "number", -1); }
componentDidMount() {
this._reactionDisposer = reaction(
@@ -113,43 +89,6 @@ export class PDFBox extends React.Component<FieldViewProps> {
}
/**
- * selection tool used for area highlighting (stickies). Kinda temporary
- */
- selectionTool = () => {
- this._toolOn = true;
- }
- /**
- * when user draws on the canvas. When mouse pointer is down
- */
- drawDown = (e: PointerEvent) => {
- this.initX = e.offsetX;
- this.initY = e.offsetY;
- this._pdfContext.beginPath();
- this._pdfContext.lineTo(this.initX, this.initY);
- this._pdfContext.strokeStyle = this._currColor;
- this._pdfCanvas.addEventListener("pointermove", this.drawMove);
- this._pdfCanvas.addEventListener("pointerup", this.drawUp);
-
- }
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = this.initX += e.movementX,
- y = this.initY += e.movementY;
- //connects the point
- this._pdfContext.lineTo(x, y);
- this._pdfContext.stroke();
- }
-
- drawUp = (e: PointerEvent) => {
- this._pdfContext.closePath();
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- }
-
-
- /**
* highlighting helper function
*/
makeEditableAndHighlight = (colour: string) => {
@@ -184,7 +123,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
child.id = "highlighted";
//@ts-ignore
obj.spans.push(child);
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
});
}
@@ -207,7 +146,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
child.id = "highlighted";
//@ts-ignore
temp.spans.push(child);
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
});
@@ -273,11 +212,20 @@ export class PDFBox extends React.Component<FieldViewProps> {
* controls the area highlighting (stickies) Kinda temporary
*/
onPointerDown = (e: React.PointerEvent) => {
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
-
+ if (this.props.isSelected() && !InkingControl.Instance.selectedTool && e.buttons === 1) {
+ if (e.altKey) {
+ this._alt = true;
+ } else {
+ if (e.metaKey)
+ e.stopPropagation();
+ }
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ if (this.props.isSelected() && e.buttons === 2) {
+ this._alt = true;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
}
}
@@ -285,110 +233,28 @@ export class PDFBox extends React.Component<FieldViewProps> {
* controls area highlighting and partially highlighting. Kinda temporary
*/
@action
- onPointerUp = (e: React.PointerEvent) => {
- if (this._highlightToolOn) {
+ onPointerUp = (e: PointerEvent) => {
+ this._alt = false;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (this.props.isSelected()) {
this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color.
- this._highlightToolOn = false;
- }
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- let finalX = mouse.offsetX;
- let finalY = mouse.offsetY;
- let width = Math.abs(finalX - this.initX); //width
- let height = Math.abs(finalY - this.initY); //height
-
- //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky
- if (finalX < this.initX) {
- this.initX = finalX;
- }
- if (finalY < this.initY) {
- this.initY = finalY;
- }
-
- if (this._mainDiv.current) {
- let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />;
- this._pageInfo.area.push(sticky);
- }
- this._toolOn = false;
}
this._interactive = true;
}
- /**
- * starts drawing the line when user presses down.
- */
- onDraw = () => {
- if (this._currTool !== null) {
- this._currTool.style.backgroundColor = "grey";
- }
-
- if (this._drawTool.current) {
- this._currTool = this._drawTool.current;
- if (this._drawToolOn) {
- this._drawToolOn = false;
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.removeEventListener("pointerup", this.drawUp);
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._drawTool.current.style.backgroundColor = "grey";
- } else {
- this._drawToolOn = true;
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- this._drawTool.current.style.backgroundColor = "cyan";
- }
- }
- }
-
-
- /**
- * for changing color (for ink/pen)
- */
- onColorChange = (e: React.PointerEvent) => {
- if (e.currentTarget.innerHTML === "Red") {
- this._currColor = "red";
- } else if (e.currentTarget.innerHTML === "Blue") {
- this._currColor = "blue";
- } else if (e.currentTarget.innerHTML === "Green") {
- this._currColor = "green";
- } else if (e.currentTarget.innerHTML === "Black") {
- this._currColor = "black";
- }
-
- }
-
-
- /**
- * For highlighting (text drag highlighting)
- */
- onHighlight = () => {
- this._drawToolOn = false;
- if (this._currTool !== null) {
- this._currTool.style.backgroundColor = "grey";
- }
- if (this._highlightTool.current) {
- this._currTool = this._drawTool.current;
- if (this._highlightToolOn) {
- this._highlightToolOn = false;
- this._highlightTool.current.style.backgroundColor = "grey";
- } else {
- this._highlightToolOn = true;
- this._highlightTool.current.style.backgroundColor = "orange";
- }
- }
- }
@action
saveThumbnail = () => {
this._renderAsSvg = false;
setTimeout(() => {
- var me = this;
- let nwidth = me.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- let nheight = me.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ let nwidth = FieldValue(this.Document.nativeWidth, 0);
+ let nheight = FieldValue(this.Document.nativeHeight, 0);
htmlToImage.toPng(this._mainDiv.current!, { width: nwidth, height: nheight, quality: 1 })
.then(action((dataUrl: string) => {
- me.props.Document.SetData(KeyStore.Thumbnail, new URL(dataUrl), ImageField);
- me.props.Document.SetNumber(KeyStore.ThumbnailPage, me.props.Document.GetNumber(KeyStore.CurPage, -1));
- me._renderAsSvg = true;
+ this.props.Document.thumbnail = new ImageField(new URL(dataUrl));
+ this.props.Document.thumbnailPage = FieldValue(this.Document.curPage, -1);
+ this._renderAsSvg = true;
}))
.catch(function (error: any) {
console.error('oops, something went wrong!', error);
@@ -398,24 +264,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
@action
onLoaded = (page: any) => {
- if (this._mainDiv.current) {
- this._mainDiv.current.childNodes.forEach((element) => {
- if (element.nodeName === "DIV") {
- element.childNodes[0].childNodes.forEach((e) => {
-
- if (e instanceof HTMLCanvasElement) {
- this._pdfCanvas = e;
- this._pdfContext = e.getContext("2d");
-
- }
-
- });
- }
- });
- }
-
// bcz: the number of pages should really be set when the document is imported.
- this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages);
+ this.props.Document.numPages = page._transport.numPages;
if (this._perPageInfo.length === 0) { //Makes sure it only runs once
this._perPageInfo = [...Array(page._transport.numPages)];
}
@@ -425,31 +275,38 @@ export class PDFBox extends React.Component<FieldViewProps> {
@action
setScaling = (r: any) => {
// bcz: the nativeHeight should really be set when the document is imported.
- // also, the native dimensions could be different for different pages of the PDF
+ // also, the native dimensions could be different for different pages of the canvas
// so this design is flawed.
- var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- if (!this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) {
+ var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+ if (!FieldValue(this.Document.nativeHeight, 0)) {
var nativeHeight = nativeWidth * r.entry.height / r.entry.width;
- this.props.Document.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
- this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight);
+ this.props.Document.height = nativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
+ this.props.Document.nativeHeight = nativeHeight;
}
}
-
+ renderHeight = 2400;
+ @computed
+ get pdfPage() {
+ return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />
+ }
@computed
get pdfContent() {
let page = this.curPage;
const renderHeight = 2400;
- let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
- let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
+ let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
+ let xf = FieldValue(this.Document.nativeHeight, 0) / renderHeight;
+ let body = NumCast(this.props.Document.nativeHeight) ?
+ this.pdfPage :
+ <Measure onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <div className="pdfBox-page" ref={measureRef}>
+ {this.pdfPage}
+ </div>
+ }
+ </Measure>;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}>
- <Measure onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="pdfBox-page" ref={measureRef}>
- <Page height={renderHeight} pageNumber={page} onLoadSuccess={this.onLoaded} />
- </div>
- }
- </Measure>
+ <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}>
+ {body}
</Document>
</div >;
}
@@ -457,8 +314,8 @@ export class PDFBox extends React.Component<FieldViewProps> {
@computed
get pdfRenderer() {
let proxy = this._loaded ? (null) : this.imageProxyRenderer;
- let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
- if ((!this._interactive && proxy) || !pdfUrl || pdfUrl === FieldWaiting) {
+ let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
+ if ((!this._interactive && proxy) || !pdfUrl) {
return proxy;
}
return [
@@ -471,18 +328,32 @@ export class PDFBox extends React.Component<FieldViewProps> {
@computed
get imageProxyRenderer() {
- let thumbField = this.props.Document.Get(KeyStore.Thumbnail);
+ let thumbField = this.props.Document.thumbnail;
if (thumbField) {
- let path = thumbField === FieldWaiting || this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
- thumbField instanceof ImageField ? thumbField.Data.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
+ let path = this.thumbnailPage !== this.curPage ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
+ thumbField instanceof ImageField ? thumbField.url.href : "http://cs.brown.edu/people/bcz/prairie.jpg";
return <img src={path} width="100%" />;
}
return (null);
}
-
+ @observable _alt = false;
+ @action
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Alt") {
+ this._alt = true;
+ }
+ }
+ @action
+ onKeyUp = (e: React.KeyboardEvent) => {
+ if (e.key === "Alt") {
+ this._alt = false;
+ }
+ }
render() {
+ trace();
+ let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
return (
- <div className="pdfBox-cont" ref={this._mainDiv} onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} >
+ <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} >
{this.pdfRenderer}
</div >
);
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
deleted file mode 100644
index 11719831b..000000000
--- a/src/client/views/nodes/Sticky.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import "react-image-lightbox/style.css"; // This only needs to be imported once in your app
-import React = require("react");
-import { observer } from "mobx-react";
-import "react-pdf/dist/Page/AnnotationLayer.css";
-
-interface IProps {
- Height: number;
- Width: number;
- X: number;
- Y: number;
-}
-
-/**
- * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
- * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
- *
- * Written By: Andrew Kim
- */
-@observer
-export class Sticky extends React.Component<IProps> {
- private initX: number = 0;
- private initY: number = 0;
-
- private _ref = React.createRef<HTMLCanvasElement>();
- private ctx: any; //context that keeps track of sticky canvas
-
- /**
- * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
- */
- drawDown = (e: React.PointerEvent) => {
- if (this._ref.current) {
- this.ctx = this._ref.current.getContext("2d");
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
- this.ctx.beginPath();
- this.ctx.lineTo(this.initX, this.initY);
- this.ctx.strokeStyle = "black";
- document.addEventListener("pointermove", this.drawMove);
- document.addEventListener("pointerup", this.drawUp);
- }
- }
-
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = (this.initX += e.movementX),
- y = (this.initY += e.movementY);
- //connects the point
- this.ctx.lineTo(x, y);
- this.ctx.stroke();
- }
-
- /**
- * when user lifts the mouse, the drawing ends
- */
- drawUp = (e: PointerEvent) => {
- this.ctx.closePath();
- console.log(this.ctx);
- document.removeEventListener("pointermove", this.drawMove);
- }
-
- render() {
- return (
- <div onPointerDown={this.drawDown}>
- <canvas
- ref={this._ref}
- height={this.props.Height}
- width={this.props.Width}
- style={{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- background: "yellow",
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
- opacity: 0.4
- }}
- />
- </div>
- );
- }
-}
diff --git a/src/client/views/nodes/Timeline.tsx b/src/client/views/nodes/Timeline.tsx
index 3296aecff..14bcd738f 100644
--- a/src/client/views/nodes/Timeline.tsx
+++ b/src/client/views/nodes/Timeline.tsx
@@ -3,18 +3,18 @@ import * as ReactDOM from "react-dom";
import { observer } from "mobx-react";
import { observable, reaction, action, IReactionDisposer, observe, IObservableArray } from "mobx";
import "./Timeline.scss";
-import { KeyStore } from "../../../fields/KeyStore";
-import { Document } from "../../../fields/Document";
import { KeyFrame } from "./KeyFrame";
import { CollectionViewProps } from "../collections/CollectionBaseView";
import { CollectionSubView, SubCollectionViewProps } from "../collections/CollectionSubView";
import { DocumentViewProps } from "./DocumentView";
-import { Opt } from '../../../fields/Field';
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { Doc } from "../../../new_fields/Doc";
+import { Document } from "../../../new_fields/Schema";
+import { FieldValue } from "../../../new_fields/Types";
@observer
-export class Timeline extends React.Component<SubCollectionViewProps> {
+export class Timeline extends CollectionSubView(Document) {
@observable private _inner = React.createRef<HTMLDivElement>();
@observable private _isRecording: Boolean = false;
@observable private _currentBar: any = null;
@@ -66,14 +66,14 @@ export class Timeline extends React.Component<SubCollectionViewProps> {
this._inner.current.appendChild(this._currentBar);
}
let doc: Document = this.props.Document;
- let childrenList = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ let childrenList = this.children;
// let keyFrame = new KeyFrame(); //should not be done here...
- // this._keyFrames.push(keyFrame);
- let keys = [KeyStore.X, KeyStore.Y];
+ // this._keyFrames.push(keyFrame)";
+ let keys = ["x", "y"];
const addReaction = (element: Document) => {
return reaction(() => {
- return keys.map(key => element.GetNumber(key, 0));
+ return keys.map(key => FieldValue(element[key]));
}, data => {
if (this._inner.current) {
let keyFrame: KeyFrame;
@@ -87,7 +87,7 @@ export class Timeline extends React.Component<SubCollectionViewProps> {
keyFrame = this._keyFrames[this._currentBarX];
}
keys.forEach((key, index) => {
- keyFrame.document.SetNumber(key, data[index]); //Tyler working on better Doc.ts functions...(this is currently not comprehensive...)
+ keyFrame.document[key] = data[index];
});
}
});
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 9d7c2bc56..422508f90 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,78 +1,82 @@
import React = require("react");
import { observer } from "mobx-react";
-import { FieldWaiting, Opt } from '../../../fields/Field';
-import { VideoField } from '../../../fields/VideoField';
import { FieldView, FieldViewProps } from './FieldView';
import "./VideoBox.scss";
+import { action, computed, trace } from "mobx";
+import { DocComponent } from "../DocComponent";
+import { positionSchema } from "./DocumentView";
+import { makeInterface } from "../../../new_fields/Schema";
+import { pageSchema } from "./ImageBox";
+import { Cast, FieldValue, NumCast, ToConstructor, ListSpec } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
import Measure from "react-measure";
-import { action, trace, observable, IReactionDisposer, computed, reaction } from "mobx";
-import { KeyStore } from "../../../fields/KeyStore";
-import { number } from "prop-types";
+import "./VideoBox.scss";
+import { Field, FieldResult, Opt } from "../../../new_fields/Doc";
+
+type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
+const VideoDocument = makeInterface(positionSchema, pageSchema);
@observer
-export class VideoBox extends React.Component<FieldViewProps> {
+export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
- private _reactionDisposer: Opt<IReactionDisposer>;
- private _videoRef = React.createRef<HTMLVideoElement>();
+ private _videoRef: HTMLVideoElement | null = null;
+ private _loaded: boolean = false;
+ private get initialTimecode() { return FieldValue(this.Document.curPage, -1); }
public static LayoutString() { return FieldView.LayoutString(VideoBox); }
- constructor(props: FieldViewProps) {
- super(props);
+ public get player(): HTMLVideoElement | undefined {
+ if (this._videoRef) {
+ return this._videoRef;
+ }
}
-
- @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
-
-
- _loaded: boolean = false;
-
@action
setScaling = (r: any) => {
if (this._loaded) {
// bcz: the nativeHeight should really be set when the document is imported.
- // also, the native dimensions could be different for different pages of the PDF
- // so this design is flawed.
- var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
+ var nativeHeight = FieldValue(this.Document.nativeHeight, 0);
var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
if (!nativeHeight && newNativeHeight !== nativeHeight && !isNaN(newNativeHeight)) {
- this.props.Document.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.Document.GetNumber(KeyStore.Width, 0));
- this.props.Document.SetNumber(KeyStore.NativeHeight, newNativeHeight);
+ this.Document.height = newNativeHeight / nativeWidth * FieldValue(this.Document.width, 0);
+ this.Document.nativeHeight = newNativeHeight;
}
} else {
this._loaded = true;
}
}
- get player(): HTMLVideoElement | undefined {
- return this._videoRef.current ? this._videoRef.current.getElementsByTagName("video")[0] : undefined;
+ componentDidMount() {
+ if (this.props.setVideoBox) this.props.setVideoBox(this);
}
@action
setVideoRef = (vref: HTMLVideoElement | null) => {
- if (this.curPage >= 0 && vref) {
- vref.currentTime = this.curPage;
- (vref as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage;
+ this._videoRef = vref;
+ if (this.initialTimecode >= 0 && vref) {
+ vref.currentTime = this.initialTimecode;
}
}
+ videoContent(path: string) {
+ return <video className="videobox-cont" ref={this.setVideoRef}>
+ <source src={path} type="video/mp4" />
+ Not supported.
+ </video>;
+ }
render() {
- let field = this.props.Document.GetT(this.props.fieldKey, VideoField);
- if (!field || field === FieldWaiting) {
+ let field = Cast(this.Document[this.props.fieldKey], VideoField);
+ if (!field) {
return <div>Loading</div>;
}
- let path = field.Data.href;
- trace();
- return (
+ let content = this.videoContent(field.url.href);
+ return NumCast(this.props.Document.nativeHeight) ?
+ content :
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div style={{ width: "100%", height: "auto" }} ref={measureRef}>
- <video className="videobox-cont" ref={this.setVideoRef}>
- <source src={path} type="video/mp4" />
- Not supported.
- </video>
+ {content}
</div>
}
- </Measure>
- );
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 2ad1129a4..eb09b0693 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,12 +1,19 @@
-.webBox-cont {
+.webBox-cont, .webBox-cont-interactive{
padding: 0vw;
position: absolute;
top: 0;
left:0;
width: 100%;
height: 100%;
- overflow: scroll;
+ overflow: auto;
+ pointer-events: none ;
+}
+.webBox-cont-interactive {
+ pointer-events: all;
+ span {
+ user-select: text !important;
+ }
}
#webBox-htmlSpan {
@@ -15,6 +22,12 @@
left:0;
}
+.webBox-overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+}
+
.webBox-button {
padding : 0vw;
border: none;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 1edb4d826..2239a8e38 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,23 +1,18 @@
import "./WebBox.scss";
import React = require("react");
-import { WebField } from '../../../fields/WebField';
import { FieldViewProps, FieldView } from './FieldView';
-import { FieldWaiting } from '../../../fields/Field';
+import { HtmlField } from "../../../new_fields/HtmlField";
+import { WebField } from "../../../new_fields/URLField";
import { observer } from "mobx-react";
-import { computed } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
+import { computed, reaction, IReactionDisposer } from 'mobx';
+import { DocumentDecorations } from "../DocumentDecorations";
+import { InkingControl } from "../InkingControl";
@observer
export class WebBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(WebBox); }
- constructor(props: FieldViewProps) {
- super(props);
- }
-
- @computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); }
-
_ignore = 0;
onPreWheel = (e: React.WheelEvent) => {
this._ignore = e.timeStamp;
@@ -36,22 +31,29 @@ export class WebBox extends React.Component<FieldViewProps> {
}
}
render() {
- let field = this.props.Document.Get(this.props.fieldKey);
- let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
- field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
-
+ let field = this.props.Document[this.props.fieldKey];
+ let view;
+ if (field instanceof HtmlField) {
+ view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
+ } else if (field instanceof WebField) {
+ view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ } else {
+ view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />;
+ }
let content =
<div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
- {this.html ? <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: this.html }} /> :
- <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }} />}
+ {view}
</div>;
+ let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+
+ let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
<>
- <div className="webBox-cont" >
+ <div className={classname} >
{content}
</div>
- {this.props.isSelected() ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
</>);
}
} \ No newline at end of file