aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorTyler Schicke <tyler_schicke@brown.edu>2019-03-16 23:22:40 -0400
committerTyler Schicke <tyler_schicke@brown.edu>2019-03-16 23:22:40 -0400
commit501e4acc3eaf619609311778ec5b9305f40384d7 (patch)
tree8352dae0a60ff640289ff1ee03cd3f4334aaac53 /src
parent4d2fe1ce791a3b9e64c52b7dce9c1af28c2523c0 (diff)
parentf505dfa8234de4ea94304b104e801f72ea30ff39 (diff)
Merge branch 'master' of github-tsch-brown:browngraphicslab/Dash-Web into kvp
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin6148 -> 6148 bytes
-rw-r--r--src/client/Server.ts30
-rw-r--r--src/client/SocketStub.ts1
-rw-r--r--src/client/documents/Documents.ts86
-rw-r--r--src/client/util/RichTextRules.ts43
-rw-r--r--src/client/util/RichTextSchema.tsx23
-rw-r--r--src/client/util/TooltipTextMenu.scss7
-rw-r--r--src/client/util/TooltipTextMenu.tsx38
-rw-r--r--src/client/views/.DS_Storebin6148 -> 8196 bytes
-rw-r--r--src/client/views/ContextMenu.scss76
-rw-r--r--src/client/views/ContextMenu.tsx26
-rw-r--r--src/client/views/DocumentDecorations.scss85
-rw-r--r--src/client/views/DocumentDecorations.tsx17
-rw-r--r--src/client/views/EditableView.scss6
-rw-r--r--src/client/views/EditableView.tsx3
-rw-r--r--src/client/views/InkingCanvas.scss131
-rw-r--r--src/client/views/InkingCanvas.tsx28
-rw-r--r--src/client/views/InkingControl.tsx66
-rw-r--r--src/client/views/InkingStroke.tsx2
-rw-r--r--src/client/views/Main.scss143
-rw-r--r--src/client/views/Main.tsx106
-rw-r--r--src/client/views/_global_variables.scss17
-rw-r--r--src/client/views/_nodeModuleOverrides.scss23
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx10
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss108
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx206
-rw-r--r--src/client/views/collections/CollectionPDFView.scss27
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx26
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss383
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx32
-rw-r--r--src/client/views/collections/CollectionTreeView.scss32
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx25
-rw-r--r--src/client/views/collections/CollectionVideoView.scss40
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx118
-rw-r--r--src/client/views/collections/CollectionView.tsx10
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx31
-rw-r--r--src/client/views/collections/MarqueeView.scss8
-rw-r--r--src/client/views/collections/MarqueeView.tsx164
-rw-r--r--src/client/views/collections/PreviewCursor.scss18
-rw-r--r--src/client/views/collections/PreviewCursor.tsx78
-rw-r--r--src/client/views/nodes/AudioBox.tsx12
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx35
-rw-r--r--src/client/views/nodes/DocumentView.scss44
-rw-r--r--src/client/views/nodes/DocumentView.tsx95
-rw-r--r--src/client/views/nodes/FieldTextBox.scss14
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss64
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx8
-rw-r--r--src/client/views/nodes/ImageBox.scss27
-rw-r--r--src/client/views/nodes/ImageBox.tsx4
-rw-r--r--src/client/views/nodes/KeyValueBox.scss42
-rw-r--r--src/client/views/nodes/LinkBox.scss42
-rw-r--r--src/client/views/nodes/LinkBox.tsx19
-rw-r--r--src/client/views/nodes/LinkEditor.scss22
-rw-r--r--src/client/views/nodes/LinkMenu.scss1
-rw-r--r--src/client/views/nodes/LinkMenu.tsx4
-rw-r--r--src/client/views/nodes/PDFBox.scss2
-rw-r--r--src/client/views/nodes/PDFBox.tsx8
-rw-r--r--src/client/views/nodes/VideoBox.scss2
-rw-r--r--src/client/views/nodes/VideoBox.tsx58
-rw-r--r--src/fields/KeyStore.ts2
-rw-r--r--src/server/database.ts4
-rw-r--r--src/server/index.ts12
62 files changed, 2012 insertions, 782 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index f20f36d63..90213270f 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/client/Server.ts b/src/client/Server.ts
index f0cf0bb9b..f2d7de75c 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -40,8 +40,8 @@ export class Server {
return this.ClientFieldsCached.get(fieldid);
}, (field, reaction) => {
if (field !== "<Waiting>") {
- callback(field)
reaction.dispose()
+ callback(field)
}
})
}
@@ -49,14 +49,38 @@ export class Server {
}
public static GetFields(fieldIds: FieldId[], callback: (fields: { [id: string]: Field }) => any) {
- SocketStub.SEND_FIELDS_REQUEST(fieldIds, (fields) => {
+ let neededFieldIds: FieldId[] = [];
+ let waitingFieldIds: FieldId[] = [];
+ let existingFields: { [id: string]: Field } = {};
+ for (let id of fieldIds) {
+ let field = this.ClientFieldsCached.get(id);
+ if (!field) {
+ neededFieldIds.push(id);
+ } else if (field === FieldWaiting) {
+ waitingFieldIds.push(id);
+ } else {
+ existingFields[id] = field;
+ }
+ }
+ SocketStub.SEND_FIELDS_REQUEST(neededFieldIds, (fields) => {
for (let key in fields) {
let field = fields[key];
if (!this.ClientFieldsCached.has(field.Id)) {
this.ClientFieldsCached.set(field.Id, field)
}
}
- callback(fields)
+ reaction(() => {
+ return waitingFieldIds.map(this.ClientFieldsCached.get);
+ }, (cachedFields, reaction) => {
+ if (!cachedFields.some(field => !field || field === FieldWaiting)) {
+ reaction.dispose();
+ for (let field of cachedFields) {
+ let realField = field as Field;
+ existingFields[realField.Id] = realField;
+ }
+ callback({ ...fields, ...existingFields })
+ }
+ }, { fireImmediately: true })
});
}
diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts
index 18df4ca0a..a0b89b7c9 100644
--- a/src/client/SocketStub.ts
+++ b/src/client/SocketStub.ts
@@ -7,6 +7,7 @@ import { Utils } from "../Utils";
import { Server } from "./Server";
import { ServerUtils } from "../server/ServerUtil";
+//TODO tfs: I think it might be cleaner to not have SocketStub deal with turning what the server gives it into Fields (in other words not call ServerUtils.FromJson), and leave that for the Server class.
export class SocketStub {
static FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 7b30dff98..bec4dd697 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -23,10 +23,13 @@ import { PDFField } from "../../fields/PDFField";
import { PDFBox } from "../views/nodes/PDFBox";
import { CollectionPDFView } from "../views/collections/CollectionPDFView";
import { RichTextField } from "../../fields/RichTextField";
+import { CollectionVideoView } from "../views/collections/CollectionVideoView";
+import { StrokeData, InkField } from "../../fields/InkField";
export interface DocumentOptions {
x?: number;
y?: number;
+ ink?: Map<string, StrokeData>;
width?: number;
height?: number;
nativeWidth?: number;
@@ -34,10 +37,12 @@ export interface DocumentOptions {
title?: string;
panx?: number;
pany?: number;
+ page?: number;
scale?: number;
layout?: string;
layoutKeys?: Key[];
viewType?: number;
+ backgroundColor?: string;
}
export namespace Documents {
@@ -78,18 +83,24 @@ export namespace Documents {
if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
if (options.panx !== undefined) { doc.SetNumber(KeyStore.PanX, options.panx); }
if (options.pany !== undefined) { doc.SetNumber(KeyStore.PanY, options.pany); }
+ if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); }
if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
+ if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); }
if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
+ if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
return doc;
}
function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
return assignOptions(new Document(protoId), { ...options, title: title, layout: layout });
}
- function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: T, ctor: { new(): U }, id?: string) {
+ function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
var deleg = doc.MakeDelegate(id);
- deleg.SetData(KeyStore.Data, value, ctor);
+ if (value instanceof Document)
+ deleg.Set(KeyStore.Data, value)
+ else
+ deleg.SetData(KeyStore.Data, value[0], value[1]);
return assignOptions(deleg, options);
}
@@ -123,7 +134,7 @@ export namespace Documents {
function GetCollectionPrototype(): Document {
return collProto ? collProto :
collProto = setupPrototypeOptions(collProtoId, "COLLECTION_PROTO", CollectionView.LayoutString("DataKey"),
- { panx: 0, pany: 0, scale: 1, layoutKeys: [KeyStore.Data] });
+ { panx: 0, pany: 0, scale: 1, width: 500, height: 500, layoutKeys: [KeyStore.Data] });
}
function GetKVPPrototype(): Document {
@@ -132,9 +143,13 @@ export namespace Documents {
{ x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
}
function GetVideoPrototype(): Document {
- return videoProto ? videoProto :
- videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", VideoBox.LayoutString(),
- { x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] })
+ if (!videoProto) {
+ videoProto = setupPrototypeOptions(videoProtoId, "VIDEO_PROTO", CollectionVideoView.LayoutString("AnnotationsKey"),
+ { x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations] });
+ videoProto.SetNumber(KeyStore.CurPage, 0);
+ videoProto.SetText(KeyStore.BackgroundLayout, VideoBox.LayoutString());
+ }
+ return videoProto;
}
function GetAudioPrototype(): Document {
return audioProto ? audioProto :
@@ -144,60 +159,73 @@ export namespace Documents {
export function ImageDocument(url: string, options: DocumentOptions = {}) {
- let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
- new URL(url), ImageField);
- doc.SetText(KeyStore.Caption, "my caption...");
- doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
- doc.SetText(KeyStore.OverlayLayout, FixedCaption());
- return doc;
+ return SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
+ [new URL(url), ImageField]);
+ // let doc = SetInstanceOptions(GetImagePrototype(), { ...options, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] },
+ // [new URL(url), ImageField]);
+ // doc.SetText(KeyStore.Caption, "my caption...");
+ // doc.SetText(KeyStore.BackgroundLayout, EmbeddedCaption());
+ // doc.SetText(KeyStore.OverlayLayout, FixedCaption());
+ // return doc;
}
export function VideoDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetVideoPrototype(), options, new URL(url), VideoField);
+ return SetInstanceOptions(GetVideoPrototype(), options, [new URL(url), VideoField]);
}
export function AudioDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetAudioPrototype(), options, new URL(url), AudioField);
+ return SetInstanceOptions(GetAudioPrototype(), options, [new URL(url), AudioField]);
}
export function TextDocument(options: DocumentOptions = {}) {
- return SetInstanceOptions(GetTextPrototype(), options, "", RichTextField);
+ return SetInstanceOptions(GetTextPrototype(), options, ["", TextField]);
}
export function PdfDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetPdfPrototype(), options, new URL(url), PDFField);
+ return SetInstanceOptions(GetPdfPrototype(), options, [new URL(url), PDFField]);
}
export function WebDocument(url: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetWebPrototype(), options, new URL(url), WebField);
+ return SetInstanceOptions(GetWebPrototype(), options, [new URL(url), WebField]);
}
export function HtmlDocument(html: string, options: DocumentOptions = {}) {
- return SetInstanceOptions(GetWebPrototype(), options, html, HtmlField);
+ return SetInstanceOptions(GetWebPrototype(), options, [html, HtmlField]);
+ }
+ export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
+ return SetInstanceOptions(GetKVPPrototype(), options, document, id)
}
export function FreeformDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Freeform }, documents, ListField, id)
+ return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Freeform }, [documents, ListField], id)
}
export function SchemaDocument(documents: Array<Document>, options: DocumentOptions, id?: string) {
- return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Schema }, documents, ListField, id)
+ return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Schema }, [documents, ListField], id)
}
export function DockDocument(config: string, options: DocumentOptions, id?: string) {
- return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Docking }, config, TextField, id)
- }
- export function KVPDocument(document: Document, options: DocumentOptions = {}, id?: string) {
- var deleg = GetKVPPrototype().MakeDelegate(id);
- deleg.Set(KeyStore.Data, document);
- return assignOptions(deleg, options);
+ return SetInstanceOptions(GetCollectionPrototype(), { ...options, viewType: CollectionViewType.Docking }, [config, TextField], id)
}
// example of custom display string for an image that shows a caption.
function EmbeddedCaption() {
return `<div style="height:100%">
- <div style="position:relative; margin:auto; height:85%;" >`
+ <div style="position:relative; margin:auto; height:85%; width:85%;" >`
+ ImageBox.LayoutString() +
`</div>
<div style="position:relative; height:15%; text-align:center; ">`
+ FormattedTextBox.LayoutString("CaptionKey") +
`</div>
</div>` };
- function FixedCaption() {
+ function FixedCaption(fieldName: string = "Caption") {
return `<div style="position:absolute; height:30px; bottom:0; width:100%">
<div style="position:absolute; width:100%; height:100%; text-align:center;bottom:0;">`
- + FormattedTextBox.LayoutString("CaptionKey") +
+ + FormattedTextBox.LayoutString(fieldName + "Key") +
`</div>
</div>` };
+
+ function Caption() {
+ return (`
+<div>
+ <div style="margin:auto; height:85%; width:85%;">
+ {layout}
+ </div>
+ <div style="height:15%">
+ <FormattedTextBox doc={Document} DocumentViewForField={DocumentView} bindings={bindings} fieldKey={"CaptionKey"} isSelected={isSelected} select={select} selectOnLoad={SelectOnLoad} isTopMost={isTopMost}/>
+ </div>
+</div>
+ `)
+ }
} \ No newline at end of file
diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts
new file mode 100644
index 000000000..3b8396510
--- /dev/null
+++ b/src/client/util/RichTextRules.ts
@@ -0,0 +1,43 @@
+import {
+ inputRules,
+ wrappingInputRule,
+ textblockTypeInputRule,
+ smartQuotes,
+ emDash,
+ ellipsis
+} from "prosemirror-inputrules";
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model";
+
+import { schema } from "./RichTextSchema";
+
+export const inpRules = {
+ rules: [
+ ...smartQuotes,
+ ellipsis,
+ emDash,
+
+ // > blockquote
+ wrappingInputRule(/^\s*>\s$/, schema.nodes.blockquote),
+
+ // 1. ordered list
+ wrappingInputRule(
+ /^(\d+)\.\s$/,
+ schema.nodes.ordered_list,
+ match => ({ order: +match[1] }),
+ (match, node) => node.childCount + node.attrs.order === +match[1]
+ ),
+
+ // * bullet list
+ wrappingInputRule(/^\s*([-+*])\s$/, schema.nodes.bullet_list),
+
+ // ``` code block
+ textblockTypeInputRule(/^```$/, schema.nodes.code_block),
+
+ // # heading
+ textblockTypeInputRule(
+ new RegExp("^(#{1,6})\\s$"),
+ schema.nodes.heading,
+ match => ({ level: match[1].length })
+ )
+ ]
+};
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index abf448c9f..2a3c1da6e 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -1,12 +1,15 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray } from "prosemirror-model"
+import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"
import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands'
import { redo, undo } from 'prosemirror-history'
-import { orderedList, bulletList, listItem } from 'prosemirror-schema-list'
+import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list'
+import { EditorState, Transaction, NodeSelection, } from "prosemirror-state";
+import { EditorView, } from "prosemirror-view";
const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"],
preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]
+
// :: Object
// [Specs](#model.NodeSpec) for the nodes defined in this schema.
export const nodes: { [index: string]: NodeSpec } = {
@@ -113,12 +116,22 @@ export const nodes: { [index: string]: NodeSpec } = {
content: 'list_item+',
group: 'block'
},
+ //this doesn't currently work for some reason
bullet_list: {
+ ...bulletList,
content: 'list_item+',
group: 'block',
- parseDOM: [{ tag: "ul" }, { style: "list-style-type=disc;" }],
- toDOM() { return ulDOM }
- },
+ // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }],
+ // toDOM() { return ulDOM }
+ },
+ //bullet_list: {
+ // content: 'list_item+',
+ // group: 'block',
+ //active: blockActive(schema.nodes.bullet_list),
+ //enable: wrapInList(schema.nodes.bullet_list),
+ //run: wrapInList(schema.nodes.bullet_list),
+ //select: state => true,
+ // },
list_item: {
...listItem,
content: 'paragraph block*'
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index fa43f5326..ea580d104 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -1,8 +1,9 @@
+@import "../views/global_variables";
.tooltipMenu {
position: absolute;
z-index: 20;
- background: rgb(19, 18, 18);
+ background: $dark-color;
border: 1px solid silver;
border-radius: 4px;
padding: 2px 10px;
@@ -31,14 +32,14 @@
bottom: -4.5px;
border: 5px solid transparent;
border-bottom-width: 0;
- border-top-color: black;
+ border-top-color: $dark-color;
}
.menuicon {
display: inline-block;
border-right: 1px solid rgba(0, 0, 0, 0.2);
//color: rgb(19, 18, 18);
- color: white;
+ color: $light-color;
line-height: 1;
padding: 0px 2px;
margin: 1px;
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 3b87fe9de..2a613ba8b 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -2,10 +2,10 @@ import { action, IReactionDisposer, reaction } from "mobx";
import { baseKeymap } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-const { exampleSetup } = require("prosemirror-example-setup")
-import { EditorState, Transaction, } from "prosemirror-state";
+import { EditorState, Transaction, NodeSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "./RichTextSchema";
+import { Schema, NodeType } from "prosemirror-model"
import React = require("react")
import "./TooltipTextMenu.scss";
const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
@@ -16,7 +16,7 @@ import {
} from '@fortawesome/free-solid-svg-icons';
-
+//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
private tooltip: HTMLElement;
@@ -39,7 +39,8 @@ export class TooltipTextMenu {
{ command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
{ command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
{ command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
- { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }
+ //this doesn't work currently - look into notion of active block
+ { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
]
items.forEach(({ dom }) => this.tooltip.appendChild(dom));
@@ -49,7 +50,9 @@ export class TooltipTextMenu {
view.focus();
items.forEach(({ command, dom }) => {
if (dom.contains(e.srcElement)) {
- command(view.state, view.dispatch, view)
+ let active = command(view.state, view.dispatch, view);
+ //uncomment this if we want the bullet button to disappear if current selection is bulleted
+ // dom.style.display = active ? "" : "none"
}
})
})
@@ -66,13 +69,25 @@ export class TooltipTextMenu {
return span;
}
- blockActive(view: EditorView) {
- const { $from, to } = view.state.selection
+ //adapted this method - use it to check if block has a tag (ie bulleting)
+ blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
+ let attrs = {};
+
+ if (state.selection instanceof NodeSelection) {
+ const sel: NodeSelection = state.selection;
+ let $from = sel.$from;
+ let to = sel.to;
+ let node = sel.node;
+
+ if (node) {
+ return node.hasMarkup(type, attrs);
+ }
- return to <= $from.end() && $from.parent.hasMarkup(schema.nodes.bulletList);
+ return to <= $from.end() && $from.parent.hasMarkup(type, attrs);
+ }
}
- //this doesn't currently work but hopefully will soon
+ //this doesn't currently work but could be used to use icons for buttons
unorderedListIcon(): HTMLSpanElement {
let span = document.createElement("span");
let icon = document.createElement("FontAwesomeIcon");
@@ -105,8 +120,6 @@ export class TooltipTextMenu {
// Otherwise, reposition it and update its content
this.tooltip.style.display = ""
let { from, to } = state.selection
- // These are in screen coordinates
- //check this - tranform
let start = view.coordsAtPos(from), end = view.coordsAtPos(to)
// The box in which the tooltip is positioned, to use as base
let box = this.tooltip.offsetParent!.getBoundingClientRect()
@@ -116,8 +129,9 @@ export class TooltipTextMenu {
this.tooltip.style.left = (left - box.left) + "px"
let width = Math.abs(start.left - end.left) / 2;
let mid = Math.min(start.left, end.left) + width;
+
//THIS WIDTH IS 15 * NUMBER OF ICONS + 15
- this.tooltip.style.width = 120 + "px";
+ this.tooltip.style.width = 122 + "px";
this.tooltip.style.bottom = (box.bottom - start.top) + "px";
}
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
index 6bd614c8b..0964d5ff3 100644
--- a/src/client/views/.DS_Store
+++ b/src/client/views/.DS_Store
Binary files differ
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index ea40c8e99..f6830d9cd 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,41 +1,55 @@
+@import "global_variables";
.contextMenu-cont {
- position: absolute;
- display: flex;
- z-index: 1000;
- box-shadow: #AAAAAA .2vw .2vw .4vw;
- flex-direction: column;
+ position: absolute;
+ display: flex;
+ z-index: 1000;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
+ flex-direction: column;
+}
+
+.contextMenu-item:first-child {
+ background: $intermediate-color;
+ color: $light-color;
+}
+
+.contextMenu-item:first-child::placeholder {
+ color: $light-color;
+}
+
+.contextMenu-item:first-child:hover {
+ background: $intermediate-color;
+ color: $light-color;
}
.contextMenu-item {
- width: auto;
- height: auto;
- background: #F0F8FF;
- display: flex;
- justify-content: left;
- align-items: center;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- transition: all .1s;
- border-width: .11px;
- border-style: none;
- border-color: rgb(187, 186, 186);
- border-bottom-style: solid;
- padding: 10px;
- white-space: nowrap;
- font-size: 1.5vw;
+ width: auto;
+ height: auto;
+ background: $light-color-secondary;
+ display: flex;
+ justify-content: left;
+ align-items: center;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all 0.1s;
+ border-width: 0.11px;
+ border-style: none;
+ border-color: $intermediate-color;
+ border-bottom-style: solid;
+ padding: 10px;
+ white-space: nowrap;
+ font-size: 13px;
}
.contextMenu-item:hover {
- transition: all .1s;
- background: #B0E0E6;
+ transition: all 0.1s;
+ background: $lighter-alt-accent;
}
.contextMenu-description {
- font-size: 1.5vw;
- text-align: left;
- width: 8vw;
-} \ No newline at end of file
+ text-align: left;
+ width: 8vw;
+}
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index fcb934860..9109b56bb 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -13,6 +13,8 @@ export class ContextMenu extends React.Component {
@observable private _pageY: number = 0;
@observable private _display: string = "none";
@observable private _searchString: string = "";
+ // afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be
+ @observable private _yRelativeToTop: boolean = true;
private ref: React.RefObject<HTMLDivElement>;
@@ -44,8 +46,13 @@ export class ContextMenu extends React.Component {
@action
displayMenu(x: number, y: number) {
- this._pageX = x
- this._pageY = y
+ //maxX and maxY will change if the UI/font size changes, but will work for any amount
+ //of items added to the menu
+ let maxX = window.innerWidth - 150;
+ let maxY = window.innerHeight - (this._items.length * 34 + 30);
+
+ this._pageX = x > maxX ? maxX : x;
+ this._pageY = y > maxY ? maxY : y;
this._searchString = "";
@@ -54,8 +61,13 @@ export class ContextMenu extends React.Component {
intersects = (x: number, y: number): boolean => {
if (this.ref.current && this._display !== "none") {
- if (x >= this._pageX && x <= this._pageX + this.ref.current.getBoundingClientRect().width) {
- if (y >= this._pageY && y <= this._pageY + this.ref.current.getBoundingClientRect().height) {
+ let menuSize = { width: this.ref.current.getBoundingClientRect().width, height: this.ref.current.getBoundingClientRect().height };
+
+ let upperLeft = { x: this._pageX, y: this._yRelativeToTop ? this._pageY : window.innerHeight - (this._pageY + menuSize.height) };
+ let bottomRight = { x: this._pageX + menuSize.width, y: this._yRelativeToTop ? this._pageY + menuSize.height : window.innerHeight - this._pageY };
+
+ if (x >= upperLeft.x && x <= bottomRight.x) {
+ if (y >= upperLeft.y && y <= bottomRight.y) {
return true;
}
}
@@ -64,8 +76,12 @@ export class ContextMenu extends React.Component {
}
render() {
+ let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } :
+ { left: this._pageX, bottom: this._pageY, display: this._display };
+
+
return (
- <div className="contextMenu-cont" style={{ left: this._pageX, top: this._pageY, display: this._display }} ref={this.ref}>
+ <div className="contextMenu-cont" style={style} ref={this.ref}>
<input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange}></input>
{this._items.filter(prop => {
return prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1;
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index fb9091dfc..11595aa01 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -1,17 +1,18 @@
+@import "global_variables";
#documentDecorations-container {
position: absolute;
display: grid;
z-index: 1000;
- grid-template-rows: 20px 1fr 20px 0px;
- grid-template-columns: 20px 1fr 20px;
+ grid-template-rows: 8px 1fr 8px 30px;
+ grid-template-columns: 8px 1fr 8px;
pointer-events: none;
#documentDecorations-centerCont {
background: none;
}
.documentDecorations-resizer {
pointer-events: auto;
- background: lightblue;
- opacity: 0.4;
+ background: $alt-accent;
+ opacity: 0.8;
}
#documentDecorations-topLeftResizer,
#documentDecorations-bottomRightResizer {
@@ -29,23 +30,89 @@
#documentDecorations-rightResizer {
cursor: ew-resize;
}
-
}
+
+// position: absolute;
+// display: grid;
+// z-index: 1000;
+// grid-template-rows: 20px 1fr 20px 0px;
+// grid-template-columns: 20px 1fr 20px;
+// pointer-events: none;
+// #documentDecorations-centerCont {
+// background: none;
+// }
+// .documentDecorations-resizer {
+// pointer-events: auto;
+// background: lightblue;
+// opacity: 0.4;
+// }
+// #documentDecorations-topLeftResizer,
+// #documentDecorations-bottomRightResizer {
+// cursor: nwse-resize;
+// }
+// #documentDecorations-topRightResizer,
+// #documentDecorations-bottomLeftResizer {
+// cursor: nesw-resize;
+// }
+// #documentDecorations-topResizer,
+// #documentDecorations-bottomResizer {
+// cursor: ns-resize;
+// }
+// #documentDecorations-leftResizer,
+// #documentDecorations-rightResizer {
+// cursor: ew-resize;
+// }
+// }
+.linkFlyout {
+ grid-column: 1/4
+}
+
+.linkButton-empty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.linkButton-nonempty:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
.linkButton-empty {
height: 20px;
width: 20px;
margin-top: 10px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: #2B6091;
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
}
+
.linkButton-nonempty {
height: 20px;
width: 20px;
margin-top: 10px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: rgb(35, 165, 42);
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ transition: transform 0.2s;
+ text-align: center;
+ display: flex;
+ justify-content: center;
+ align-items: center;
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index dc62f97cf..3bdb7d5b3 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -18,6 +18,8 @@ export class DocumentDecorations extends React.Component {
static Instance: DocumentDecorations
private _resizer = ""
private _isPointerDown = false;
+
+ private _resizeBorderWidth = 16;
private _linkButton = React.createRef<HTMLDivElement>();
@observable private _hidden = false;
@@ -206,21 +208,24 @@ export class DocumentDecorations extends React.Component {
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
let selFirst = SelectionManager.SelectedDocuments()[0];
+ let linkToSize = selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length;
+ let linkFromSize = selFirst.props.Document.GetData(KeyStore.LinkedFromDocs, ListField, []).length;
+ let linkCount = linkToSize + linkFromSize;
linkButton = (<Flyout
anchorPoint={anchorPoints.RIGHT_TOP}
content={
<LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}>
</LinkMenu>
}>
- <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton} />
+ <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton}>{linkCount}</div>
</Flyout>);
}
return (
<div id="documentDecorations-container" style={{
- width: (bounds.r - bounds.x + 40) + "px",
- height: (bounds.b - bounds.y + 40) + "px",
- left: bounds.x - 20,
- top: bounds.y - 20,
+ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
+ height: (bounds.b - bounds.y + this._resizeBorderWidth + 30) + "px",
+ left: bounds.x - this._resizeBorderWidth / 2,
+ top: bounds.y - this._resizeBorderWidth / 2,
}}>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
@@ -232,7 +237,7 @@ export class DocumentDecorations extends React.Component {
<div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- {linkButton}
+ <div title="View Links" className="linkFlyout">{linkButton}</div>
</div >
)
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
new file mode 100644
index 000000000..be3c5069a
--- /dev/null
+++ b/src/client/views/EditableView.scss
@@ -0,0 +1,6 @@
+.editableView-container-editing {
+ overflow-wrap: break-word;
+ word-wrap: break-word;
+ hyphens: auto;
+ max-width: 300px;
+} \ No newline at end of file
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 84b1b91c3..3b54c0dbb 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -1,6 +1,7 @@
import React = require('react')
import { observer } from 'mobx-react';
import { observable, action } from 'mobx';
+import "./EditableView.scss"
export interface EditableProps {
/**
@@ -49,7 +50,7 @@ export class EditableView extends React.Component<EditableProps> {
style={{ display: "inline" }}></input>
} else {
return (
- <div className="editableView-container-editing" style={{ display: "inline", height: "100%", maxHeight: `${this.props.height}` }}
+ <div className="editableView-container-editing" style={{ display: "inline", height: "auto", maxHeight: `${this.props.height}` }}
onClick={action(() => this.editing = true)}>
{this.props.contents}
</div>
diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss
index f654b194b..e79b146b9 100644
--- a/src/client/views/InkingCanvas.scss
+++ b/src/client/views/InkingCanvas.scss
@@ -1,8 +1,10 @@
+@import "global_variables";
.inking-canvas {
- position: fixed;
+ position: absolute;
top: -50000px;
left: -50000px; // z-index: 99; //overlays ink on top of everything
svg {
+ position:absolute;
width: 100000px;
height: 100000px;
.highlight {
@@ -13,20 +15,135 @@
.inking-control {
position: absolute;
- right: 0;
- bottom: 75px;
- text-align: right;
+ left: 70px;
+ bottom: 70px;
+ margin: 0;
+ padding: 0;
+ display: flex;
+ label,
+ input,
+ option {
+ font-size: 12px;
+ }
+ input[type="range"] {
+ -webkit-appearance: none;
+ background-color: transparent;
+ vertical-align: middle;
+ margin-top: 8px;
+ &:focus {
+ outline: none;
+ }
+ &::-webkit-slider-runnable-track {
+ width: 100%;
+ height: 3px;
+ border-radius: 1.5px;
+ cursor: pointer;
+ background: $intermediate-color;
+ }
+ &::-webkit-slider-thumb {
+ height: 12px;
+ width: 12px;
+ border: 1px solid $intermediate-color;
+ border-radius: 6px;
+ background: $light-color;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -4px;
+ }
+ &::-moz-range-track {
+ width: 100%;
+ height: 3px;
+ border-radius: 1.5px;
+ cursor: pointer;
+ background: $light-color;
+ }
+ &::-moz-range-thumb {
+ height: 12px;
+ width: 12px;
+ border: 1px solid $intermediate-color;
+ border-radius: 6px;
+ background: $light-color;
+ cursor: pointer;
+ -webkit-appearance: none;
+ margin-top: -4px;
+ }
+ }
+ input[type="text"] {
+ border: none;
+ padding: 0 0px;
+ background: transparent;
+ color: $dark-color;
+ font-size: 12px;
+ margin-top: 4px;
+ }
.ink-panel {
- margin-top: 12px;
+ margin: 6px 12px 6px 0;
+ height: 30px;
+ vertical-align: middle;
+ line-height: 36px;
+ padding: 0 10px;
+ color: $intermediate-color;
&:first {
margin-top: 0;
}
}
+ .ink-tools {
+ display: flex;
+ background-color: transparent;
+ border-radius: 0;
+ padding: 0;
+ button {
+ height: 36px;
+ padding: 0px;
+ padding-bottom: 3px;
+ margin-left: 10px;
+ background-color: transparent;
+ color: $intermediate-color;
+ }
+ button:hover {
+ transform: scale(1.15);
+ }
+ }
.ink-size {
display: flex;
justify-content: space-between;
- input {
- width: 85%;
+ input[type="text"] {
+ width: 42px;
+ }
+ >* {
+ margin-right: 6px;
+ &:last-child {
+ margin-right: 0;
+ }
+ }
+ }
+ .ink-color {
+ display: flex;
+ position: relative;
+ padding-right: 0;
+ label {
+ margin-right: 6px;
+ }
+ .ink-color-display {
+ border-radius: 11px;
+ width: 22px;
+ height: 22px;
+ margin-top: 6px;
+ cursor: pointer;
+ text-align: center; // span {
+ // color: $light-color;
+ // font-size: 8px;
+ // user-select: none;
+ // }
+ }
+ .ink-color-picker {
+ background-color: $light-color;
+ border-radius: 5px;
+ padding: 12px;
+ position: absolute;
+ bottom: 36px;
+ left: -3px;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
}
}
} \ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 0d87c1239..84c47f616 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -1,4 +1,5 @@
import { observer } from "mobx-react";
+import { observable } from "mobx";
import { action, computed } from "mobx";
import { InkingControl } from "./InkingControl";
import React = require("react");
@@ -6,14 +7,10 @@ import { Transform } from "../util/Transform";
import { Document } from "../../fields/Document";
import { KeyStore } from "../../fields/KeyStore";
import { InkField, InkTool, StrokeData, StrokeMap } from "../../fields/InkField";
-import { JsxArgs } from "./nodes/DocumentView";
import { InkingStroke } from "./InkingStroke";
import "./InkingCanvas.scss"
-import { CollectionDockingView } from "./collections/CollectionDockingView";
import { Utils } from "../../Utils";
import { FieldWaiting } from "../../fields/Field";
-import { getMapLikeKeys } from "mobx/lib/internal";
-
interface InkCanvasProps {
getScreenTransform: () => Transform;
@@ -22,7 +19,16 @@ interface InkCanvasProps {
@observer
export class InkingCanvas extends React.Component<InkCanvasProps> {
-
+ static InkOffset: number = 50000;
+ public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
+ let inside = false;
+ stroke.pathData.map(val => {
+ if (selRect.left < val.x - InkingCanvas.InkOffset && selRect.left + selRect.width > val.x - InkingCanvas.InkOffset &&
+ selRect.top < val.y - InkingCanvas.InkOffset && selRect.top + selRect.height > val.y - InkingCanvas.InkOffset)
+ inside = true;
+ });
+ return inside
+ }
private _isDrawing: boolean = false;
private _idGenerator: string = "";
@@ -51,7 +57,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
document.removeEventListener("mouseup", this.handleMouseUp);
}
-
@action
handleMouseDown = (e: React.PointerEvent): void => {
if (e.button != 0 ||
@@ -62,7 +67,6 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
if (InkingControl.Instance.selectedTool === InkTool.Eraser) {
return
}
- e.stopPropagation()
const point = this.relativeCoordinatesForEvent(e);
// start the new line, saves a uuid to represent the field of the stroke
@@ -74,7 +78,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
color: InkingControl.Instance.selectedColor,
width: InkingControl.Instance.selectedWidth,
tool: InkingControl.Instance.selectedTool,
- page: this.props.Document.GetNumber(KeyStore.CurPage, 0)
+ page: this.props.Document.GetNumber(KeyStore.CurPage, -1)
});
this.inkData = data;
this._isDrawing = true;
@@ -110,8 +114,8 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
relativeCoordinatesForEvent = (e: React.MouseEvent): { x: number, y: number } => {
let [x, y] = this.props.getScreenTransform().transformPoint(e.clientX, e.clientY);
- x += 50000
- y += 50000
+ x += InkingCanvas.InkOffset;
+ y += InkingCanvas.InkOffset;
return { x, y };
}
@@ -145,11 +149,11 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
// parse data from server
let paths: Array<JSX.Element> = []
- let curPage = this.props.Document.GetNumber(KeyStore.CurPage, 0)
+ let curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1)
Array.from(lines).map(item => {
let id = item[0];
let strokeData = item[1];
- if (strokeData.page == 0 || strokeData.page == curPage)
+ if (strokeData.page == -1 || strokeData.page == curPage)
paths.push(<InkingStroke key={id} id={id}
line={strokeData.pathData}
color={strokeData.color}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 955831fb6..fb75ef2a5 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -1,16 +1,25 @@
import { observable, action, computed } from "mobx";
-import { CirclePicker, ColorResult } from 'react-color';
+
+import { CirclePicker, ColorResult } from 'react-color'
import React = require("react");
import "./InkingCanvas.scss"
import { InkTool } from "../../fields/InkField";
import { observer } from "mobx-react";
+import "./InkingCanvas.scss"
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
+
+library.add(faPen, faHighlighter, faEraser, faBan);
@observer
export class InkingControl extends React.Component {
static Instance: InkingControl = new InkingControl({});
@observable private _selectedTool: InkTool = InkTool.None;
- @observable private _selectedColor: string = "#f44336";
+ @observable private _selectedColor: string = "rgb(244, 67, 54)";
@observable private _selectedWidth: string = "25";
+ @observable private _open: boolean = false;
+ @observable private _colorPickerDisplay: boolean = false;
constructor(props: Readonly<{}>) {
super(props);
@@ -49,29 +58,50 @@ export class InkingControl extends React.Component {
selected = (tool: InkTool) => {
if (this._selectedTool === tool) {
- return { backgroundColor: "black", color: "white" }
+ return { color: "#61aaa3" }
}
return {}
}
+ @action
+ toggleDisplay = () => {
+ this._open = !this._open;
+ }
+
+ @action
+ toggleColorPicker = () => {
+ this._colorPickerDisplay = !this._colorPickerDisplay;
+ }
+
render() {
return (
- <div className="inking-control">
- <div className="ink-tools ink-panel">
- <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}>Pen</button>
- <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}>Highlighter</button>
- <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}>Eraser</button>
- <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}> None</button>
- </div>
- <div className="ink-size ink-panel">
- <label htmlFor="stroke-width">Size</label>
- <input type="range" min="1" max="100" defaultValue="25" name="stroke-width"
+ <ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}>
+ <li className="ink-tools ink-panel">
+ <div className="ink-tool-buttons">
+ <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button>
+ <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Highlighter" /></button>
+ <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Eraser" /></button>
+ <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="lg" title="Pointer" /></button>
+ </div>
+ </li>
+ <li className="ink-color ink-panel">
+ <label>COLOR: </label>
+ <div className="ink-color-display" style={{ backgroundColor: this._selectedColor }}
+ onClick={() => this.toggleColorPicker()}>
+ {/* {this._colorPickerDisplay ? <span>&#9660;</span> : <span>&#9650;</span>} */}
+ </div>
+ <div className="ink-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}>
+ <CirclePicker onChange={this.switchColor} circleSize={22} width={"220"} />
+ </div>
+ </li>
+ <li className="ink-size ink-panel">
+ <label htmlFor="stroke-width">SIZE: </label>
+ <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width"
+ onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
+ <input type="range" min="1" max="100" value={this._selectedWidth} name="stroke-width"
onChange={(e: React.ChangeEvent<HTMLInputElement>) => this.switchWidth(e.target.value)} />
- </div>
- <div className="ink-color ink-panel">
- <CirclePicker onChange={this.switchColor} />
- </div>
- </div>
+ </li>
+ </ul >
)
}
} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index d724421d3..87b5c43d8 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -21,8 +21,6 @@ export class InkingStroke extends React.Component<StrokeProps> {
@observable private _strokeColor: string = this.props.color;
@observable private _strokeWidth: string = this.props.width;
- private _canvasColor: string = "#cdcdcd";
-
deleteStroke = (e: React.MouseEvent): void => {
if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
this.props.deleteCallback(this.props.id);
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4334ed299..bb42db202 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -1,22 +1,35 @@
+@import "global_variables";
+@import "nodeModuleOverrides";
html,
body {
width: 100%;
height: 100%;
overflow: hidden;
- font-family: 'Hind Siliguri', sans-serif;
+ font-family: $sans-serif;
margin: 0;
}
+#dash-title {
+ position: absolute;
+ right: 46.5%;
+ letter-spacing: 3px;
+ top: 9px;
+ font-size: 12px;
+ color: $alt-accent;
+ z-index: 9999;
+}
+
h1 {
font-size: 50px;
position: fixed;
top: 30px;
left: 50%;
transform: translateX(-50%);
- color: black;
+ color: $dark-color;
text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
z-index: 9999;
- font-family: 'Fjalla One', sans-serif;
+ font-family: $sans-serif;
+ font-weight: 700;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
@@ -25,27 +38,139 @@ h1 {
user-select: none;
}
+.jsx-parser {
+ width:100%
+}
+
p {
margin: 0px;
padding: 0px;
}
+
::-webkit-scrollbar {
-webkit-appearance: none;
- height:5px;
- width:5px;
+ height: 5px;
+ width: 5px;
}
+
::-webkit-scrollbar-thumb {
border-radius: 2px;
- background-color: rgba(0,0,0,.5);
+ background-color: rgba(0, 0, 0, 0.5);
}
-.main-buttonDiv {
+// button stuff
+button {
+ background: $dark-color;
+ outline: none;
+ border: 0px;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 75%;
+ padding: 10px;
+ transition: transform 0.2s;
+}
+
+button:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+}
+
+.clear-db-button {
position: absolute;
- width: 150px;
- left: 0px;
+ right: 45%;
+ bottom: 3%;
+ font-size: 50%;
+}
+
+.round-button {
+ width: 36px;
+ height: 36px;
+ border-radius: 18px;
+ font-size: 15px;
}
+
+.round-button:hover {
+ transform: scale(1.15);
+}
+
+.add-button {
+ position: relative;
+ margin-right: 10px;
+}
+
.main-undoButtons {
position: absolute;
width: 150px;
right: 0px;
}
+
+//toolbar stuff
+#toolbar {
+ position: absolute;
+ bottom: 62px;
+ left: 24px;
+ .toolbar-button {
+ display: block;
+ margin-bottom: 10px;
+ }
+}
+
+// add nodes menu. Note that the + button is actually an input label, not an actual button.
+#add-nodes-menu {
+ position: absolute;
+ bottom: 24px;
+ left: 24px;
+ label {
+ background: $dark-color;
+ color: $light-color;
+ display: inline-block;
+ border-radius: 18px;
+ font-size: 25px;
+ width: 36px;
+ height: 36px;
+ margin-right: 10px;
+ cursor: pointer;
+ transition: transform 0.2s;
+ }
+ label p {
+ padding-left: 10.5px;
+ padding-top: 3px;
+ }
+ label:hover {
+ background: $main-accent;
+ transform: scale(1.15);
+ }
+ input {
+ display: none;
+ }
+ input:not(:checked)~#add-options-content {
+ display: none;
+ }
+ input:checked~label {
+ transform: rotate(45deg);
+ transition: transform 0.5s;
+ cursor: pointer;
+ }
+}
+
+#add-options-content {
+ display: table;
+ opacity: 1;
+ margin: 0;
+ padding: 0;
+ position: relative;
+ float: right;
+ bottom: 0.3em;
+ margin-bottom: -1.68em;
+}
+
+ul#add-options-list {
+ list-style: none;
+ padding: 0;
+ li {
+ display: inline-block;
+ padding: 0;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index b78f59681..f938e83b9 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -18,6 +18,19 @@ import { DocumentDecorations } from './DocumentDecorations';
import { DocumentView } from './nodes/DocumentView';
import "./Main.scss";
import { InkingControl } from './InkingControl';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faFont } from '@fortawesome/free-solid-svg-icons';
+import { faImage } from '@fortawesome/free-solid-svg-icons';
+import { faFilePdf } from '@fortawesome/free-solid-svg-icons';
+import { faObjectGroup } from '@fortawesome/free-solid-svg-icons';
+import { faTable } from '@fortawesome/free-solid-svg-icons';
+import { faGlobeAsia } from '@fortawesome/free-solid-svg-icons';
+import { faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { faRedoAlt } from '@fortawesome/free-solid-svg-icons';
+import { faPenNib } from '@fortawesome/free-solid-svg-icons';
+import { faFilm } from '@fortawesome/free-solid-svg-icons';
+import { faMusic } from '@fortawesome/free-solid-svg-icons';
configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action
@@ -28,11 +41,23 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
ContextMenu.Instance.clearItems()
}
}), true)
-
-
-const mainDocId = "mainDoc";
+const pathname = window.location.pathname.split("/");
+const mainDocId = pathname[pathname.length - 1];
let mainContainer: Document;
let mainfreeform: Document;
+
+library.add(faFont);
+library.add(faImage);
+library.add(faFilePdf);
+library.add(faObjectGroup);
+library.add(faTable);
+library.add(faGlobeAsia);
+library.add(faUndoAlt);
+library.add(faRedoAlt);
+library.add(faPenNib);
+library.add(faFilm);
+library.add(faMusic);
+
Documents.initProtos(mainDocId, (res?: Document) => {
if (res instanceof Document) {
mainContainer = res;
@@ -60,9 +85,9 @@ Documents.initProtos(mainDocId, (res?: Document) => {
let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" }));
- let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
- let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
- let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
+ let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, title: "video node" }));
+ let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, title: "a schema collection" }));
+ let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
let addClick = (creator: () => Document) => action(() =>
@@ -80,6 +105,8 @@ Documents.initProtos(mainDocId, (res?: Document) => {
ReactDOM.render((
<div style={{ position: "absolute", width: "100%", height: "100%" }}>
+ {/* <div id="dash-title">— DASH —</div> */}
+
<DocumentView Document={mainContainer}
AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
ContentScaling={() => 1}
@@ -91,27 +118,52 @@ Documents.initProtos(mainDocId, (res?: Document) => {
ContainingCollectionView={undefined} />
<DocumentDecorations />
<ContextMenu />
- <div className="main-buttonDiv" style={{ bottom: '0px' }} ref={imgRef} >
- <button onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>Add Image</button></div>
- <div className="main-buttonDiv" style={{ bottom: '25px' }} ref={webRef} >
- <button onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>Add Web</button></div>
- <div className="main-buttonDiv" style={{ bottom: '50px' }} ref={textRef}>
- <button onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>Add Text</button></div>
- <div className="main-buttonDiv" style={{ bottom: '75px' }} ref={colRef}>
- <button onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>Add Collection</button></div>
- <div className="main-buttonDiv" style={{ bottom: '100px' }} ref={schemaRef}>
- <button onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>Add Schema</button></div>
- <div className="main-buttonDiv" style={{ bottom: '125px' }} >
- <button onClick={clearDatabase}>Clear Database</button></div>
- <div className="main-buttonDiv" style={{ bottom: '175px' }} ref={videoRef}>
- <button onPointerDown={setupDrag(videoRef, addVideoNode)} onClick={addClick(addVideoNode)}>Add Video</button></div>
- <div className="main-buttonDiv" style={{ bottom: '200px' }} ref={audioRef}>
- <button onPointerDown={setupDrag(audioRef, addAudioNode)} onClick={addClick(addAudioNode)}>Add Audio</button></div>
- <div className="main-buttonDiv" style={{ bottom: '150px' }} ref={pdfRef}>
- <button onPointerDown={setupDrag(pdfRef, addPDFNode)} onClick={addClick(addPDFNode)}>Add PDF</button></div>
- <button className="main-undoButtons" style={{ bottom: '25px' }} onClick={() => UndoManager.Undo()}>Undo</button>
- <button className="main-undoButtons" style={{ bottom: '0px' }} onClick={() => UndoManager.Redo()}>Redo</button>
+ <button className="clear-db-button" onClick={clearDatabase}>Clear Database</button>
+
+ {/* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */}
+ < div id="toolbar" >
+ <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
+ <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
+ <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
+ </div >
+
+ {/* for the expandable add nodes menu. Not included with the above because once it expands it expands the whole div with it, making canvas interactions limited. */}
+ < div id="add-nodes-menu" >
+ <input type="checkbox" id="add-menu-toggle" />
+ <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
+
+ <div id="add-options-content">
+ <ul id="add-options-list">
+ <li><div ref={textRef}><button className="round-button add-button" title="Add Textbox" onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>
+ <FontAwesomeIcon icon="font" size="sm" />
+ </button></div></li>
+ <li><div ref={imgRef}><button className="round-button add-button" title="Add Image" onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>
+ <FontAwesomeIcon icon="image" size="sm" />
+ </button></div></li>
+ <li><div ref={pdfRef}><button className="round-button add-button" title="Add PDF" onPointerDown={setupDrag(pdfRef, addPDFNode)} onClick={addClick(addPDFNode)}>
+ <FontAwesomeIcon icon="file-pdf" size="sm" />
+ </button></div></li>
+ <li><div ref={videoRef}><button className="round-button add-button" title="Add Video" onPointerDown={setupDrag(videoRef, addVideoNode)} onClick={addClick(addVideoNode)}>
+ <FontAwesomeIcon icon="film" size="sm" />
+ </button></div></li>
+ <li><div ref={audioRef}><button className="round-button add-button" title="Add Audio" onPointerDown={setupDrag(audioRef, addAudioNode)} onClick={addClick(addAudioNode)}>
+ <FontAwesomeIcon icon="music" size="sm" />
+ </button></div></li>
+ <li><div ref={webRef}><button className="round-button add-button" title="Add Web Clipping" onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>
+ <FontAwesomeIcon icon="globe-asia" size="sm" />
+ </button></div></li>
+ <li><div ref={colRef}><button className="round-button add-button" title="Add Collection" onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>
+ <FontAwesomeIcon icon="object-group" size="sm" />
+ </button></div></li>
+ <li><div ref={schemaRef}><button className="round-button add-button" title="Add Schema" onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>
+ <FontAwesomeIcon icon="table" size="sm" />
+ </button></div></li>
+ </ul>
+ </div>
+
+ </div >
+
<InkingControl />
- </div>),
+ </div >),
document.getElementById('root'));
})
diff --git a/src/client/views/_global_variables.scss b/src/client/views/_global_variables.scss
new file mode 100644
index 000000000..44a819b79
--- /dev/null
+++ b/src/client/views/_global_variables.scss
@@ -0,0 +1,17 @@
+@import url("https://fonts.googleapis.com/css?family=Noto+Sans:400,700|Crimson+Text:400,400i,700");
+// colors
+$light-color: #fcfbf7;
+$light-color-secondary: rgb(241, 239, 235);
+$main-accent: #61aaa3;
+// $alt-accent: #cdd5ec;
+// $alt-accent: #cdeceb;
+$alt-accent: #59dff7;
+$lighter-alt-accent: rgb(207, 220, 240);
+$intermediate-color: #9c9396;
+$dark-color: #121721;
+// fonts
+$sans-serif: "Noto Sans", sans-serif;
+// $sans-serif: "Roboto Slab", sans-serif;
+$serif: "Crimson Text", serif;
+// misc values
+$border-radius: 0.3em;
diff --git a/src/client/views/_nodeModuleOverrides.scss b/src/client/views/_nodeModuleOverrides.scss
new file mode 100644
index 000000000..6f97e60f8
--- /dev/null
+++ b/src/client/views/_nodeModuleOverrides.scss
@@ -0,0 +1,23 @@
+// this file is for overriding all the css from installed node modules
+
+// goldenlayout stuff
+div .lm_header {
+ background: $dark-color;
+ min-height: 2em;
+}
+
+.lm_tab {
+ margin-top: 0.6em !important;
+ padding-top: 0.5em !important;
+ min-height: 1.35em;
+ padding-bottom: 0px;
+ border-radius: 5px;
+ font-family: $sans-serif !important;
+}
+
+.lm_header .lm_controls {
+ right: 1em !important;
+}
+
+// @TODO the ril__navgiation buttons in the img gallery are a lil messed up but I can't figure out
+// why. Low priority for now but it's bugging me. --Julie
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 6a0404663..94005a4c0 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -150,9 +150,13 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
}
componentWillUnmount: () => void = () => {
- this._goldenLayout.unbind('itemDropped', this.itemDropped);
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ try {
+ this._goldenLayout.unbind('itemDropped', this.itemDropped);
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ } catch (e) {
+
+ }
this._goldenLayout.destroy();
this._goldenLayout = null;
window.removeEventListener('resize', this.onResize);
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
index d487cd7ce..9c7a03b52 100644
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ b/src/client/views/collections/CollectionFreeFormView.scss
@@ -1,36 +1,56 @@
+@import "../global_variables";
+
.collectionfreeformview-container {
+ .collectionfreeformview > .jsx-parser {
+ position: absolute;
+ height: 100%;
+ width: 100%;
+ }
- .collectionfreeformview > .jsx-parser{
- position:absolute;
- height: 100%;
- width: 100%;
- }
-
- border-style: solid;
- box-sizing: border-box;
- position: relative;
+ //nested freeform views
+ .collectionfreeformview-container {
+ // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
+ // linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
+ // background-size: 30px 30px;
+ }
+
+ border: 0px solid $light-color-secondary;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ position: relative;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+ .collectionfreeformview {
+ position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
- overflow: hidden;
- .collectionfreeformview {
- position: absolute;
- top: 0;
- left: 0;
- width:100%;
- height: 100%;
- }
-}
-.collectionfreeformview-marquee{
- border-style: dashed;
- box-sizing: border-box;
- position: absolute;
- border-width: 1px;
- border-color: black;
+ }
}
.collectionfreeformview-overlay {
+ .collectionfreeformview > .jsx-parser {
+ position: absolute;
+ height: 100%;
+ }
+ .formattedTextBox-cont {
+ background: $light-color-secondary;
+ }
+ position:absolute;
+ border: 0px solid transparent;
+ border-radius: $border-radius;
+ overflow: hidden;
+ box-sizing: border-box;
+ top: 0;
+ left: 0;
+ width: 100%;
+ height: 100%;
+ .collectionfreeformview {
.collectionfreeformview > .jsx-parser{
position:absolute;
height: 100%;
@@ -39,37 +59,39 @@
background:yellow;
}
- border-style: solid;
- box-sizing: border-box;
+ // overflow: hidden;
+ // border-style: solid;
+ // box-sizing: border-box;
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
- overflow: hidden;
- .collectionfreeformview {
- position: absolute;
- top: 0;
- left: 0;
- width:100%;
- height: 100%;
- }
+ }
}
+// selection border...?
.border {
- border-style: solid;
- box-sizing: border-box;
- width: 100%;
- height: 100%;
+ border-style: solid;
+ box-sizing: border-box;
+ width: 98%;
+ height: 98%;
+ border-radius: $border-radius;
}
//this is an animation for the blinking cursor!
@keyframes blink {
- 0% {opacity: 0}
- 49%{opacity: 0}
- 50% {opacity: 1}
+ 0% {
+ opacity: 0;
+ }
+ 49% {
+ opacity: 0;
+ }
+ 50% {
+ opacity: 1;
+ }
}
#prevCursor {
- animation: blink 1s infinite;
-} \ No newline at end of file
+ animation: blink 1s infinite;
+}
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index b0cd7e017..9dc1ae847 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -5,46 +5,74 @@ import { FieldWaiting } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
import { TextField } from "../../../fields/TextField";
-import { Documents } from "../../documents/Documents";
import { DragManager } from "../../util/DragManager";
import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { InkingCanvas } from "../InkingCanvas";
+import { AudioBox } from "../nodes/AudioBox";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentView } from "../nodes/DocumentView";
import { FormattedTextBox } from "../nodes/FormattedTextBox";
import { ImageBox } from "../nodes/ImageBox";
import { KeyValueBox } from "../nodes/KeyValueBox";
import { PDFBox } from "../nodes/PDFBox";
+import { VideoBox } from "../nodes/VideoBox";
import { WebBox } from "../nodes/WebBox";
import "./CollectionFreeFormView.scss";
import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { CollectionViewBase } from "./CollectionViewBase";
+import { MarqueeView } from "./MarqueeView";
+import { PreviewCursor } from "./PreviewCursor";
import React = require("react");
-import { SelectionManager } from "../../util/SelectionManager";
const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
@observer
export class CollectionFreeFormView extends CollectionViewBase {
- private _canvasRef = React.createRef<HTMLDivElement>();
- @observable
- private _lastX: number = 0;
- @observable
- private _lastY: number = 0;
+ public _canvasRef = React.createRef<HTMLDivElement>();
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
- @observable
- private _downX: number = 0;
- @observable
- private _downY: number = 0;
+ public addLiveTextBox = (newBox: Document) => {
+ // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
+ this._selectOnLoaded = newBox.Id;
+ //set text to be the typed key and get focus on text box
+ this.props.addDocument(newBox);
+ //remove cursor from screen
+ this.PreviewCursorVisible = false;
+ }
+
+ public selectDocuments = (docs: Document[]) => {
+ this.props.CollectionView.SelectedDocs.length = 0;
+ docs.map(d => this.props.CollectionView.SelectedDocs.push(d.Id));
+ }
+
+ public getActiveDocuments = () => {
+ var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
+ const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
+ let active: Document[] = [];
+ if (lvalue && lvalue != FieldWaiting) {
+ lvalue.Data.map(doc => {
+ var page = doc.GetNumber(KeyStore.Page, -1);
+ if (page == curPage || page == -1) {
+ active.push(doc);
+ }
+ })
+ }
+
+ return active;
+ }
//determines whether the blinking cursor for indicating whether a text will be made on key down is visible
- @observable
- private _previewCursorVisible: boolean = false;
+ @observable public PreviewCursorVisible: boolean = false;
+ @observable public MarqueeVisible = false;
+ @observable public DownX: number = 0;
+ @observable public DownY: number = 0;
+ @observable private _lastX: number = 0;
+ @observable private _lastY: number = 0;
@computed get panX(): number { return this.props.Document.GetNumber(KeyStore.PanX, 0) }
@computed get panY(): number { return this.props.Document.GetNumber(KeyStore.PanY, 0) }
@@ -72,117 +100,68 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
}
- @observable
- _marquee = false;
+
+ @action
+ cleanupInteractions = () => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ this.MarqueeVisible = false;
+ }
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (((e.button === 2 && this.props.active()) || !e.defaultPrevented) && !e.shiftKey &&
- (!this.isAnnotationOverlay || this.zoomScaling != 1 || e.button == 0)) {
+ this.PreviewCursorVisible = false;
+ if ((e.button === 2 && this.props.active() && (!this.isAnnotationOverlay || this.zoomScaling != 1)) || e.button == 0) {
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
- this._downX = e.pageX;
- this._downY = e.pageY;
+ this._lastX = this.DownX = e.pageX;
+ this._lastY = this.DownY = e.pageY;
}
}
@action
onPointerUp = (e: PointerEvent): void => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
- if (this._marquee) {
- if (!e.shiftKey) {
- SelectionManager.DeselectAll();
- }
- var selectedDocs = this.marqueeSelect();
- selectedDocs.map(s => this.props.CollectionView.SelectedDocs.push(s.Id));
- this._marquee = false;
- }
- else if (!this._marquee && Math.abs(this._downX - e.clientX) < 3 && Math.abs(this._downY - e.clientY) < 3) {
+ if (Math.abs(this.DownX - e.clientX) < 4 && Math.abs(this.DownY - e.clientY) < 4) {
//show preview text cursor on tap
- this._previewCursorVisible = true;
+ this.PreviewCursorVisible = true;
//select is not already selected
if (!this.props.isSelected()) {
this.props.select(false);
}
}
-
- }
-
- intersectRect(r1: { left: number, right: number, top: number, bottom: number },
- r2: { left: number, right: number, top: number, bottom: number }) {
- return !(r2.left > r1.right ||
- r2.right < r1.left ||
- r2.top > r1.bottom ||
- r2.bottom < r1.top);
- }
-
- marqueeSelect() {
- this.props.CollectionView.SelectedDocs.length = 0;
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
- let p = this.getTransform().transformPoint(this._downX, this._downY);
- let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- let selRect = { left: p[0], top: p[1], right: p[0] + v[0], bottom: p[1] + v[1] }
-
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
- const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
- let selection: Document[] = [];
- if (lvalue && lvalue != FieldWaiting) {
- lvalue.Data.map(doc => {
- var page = doc.GetNumber(KeyStore.Page, 0);
- if (page == curPage || page == 0) {
- var x = doc.GetNumber(KeyStore.X, 0);
- var y = doc.GetNumber(KeyStore.Y, 0);
- var w = doc.GetNumber(KeyStore.Width, 0);
- var h = doc.GetNumber(KeyStore.Height, 0);
- if (this.intersectRect({ left: x, top: y, right: x + w, bottom: y + h }, selRect))
- selection.push(doc)
- }
- })
- }
- return selection;
+ this.cleanupInteractions();
}
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble && this.props.active()) {
- e.stopPropagation();
- e.preventDefault();
- let wasMarquee = this._marquee;
- this._marquee = e.buttons != 2;
- if (this._marquee && !wasMarquee) {
- document.addEventListener("keydown", this.marqueeCommand);
+ if (e.buttons == 1 && !e.altKey && !e.metaKey) {
+ this.MarqueeVisible = true;
}
-
- if (!this._marquee) {
+ if (this.MarqueeVisible) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ else if ((!this.isAnnotationOverlay || this.zoomScaling != 1) && !e.shiftKey) {
let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- this._previewCursorVisible = false;
this.SetPan(x - dx, y - dy);
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ e.stopPropagation();
+ e.preventDefault();
}
}
- this._lastX = e.pageX;
- this._lastY = e.pageY;
- }
-
- @action
- marqueeCommand = (e: KeyboardEvent) => {
- if (e.key == "Backspace") {
- this.marqueeSelect().map(d => this.props.removeDocument(d));
- }
- if (e.key == "c") {
- }
}
@action
onPointerWheel = (e: React.WheelEvent): void => {
+ this.props.select(false);
e.stopPropagation();
e.preventDefault();
let coefficient = 1000;
@@ -218,7 +197,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
private SetPan(panX: number, panY: number) {
var x1 = this.getLocalTransform().inverse().Scale;
- var x2 = this.getTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY));
this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
@@ -235,24 +213,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
@action
- onKeyDown = (e: React.KeyboardEvent<Element>) => {
- //if not these keys, make a textbox if preview cursor is active!
- if (!e.ctrlKey && !e.altKey) {
- if (this._previewCursorVisible) {
- //make textbox and add it to this collection
- let [x, y] = this.getTransform().transformPoint(this._downX, this._downY); (this._downX, this._downY);
- let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" });
- // mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
- this._selectOnLoaded = newBox.Id;
- //set text to be the typed key and get focus on text box
- this.props.CollectionView.addDocument(newBox);
- //remove cursor from screen
- this._previewCursorVisible = false;
- }
- }
- }
-
- @action
bringToFront(doc: Document) {
const { fieldKey: fieldKey, Document: Document } = this.props;
@@ -293,7 +253,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
@computed
get views() {
- var curPage = this.props.Document.GetNumber(KeyStore.CurPage, 1);
+ var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
const lvalue = this.props.Document.GetT<ListField<Document>>(this.props.fieldKey, ListField);
if (lvalue && lvalue != FieldWaiting) {
return lvalue.Data.map(doc => {
@@ -320,7 +280,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
get backgroundView() {
return !this.backgroundLayout ? (null) :
(<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
bindings={this.props.bindings}
jsx={this.backgroundLayout}
showWarnings={true}
@@ -331,7 +291,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
get overlayView() {
return !this.overlayLayout ? (null) :
(<JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, PDFBox }}
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
bindings={this.props.bindings}
jsx={this.overlayLayout}
showWarnings={true}
@@ -340,28 +300,17 @@ export class CollectionFreeFormView extends CollectionViewBase {
}
getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform())
+ getMarqueeTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH)
getLocalTransform = (): Transform => Transform.Identity.scale(1 / this.scale).translate(this.panX, this.panY);
noScaling = () => 1;
//when focus is lost, this will remove the preview cursor
@action
- onBlur = (e: React.FocusEvent<HTMLDivElement>): void => {
- this._previewCursorVisible = false;
+ onBlur = (): void => {
+ this.PreviewCursorVisible = false;
}
render() {
- //determines whether preview text cursor should be visible (ie when user taps this collection it should)
- let cursor = null;
- if (this._previewCursorVisible) {
- //get local position and place cursor there!
- let [x, y] = this.getTransform().transformPoint(this._downX, this._downY);
- cursor = <div id="prevCursor" onKeyPress={this.onKeyDown} style={{ color: "black", position: "absolute", transformOrigin: "left top", transform: `translate(${x}px, ${y}px)` }}>I</div>
- }
-
- let p = this.getTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
- let v = this.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
- var marquee = this._marquee ? <div className="collectionfreeformview-marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }}></div> : (null);
-
let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
@@ -370,7 +319,6 @@ export class CollectionFreeFormView extends CollectionViewBase {
return (
<div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
onPointerDown={this.onPointerDown}
- onKeyPress={this.onKeyDown}
onWheel={this.onPointerWheel}
onDrop={this.onDrop.bind(this)}
onDragOver={this.onDragOver}
@@ -383,12 +331,14 @@ export class CollectionFreeFormView extends CollectionViewBase {
ref={this._canvasRef}>
{this.backgroundView}
<InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
- {cursor}
+ <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
{this.views}
- {marquee}
</div>
+ <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+ addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+ getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
{this.overlayView}
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
new file mode 100644
index 000000000..0144625c1
--- /dev/null
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -0,0 +1,27 @@
+.collectionPdfView-buttonTray {
+ top : 25px;
+ left : 20px;
+ position: relative;
+ transform-origin: left top;
+ position: absolute;
+}
+.collectionPdfView-cont{
+ width: 100%;
+ height: 100%;
+ position: absolute;
+
+}
+.collectionPdfView-backward {
+ color : white;
+ top :0px;
+ left : 0px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+}
+.collectionPdfView-forward {
+ color : white;
+ top :0px;
+ left : 35px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index f22c07060..124d82c8b 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,10 +1,11 @@
-import { action, computed } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
import { ContextMenu } from "../ContextMenu";
import { CollectionView, CollectionViewType } from "./CollectionView";
import { CollectionViewProps } from "./CollectionViewBase";
+import "./CollectionPDFView.scss"
import React = require("react");
import { FieldId } from "../../../fields/Field";
@@ -18,22 +19,23 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
}
- public SelectedDocs: FieldId[] = []
- @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : 0;
- @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : 0;
+ private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
+ private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); }
+ @action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : -1;
+ @action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1;
- @computed private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, 0); }
- @computed private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); }
- @computed private get uIButtons() {
+ private get uIButtons() {
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
return (
- <div className="pdfBox-buttonTray" key="tray">
- <button className="pdfButton" onClick={this.onPageBack}>{"<"}</button>
- <button className="pdfButton" onClick={this.onPageForward}>{">"}</button>
+ <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
+ <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
</div>);
}
// "inherited" CollectionView API starts here...
-
+ @observable
+ public SelectedDocs: FieldId[] = []
public active: () => boolean = () => CollectionView.Active(this);
addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
@@ -49,7 +51,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
get subView(): any { return CollectionView.SubView(this); }
render() {
- return (<div className="collectionView-cont" onContextMenu={this.specificContextMenu}>
+ return (<div className="collectionPdfView-cont" onContextMenu={this.specificContextMenu}>
{this.subView}
{this.props.isSelected() ? this.uIButtons : (null)}
</div>)
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index d40e6d314..0d615dc01 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,185 +1,162 @@
-
+@import "../global_variables";
.collectionSchemaView-container {
- border-style: solid;
+ border: 1px solid $intermediate-color;
+ border-radius: $border-radius;
+ box-sizing: border-box;
+ position: absolute;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+
+.collectionSchemaView-content {
+ position: absolute;
+ height:100%;
+ width:100%;
+ overflow:auto;
+}
+ .collectionSchemaView-previewRegion {
+ position: relative;
+ background: $light-color;
+ float: left;
+ height: 100%;
+ }
+ .collectionSchemaView-previewHandle {
+ position: absolute;
+ height: 37px;
+ width: 20px;
+ z-index: 20;
+ right: 0;
+ top: 0;
+ background: $main-accent;
+ }
+ .collectionSchemaView-dividerDragger {
+ position: relative;
box-sizing: border-box;
+ border-left: 1px solid $intermediate-color;
+ border-right: 1px solid $intermediate-color;
+ float: left;
+ height: 100%;
+ }
+ .collectionSchemaView-tableContainer {
+ position: relative;
+ float: left;
+ height: 100%;
+ }
+
+ .ReactTable {
position: absolute;
+ // display: inline-block;
+ // overflow: auto;
width: 100%;
height: 100%;
- .collectionSchemaView-previewRegion {
- position: relative;
- background: black;
- float: left;
- height: 100%;
- }
- .collectionSchemaView-previewHandle {
- position: absolute;
- height: 37px;
- width: 20px;
- z-index: 20;
- right: 0;
- top: 0;
- background: Black ;
- }
- .collectionSchemaView-dividerDragger{
- position: relative;
- background: black;
- float: left;
- height: 100%;
- }
- .collectionSchemaView-tableContainer {
+ background: $light-color;
+ box-sizing: border-box;
+ border: none !important;
+ .rt-table {
+ overflow-y: auto;
+ overflow-x: auto;
+ height: 100%;
+ display: -webkit-inline-box;
+ direction: ltr;
+ // direction:rtl;
+ // display:block;
+ }
+ .rt-tbody {
+ //direction: ltr;
+ direction: rtl;
+ }
+ .rt-tr-group {
+ direction: ltr;
+ max-height: 44px;
+ }
+ .rt-td {
+ border-width: 1px;
+ border-right-color: $intermediate-color;
+ .imageBox-cont {
position: relative;
- float: left;
- height: 100%;
- }
-
- .ReactTable {
- position: absolute;
- // display: inline-block;
- // overflow: auto;
- width: 100%;
+ max-height: 100%;
+ }
+ .imageBox-cont img {
+ object-fit: contain;
+ max-width: 100%;
height: 100%;
- background: white;
- box-sizing: border-box;
- .rt-table {
- overflow-y: auto;
- overflow-x: auto;
- height: 100%;
-
- display: -webkit-inline-box;
- direction: ltr;
- // direction:rtl;
- // display:block;
- }
- .rt-tbody {
- //direction: ltr;
- direction: rtl;
- }
- .rt-tr-group {
- direction: ltr;
- max-height: 44px;
- }
- .rt-td {
- border-width: 1;
- border-right-color: #aaa;
- .imageBox-cont {
- position:relative;
- max-height:100%;
- }
- .imageBox-cont img {
- object-fit: contain;
- max-width: 100%;
- height: 100%
- }
- }
- .rt-tr-group {
- border-width: 1;
- border-bottom-color: #aaa
- }
- }
- .ReactTable .rt-thead.-header {
- background:grey;
- }
- .ReactTable .rt-th, .ReactTable .rt-td {
- max-height: 44;
- padding: 3px 7px;
- }
- .ReactTable .rt-tbody .rt-tr-group:last-child {
- border-bottom: grey;
- border-bottom-style: solid;
- border-bottom-width: 1;
- }
- .documentView-node:first-child {
- background: grey;
- .imageBox-cont img {
- object-fit: contain;
- }
- }
+ }
+ .videobox-cont {
+ object-fit: contain;
+ width:auto;
+ height: 100%;
+ }
+ }
+ }
+ .ReactTable .rt-thead.-header {
+ background: $intermediate-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 12px;
+ height: 30px;
+ padding-top: 4px;
+ }
+ .ReactTable .rt-th,
+ .ReactTable .rt-td {
+ max-height: 44;
+ padding: 3px 7px;
+ font-size: 13px;
+ text-align: center;
+ }
+ .ReactTable .rt-tbody .rt-tr-group:last-child {
+ border-bottom: $intermediate-color;
+ border-bottom-style: solid;
+ border-bottom-width: 1;
+ }
+ .documentView-node:first-child {
+ background: $light-color;
+ .imageBox-cont img {
+ object-fit: contain;
+ }
+ }
}
.Resizer {
- box-sizing: border-box;
- background: #000;
- opacity: 0.5;
- z-index: 1;
- background-clip: padding-box;
- &.horizontal {
- height: 11px;
- margin: -5px 0;
- border-top: 5px solid rgba(255, 255, 255, 0);
- border-bottom: 5px solid rgba(255, 255, 255, 0);
- cursor: row-resize;
- width: 100%;
- &:hover {
- border-top: 5px solid rgba(0, 0, 0, 0.5);
- border-bottom: 5px solid rgba(0, 0, 0, 0.5);
- }
- }
- &.vertical {
- width: 11px;
- margin: 0 -5px;
- border-left: 5px solid rgba(255, 255, 255, 0);
- border-right: 5px solid rgba(255, 255, 255, 0);
- cursor: col-resize;
- &:hover {
- border-left: 5px solid rgba(0, 0, 0, 0.5);
- border-right: 5px solid rgba(0, 0, 0, 0.5);
- }
- }
+ box-sizing: border-box;
+ background: #000;
+ opacity: 0.5;
+ z-index: 1;
+ background-clip: padding-box;
+ &.horizontal {
+ height: 11px;
+ margin: -5px 0;
+ border-top: 5px solid rgba(255, 255, 255, 0);
+ border-bottom: 5px solid rgba(255, 255, 255, 0);
+ cursor: row-resize;
+ width: 100%;
&:hover {
- -webkit-transition: all 2s ease;
- transition: all 2s ease;
- }
+ border-top: 5px solid rgba(0, 0, 0, 0.5);
+ border-bottom: 5px solid rgba(0, 0, 0, 0.5);
+ }
+ }
+ &.vertical {
+ width: 11px;
+ margin: 0 -5px;
+ border-left: 5px solid rgba(255, 255, 255, 0);
+ border-right: 5px solid rgba(255, 255, 255, 0);
+ cursor: col-resize;
+ &:hover {
+ border-left: 5px solid rgba(0, 0, 0, 0.5);
+ border-right: 5px solid rgba(0, 0, 0, 0.5);
+ }
+ }
+ &:hover {
+ -webkit-transition: all 2s ease;
+ transition: all 2s ease;
+ }
}
.vertical {
- section {
- width: 100vh;
- height: 100vh;
- display: -webkit-box;
- display: -webkit-flex;
- display: -ms-flexbox;
- display: flex;
- -webkit-box-orient: vertical;
- -webkit-box-direction: normal;
- -webkit-flex-direction: column;
- -ms-flex-direction: column;
- flex-direction: column;
- }
- header {
- padding: 1rem;
- background: #eee;
- }
- footer {
- padding: 1rem;
- background: #eee;
- }
-}
-
-.horizontal {
- section {
- width: 100vh;
- height: 100vh;
- display: flex;
- flex-direction: column;
- }
- header {
- padding: 1rem;
- background: #eee;
- }
- footer {
- padding: 1rem;
- background: #eee;
- }
-}
-
-.parent {
- width: 100%;
- height: 100%;
- -webkit-box-flex: 1;
- -webkit-flex: 1;
- -ms-flex: 1;
- flex: 1;
+ section {
+ width: 100vh;
+ height: 100vh;
display: -webkit-box;
display: -webkit-flex;
display: -ms-flexbox;
@@ -189,19 +166,71 @@
-webkit-flex-direction: column;
-ms-flex-direction: column;
flex-direction: column;
+ }
+ header {
+ padding: 1rem;
+ background: #eee;
+ }
+ footer {
+ padding: 1rem;
+ background: #eee;
+ }
+}
+
+.horizontal {
+ section {
+ width: 100vh;
+ height: 100vh;
+ display: flex;
+ flex-direction: column;
+ }
+ header {
+ padding: 1rem;
+ background: #eee;
+ }
+ footer {
+ padding: 1rem;
+ background: #eee;
+ }
+}
+
+.parent {
+ width: 100%;
+ height: 100%;
+ -webkit-box-flex: 1;
+ -webkit-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+ display: -webkit-box;
+ display: -webkit-flex;
+ display: -ms-flexbox;
+ display: flex;
+ -webkit-box-orient: vertical;
+ -webkit-box-direction: normal;
+ -webkit-flex-direction: column;
+ -ms-flex-direction: column;
+ flex-direction: column;
}
.header {
- background: #aaa;
- height: 3rem;
- line-height: 3rem;
+ background: #aaa;
+ height: 3rem;
+ line-height: 3rem;
}
.wrapper {
- background: #ffa;
- margin: 5rem;
- -webkit-box-flex: 1;
- -webkit-flex: 1;
- -ms-flex: 1;
- flex: 1;
-} \ No newline at end of file
+ background: #ffa;
+ margin: 5rem;
+ -webkit-box-flex: 1;
+ -webkit-flex: 1;
+ -ms-flex: 1;
+ flex: 1;
+}
+
+.-even {
+ background: $light-color !important;
+}
+
+.-odd {
+ background: $light-color-secondary !important;
+}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 04f017378..8c1aeef2c 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -1,5 +1,5 @@
import React = require("react")
-import { action, observable } from "mobx";
+import { action, observable, computed } from "mobx";
import { observer } from "mobx-react";
import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
@@ -24,14 +24,14 @@ import { setupDrag } from "../../util/DragManager";
@observer
export class CollectionSchemaView extends CollectionViewBase {
private _mainCont = React.createRef<HTMLDivElement>();
- private DIVIDER_WIDTH = 5;
+ private DIVIDER_WIDTH = 4;
@observable _contentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ContentScaling prop of the DocumentView
@observable _dividerX = 0;
@observable _panelWidth = 0;
@observable _panelHeight = 0;
@observable _selectedIndex = 0;
- @observable _splitPercentage: number = 50;
+ @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); }
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
@@ -49,7 +49,7 @@ export class CollectionSchemaView extends CollectionViewBase {
let reference = React.createRef<HTMLDivElement>();
let onItemDown = setupDrag(reference, () => props.doc);
return (
- <div onPointerDown={onItemDown} key={props.doc.Id} ref={reference}>
+ <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "36px" }} key={props.doc.Id} ref={reference}>
<EditableView contents={contents}
height={36} GetValue={() => {
let field = props.doc.Get(props.fieldKey);
@@ -89,7 +89,8 @@ export class CollectionSchemaView extends CollectionViewBase {
return {
onClick: action((e: React.MouseEvent, handleOriginal: Function) => {
that._selectedIndex = rowInfo.index;
- this._splitPercentage += 0.05; // bcz - ugh - needed to force Measure to do its thing and call onResize
+ // bcz - ugh - needed to force Measure to do its thing and call onResize
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage - 0.05)
if (handleOriginal) {
handleOriginal()
@@ -106,18 +107,18 @@ export class CollectionSchemaView extends CollectionViewBase {
@action
onDividerMove = (e: PointerEvent): void => {
let nativeWidth = this._mainCont.current!.getBoundingClientRect();
- this._splitPercentage = Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100);
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
}
@action
onDividerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
- if (this._startSplitPercent == this._splitPercentage) {
- this._splitPercentage = this._splitPercentage == 1 ? 66 : 100;
+ if (this._startSplitPercent == this.splitPercentage) {
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
}
}
onDividerDown = (e: React.PointerEvent) => {
- this._startSplitPercent = this._splitPercentage;
+ this._startSplitPercent = this.splitPercentage;
e.stopPropagation();
e.preventDefault();
document.addEventListener("pointermove", this.onDividerMove);
@@ -134,12 +135,12 @@ export class CollectionSchemaView extends CollectionViewBase {
e.preventDefault();
document.removeEventListener("pointermove", this.onExpanderMove);
document.removeEventListener('pointerup', this.onExpanderUp);
- if (this._startSplitPercent == this._splitPercentage) {
- this._splitPercentage = this._splitPercentage == 100 ? 66 : 100;
+ if (this._startSplitPercent == this.splitPercentage) {
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage == 0 ? 33 : 0);
}
}
onExpanderDown = (e: React.PointerEvent) => {
- this._startSplitPercent = this._splitPercentage;
+ this._startSplitPercent = this.splitPercentage;
e.stopPropagation();
e.preventDefault();
document.addEventListener("pointermove", this.onExpanderMove);
@@ -203,7 +204,7 @@ export class CollectionSchemaView extends CollectionViewBase {
)
let previewHandle = !this.props.active() ? (null) : (
<div className="collectionSchemaView-previewHandle" onPointerDown={this.onExpanderDown} />);
- let dividerDragger = this._splitPercentage == 100 ? (null) :
+ let dividerDragger = this.splitPercentage == 0 ? (null) :
<div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />
return (
<div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} >
@@ -213,7 +214,8 @@ export class CollectionSchemaView extends CollectionViewBase {
this._panelHeight = r.entry.height;
})}>
{({ measureRef }) =>
- <div ref={measureRef} className="collectionSchemaView-tableContainer" style={{ width: `${this._splitPercentage}%` }}>
+ <div ref={measureRef} className="collectionSchemaView-tableContainer"
+ style={{ width: `calc(100% - ${this.splitPercentage}%)` }}>
<ReactTable
data={children}
pageSize={children.length}
@@ -235,7 +237,7 @@ export class CollectionSchemaView extends CollectionViewBase {
}
</Measure>
{dividerDragger}
- <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${100 - this._splitPercentage}% - ${this.DIVIDER_WIDTH}px)` }}>
+ <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}>
{content}
</div>
{previewHandle}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index f8d580a7b..fa0f1c761 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -1,16 +1,25 @@
+@import "../global_variables";
#body {
padding: 20px;
- background: #bbbbbb;
+ background: $light-color-secondary;
+ font-size: 13px;
+ overflow: scroll;
}
ul {
list-style: none;
+ padding-left: 20px;
}
li {
margin: 5px 0;
}
+.collection-child {
+ margin-top: 10px;
+ margin-bottom: 10px;
+}
+
.no-indent {
padding-left: 0;
}
@@ -18,10 +27,17 @@ li {
.bullet {
width: 1.5em;
display: inline-block;
+ color: $intermediate-color;
+}
+
+.coll-title {
+ font-size: 24px;
+ margin-bottom: 20px;
}
.collectionTreeView-dropTarget {
- border-style: solid;
+ border: 0px solid transparent;
+ border-radius: $border-radius;
box-sizing: border-box;
height: 100%;
}
@@ -30,8 +46,16 @@ li {
display: inline-table;
}
+.docContainer:hover {
+ .delete-button {
+ display: inline;
+ }
+}
+
.delete-button {
- color: #999999;
+ color: $intermediate-color;
float: right;
- margin-left: 1em;
+ margin-left: 15px;
+ margin-top: 3px;
+ display: none;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 8b06d9ac4..80fc89712 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -12,6 +12,10 @@ import { setupDrag } from "../../util/DragManager";
import { FieldWaiting } from "../../../fields/Field";
import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faTrashAlt, faCaretRight, faCaretDown } from '@fortawesome/free-solid-svg-icons';
+
export interface TreeViewProps {
document: Document;
deleteDoc: (doc: Document) => void;
@@ -23,6 +27,10 @@ export enum BulletType {
List
}
+library.add(faTrashAlt);
+library.add(faCaretDown);
+library.add(faCaretRight);
+
@observer
/**
* Component that takes in a document prop and a boolean whether it's collapsed or not.
@@ -50,11 +58,11 @@ class TreeView extends React.Component<TreeViewProps> {
switch (type) {
case BulletType.Collapsed:
- return <div className="bullet" onClick={onClicked}>&#9654;</div>
+ return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-right" /></div>
case BulletType.Collapsible:
- return <div className="bullet" onClick={onClicked}>&#9660;</div>
+ return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-down" /></div>
case BulletType.List:
- return <div className="bullet">&mdash;</div>
+ return <div className="bullet"></div>
}
}
@@ -79,7 +87,7 @@ class TreeView extends React.Component<TreeViewProps> {
this.props.document.SetData(KeyStore.Title, value, TextField);
return true;
}} />
- <div className="delete-button" onClick={this.delete}>x</div>
+ <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>
</div >
}
@@ -101,7 +109,7 @@ class TreeView extends React.Component<TreeViewProps> {
<TreeView document={value} deleteDoc={this.remove} />)
)
subView =
- <li key={this.props.document.Id} >
+ <li className="collection-child" key={this.props.document.Id} >
{this.renderBullet(BulletType.Collapsible)}
{titleElement}
<ul key={this.props.document.Id}>
@@ -109,7 +117,7 @@ class TreeView extends React.Component<TreeViewProps> {
</ul>
</li>
} else {
- subView = <li key={this.props.document.Id}>
+ subView = <li className="collection-child" key={this.props.document.Id}>
{this.renderBullet(BulletType.Collapsed)}
{titleElement}
</li>
@@ -157,7 +165,7 @@ export class CollectionTreeView extends CollectionViewBase {
return (
<div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
- <h3>
+ <div className="coll-title">
<EditableView contents={titleStr}
height={72} GetValue={() => {
return this.props.Document.Title;
@@ -165,7 +173,8 @@ export class CollectionTreeView extends CollectionViewBase {
this.props.Document.SetData(KeyStore.Title, value, TextField);
return true;
}} />
- </h3>
+ </div>
+ <hr />
<ul className="no-indent">
{childrenElement}
</ul>
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
new file mode 100644
index 000000000..cbb981b13
--- /dev/null
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -0,0 +1,40 @@
+
+.collectionVideoView-cont{
+ width: 100%;
+ height: 100%;
+ position: absolute;
+
+}
+.collectionVideoView-time{
+ color : white;
+ top :25px;
+ left : 25px;
+ position: absolute;
+ background-color: rgba(50, 50, 50, 0.2);
+ transform-origin: left top;
+}
+.collectionVideoView-play {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ left : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: left bottom;
+}
+.collectionVideoView-full {
+ width: 25px;
+ height: 20px;
+ bottom: 25px;
+ right : 25px;
+ position: absolute;
+ color : white;
+ background-color: rgba(50, 50, 50, 0.2);
+ border-radius: 4px;
+ text-align: center;
+ transform-origin: right bottom;
+
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
new file mode 100644
index 000000000..b64ef3c07
--- /dev/null
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -0,0 +1,118 @@
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../fields/Document";
+import { KeyStore } from "../../../fields/KeyStore";
+import { ContextMenu } from "../ContextMenu";
+import { CollectionView, CollectionViewType } from "./CollectionView";
+import { CollectionViewProps } from "./CollectionViewBase";
+import React = require("react");
+import { FieldId } from "../../../fields/Field";
+import "./CollectionVideoView.scss"
+
+
+@observer
+export class CollectionVideoView extends React.Component<CollectionViewProps> {
+
+ public static LayoutString(fieldKey: string = "DataKey") {
+ return `<${CollectionVideoView.name} Document={Document}
+ ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
+ isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
+ }
+
+ private _mainCont = React.createRef<HTMLDivElement>();
+
+ private get uIButtons() {
+ let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().transformDirection(1, 1)[0]);
+ return ([
+ <div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ <span>{"" + Math.round(this.ctime)}</span>
+ <span style={{ fontSize: 8 }}>{" " + Math.round((this.ctime - Math.trunc(this.ctime)) * 100)}</span>
+ </div>,
+ <div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ {this.playing ? "\"" : ">"}
+ </div>,
+ <div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ F
+ </div>
+ ]);
+ }
+
+
+ // "inherited" CollectionView API starts here...
+
+ @observable
+ public SelectedDocs: FieldId[] = []
+ public active: () => boolean = () => CollectionView.Active(this);
+
+ addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
+ removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
+
+ specificContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document.Id != "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "VideoOptions", event: () => { } });
+ }
+ }
+
+ get collectionViewType(): CollectionViewType { return CollectionViewType.Freeform; }
+ get subView(): any { return CollectionView.SubView(this); }
+
+ componentDidMount() {
+ this.updateTimecode();
+ }
+
+ get player(): HTMLVideoElement | undefined {
+ return this._mainCont.current ? this._mainCont.current.getElementsByTagName("video")[0] : undefined;
+ }
+
+ @action
+ updateTimecode = () => {
+ if (this.player) {
+ this.ctime = this.player.currentTime;
+ this.props.Document.SetNumber(KeyStore.CurPage, Math.round(this.ctime));
+ }
+ setTimeout(() => this.updateTimecode(), 100)
+ }
+
+
+ @observable
+ ctime: number = 0
+ @observable
+ playing: boolean = false;
+
+ @action
+ onPlayDown = () => {
+ if (this.player) {
+ if (this.player.paused) {
+ this.player.play();
+ this.playing = true;
+ } else {
+ this.player.pause();
+ this.playing = false;
+ }
+ }
+ }
+ @action
+ onFullDown = (e: React.PointerEvent) => {
+ if (this.player) {
+ this.player.requestFullscreen();
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ @action
+ onResetDown = () => {
+ if (this.player) {
+ this.player.pause();
+ this.player.currentTime = 0;
+ }
+
+ }
+
+ render() {
+ return (<div className="collectionVideoView-cont" ref={this._mainCont} onContextMenu={this.specificContextMenu}>
+ {this.subView}
+ {this.uIButtons}
+ </div>)
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 548a51bf1..d9b2722a6 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -22,20 +22,19 @@ export enum CollectionViewType {
Tree
}
-export const COLLECTION_BORDER_WIDTH = 2;
+export const COLLECTION_BORDER_WIDTH = 1;
@observer
export class CollectionView extends React.Component<CollectionViewProps> {
- @observable
- public SelectedDocs: FieldId[] = [];
-
public static LayoutString(fieldKey: string = "DataKey") {
return `<${CollectionView.name} Document={Document}
ScreenToLocalTransform={ScreenToLocalTransform} fieldKey={${fieldKey}} panelWidth={PanelWidth} panelHeight={PanelHeight} isSelected={isSelected} select={select} bindings={bindings}
isTopMost={isTopMost} SelectOnLoad={selectOnLoad} BackgroundView={BackgroundView} focus={focus}/>`;
}
+ @observable
+ public SelectedDocs: FieldId[] = [];
public active: () => boolean = () => CollectionView.Active(this);
addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
@@ -50,7 +49,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
@action
public static AddDocument(props: CollectionViewProps, doc: Document) {
- doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, 0));
+ doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, -1));
if (props.Document.Get(props.fieldKey) instanceof Field) {
//TODO This won't create the field if it doesn't already exist
const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
@@ -99,7 +98,6 @@ export class CollectionView extends React.Component<CollectionViewProps> {
ContextMenu.Instance.addItem({ description: "Freeform", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform) })
ContextMenu.Instance.addItem({ description: "Schema", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema) })
ContextMenu.Instance.addItem({ description: "Treeview", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree) })
- ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
}
}
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index b126b40a9..d9598aa72 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -47,6 +47,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
protected drop(e: Event, de: DragManager.DropEvent) {
const docView: DocumentView = de.data["documentView"];
const doc: Document = de.data["document"];
+
if (docView && (!docView.props.ContainingCollectionView || docView.props.ContainingCollectionView !== this.props.CollectionView)) {
if (docView.props.RemoveDocument) {
docView.props.RemoveDocument(docView.props.Document);
@@ -61,12 +62,17 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
@action
protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
- e.stopPropagation()
- e.preventDefault()
let that = this;
let html = e.dataTransfer.getData("text/html");
let text = e.dataTransfer.getData("text/plain");
+
+ if (text && text.startsWith("<div")) {
+ return;
+ }
+ e.stopPropagation()
+ e.preventDefault()
+
if (html && html.indexOf("<img") != 0) {
console.log("not good");
let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
@@ -81,21 +87,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
const upload = window.location.origin + "/upload";
let item = e.dataTransfer.items[i];
if (item.kind === "string" && item.type.indexOf("uri") != -1) {
- e.dataTransfer.items[i].getAsString(function (s) {
- action(() => {
- var img = Documents.ImageDocument(s, { ...options, nativeWidth: 300, width: 300, })
-
- let docs = that.props.Document.GetT(KeyStore.Data, ListField);
- if (docs != FieldWaiting) {
- if (!docs) {
- docs = new ListField<Document>();
- that.props.Document.Set(KeyStore.Data, docs)
- }
- docs.Data.push(img);
- }
- })()
-
- })
+ e.dataTransfer.items[i].getAsString(action((s: string) => this.props.addDocument(Documents.WebDocument(s, options))))
}
let type = item.type
console.log(type)
@@ -122,7 +114,7 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
var doc: any;
if (type.indexOf("image") !== -1) {
- doc = Documents.ImageDocument(path, { ...options, nativeWidth: 300, width: 300, })
+ doc = Documents.ImageDocument(path, { ...options, nativeWidth: 200, width: 200, })
}
if (type.indexOf("video") !== -1) {
doc = Documents.VideoDocument(path, { ...options, nativeWidth: 300, width: 300, })
@@ -130,6 +122,9 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
if (type.indexOf("audio") !== -1) {
doc = Documents.AudioDocument(path, { ...options, nativeWidth: 300, width: 300, })
}
+ if (type.indexOf("pdf") !== -1) {
+ doc = Documents.PdfDocument(path, { ...options, nativeWidth: 300, width: 300, })
+ }
let docs = that.props.Document.GetT(KeyStore.Data, ListField);
if (docs != FieldWaiting) {
if (!docs) {
diff --git a/src/client/views/collections/MarqueeView.scss b/src/client/views/collections/MarqueeView.scss
new file mode 100644
index 000000000..6d9a79344
--- /dev/null
+++ b/src/client/views/collections/MarqueeView.scss
@@ -0,0 +1,8 @@
+
+.marqueeView {
+ border-style: dashed;
+ box-sizing: border-box;
+ position: absolute;
+ border-width: 1px;
+ border-color: black;
+} \ No newline at end of file
diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
new file mode 100644
index 000000000..65aaa837f
--- /dev/null
+++ b/src/client/views/collections/MarqueeView.tsx
@@ -0,0 +1,164 @@
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../fields/Document";
+import { FieldWaiting, Opt } from "../../../fields/Field";
+import { KeyStore } from "../../../fields/KeyStore";
+import { Documents } from "../../documents/Documents";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import "./MarqueeView.scss";
+import React = require("react");
+import { InkField, StrokeData } from "../../../fields/InkField";
+import { Utils } from "../../../Utils";
+import { InkingCanvas } from "../InkingCanvas";
+
+interface MarqueeViewProps {
+ getMarqueeTransform: () => Transform;
+ getTransform: () => Transform;
+ container: CollectionFreeFormView;
+ addDocument: (doc: Document) => void;
+ activeDocuments: () => Document[];
+ selectDocuments: (docs: Document[]) => void;
+ removeDocument: (doc: Document) => boolean;
+}
+
+@observer
+export class MarqueeView extends React.Component<MarqueeViewProps>
+{
+ private _reactionDisposer: Opt<IReactionDisposer>;
+
+ @observable _lastX: number = 0;
+ @observable _lastY: number = 0;
+ @observable _downX: number = 0;
+ @observable _downY: number = 0;
+
+ componentDidMount() {
+ this._reactionDisposer = reaction(
+ () => this.props.container.MarqueeVisible,
+ (visible: boolean) => this.onPointerDown(visible, this.props.container.DownX, this.props.container.DownY))
+ }
+ componentWillUnmount() {
+ if (this._reactionDisposer) {
+ this._reactionDisposer();
+ }
+ this.cleanupInteractions();
+ }
+
+ @action
+ cleanupInteractions = () => {
+ document.removeEventListener("pointermove", this.onPointerMove, true)
+ document.removeEventListener("pointerup", this.onPointerUp, true);
+ document.removeEventListener("keydown", this.marqueeCommand, true);
+ }
+
+ @action
+ onPointerDown = (visible: boolean, downX: number, downY: number): void => {
+ if (visible) {
+ this._downX = this._lastX = downX;
+ this._downY = this._lastY = downY;
+ document.addEventListener("pointermove", this.onPointerMove, true)
+ document.addEventListener("pointerup", this.onPointerUp, true);
+ document.addEventListener("keydown", this.marqueeCommand, true);
+ }
+ }
+
+ @action
+ onPointerMove = (e: PointerEvent): void => {
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ this.cleanupInteractions();
+ if (!e.shiftKey) {
+ SelectionManager.DeselectAll();
+ }
+ this.props.selectDocuments(this.marqueeSelect());
+ }
+
+ intersectRect(r1: { left: number, top: number, width: number, height: number },
+ r2: { left: number, top: number, width: number, height: number }) {
+ return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top);
+ }
+
+ get Bounds() {
+ let left = this._downX < this._lastX ? this._downX : this._lastX;
+ let top = this._downY < this._lastY ? this._downY : this._lastY;
+ let topLeft = this.props.getTransform().transformPoint(left, top);
+ let size = this.props.getTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }
+ }
+
+ @action
+ marqueeCommand = (e: KeyboardEvent) => {
+ if (e.key == "Backspace" || e.key == "Delete") {
+ this.marqueeSelect().map(d => this.props.removeDocument(d));
+ this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
+ this.cleanupInteractions();
+ }
+ if (e.key == "c") {
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect().map(d => {
+ this.props.removeDocument(d);
+ d.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
+ d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
+ d.SetNumber(KeyStore.Page, 0);
+ d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
+ return d;
+ });
+ let liftedInk = this.marqueeInkSelect(true);
+ this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
+ //setTimeout(() => {
+ this.props.addDocument(Documents.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panx: 0, pany: 0, width: bounds.width, backgroundColor: "Transparent", height: bounds.height, ink: liftedInk, title: "a nested collection" }));
+ // }, 100);
+ this.cleanupInteractions();
+ }
+ }
+ marqueeInkSelect(select: boolean) {
+ let selRect = this.Bounds;
+ let centerShiftX = 0 - (selRect.left + selRect.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+ let centerShiftY = 0 - (selRect.top + selRect.height / 2);
+ let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
+ if (ink && ink != FieldWaiting) {
+ let idata = new Map();
+ ink.Data.forEach((value: StrokeData, key: string, map: any) => {
+ let inside = InkingCanvas.IntersectStrokeRect(value, selRect);
+ if (inside && select) {
+ idata.set(key,
+ {
+ pathData: value.pathData.map(val => { return { x: val.x + centerShiftX, y: val.y + centerShiftY } }),
+ color: value.color,
+ width: value.width,
+ tool: value.tool,
+ page: -1
+ });
+ } else if (!inside && !select) {
+ idata.set(key, value);
+ }
+ })
+ return idata;
+ }
+ }
+
+ marqueeSelect() {
+ let selRect = this.Bounds;
+ let selection: Document[] = [];
+ this.props.activeDocuments().map(doc => {
+ var x = doc.GetNumber(KeyStore.X, 0);
+ var y = doc.GetNumber(KeyStore.Y, 0);
+ var w = doc.GetNumber(KeyStore.Width, 0);
+ var h = doc.GetNumber(KeyStore.Height, 0);
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect))
+ selection.push(doc)
+ })
+ return selection;
+ }
+
+ render() {
+ let p = this.props.getMarqueeTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+ let v = this.props.getMarqueeTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ return (!this.props.container.MarqueeVisible ? (null) : <div className="marqueeView" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}` }} />);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/PreviewCursor.scss b/src/client/views/collections/PreviewCursor.scss
new file mode 100644
index 000000000..a797411f6
--- /dev/null
+++ b/src/client/views/collections/PreviewCursor.scss
@@ -0,0 +1,18 @@
+
+.previewCursor {
+ color: black;
+ position: absolute;
+ transform-origin: left top;
+ pointer-events: none;
+}
+
+//this is an animation for the blinking cursor!
+@keyframes blink {
+ 0% {opacity: 0}
+ 49%{opacity: 0}
+ 50% {opacity: 1}
+}
+
+#previewCursor {
+ animation: blink 1s infinite;
+} \ No newline at end of file
diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx
new file mode 100644
index 000000000..a1411250a
--- /dev/null
+++ b/src/client/views/collections/PreviewCursor.tsx
@@ -0,0 +1,78 @@
+import { trace } from "mobx";
+import "./PreviewCursor.scss";
+import React = require("react");
+import { action, IReactionDisposer, observable, reaction } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from "../../../fields/Document";
+import { FieldWaiting, Opt } from "../../../fields/Field";
+import { KeyStore } from "../../../fields/KeyStore";
+import { ListField } from "../../../fields/ListField";
+import { Documents } from "../../documents/Documents";
+import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
+import { CollectionFreeFormView } from "./CollectionFreeFormView";
+
+
+export interface PreviewCursorProps {
+ getTransform: () => Transform;
+ container: CollectionFreeFormView;
+ addLiveTextDocument: (doc: Document) => void;
+}
+
+@observer
+export class PreviewCursor extends React.Component<PreviewCursorProps> {
+ private _reactionDisposer: Opt<IReactionDisposer>;
+
+ @observable _lastX: number = 0;
+ @observable _lastY: number = 0;
+
+ componentDidMount() {
+ this._reactionDisposer = reaction(
+ () => this.props.container.PreviewCursorVisible,
+ (visible: boolean) => this.onCursorPlaced(visible, this.props.container.DownX, this.props.container.DownY))
+ }
+ componentWillUnmount() {
+ if (this._reactionDisposer) {
+ this._reactionDisposer();
+ }
+ this.cleanupInteractions();
+ }
+
+
+ @action
+ cleanupInteractions = () => {
+ document.removeEventListener("keypress", this.onKeyPress, true);
+ }
+
+ @action
+ onCursorPlaced = (visible: boolean, downX: number, downY: number): void => {
+ if (visible) {
+ document.addEventListener("keypress", this.onKeyPress, true);
+ this._lastX = downX;
+ this._lastY = downY;
+ } else
+ this.cleanupInteractions();
+ }
+
+ @action
+ onKeyPress = (e: KeyboardEvent) => {
+ //if not these keys, make a textbox if preview cursor is active!
+ if (!e.ctrlKey && !e.altKey && !e.defaultPrevented) {
+ //make textbox and add it to this collection
+ let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+ let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" });
+ this.props.addLiveTextDocument(newBox);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ render() {
+ //get local position and place cursor there!
+ let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY);
+ return (
+ !this.props.container.PreviewCursorVisible ? (null) :
+ <div className="previewCursor" id="previewCursor" style={{ transform: `translate(${x}px, ${y}px)` }}>I</div>)
+
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index f7d89843d..6daf15f5f 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -18,7 +18,7 @@ export class AudioBox extends React.Component<FieldViewProps> {
super(props);
}
-
+
componentDidMount() {
}
@@ -26,16 +26,16 @@ export class AudioBox extends React.Component<FieldViewProps> {
componentWillUnmount() {
}
-
+
render() {
let field = this.props.doc.Get(this.props.fieldKey)
- let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3":
+ let path = field == FieldWaiting ? "http://techslides.com/demos/samples/sample.mp3" :
field instanceof AudioField ? field.Data.href : "http://techslides.com/demos/samples/sample.mp3";
-
+
return (
<div>
- <audio controls className = "audiobox-cont">
- <source src = {path} type="audio/mpeg"/>
+ <audio controls className="audiobox-cont">
+ <source src={path} type="audio/mpeg" />
Not supported.
</audio>
</div>
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
new file mode 100644
index 000000000..55b4938a0
--- /dev/null
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -0,0 +1,35 @@
+import { Document } from "../../../fields/Document";
+import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionSchemaView } from "../collections/CollectionSchemaView";
+import { CollectionView, CollectionViewType } from "../collections/CollectionView";
+import { CollectionPDFView } from "../collections/CollectionPDFView";
+import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { ImageBox } from "../nodes/ImageBox";
+import { VideoBox } from "../nodes/VideoBox";
+import { AudioBox } from "../nodes/AudioBox";
+import { KeyValueBox } from "./KeyValueBox"
+import { WebBox } from "../nodes/WebBox";
+import { PDFBox } from "../nodes/PDFBox";
+import "./DocumentView.scss";
+import React = require("react");
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+
+interface JsxBindings {
+ Document: Document;
+ layout: string;
+ [prop: string]: any;
+}
+
+export class DocumentContentsView extends React.PureComponent<JsxBindings> {
+ render() {
+ return <JsxParser
+ components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox }}
+ bindings={this.props}
+ jsx={this.props.layout}
+ showWarnings={true}
+ onError={(test: any) => { console.log(test) }}
+ />
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index ab913897b..85a115f1c 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,23 +1,23 @@
+@import "../global_variables";
.documentView-node {
- position: absolute;
- background: #cdcdcd;
- //overflow: hidden;
- &.minimized {
- width: 30px;
- height: 30px;
- }
- .top {
- background: #232323;
- height: 20px;
- cursor: pointer;
- }
- .content {
- padding: 20px 20px;
- height: auto;
- box-sizing: border-box;
- }
- .scroll-box {
- overflow-y: scroll;
- height: calc(100% - 20px);
- }
-} \ No newline at end of file
+ position: absolute;
+ background: $light-color; //overflow: hidden;
+ &.minimized {
+ width: 30px;
+ height: 30px;
+ }
+ .top {
+ background: #232323;
+ height: 20px;
+ cursor: pointer;
+ }
+ .content {
+ padding: 20px 20px;
+ height: auto;
+ box-sizing: border-box;
+ }
+ .scroll-box {
+ overflow-y: scroll;
+ height: calc(100% - 20px);
+ }
+}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index dc793c16d..7a43c34d0 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,31 +1,23 @@
-import { action, computed, IReactionDisposer, runInAction, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { Field, FieldWaiting, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
+import { TextField } from "../../../fields/TextField";
+import { Documents } from "../../documents/Documents";
+import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { CollectionFreeFormView } from "../collections/CollectionFreeFormView";
-import { CollectionSchemaView } from "../collections/CollectionSchemaView";
import { CollectionView, CollectionViewType } from "../collections/CollectionView";
-import { CollectionPDFView } from "../collections/CollectionPDFView";
import { ContextMenu } from "../ContextMenu";
-import { FormattedTextBox } from "../nodes/FormattedTextBox";
-import { ImageBox } from "../nodes/ImageBox";
-import { VideoBox } from "../nodes/VideoBox";
-import { AudioBox } from "../nodes/AudioBox";
-import { Documents } from "../../documents/Documents"
-import { KeyValueBox } from "./KeyValueBox"
-import { WebBox } from "../nodes/WebBox";
-import { PDFBox } from "../nodes/PDFBox";
import "./DocumentView.scss";
import React = require("react");
-import { TextField } from "../../../fields/TextField";
-import { DocumentManager } from "../../util/DocumentManager";
+import { DocumentContentsView } from "./DocumentContentsView";
+import { Utils } from "../../../Utils";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -87,7 +79,6 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
- private _documentBindings: any = null;
private _downX: number = 0;
private _downY: number = 0;
private _reactionDisposer: Opt<IReactionDisposer>;
@@ -182,7 +173,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
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 (!this.topMost || e.buttons == 2) {
+ if (!this.topMost || e.buttons == 2 || e.altKey) {
this.startDragging(e.x, e.y);
}
}
@@ -197,6 +188,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.SelectDoc(this, e.ctrlKey);
}
}
+ stopPropogation = (e: React.SyntheticEvent) => {
+ e.stopPropagation();
+ }
deleteClicked = (): void => {
if (this.props.RemoveDocument) {
@@ -246,11 +240,23 @@ export class DocumentView extends React.Component<DocumentViewProps> {
destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc);
-
-
e.stopPropagation();
}
+ onDrop = (e: React.DragEvent) => {
+ if (e.isDefaultPrevented()) {
+ return;
+ }
+ let text = e.dataTransfer.getData("text/plain");
+ if (text && text.startsWith("<div")) {
+ let oldLayout = this.props.Document.GetText(KeyStore.Layout, "");
+ let layout = text.replace("{layout}", oldLayout);
+ this.props.Document.SetText(KeyStore.Layout, layout);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
@@ -265,6 +271,12 @@ export class DocumentView extends React.Component<DocumentViewProps> {
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 ID",
+ event: () => {
+ Utils.CopyText(this.props.Document.Id);
+ }
+ });
//ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15)
if (!this.topMost) {
@@ -277,15 +289,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.SelectDoc(this, e.ctrlKey);
}
- get mainContent() {
- return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, WebBox, KeyValueBox, VideoBox, AudioBox, PDFBox }}
- bindings={this._documentBindings}
- jsx={this.layout}
- showWarnings={true}
- onError={(test: any) => { console.log(test) }}
- />
- }
isSelected = () => {
return SelectionManager.IsSelected(this);
@@ -295,40 +298,52 @@ export class DocumentView extends React.Component<DocumentViewProps> {
SelectionManager.SelectDoc(this, ctrlPressed)
}
- render() {
- if (!this.props.Document) return <div></div>
- let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
- if (!lkeys || lkeys === "<Waiting>") {
- return <p>Error loading layout keys</p>;
- }
- this._documentBindings = {
+ @computed
+ get getProps() {
+ let bindings: any = {
...this.props,
isSelected: this.isSelected,
select: this.select,
- focus: this.props.focus
+ layout: this.layout
};
for (const key of this.layoutKeys) {
- this._documentBindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
+ 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);
- this._documentBindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+ bindings[key.Name] = field && field != FieldWaiting ? field.GetValue() : field;
+ }
+ bindings.bindings = bindings;
+
+ return bindings
+ }
+
+ render() {
+ if (!this.props.Document) {
+ return (null);
+ }
+ let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField);
+ if (!lkeys || lkeys === "<Waiting>") {
+ return <p>Error loading layout keys</p>;
}
- this._documentBindings.bindings = this._documentBindings;
var scaling = this.props.ContentScaling();
var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
+ var backgroundcolor = this.props.Document.GetText(KeyStore.BackgroundColor, "");
return (
<div className="documentView-node" ref={this._mainCont}
style={{
+ background: backgroundcolor,
width: nativeWidth > 0 ? nativeWidth.toString() + "px" : "100%",
height: nativeHeight > 0 ? nativeHeight.toString() + "px" : "100%",
transformOrigin: "left top",
transform: `scale(${scaling} , ${scaling})`
}}
+ onDrop={this.onDrop}
onContextMenu={this.onContextMenu}
- onPointerDown={this.onPointerDown} >
- {this.mainContent}
+ onPointerDown={this.onPointerDown}
+ onPointerUp={this.stopPropogation} >
+ <DocumentContentsView {...this.getProps} />
</div>
)
}
diff --git a/src/client/views/nodes/FieldTextBox.scss b/src/client/views/nodes/FieldTextBox.scss
index b6ce2fabc..d2cd61b0d 100644
--- a/src/client/views/nodes/FieldTextBox.scss
+++ b/src/client/views/nodes/FieldTextBox.scss
@@ -1,14 +1,14 @@
.ProseMirror {
- margin-top: -1em;
- width: 100%;
- height: 100%;
+ margin-top: -1em;
+ width: 100%;
+ height: 100%;
}
.ProseMirror:focus {
- outline: none !important
+ outline: none !important;
}
.fieldTextBox-cont {
- background: white;
- padding: 1vw;
-} \ No newline at end of file
+ background: white;
+ padding: 1vw;
+}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index ab5849f09..32da2632e 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -1,38 +1,46 @@
+@import "../global_variables";
.ProseMirror {
- width: 100%;
- height: auto;
- min-height: 100%
+ width: 100%;
+ height: auto;
+ min-height: 100%;
+ font-family: $serif;
}
.ProseMirror:focus {
- outline: none !important
+ outline: none !important;
}
.formattedTextBox-cont {
- background: white;
- padding: 1;
- border-width: 1px;
- border-radius: 2px;
- border-color:black;
- box-sizing: border-box;
- background: white;
- border-style:solid;
- overflow-y: scroll;
- overflow-x: hidden;
- color: initial;
- height: 100%;
+ background: $light-color-secondary;
+ padding: 0.9em;
+ border-width: 0px;
+ border-radius: $border-radius;
+ border-color: $intermediate-color;
+ box-sizing: border-box;
+ border-style: solid;
+ overflow-y: scroll;
+ overflow-x: hidden;
+ color: initial;
+ height: 100%;
}
.menuicon {
- display: inline-block;
- border-right: 1px solid rgba(0, 0, 0, 0.2);
- color: #888;
- line-height: 1;
- padding: 0 7px;
- margin: 1px;
- cursor: pointer;
- text-align: center;
- min-width: 1.4em;
- }
- .strong, .heading { font-weight: bold; }
- .em { font-style: italic; } \ No newline at end of file
+ display: inline-block;
+ border-right: 1px solid rgba(0, 0, 0, 0.2);
+ color: #888;
+ line-height: 1;
+ padding: 0 7px;
+ margin: 1px;
+ cursor: pointer;
+ text-align: center;
+ min-width: 1.4em;
+}
+
+.strong,
+.heading {
+ font-weight: bold;
+}
+
+.em {
+ font-style: italic;
+}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index ad7ddf37a..d7026ed67 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -14,6 +14,9 @@ import { Plugin } from 'prosemirror-state'
import { Decoration, DecorationSet } from 'prosemirror-view'
import { TooltipTextMenu } from "../../util/TooltipTextMenu"
import { ContextMenu } from "../../views/ContextMenu";
+import { inpRules } from "../../util/RichTextRules";
+const { buildMenuItems } = require("prosemirror-example-setup");
+const { menuBar } = require("prosemirror-menu");
@@ -31,7 +34,7 @@ import { ContextMenu } from "../../views/ContextMenu";
// and 'doc' property to the document that is being rendered
//
// When rendered() by React, this extracts the TextController from the Document stored at the
-// specified Key and assigns it to an HTML input node. When changes are made tot his node,
+// specified Key and assigns it to an HTML input node. When changes are made to this node,
// this will edit the document and assign the new value to that field.
//]
export class FormattedTextBox extends React.Component<FieldViewProps> {
@@ -60,6 +63,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
let state: EditorState;
const config = {
schema,
+ inpRules, //these currently don't do anything, but could eventually be helpful
plugins: [
history(),
keymap({ "Mod-z": undo, "Mod-y": redo }),
@@ -113,7 +117,7 @@ export class FormattedTextBox extends React.Component<FieldViewProps> {
this.props.doc.SetData(this.props.fieldKey, e.target.value, RichTextField);
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && this.props.isSelected()) {
+ if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index ea459b911..487038841 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -1,22 +1,21 @@
-
.imageBox-cont {
- padding: 0vw;
- position: relative;
- text-align: center;
- width: 100%;
- height: auto;
- max-width: 100%;
- max-height: 100%
+ padding: 0vw;
+ position: relative;
+ text-align: center;
+ width: 100%;
+ height: auto;
+ max-width: 100%;
+ max-height: 100%;
}
.imageBox-cont img {
- object-fit: contain;
height: 100%;
+ width:100%;
}
.imageBox-button {
- padding : 0vw;
- border: none;
- width : 100%;
- height: 100%;
-} \ No newline at end of file
+ padding: 0vw;
+ border: none;
+ width: 100%;
+ height: 100%;
+}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 30910fb1f..cad8904d0 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,5 @@
-import { action, observable } from 'mobx';
+import { action, observable, trace } 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
@@ -70,7 +70,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
lightbox = (path: string) => {
- const images = [path, "http://www.cs.brown.edu/~bcz/face.gif"];
+ const images = [path];
if (this._isOpen && this.props.isSelected()) {
return (<Lightbox
mainSrc={images[this._photoIndex]}
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 1295266e5..e946275fa 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -1,31 +1,31 @@
.keyValueBox-cont {
- overflow-y:scroll;
- height: 100%;
- border: black;
- border-width: 1px;
- border-style: solid;
- box-sizing: border-box;
- display: inline-block;
- .imageBox-cont img {
- max-height:45px;
- height: auto;
- }
+ overflow-y: scroll;
+ height: 100%;
+ border: black;
+ border-width: 1px;
+ border-style: solid;
+ box-sizing: border-box;
+ display: inline-block;
+ .imageBox-cont img {
+ max-height: 45px;
+ height: auto;
+ }
}
.keyValueBox-table {
- position: relative;
+ position: relative;
}
.keyValueBox-header {
- background:gray;
+ background: gray;
}
.keyValueBox-evenRow {
+ background: white;
+ .formattedTextBox-cont {
background: white;
- .formattedTextBox-cont {
- background: white;
- }
+ }
}
.keyValueBox-oddRow {
- background: lightGray;
- .formattedTextBox-cont {
- background: lightgray;
- }
-} \ No newline at end of file
+ background: lightGray;
+ .formattedTextBox-cont {
+ background: lightgray;
+ }
+}
diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss
index 00e5ebb3d..5d5f782d2 100644
--- a/src/client/views/nodes/LinkBox.scss
+++ b/src/client/views/nodes/LinkBox.scss
@@ -1,13 +1,14 @@
+@import "../global_variables";
.link-container {
width: 100%;
- height: 30px;
+ height: 35px;
display: flex;
flex-direction: row;
border-top: 0.5px solid #bababa;
}
.info-container {
- width: 60%;
+ width: 55%;
padding-top: 5px;
padding-left: 5px;
display: flex;
@@ -23,17 +24,42 @@
}
.button-container {
- width: 40%;
+ width: 45%;
display: flex;
flex-direction: row;
}
.button {
- height: 15px;
- width: 15px;
- margin: 8px 5px;
+ height: 20px;
+ width: 20px;
+ margin: 8px 4px;
border-radius: 50%;
- opacity: 0.6;
+ opacity: 0.9;
pointer-events: auto;
- background-color: #2B6091;
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ font-size: 60%;
+ transition: transform 0.2s;
+}
+
+.button:hover {
+ background: $main-accent;
+ cursor: pointer;
+}
+
+.fa-icon-view {
+ margin-left: 3px;
+ margin-top: 5px;
+}
+
+.fa-icon-edit {
+ margin-left: 5px;
+ margin-top: 5px;
+}
+
+.fa-icon-delete {
+ margin-left: 6px;
+ margin-top: 5px;
} \ No newline at end of file
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 69df676ff..430c1b694 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -11,6 +11,16 @@ import { ListField } from "../../../fields/ListField";
import { DocumentManager } from "../../util/DocumentManager";
import { LinkEditor } from "./LinkEditor";
import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faEye } from '@fortawesome/free-solid-svg-icons';
+import { faEdit } from '@fortawesome/free-solid-svg-icons';
+import { faTimes } from '@fortawesome/free-solid-svg-icons';
+
+
+library.add(faEye);
+library.add(faEdit);
+library.add(faTimes);
interface Props {
linkDoc: Document;
@@ -79,9 +89,12 @@ export class LinkBox extends React.Component<Props> {
</div>
<div className="button-container">
- <div className="button" onPointerDown={this.onViewButtonPressed}></div>
- <div className="button" onPointerDown={this.onEditButtonPressed}></div>
- <div className="button" onPointerDown={this.onDeleteButtonPressed}></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}>
+ <FontAwesomeIcon className="fa-icon-delete" icon="times" size="sm" /></div>
</div>
</div>
)
diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss
index cb191dc8c..fb0c69cff 100644
--- a/src/client/views/nodes/LinkEditor.scss
+++ b/src/client/views/nodes/LinkEditor.scss
@@ -1,3 +1,4 @@
+@import "../global_variables";
.edit-container {
width: 100%;
height: auto;
@@ -9,21 +10,34 @@
margin-bottom: 10px;
padding: 5px;
font-size: 12px;
+ border: 1px solid #bababa;
}
.description-input {
- font-size: 12px;
+ font-size: 11px;
padding: 5px;
margin-bottom: 10px;
+ border: 1px solid #bababa;
}
.save-button {
width: 50px;
height: 20px;
- background-color: #2B6091;
+ pointer-events: auto;
+ background-color: $dark-color;
+ color: $light-color;
+ text-transform: uppercase;
+ letter-spacing: 2px;
+ padding: 2px;
+ font-size: 10px;
margin: 0 auto;
- color: white;
+ transition: transform 0.2s;
text-align: center;
line-height: 20px;
- font-size: 12px;
+}
+
+.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/LinkMenu.scss b/src/client/views/nodes/LinkMenu.scss
index a120ab2a7..dedcce6ef 100644
--- a/src/client/views/nodes/LinkMenu.scss
+++ b/src/client/views/nodes/LinkMenu.scss
@@ -10,6 +10,7 @@
padding: 5px;
margin-bottom: 10px;
font-size: 12px;
+ border: 1px solid #bababa;
}
#linkMenu-list {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 5c6b06d00..5eeb40772 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -39,8 +39,8 @@ export class LinkMenu extends React.Component<Props> {
<div id="linkMenu-container">
<input id="linkMenu-searchBar" type="text" placeholder="Search..."></input>
<div id="linkMenu-list">
- {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Source: ")}
- {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Destination: ")}
+ {this.renderLinkItems(linkTo, KeyStore.LinkedToDocs, "Destination: ")}
+ {this.renderLinkItems(linkFrom, KeyStore.LinkedFromDocs, "Source: ")}
</div>
</div>
)
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 9f92410d4..ad947afd5 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -11,5 +11,5 @@
}
.pdfBox-contentContainer {
position: absolute;
- transform-origin: "left top";
+ transform-origin: left top;
} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 70a70c7c8..b0b5a63a4 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
import * as htmlToImage from "html-to-image";
-import { action, computed, observable, reaction, IReactionDisposer } from 'mobx';
+import { action, computed, observable, reaction, IReactionDisposer, trace } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import Measure from "react-measure";
@@ -86,7 +86,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
@observable private _interactive: boolean = false;
@observable private _loaded: boolean = false;
- @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, 0); }
+ @computed private get curPage() { return this.props.doc.GetNumber(KeyStore.CurPage, -1); }
componentDidMount() {
this._reactionDisposer = reaction(
@@ -423,7 +423,9 @@ export class PDFBox extends React.Component<FieldViewProps> {
// so this design is flawed.
var nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 0);
if (!this.props.doc.GetNumber(KeyStore.NativeHeight, 0)) {
- this.props.doc.SetNumber(KeyStore.NativeHeight, nativeWidth * r.entry.height / r.entry.width);
+ var nativeHeight = nativeWidth * r.entry.height / r.entry.width;
+ this.props.doc.SetNumber(KeyStore.Height, nativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
+ this.props.doc.SetNumber(KeyStore.NativeHeight, nativeHeight);
}
if (!this.props.doc.GetT(KeyStore.Thumbnail, ImageField)) {
this.saveThumbnail();
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 7306450d9..76bbeb37c 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,4 @@
.videobox-cont{
width: 100%;
- height: 100%;
+ height: Auto;
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 22ff5c5ad..8c1ee669f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,12 +1,13 @@
import React = require("react")
-import { FieldViewProps, FieldView } from './FieldView';
+import { observer } from "mobx-react";
import { FieldWaiting } from '../../../fields/Field';
-import { observer } from "mobx-react"
-import { VideoField } from '../../../fields/VideoField';
-import "./VideoBox.scss"
-import { ContextMenu } from "../../views/ContextMenu";
-import { observable, action } from 'mobx';
-import { KeyStore } from '../../../fields/KeyStore';
+import { VideoField } from '../../../fields/VideoField';
+import { FieldView, FieldViewProps } from './FieldView';
+import "./VideoBox.scss";
+import Measure from "react-measure";
+import { action, trace, observable } from "mobx";
+import { KeyStore } from "../../../fields/KeyStore";
+import { number } from "prop-types";
@observer
export class VideoBox extends React.Component<FieldViewProps> {
@@ -17,27 +18,44 @@ export class VideoBox extends React.Component<FieldViewProps> {
super(props);
}
-
- componentDidMount() {
- }
+ _loaded: boolean = false;
- componentWillUnmount() {
+ @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.doc.GetNumber(KeyStore.NativeWidth, 0);
+ var nativeHeight = this.props.doc.GetNumber(KeyStore.NativeHeight, 0);
+ var newNativeHeight = nativeWidth * r.entry.height / r.entry.width;
+ if (!nativeHeight && newNativeHeight != nativeHeight && !isNaN(newNativeHeight)) {
+ this.props.doc.SetNumber(KeyStore.Height, newNativeHeight / nativeWidth * this.props.doc.GetNumber(KeyStore.Width, 0));
+ this.props.doc.SetNumber(KeyStore.NativeHeight, newNativeHeight);
+ }
+ } else {
+ this._loaded = true;
+ }
}
-
+
+
render() {
let field = this.props.doc.Get(this.props.fieldKey)
- let path = field == FieldWaiting ? "http://techslides.com/demos/sample-videos/small.mp4":
+ let path = field == FieldWaiting ? "http://techslides.com/demos/sample-videos/small.mp4" :
field instanceof VideoField ? field.Data.href : "http://techslides.com/demos/sample-videos/small.mp4";
-
+
+ //setTimeout(action(() => this._loaded = true), 500);
return (
- <div>
- <video width = {200} height = {200} controls className = "videobox-cont">
- <source src = {path} type = "video/mp4"/>
- Not supported.
- </video>
- </div>
+ <Measure onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <video className="videobox-cont" ref={measureRef}>
+ <source src={path} type="video/mp4" />
+ Not supported.
+ </video>
+ }
+ </Measure>
)
}
} \ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index f93a68c85..06cdc8fc8 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -19,11 +19,13 @@ export namespace KeyStore {
export const Annotations = new Key("Annotations");
export const ViewType = new Key("ViewType");
export const Layout = new Key("Layout");
+ export const BackgroundColor = new Key("BackgroundColor");
export const BackgroundLayout = new Key("BackgroundLayout");
export const OverlayLayout = new Key("OverlayLayout");
export const LayoutKeys = new Key("LayoutKeys");
export const LayoutFields = new Key("LayoutFields");
export const ColumnsKey = new Key("SchemaColumns");
+ export const SchemaSplitPercentage = new Key("SchemaSplitPercentage");
export const Caption = new Key("Caption");
export const ActiveFrame = new Key("ActiveFrame");
export const DocumentText = new Key("DocumentText");
diff --git a/src/server/database.ts b/src/server/database.ts
index 07c5819ab..f3c1c9427 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -16,12 +16,12 @@ export class Database {
})
}
- public update(id: string, value: any) {
+ public update(id: string, value: any, callback: () => void) {
if (this.db) {
let collection = this.db.collection('documents');
collection.update({ _id: id }, { $set: value }, {
upsert: true
- });
+ }, callback);
}
}
diff --git a/src/server/index.ts b/src/server/index.ts
index 0d0b65b22..7f1e95964 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -100,9 +100,14 @@ let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap();
// define a route handler for the default home page
app.get("/", (req, res) => {
- res.sendFile(path.join(__dirname, '../../deploy/index.html'));
+ res.redirect("/doc/mainDoc");
+ // res.sendFile(path.join(__dirname, '../../deploy/index.html'));
});
+app.get("/doc/:docId", (req, res) => {
+ res.sendFile(path.join(__dirname, '../../deploy/index.html'));
+})
+
app.get("/hello", (req, res) => {
res.send("<p>Hello</p>");
})
@@ -173,8 +178,9 @@ function getFields([ids, callback]: [string[], (result: any) => void]) {
}
function setField(socket: Socket, newValue: Transferable) {
- Database.Instance.update(newValue._id, newValue)
- socket.broadcast.emit(MessageStore.SetField.Message, newValue)
+ Database.Instance.update(newValue._id, newValue, () => {
+ socket.broadcast.emit(MessageStore.SetField.Message, newValue);
+ })
}
server.listen(serverPort);