aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/.DS_Storebin6148 -> 6148 bytes
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/documents/Documents.ts18
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.scss6
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.tsx4
-rw-r--r--src/client/util/DocumentManager.ts14
-rw-r--r--src/client/util/DragManager.ts10
-rw-r--r--src/client/util/ProsemirrorKeymap.ts100
-rw-r--r--src/client/util/RichTextSchema.tsx507
-rw-r--r--src/client/util/SelectionManager.ts19
-rw-r--r--src/client/util/TooltipLinkingMenu.tsx86
-rw-r--r--src/client/util/TooltipTextMenu.scss255
-rw-r--r--src/client/util/TooltipTextMenu.tsx235
-rw-r--r--src/client/views/DocumentDecorations.scss13
-rw-r--r--src/client/views/DocumentDecorations.tsx97
-rw-r--r--src/client/views/EditableView.scss1
-rw-r--r--src/client/views/Main.tsx23
-rw-r--r--src/client/views/MainOverlayTextBox.scss1
-rw-r--r--src/client/views/MainOverlayTextBox.tsx33
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx96
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx56
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss143
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx300
-rw-r--r--src/client/views/collections/CollectionSubView.tsx44
-rw-r--r--src/client/views/collections/CollectionTreeView.scss6
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx9
-rw-r--r--src/client/views/collections/CollectionView.tsx5
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx376
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx17
-rw-r--r--src/client/views/globalCssVariables.scss4
-rw-r--r--src/client/views/globalCssVariables.scss.d.ts4
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx46
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx3
-rw-r--r--src/client/views/nodes/DocumentView.scss20
-rw-r--r--src/client/views/nodes/DocumentView.tsx362
-rw-r--r--src/client/views/nodes/FieldView.tsx9
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss6
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx98
-rw-r--r--src/client/views/nodes/IconBox.scss12
-rw-r--r--src/client/views/nodes/IconBox.tsx45
-rw-r--r--src/client/views/nodes/ImageBox.scss12
-rw-r--r--src/client/views/nodes/ImageBox.tsx91
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx6
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx2
-rw-r--r--src/client/views/nodes/WebBox.scss6
-rw-r--r--src/client/views/nodes/WebBox.tsx35
-rw-r--r--src/fields/IconFIeld.ts25
-rw-r--r--src/fields/KeyStore.ts10
-rw-r--r--src/server/Message.ts2
-rw-r--r--src/server/ServerUtil.ts5
-rw-r--r--src/server/authentication/models/user_model.ts3
-rw-r--r--src/server/database.ts2
-rw-r--r--src/server/index.ts2
57 files changed, 2150 insertions, 1160 deletions
diff --git a/src/.DS_Store b/src/.DS_Store
index 90213270f..d70e95c0a 100644
--- a/src/.DS_Store
+++ b/src/.DS_Store
Binary files differ
diff --git a/src/Utils.ts b/src/Utils.ts
index dec6245ef..98f75d3b9 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,7 +1,7 @@
import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
import { Socket } from 'socket.io';
-import { Message, Types, Transferable } from './server/Message';
+import { Message } from './server/Message';
import { Document } from './fields/Document';
export class Utils {
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c93e65edb..a1f3e3334 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -34,6 +34,9 @@ import { AttributeTransformationModel } from "../northstar/core/attribute/Attrib
import { AggregateFunction } from "../northstar/model/idea/idea";
import { Template } from "../views/Templates";
import { TemplateField } from "../../fields/TemplateField";
+import { MINIMIZED_ICON_SIZE } from "../views/globalCssVariables.scss";
+import { IconBox } from "../views/nodes/IconBox";
+import { IconField } from "../../fields/IconFIeld";
export interface DocumentOptions {
x?: number;
@@ -55,6 +58,7 @@ export interface DocumentOptions {
viewType?: number;
backgroundColor?: string;
copyDraggedItems?: boolean;
+ documentText?: string;
}
export namespace Documents {
@@ -67,6 +71,7 @@ export namespace Documents {
let videoProto: Document;
let audioProto: Document;
let pdfProto: Document;
+ let iconProto: Document;
const textProtoId = "textProto";
const histoProtoId = "histoProto";
const pdfProtoId = "pdfProto";
@@ -76,6 +81,7 @@ export namespace Documents {
const kvpProtoId = "kvpProto";
const videoProtoId = "videoProto";
const audioProtoId = "audioProto";
+ const iconProtoId = "iconProto";
export function initProtos(): Promise<void> {
return Server.GetFields([textProtoId, histoProtoId, collProtoId, pdfProtoId, imageProtoId, videoProtoId, audioProtoId, webProtoId, kvpProtoId]).then(fields => {
@@ -88,6 +94,7 @@ export namespace Documents {
videoProto = fields[videoProtoId] as Document || CreateVideoPrototype();
audioProto = fields[audioProtoId] as Document || CreateAudioPrototype();
pdfProto = fields[pdfProtoId] as Document || CreatePdfPrototype();
+ iconProto = fields[iconProtoId] as Document || CreateIconPrototype();
});
}
function assignOptions(doc: Document, options: DocumentOptions): Document {
@@ -95,7 +102,10 @@ export namespace Documents {
if (options.nativeHeight !== undefined) { doc.SetNumber(KeyStore.NativeHeight, options.nativeHeight); }
if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); }
+ if (options.documentText !== undefined) { doc.SetText(KeyStore.DocumentText, options.documentText); }
if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
+ if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
+ if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); }
if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
@@ -146,6 +156,11 @@ export namespace Documents {
histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
return histoProto;
}
+ function CreateIconPrototype(): Document {
+ let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
+ { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data] });
+ return iconProto;
+ }
function CreateTextPrototype(): Document {
let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
{ x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
@@ -210,6 +225,9 @@ export namespace Documents {
export function TextDocument(options: DocumentOptions = {}) {
return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options);
}
+ export function IconDocument(icon: string, options: DocumentOptions = {}) {
+ return assignToDelegate(SetInstanceOptions(iconProto, { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data], layout: IconBox.LayoutString(), ...options }, [icon, IconField]), options);
+ }
export function PdfDocument(url: string, options: DocumentOptions = {}) {
return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options);
}
diff --git a/src/client/northstar/dash-nodes/HistogramBox.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
index e899cf15e..06d781263 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.scss
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -1,12 +1,12 @@
.histogrambox-container {
padding: 0vw;
position: absolute;
- top: 0;
- left:0;
+ top: -50%;
+ left:-50%;
text-align: center;
width: 100%;
height: 100%;
- background: black;
+ background: black;
}
.histogrambox-xaxislabel {
position:absolute;
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 3e94fed81..e2ecc8c83 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -118,7 +118,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
if (this.HistoOp !== HistogramOperation.Empty) {
- reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+ reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, [] as Document[]), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length,
() => {
let brushingDocs = this.props.Document.GetList(KeyStore.BrushingDocs, [] as Document[]);
@@ -146,7 +146,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
return (
<Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}>
{({ measureRef }) =>
- <div className="histogrambox-container" ref={measureRef} style={{ transform: `translate(-50%, -50%)` }}>
+ <div className="histogrambox-container" ref={measureRef}>
<div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
<span className="histogrambox-yaxislabel-text">
{labelY}
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 3b5a5b470..56669fb79 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -38,11 +38,17 @@ export class DocumentManager {
toReturn = view;
return;
}
- let docSrc = doc.GetT(KeyStore.Prototype, Document);
- if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
- toReturn = view;
- }
});
+ if (!toReturn) {
+ DocumentManager.Instance.DocumentViews.map(view => {
+ let doc = view.props.Document;
+
+ let docSrc = doc.GetT(KeyStore.Prototype, Document);
+ if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
+ toReturn = view;
+ }
+ });
+ }
return toReturn;
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4bd654e15..46658867b 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -250,7 +250,7 @@ export namespace DragManager {
dragData.aliasOnDrop = e.ctrlKey || e.altKey;
}
if (e.shiftKey) {
- abortDrag();
+ AbortDrag();
CollectionDockingView.Instance.StartOtherDrag(docs, {
pageX: e.pageX,
pageY: e.pageY,
@@ -269,20 +269,22 @@ export namespace DragManager {
);
};
- const abortDrag = () => {
+ AbortDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- dragElements.map(dragElement => dragDiv.removeChild(dragElement));
+ dragElements.map(dragElement => { if (dragElement.parentNode == dragDiv) dragDiv.removeChild(dragElement); });
eles.map(ele => (ele.hidden = false));
};
const upHandler = (e: PointerEvent) => {
- abortDrag();
+ AbortDrag();
FinishDrag(eles, e, dragData, options, finishDrag);
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
+ export let AbortDrag: () => void = emptyFunction;
+
function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
let removed = dragEles.map(dragEle => {
let parent = dragEle.parentElement;
diff --git a/src/client/util/ProsemirrorKeymap.ts b/src/client/util/ProsemirrorKeymap.ts
new file mode 100644
index 000000000..00d086b97
--- /dev/null
+++ b/src/client/util/ProsemirrorKeymap.ts
@@ -0,0 +1,100 @@
+import { Schema } from "prosemirror-model";
+import {
+ wrapIn, setBlockType, chainCommands, toggleMark, exitCode,
+ joinUp, joinDown, lift, selectParentNode
+} from "prosemirror-commands";
+import { wrapInList, splitListItem, liftListItem, sinkListItem } from "prosemirror-schema-list";
+import { undo, redo } from "prosemirror-history";
+import { undoInputRule } from "prosemirror-inputrules";
+import { Transaction, EditorState } from "prosemirror-state";
+
+const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
+
+export type KeyMap = { [key: string]: any };
+
+export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: KeyMap): KeyMap {
+ let keys: { [key: string]: any } = {}, type;
+
+ function bind(key: string, cmd: any) {
+ if (mapKeys) {
+ let mapped = mapKeys[key];
+ if (mapped === false) return;
+ if (mapped) key = mapped;
+ }
+ keys[key] = cmd;
+ }
+
+ bind("Mod-z", undo);
+ bind("Shift-Mod-z", redo);
+ bind("Backspace", undoInputRule);
+
+ if (!mac) {
+ bind("Mod-y", redo);
+ }
+
+ bind("Alt-ArrowUp", joinUp);
+ bind("Alt-ArrowDown", joinDown);
+ bind("Mod-BracketLeft", lift);
+ bind("Escape", selectParentNode);
+
+ if (type = schema.marks.strong) {
+ bind("Mod-b", toggleMark(type));
+ bind("Mod-B", toggleMark(type));
+ }
+ if (type = schema.marks.em) {
+ bind("Mod-i", toggleMark(type));
+ bind("Mod-I", toggleMark(type));
+ }
+ if (type = schema.marks.code) {
+ bind("Mod-`", toggleMark(type));
+ }
+
+ if (type = schema.nodes.bullet_list) {
+ bind("Ctrl-b", wrapInList(type));
+ }
+ if (type = schema.nodes.ordered_list) {
+ bind("Ctrl-n", wrapInList(type));
+ }
+ if (type = schema.nodes.blockquote) {
+ bind("Ctrl->", wrapIn(type));
+ }
+ if (type = schema.nodes.hard_break) {
+ let br = type, cmd = chainCommands(exitCode, (state, dispatch) => {
+ if (dispatch) {
+ dispatch(state.tr.replaceSelectionWith(br.create()).scrollIntoView());
+ return true;
+ }
+ return false;
+ });
+ bind("Mod-Enter", cmd);
+ bind("Shift-Enter", cmd);
+ if (mac) {
+ bind("Ctrl-Enter", cmd);
+ }
+ }
+ if (type = schema.nodes.list_item) {
+ bind("Enter", splitListItem(type));
+ bind("Shift-Tab", liftListItem(type));
+ bind("Tab", sinkListItem(type));
+ }
+ if (type = schema.nodes.paragraph) {
+ bind("Shift-Ctrl-0", setBlockType(type));
+ }
+ if (type = schema.nodes.code_block) {
+ bind("Shift-Ctrl-\\", setBlockType(type));
+ }
+ if (type = schema.nodes.heading) {
+ for (let i = 1; i <= 6; i++) {
+ bind("Shift-Ctrl-" + i, setBlockType(type, { level: i }));
+ }
+ }
+ if (type = schema.nodes.horizontal_rule) {
+ let hr = type;
+ bind("Mod-_", (state: EditorState<S>, dispatch: (tx: Transaction<S>) => void) => {
+ dispatch(state.tr.replaceSelectionWith(hr.create()).scrollIntoView());
+ return true;
+ });
+ }
+
+ return keys;
+} \ No newline at end of file
diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx
index 92944bec0..9ef71e305 100644
--- a/src/client/util/RichTextSchema.tsx
+++ b/src/client/util/RichTextSchema.tsx
@@ -7,135 +7,134 @@ 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];
-
+ 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 } = {
- // :: NodeSpec The top level document node.
- doc: {
- content: "block+"
- },
-
- // :: NodeSpec A plain paragraph textblock. Represented in the DOM
- // as a `<p>` element.
- paragraph: {
- content: "inline*",
- group: "block",
- parseDOM: [{ tag: "p" }],
- toDOM() { return pDOM; }
- },
-
- // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
- blockquote: {
- content: "block+",
- group: "block",
- defining: true,
- parseDOM: [{ tag: "blockquote" }],
- toDOM() { return blockquoteDOM; }
- },
-
- // :: NodeSpec A horizontal rule (`<hr>`).
- horizontal_rule: {
- group: "block",
- parseDOM: [{ tag: "hr" }],
- toDOM() { return hrDOM; }
- },
-
- // :: NodeSpec A heading textblock, with a `level` attribute that
- // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
- // `<h6>` elements.
- heading: {
- attrs: { level: { default: 1 } },
- content: "inline*",
- group: "block",
- defining: true,
- parseDOM: [{ tag: "h1", attrs: { level: 1 } },
- { tag: "h2", attrs: { level: 2 } },
- { tag: "h3", attrs: { level: 3 } },
- { tag: "h4", attrs: { level: 4 } },
- { tag: "h5", attrs: { level: 5 } },
- { tag: "h6", attrs: { level: 6 } }],
- toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
- },
-
- // :: NodeSpec A code listing. Disallows marks or non-text inline
- // nodes by default. Represented as a `<pre>` element with a
- // `<code>` element inside of it.
- code_block: {
- content: "text*",
- marks: "",
- group: "block",
- code: true,
- defining: true,
- parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
- toDOM() { return preDOM; }
- },
-
- // :: NodeSpec The text node.
- text: {
- group: "inline"
- },
-
- // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
- // `alt`, and `href` attributes. The latter two default to the empty
- // string.
- image: {
- inline: true,
- attrs: {
- src: {},
- alt: { default: null },
- title: { default: null }
- },
- group: "inline",
- draggable: true,
- parseDOM: [{
- tag: "img[src]", getAttrs(dom: any) {
- return {
- src: dom.getAttribute("src"),
- title: dom.getAttribute("title"),
- alt: dom.getAttribute("alt")
- };
- }
- }],
- toDOM(node: any) { return ["img", node.attrs]; }
- },
-
- // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
- hard_break: {
- inline: true,
- group: "inline",
- selectable: false,
- parseDOM: [{ tag: "br" }],
- toDOM() { return brDOM; }
- },
-
- ordered_list: {
- ...orderedList,
- 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 }
- },
- //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*'
- }
+ // :: NodeSpec The top level document node.
+ doc: {
+ content: "block+"
+ },
+
+ // :: NodeSpec A plain paragraph textblock. Represented in the DOM
+ // as a `<p>` element.
+ paragraph: {
+ content: "inline*",
+ group: "block",
+ parseDOM: [{ tag: "p" }],
+ toDOM() { return pDOM; }
+ },
+
+ // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks.
+ blockquote: {
+ content: "block+",
+ group: "block",
+ defining: true,
+ parseDOM: [{ tag: "blockquote" }],
+ toDOM() { return blockquoteDOM; }
+ },
+
+ // :: NodeSpec A horizontal rule (`<hr>`).
+ horizontal_rule: {
+ group: "block",
+ parseDOM: [{ tag: "hr" }],
+ toDOM() { return hrDOM; }
+ },
+
+ // :: NodeSpec A heading textblock, with a `level` attribute that
+ // should hold the number 1 to 6. Parsed and serialized as `<h1>` to
+ // `<h6>` elements.
+ heading: {
+ attrs: { level: { default: 1 } },
+ content: "inline*",
+ group: "block",
+ defining: true,
+ parseDOM: [{ tag: "h1", attrs: { level: 1 } },
+ { tag: "h2", attrs: { level: 2 } },
+ { tag: "h3", attrs: { level: 3 } },
+ { tag: "h4", attrs: { level: 4 } },
+ { tag: "h5", attrs: { level: 5 } },
+ { tag: "h6", attrs: { level: 6 } }],
+ toDOM(node: any) { return ["h" + node.attrs.level, 0]; }
+ },
+
+ // :: NodeSpec A code listing. Disallows marks or non-text inline
+ // nodes by default. Represented as a `<pre>` element with a
+ // `<code>` element inside of it.
+ code_block: {
+ content: "text*",
+ marks: "",
+ group: "block",
+ code: true,
+ defining: true,
+ parseDOM: [{ tag: "pre", preserveWhitespace: "full" }],
+ toDOM() { return preDOM; }
+ },
+
+ // :: NodeSpec The text node.
+ text: {
+ group: "inline"
+ },
+
+ // :: NodeSpec An inline image (`<img>`) node. Supports `src`,
+ // `alt`, and `href` attributes. The latter two default to the empty
+ // string.
+ image: {
+ inline: true,
+ attrs: {
+ src: {},
+ alt: { default: null },
+ title: { default: null }
+ },
+ group: "inline",
+ draggable: true,
+ parseDOM: [{
+ tag: "img[src]", getAttrs(dom: any) {
+ return {
+ src: dom.getAttribute("src"),
+ title: dom.getAttribute("title"),
+ alt: dom.getAttribute("alt")
+ };
+ }
+ }],
+ toDOM(node: any) { return ["img", node.attrs]; }
+ },
+
+ // :: NodeSpec A hard line break, represented in the DOM as `<br>`.
+ hard_break: {
+ inline: true,
+ group: "inline",
+ selectable: false,
+ parseDOM: [{ tag: "br" }],
+ toDOM() { return brDOM; }
+ },
+
+ ordered_list: {
+ ...orderedList,
+ 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 }
+ },
+ //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*'
+ }
};
const emDOM: DOMOutputSpecArray = ["em", 0];
@@ -145,84 +144,186 @@ const underlineDOM: DOMOutputSpecArray = ["underline", 0];
// :: Object [Specs](#model.MarkSpec) for the marks in the schema.
export const marks: { [index: string]: MarkSpec } = {
- // :: MarkSpec A link. Has `href` and `title` attributes. `title`
- // defaults to the empty string. Rendered and parsed as an `<a>`
- // element.
- link: {
- attrs: {
- href: {},
- title: { default: null }
- },
- inclusive: false,
- parseDOM: [{
- tag: "a[href]", getAttrs(dom: any) {
- return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
- }
- }],
- toDOM(node: any) { return ["a", node.attrs, 0]; }
- },
-
- // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
- // Has parse rules that also match `<i>` and `font-style: italic`.
- em: {
- parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
- toDOM() { return emDOM; }
- },
-
- // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
- // also match `<b>` and `font-weight: bold`.
- strong: {
- parseDOM: [{ tag: "strong" },
- { tag: "b" },
- { style: "font-weight" }],
- toDOM() { return strongDOM; }
- },
-
- underline: {
- parseDOM: [
- { tag: 'u' },
- { style: 'text-decoration=underline' }
- ],
- toDOM: () => ['span', {
- style: 'text-decoration:underline'
- }]
- },
-
- strikethrough: {
- parseDOM: [
- { tag: 'strike' },
- { style: 'text-decoration=line-through' },
- { style: 'text-decoration-line=line-through' }
- ],
- toDOM: () => ['span', {
- style: 'text-decoration-line:line-through'
- }]
- },
-
- subscript: {
- excludes: 'superscript',
- parseDOM: [
- { tag: 'sub' },
- { style: 'vertical-align=sub' }
- ],
- toDOM: () => ['sub']
- },
-
- superscript: {
- excludes: 'subscript',
- parseDOM: [
- { tag: 'sup' },
- { style: 'vertical-align=super' }
- ],
- toDOM: () => ['sup']
- },
-
-
- // :: MarkSpec Code font mark. Represented as a `<code>` element.
- code: {
- parseDOM: [{ tag: "code" }],
- toDOM() { return codeDOM; }
- }
+ // :: MarkSpec A link. Has `href` and `title` attributes. `title`
+ // defaults to the empty string. Rendered and parsed as an `<a>`
+ // element.
+ link: {
+ attrs: {
+ href: {},
+ title: { default: null }
+ },
+ inclusive: false,
+ parseDOM: [{
+ tag: "a[href]", getAttrs(dom: any) {
+ return { href: dom.getAttribute("href"), title: dom.getAttribute("title") };
+ }
+ }],
+ toDOM(node: any) { return ["a", node.attrs, 0]; }
+ },
+
+ // :: MarkSpec An emphasis mark. Rendered as an `<em>` element.
+ // Has parse rules that also match `<i>` and `font-style: italic`.
+ em: {
+ parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }],
+ toDOM() { return emDOM; }
+ },
+
+ // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules
+ // also match `<b>` and `font-weight: bold`.
+ strong: {
+ parseDOM: [{ tag: "strong" },
+ { tag: "b" },
+ { style: "font-weight" }],
+ toDOM() { return strongDOM; }
+ },
+
+ underline: {
+ parseDOM: [
+ { tag: 'u' },
+ { style: 'text-decoration=underline' }
+ ],
+ toDOM: () => ['span', {
+ style: 'text-decoration:underline'
+ }]
+ },
+
+ strikethrough: {
+ parseDOM: [
+ { tag: 'strike' },
+ { style: 'text-decoration=line-through' },
+ { style: 'text-decoration-line=line-through' }
+ ],
+ toDOM: () => ['span', {
+ style: 'text-decoration-line:line-through'
+ }]
+ },
+
+ subscript: {
+ excludes: 'superscript',
+ parseDOM: [
+ { tag: 'sub' },
+ { style: 'vertical-align=sub' }
+ ],
+ toDOM: () => ['sub']
+ },
+
+ superscript: {
+ excludes: 'subscript',
+ parseDOM: [
+ { tag: 'sup' },
+ { style: 'vertical-align=super' }
+ ],
+ toDOM: () => ['sup']
+ },
+
+
+ // :: MarkSpec Code font mark. Represented as a `<code>` element.
+ code: {
+ parseDOM: [{ tag: "code" }],
+ toDOM() { return codeDOM; }
+ },
+
+
+ /* FONTS */
+ timesNewRoman: {
+ parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: "Times New Roman", Times, serif;'
+ }]
+ },
+
+ arial: {
+ parseDOM: [{ style: 'font-family: Arial, Helvetica, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Arial, Helvetica, sans-serif;'
+ }]
+ },
+
+ georgia: {
+ parseDOM: [{ style: 'font-family: Georgia, serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Georgia, serif;'
+ }]
+ },
+
+ comicSans: {
+ parseDOM: [{ style: 'font-family: "Comic Sans MS", cursive, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: "Comic Sans MS", cursive, sans-serif;'
+ }]
+ },
+
+ tahoma: {
+ parseDOM: [{ style: 'font-family: Tahoma, Geneva, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Tahoma, Geneva, sans-serif;'
+ }]
+ },
+
+ impact: {
+ parseDOM: [{ style: 'font-family: Impact, Charcoal, sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: Impact, Charcoal, sans-serif;'
+ }]
+ },
+
+ crimson: {
+ parseDOM: [{ style: 'font-family: "Crimson Text", sans-serif;' }],
+ toDOM: () => ['span', {
+ style: 'font-family: "Crimson Text", sans-serif;'
+ }]
+ },
+
+ /** FONT SIZES */
+
+ p10: {
+ parseDOM: [{ style: 'font-size: 10px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 10px;'
+ }]
+ },
+
+ p12: {
+ parseDOM: [{ style: 'font-size: 12px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 12px;'
+ }]
+ },
+
+ p16: {
+ parseDOM: [{ style: 'font-size: 16px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 16px;'
+ }]
+ },
+
+ p24: {
+ parseDOM: [{ style: 'font-size: 24px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 24px;'
+ }]
+ },
+
+ p32: {
+ parseDOM: [{ style: 'font-size: 32px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 32px;'
+ }]
+ },
+
+ p48: {
+ parseDOM: [{ style: 'font-size: 48px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 48px;'
+ }]
+ },
+
+ p72: {
+ parseDOM: [{ style: 'font-size: 72px;' }],
+ toDOM: () => ['span', {
+ style: 'font-size: 72px;'
+ }]
+ },
};
// :: Schema
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index c56f6a4ff..92d78696e 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,6 +3,7 @@ import { DocumentView } from "../views/nodes/DocumentView";
import { Document } from "../../fields/Document";
import { Main } from "../views/Main";
import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
+import { DragManager } from "./DragManager";
export namespace SelectionManager {
class Manager {
@@ -18,16 +19,26 @@ export namespace SelectionManager {
if (manager.SelectedDocuments.indexOf(doc) === -1) {
manager.SelectedDocuments.push(doc);
- doc.props.onActiveChanged(true);
+ doc.props.whenActiveChanged(true);
}
}
@action
DeselectAll(): void {
- manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false));
+ manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
manager.SelectedDocuments = [];
MainOverlayTextBox.Instance.SetTextDoc();
}
+ @action
+ ReselectAll() {
+ let sdocs = manager.SelectedDocuments.map(d => d);
+ manager.SelectedDocuments = [];
+ return sdocs;
+ }
+ @action
+ ReselectAll2(sdocs: DocumentView[]) {
+ sdocs.map(s => SelectionManager.SelectDoc(s, true));
+ }
}
const manager = new Manager();
@@ -52,6 +63,10 @@ export namespace SelectionManager {
if (found) manager.SelectDoc(found, false);
}
+ export function ReselectAll() {
+ let sdocs = manager.ReselectAll();
+ manager.ReselectAll2(sdocs);
+ }
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
}
diff --git a/src/client/util/TooltipLinkingMenu.tsx b/src/client/util/TooltipLinkingMenu.tsx
new file mode 100644
index 000000000..55e0eb909
--- /dev/null
+++ b/src/client/util/TooltipLinkingMenu.tsx
@@ -0,0 +1,86 @@
+import { action, IReactionDisposer, reaction } from "mobx";
+import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
+import { baseKeymap, lift } from "prosemirror-commands";
+import { history, redo, undo } from "prosemirror-history";
+import { keymap } from "prosemirror-keymap";
+import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
+import { EditorView } from "prosemirror-view";
+import { schema } from "./RichTextSchema";
+import { Schema, NodeType, MarkType } from "prosemirror-model";
+import React = require("react");
+import "./TooltipTextMenu.scss";
+const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { wrapInList, bulletList, liftListItem, listItem } from 'prosemirror-schema-list';
+import {
+ faListUl,
+} from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { FieldViewProps } from "../views/nodes/FieldView";
+import { throwStatement } from "babel-types";
+
+const SVG = "http://www.w3.org/2000/svg";
+
+//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
+export class TooltipLinkingMenu {
+
+ private tooltip: HTMLElement;
+ private view: EditorView;
+ private editorProps: FieldViewProps;
+
+ constructor(view: EditorView, editorProps: FieldViewProps) {
+ this.view = view;
+ this.editorProps = editorProps;
+ this.tooltip = document.createElement("div");
+ this.tooltip.className = "tooltipMenu linking";
+
+ //add the div which is the tooltip
+ view.dom.parentNode!.parentNode!.appendChild(this.tooltip);
+
+ let target = "https://www.google.com";
+
+ let link = document.createElement("a");
+ link.href = target;
+ link.textContent = target;
+ link.target = "_blank";
+ link.style.color = "white";
+ this.tooltip.appendChild(link);
+
+ this.update(view, undefined);
+ }
+
+ //updates the tooltip menu when the selection changes
+ update(view: EditorView, lastState: EditorState | undefined) {
+ let state = view.state;
+ // Don't do anything if the document/selection didn't change
+ if (lastState && lastState.doc.eq(state.doc) &&
+ lastState.selection.eq(state.selection)) return;
+
+ // Hide the tooltip if the selection is empty
+ if (state.selection.empty) {
+ this.tooltip.style.display = "none";
+ return;
+ }
+
+ console.log("STORED:");
+ console.log(state.doc.content.firstChild!.content);
+
+ // Otherwise, reposition it and update its content
+ this.tooltip.style.display = "";
+ let { from, to } = state.selection;
+ 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();
+ // Find a center-ish x position from the selection endpoints (when
+ // crossing lines, end may be more to the left)
+ let left = Math.max((start.left + end.left) / 2, start.left + 3);
+ this.tooltip.style.left = (left - box.left) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
+ let mid = Math.min(start.left, end.left) + width;
+
+ this.tooltip.style.width = "auto";
+ this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ }
+
+ destroy() { this.tooltip.remove(); }
+}
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 7deea3be6..70d9ad772 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -1,8 +1,252 @@
@import "../views/globalCssVariables";
+.ProseMirror-textblock-dropdown {
+ min-width: 3em;
+ }
+
+ .ProseMirror-menu {
+ margin: 0 -4px;
+ line-height: 1;
+ }
+
+ .ProseMirror-tooltip .ProseMirror-menu {
+ width: -webkit-fit-content;
+ width: fit-content;
+ white-space: pre;
+ }
+
+ .ProseMirror-menuitem {
+ margin-right: 3px;
+ display: inline-block;
+ }
+
+ .ProseMirror-menuseparator {
+ // border-right: 1px solid #ddd;
+ margin-right: 3px;
+ }
+
+ .ProseMirror-menu-dropdown, .ProseMirror-menu-dropdown-menu {
+ font-size: 90%;
+ white-space: nowrap;
+ }
+
+ .ProseMirror-menu-dropdown {
+ vertical-align: 1px;
+ cursor: pointer;
+ position: relative;
+ padding-right: 15px;
+ margin: 3px;
+ background: #333333;
+ border-radius: 3px;
+ text-align: center;
+ }
+
+ .ProseMirror-menu-dropdown-wrap {
+ padding: 1px 0 1px 4px;
+ display: inline-block;
+ position: relative;
+ }
+
+ .ProseMirror-menu-dropdown:after {
+ content: "";
+ border-left: 4px solid transparent;
+ border-right: 4px solid transparent;
+ border-top: 4px solid currentColor;
+ opacity: .6;
+ position: absolute;
+ right: 4px;
+ top: calc(50% - 2px);
+ }
+
+ .ProseMirror-menu-dropdown-menu, .ProseMirror-menu-submenu {
+ position: absolute;
+ background: $dark-color;
+ color:white;
+ border: 1px solid rgb(223, 223, 223);
+ padding: 2px;
+ }
+
+ .ProseMirror-menu-dropdown-menu {
+ z-index: 15;
+ min-width: 6em;
+ }
+
+ .linking {
+ text-align: center;
+ }
+
+ .ProseMirror-menu-dropdown-item {
+ cursor: pointer;
+ padding: 2px 8px 2px 4px;
+ width: auto;
+ }
+
+ .ProseMirror-menu-dropdown-item:hover {
+ background: #2e2b2b;
+ }
+
+ .ProseMirror-menu-submenu-wrap {
+ position: relative;
+ margin-right: -4px;
+ }
+
+ .ProseMirror-menu-submenu-label:after {
+ content: "";
+ border-top: 4px solid transparent;
+ border-bottom: 4px solid transparent;
+ border-left: 4px solid currentColor;
+ opacity: .6;
+ position: absolute;
+ right: 4px;
+ top: calc(50% - 4px);
+ }
+
+ .ProseMirror-menu-submenu {
+ display: none;
+ min-width: 4em;
+ left: 100%;
+ top: -3px;
+ }
+
+ .ProseMirror-menu-active {
+ background: #eee;
+ border-radius: 4px;
+ }
+
+ .ProseMirror-menu-active {
+ background: #eee;
+ border-radius: 4px;
+ }
+
+ .ProseMirror-menu-disabled {
+ opacity: .3;
+ }
+
+ .ProseMirror-menu-submenu-wrap:hover .ProseMirror-menu-submenu, .ProseMirror-menu-submenu-wrap-active .ProseMirror-menu-submenu {
+ display: block;
+ }
+
+ .ProseMirror-menubar {
+ border-top-left-radius: inherit;
+ border-top-right-radius: inherit;
+ position: relative;
+ min-height: 1em;
+ color: white;
+ padding: 1px 6px;
+ top: 0; left: 0; right: 0;
+ border-bottom: 1px solid silver;
+ background:$dark-color;
+ z-index: 10;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ overflow: visible;
+ }
+
+ .ProseMirror-icon {
+ display: inline-block;
+ line-height: .8;
+ vertical-align: -2px; /* Compensate for padding */
+ padding: 2px 8px;
+ cursor: pointer;
+ }
+
+ .ProseMirror-menu-disabled.ProseMirror-icon {
+ cursor: default;
+ }
+
+ .ProseMirror-icon svg {
+ fill: currentColor;
+ height: 1em;
+ }
+
+ .ProseMirror-icon span {
+ vertical-align: text-top;
+ }
+.ProseMirror-example-setup-style hr {
+ padding: 2px 10px;
+ border: none;
+ margin: 1em 0;
+ }
+
+ .ProseMirror-example-setup-style hr:after {
+ content: "";
+ display: block;
+ height: 1px;
+ background-color: silver;
+ line-height: 2px;
+ }
+
+ .ProseMirror ul, .ProseMirror ol {
+ padding-left: 30px;
+ }
+
+ .ProseMirror blockquote {
+ padding-left: 1em;
+ border-left: 3px solid #eee;
+ margin-left: 0; margin-right: 0;
+ }
+
+ .ProseMirror-example-setup-style img {
+ cursor: default;
+ }
+
+ .ProseMirror-prompt {
+ background: white;
+ padding: 5px 10px 5px 15px;
+ border: 1px solid silver;
+ position: fixed;
+ border-radius: 3px;
+ z-index: 11;
+ box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2);
+ }
+
+ .ProseMirror-prompt h5 {
+ margin: 0;
+ font-weight: normal;
+ font-size: 100%;
+ color: #444;
+ }
+
+ .ProseMirror-prompt input[type="text"],
+ .ProseMirror-prompt textarea {
+ background: #eee;
+ border: none;
+ outline: none;
+ }
+
+ .ProseMirror-prompt input[type="text"] {
+ padding: 0 4px;
+ }
+
+ .ProseMirror-prompt-close {
+ position: absolute;
+ left: 2px; top: 1px;
+ color: #666;
+ border: none; background: transparent; padding: 0;
+ }
+
+ .ProseMirror-prompt-close:after {
+ content: "✕";
+ font-size: 12px;
+ }
+
+ .ProseMirror-invalid {
+ background: #ffc;
+ border: 1px solid #cc7;
+ border-radius: 4px;
+ padding: 5px 10px;
+ position: absolute;
+ min-width: 10em;
+ }
+
+ .ProseMirror-prompt-buttons {
+ margin-top: 5px;
+ display: none;
+ }
+
.tooltipMenu {
position: absolute;
- z-index: 20;
+ z-index: 200;
background: $dark-color;
border: 1px solid silver;
border-radius: 4px;
@@ -10,6 +254,7 @@
margin-bottom: 7px;
-webkit-transform: translateX(-50%);
transform: translateX(-50%);
+ pointer-events: all;
}
.tooltipMenu:before {
@@ -39,7 +284,7 @@
display: inline-block;
border-right: 1px solid rgba(0, 0, 0, 0.2);
//color: rgb(19, 18, 18);
- color: $light-color;
+ color: white;
line-height: 1;
padding: 0px 2px;
margin: 1px;
@@ -52,4 +297,8 @@
.underline {text-decoration: underline}
.superscript {vertical-align:super}
.subscript { vertical-align:sub }
- .strikethrough {text-decoration-line:line-through} \ No newline at end of file
+ .strikethrough {text-decoration-line:line-through}
+ .font-size-indicator {
+ font-size: 12px;
+ padding-right: 0px;
+ }
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 7951e5686..4f0eb7d63 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -1,28 +1,46 @@
import { action, IReactionDisposer, reaction } from "mobx";
-import { baseKeymap } from "prosemirror-commands";
+import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css
+import { baseKeymap, lift } from "prosemirror-commands";
import { history, redo, undo } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
-import { EditorState, Transaction, NodeSelection } from "prosemirror-state";
+import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { schema } from "./RichTextSchema";
-import { Schema, NodeType } from "prosemirror-model";
+import { Schema, NodeType, MarkType } from "prosemirror-model";
import React = require("react");
import "./TooltipTextMenu.scss";
const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands");
import { library } from '@fortawesome/fontawesome-svg-core';
-import { wrapInList, bulletList } from 'prosemirror-schema-list';
-import { faListUl } from '@fortawesome/free-solid-svg-icons';
+import { wrapInList, bulletList, liftListItem, listItem } from 'prosemirror-schema-list';
+import {
+ faListUl,
+} from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { FieldViewProps } from "../views/nodes/FieldView";
import { throwStatement } from "babel-types";
+const SVG = "http://www.w3.org/2000/svg";
//appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc.
export class TooltipTextMenu {
private tooltip: HTMLElement;
+ private num_icons = 0;
+ private view: EditorView;
+ private fontStyles: MarkType[];
+ private fontSizes: MarkType[];
private editorProps: FieldViewProps;
+ private state: EditorState;
+ private fontSizeToNum: Map<MarkType, number>;
+ private fontStylesToName: Map<MarkType, string>;
+ private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
+ //dropdown doms
+ private fontSizeDom: Node;
+ private fontStyleDom: Node;
constructor(view: EditorView, editorProps: FieldViewProps) {
+ this.view = view;
+ this.state = view.state;
this.editorProps = editorProps;
this.tooltip = document.createElement("div");
this.tooltip.className = "tooltipMenu";
@@ -32,7 +50,6 @@ export class TooltipTextMenu {
//add additional icons
library.add(faListUl);
-
//add the buttons to the tooltip
let items = [
{ command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") },
@@ -41,36 +58,155 @@ 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") },
- //this doesn't work currently - look into notion of active block
{ command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
+ { command: lift, dom: this.icon("<", "lift") },
];
- items.forEach(({ dom }) => this.tooltip.appendChild(dom));
-
- //pointer down handler to activate button effects
- this.tooltip.addEventListener("pointerdown", e => {
- e.preventDefault();
- view.focus();
- items.forEach(({ command, dom }) => {
- if (dom.contains(e.target as Node)) {
- 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"
- }
+ //add menu items
+ items.forEach(({ dom, command }) => {
+ this.tooltip.appendChild(dom);
+
+ //pointer down handler to activate button effects
+ dom.addEventListener("pointerdown", e => {
+ e.preventDefault();
+ view.focus();
+ command(view.state, view.dispatch, view);
});
+
});
+ //list of font styles
+ this.fontStylesToName = new Map();
+ this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman");
+ this.fontStylesToName.set(schema.marks.arial, "Arial");
+ this.fontStylesToName.set(schema.marks.georgia, "Georgia");
+ this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans MS");
+ this.fontStylesToName.set(schema.marks.tahoma, "Tahoma");
+ this.fontStylesToName.set(schema.marks.impact, "Impact");
+ this.fontStylesToName.set(schema.marks.crimson, "Crimson Text");
+ this.fontStyles = Array.from(this.fontStylesToName.keys());
+
+ //font sizes
+ this.fontSizeToNum = new Map();
+ this.fontSizeToNum.set(schema.marks.p10, 10);
+ this.fontSizeToNum.set(schema.marks.p12, 12);
+ this.fontSizeToNum.set(schema.marks.p16, 16);
+ this.fontSizeToNum.set(schema.marks.p24, 24);
+ this.fontSizeToNum.set(schema.marks.p32, 32);
+ this.fontSizeToNum.set(schema.marks.p48, 48);
+ this.fontSizeToNum.set(schema.marks.p72, 72);
+ this.fontSizes = Array.from(this.fontSizeToNum.keys());
+
+ //this.addFontDropdowns();
+
this.update(view, undefined);
}
+ //label of dropdown will change to given label
+ updateFontSizeDropdown(label: string) {
+ //filtering function - might be unecessary
+ let cut = (arr: MenuItem[]) => arr.filter(x => x);
+
+ //font SIZES
+ let fontSizeBtns: MenuItem[] = [];
+ this.fontSizeToNum.forEach((number, mark) => {
+ fontSizeBtns.push(this.dropdownBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+ });
+
+ if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
+ this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), {
+ label: label,
+ css: "color:white; min-width: 60px; padding-left: 5px; margin-right: 0;"
+ }) as MenuItem).render(this.view).dom;
+ this.tooltip.appendChild(this.fontSizeDom);
+ }
+
+ //label of dropdown will change to given label
+ updateFontStyleDropdown(label: string) {
+ //filtering function - might be unecessary
+ let cut = (arr: MenuItem[]) => arr.filter(x => x);
+
+ //font STYLES
+ let fontBtns: MenuItem[] = [];
+ this.fontStylesToName.forEach((name, mark) => {
+ fontBtns.push(this.dropdownBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
+ });
+
+ if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
+ this.fontStyleDom = (new Dropdown(cut(fontBtns), {
+ label: label,
+ css: "color:white; width: 125px; margin-left: -3px; padding-left: 2px;"
+ }) as MenuItem).render(this.view).dom;
+
+ this.tooltip.appendChild(this.fontStyleDom);
+ }
+
+ //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text
+ changeToMarkInGroup(markType: MarkType, view: EditorView, fontMarks: MarkType[]) {
+ let { empty, $cursor, ranges } = view.state.selection as TextSelection;
+ let state = view.state;
+ let dispatch = view.dispatch;
+
+ //remove all other active font marks
+ fontMarks.forEach((type) => {
+ if (dispatch) {
+ if ($cursor) {
+ if (type.isInSet(state.storedMarks || $cursor.marks())) {
+ dispatch(state.tr.removeStoredMark(type));
+ }
+ } else {
+ let has = false, tr = state.tr;
+ for (let i = 0; !has && i < ranges.length; i++) {
+ let { $from, $to } = ranges[i];
+ has = state.doc.rangeHasMark($from.pos, $to.pos, type);
+ }
+ for (let i of ranges) {
+ let { $from, $to } = i;
+ if (has) {
+ toggleMark(type)(view.state, view.dispatch, view);
+ }
+ }
+ }
+ }
+ }); //actually apply font
+ return toggleMark(markType)(view.state, view.dispatch, view);
+ }
+
+ //makes a button for the drop down
+ //css is the style you want applied to the button
+ dropdownBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType<any>, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) {
+ return new MenuItem({
+ title: "",
+ label: label,
+ execEvent: "",
+ class: "menuicon",
+ css: css,
+ enable(state) { return true; },
+ run() {
+ changeToMarkInGroup(markType, view, groupMarks);
+ }
+ });
+ }
// Helper function to create menu icons
icon(text: string, name: string) {
let span = document.createElement("span");
- span.className = "menuicon " + name;
+ span.className = name + " menuicon";
span.title = name;
span.textContent = text;
+ span.style.color = "white";
return span;
}
+ //method for checking whether node can be inserted
+ canInsert(state: EditorState, nodeType: NodeType<Schema<string, string>>) {
+ let $from = state.selection.$from;
+ for (let d = $from.depth; d >= 0; d--) {
+ let index = $from.index(d);
+ if ($from.node(d).canReplaceWith(index, index, nodeType)) return true;
+ }
+ return false;
+ }
+
+
//adapted this method - use it to check if block has a tag (ie bulleting)
blockActive(type: NodeType<Schema<string, string>>, state: EditorState) {
let attrs = {};
@@ -89,15 +225,6 @@ export class TooltipTextMenu {
}
}
- //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");
- icon.className = "menuicon fa fa-smile-o";
- span.appendChild(icon);
- return span;
- }
-
// Create an icon for a heading at the given level
heading(level: number) {
return {
@@ -132,10 +259,54 @@ export class TooltipTextMenu {
let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
let mid = Math.min(start.left, end.left) + width;
- //THIS WIDTH IS 15 * NUMBER OF ICONS + 15
- this.tooltip.style.width = 122 + "px";
+ this.tooltip.style.width = 225 + "px";
this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+
+ //UPDATE FONT STYLE DROPDOWN
+ let activeStyles = this.activeMarksOnSelection(this.fontStyles);
+ if (activeStyles.length === 1) {
+ // if we want to update something somewhere with active font name
+ let fontName = this.fontStylesToName.get(activeStyles[0]);
+ if (fontName) { this.updateFontStyleDropdown(fontName); }
+ } else if (activeStyles.length === 0) {
+ //crimson on default
+ this.updateFontStyleDropdown("Crimson Text");
+ } else {
+ this.updateFontStyleDropdown("Various");
+ }
+
+ //UPDATE FONT SIZE DROPDOWN
+ let activeSizes = this.activeMarksOnSelection(this.fontSizes);
+ if (activeSizes.length === 1) { //if there's only one active font size
+ let size = this.fontSizeToNum.get(activeSizes[0]);
+ if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
+ } else if (activeSizes.length === 0) {
+ //should be 14 on default
+ this.updateFontSizeDropdown("14 pt");
+ } else { //multiple font sizes selected
+ this.updateFontSizeDropdown("Various");
+ }
+ }
+
+ //finds all active marks on selection
+ activeMarksOnSelection(markGroup: MarkType[]) {
+ //current selection
+ let { empty, $cursor, ranges } = this.view.state.selection as TextSelection;
+ let state = this.view.state;
+ let dispatch = this.view.dispatch;
+
+ let activeMarks = markGroup.filter(mark => {
+ if (dispatch) {
+ let has = false, tr = state.tr;
+ for (let i = 0; !has && i < ranges.length; i++) {
+ let { $from, $to } = ranges[i];
+ return state.doc.rangeHasMark($from.pos, $to.pos, mark);
+ }
+ }
+ return false;
+ });
+ return activeMarks;
}
destroy() { this.tooltip.remove(); }
-} \ No newline at end of file
+}
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index 14f3359ca..7bd50b716 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -3,15 +3,19 @@
.documentDecorations {
position: absolute;
}
+<<<<<<< HEAD
#documentDecorations-container {
+=======
+.documentDecorations-container {
+ z-index: $docDecorations-zindex;
+>>>>>>> b63bcb791013766d5d16e4f38964499268f904c4
position: absolute;
top: 0;
left: 0;
display: grid;
- z-index: $docDecorations-zindex;
grid-template-rows: 20px 8px 1fr 8px;
- grid-template-columns: 8px 8px 1fr 8px 8px;
+ grid-template-columns: 8px 16px 1fr 8px 8px;
pointer-events: none;
#documentDecorations-centerCont {
@@ -91,6 +95,11 @@
pointer-events: all;
text-align: center;
cursor: pointer;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: $MINIMIZED_ICON_SIZE;
+ height: $MINIMIZED_ICON_SIZE;
}
.documentDecorations-background {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 2b22d9e19..c0360a5f1 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,11 +1,11 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
import { Key } from "../../fields/Key";
-//import ContentEditable from 'react-contenteditable'
import { KeyStore } from "../../fields/KeyStore";
import { ListField } from "../../fields/ListField";
import { NumberField } from "../../fields/NumberField";
import { TextField } from "../../fields/TextField";
+import { Document } from "../../fields/Document";
import { emptyFunction } from "../../Utils";
import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
@@ -17,6 +17,9 @@ import { LinkMenu } from "./nodes/LinkMenu";
import { TemplateMenu } from "./TemplateMenu";
import React = require("react");
import { Template, Templates } from "./Templates";
+import { CompileScript } from "../util/Scripting";
+import { IconBox } from "./nodes/IconBox";
+import { FieldValue, Field } from "../../fields/Field";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -33,12 +36,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
private _linkerButton = React.createRef<HTMLDivElement>();
+ private _downX = 0;
+ private _downY = 0;
+ @observable private _minimizedX = 0;
+ @observable private _minimizedY = 0;
//@observable private _title: string = this._documents[0].props.Document.Title;
@observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : "";
@observable private _fieldKey: Key = KeyStore.Title;
@observable private _hidden = false;
@observable private _opacity = 1;
@observable private _dragging = false;
+ @observable private _iconifying = false;
constructor(props: Readonly<{}>) {
@@ -72,7 +80,18 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
// TODO: Change field with switch statement
}
else {
- this._title = "changed";
+ if (this._documents.length > 0) {
+ let field = this._documents[0].props.Document.Get(this._fieldKey);
+ if (field instanceof TextField) {
+ this._documents.forEach(d =>
+ d.props.Document.Set(this._fieldKey, new TextField(this._title)));
+ }
+ else if (field instanceof NumberField) {
+ this._documents.forEach(d =>
+ d.props.Document.Set(this._fieldKey, new NumberField(+this._title)));
+ }
+ this._title = "changed";
+ }
}
e.target.blur();
}
@@ -126,7 +145,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this._dragging = true;
document.removeEventListener("pointermove", this.onBackgroundMove);
document.removeEventListener("pointerup", this.onBackgroundUp);
- DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentRef.current!), dragData, e.x, e.y, {
+ DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
handlers: {
dragComplete: action(() => this._dragging = false),
},
@@ -167,28 +186,76 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.removeEventListener("pointerup", this.onCloseUp);
}
}
+ @action
onMinimizeDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
+ this._downX = e.pageX;
+ this._downY = e.pageY;
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ this._minimizedX = selDocPos[0] + 12;
+ this._minimizedY = selDocPos[1] + 12;
document.removeEventListener("pointermove", this.onMinimizeMove);
document.addEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
document.addEventListener("pointerup", this.onMinimizeUp);
}
}
+
+ @action
onMinimizeMove = (e: PointerEvent): void => {
e.stopPropagation();
- if (e.button === 0) {
+ let moved = Math.abs(e.pageX - this._downX) > 4 || Math.abs(e.pageY - this._downY) > 4;
+ if (moved) {
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ let snapped = Math.abs(e.pageX - selDocPos[0]) < 20 && Math.abs(e.pageY - selDocPos[1]) < 20;
+ this._minimizedX = snapped ? selDocPos[0] + 4 : e.clientX;
+ this._minimizedY = snapped ? selDocPos[1] - 18 : e.clientY;
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+ Promise.all(selectedDocs.map(async selDoc => await selDoc.getIconDoc())).then(minDocSet =>
+ this.moveIconDocs(SelectionManager.SelectedDocuments())
+ );
+ this._iconifying = snapped;
}
}
+ @action
onMinimizeUp = (e: PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
- SelectionManager.SelectedDocuments().map(dv => dv.minimize());
document.removeEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+ Promise.all(selectedDocs.map(async selDoc => await selDoc.getIconDoc())).then(minDocSet => {
+ let minDocs = minDocSet.filter(minDoc => minDoc instanceof Document).map(minDoc => minDoc as Document);
+ minDocs.map(minDoc => {
+ minDoc.SetNumber(KeyStore.X, minDocs[0].GetNumber(KeyStore.X, 0));
+ minDoc.SetNumber(KeyStore.Y, minDocs[0].GetNumber(KeyStore.Y, 0));
+ minDoc.SetData(KeyStore.LinkTags, minDocs, ListField);
+ if (this._iconifying && selectedDocs[0].props.removeDocument) {
+ selectedDocs[0].props.removeDocument(minDoc);
+ (minDoc.Get(KeyStore.MaximizedDoc, false) as Document)!.Set(KeyStore.MinimizedDoc, undefined);
+ }
+ });
+ runInAction(() => this._minimizedX = this._minimizedY = 0);
+ if (!this._iconifying) selectedDocs[0].toggleIcon();
+ this._iconifying = false;
+ });
}
}
+ moveIconDocs(selViews: DocumentView[], minDocSet?: FieldValue<Field>[]) {
+ selViews.map(selDoc => {
+ let minDoc = selDoc.props.Document.Get(KeyStore.MinimizedDoc);
+ if (minDoc instanceof Document) {
+ let zoom = selDoc.props.Document.GetNumber(KeyStore.ZoomBasis, 1);
+ let where = (selDoc.props.ScreenToLocalTransform()).scale(selDoc.props.ContentScaling()).scale(1 / zoom).
+ transformPoint(this._minimizedX - 12, this._minimizedY - 12);
+ minDoc.SetNumber(KeyStore.X, where[0] + selDoc.props.Document.GetNumber(KeyStore.X, 0));
+ minDoc.SetNumber(KeyStore.Y, where[1] + selDoc.props.Document.GetNumber(KeyStore.Y, 0));
+ }
+ });
+ }
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
@@ -304,7 +371,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
MainOverlayTextBox.Instance.SetTextDoc();
SelectionManager.SelectedDocuments().forEach(element => {
- const rect = element.screenRect();
+ const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
+
if (rect.width !== 0) {
let doc = element.props.Document;
let width = doc.GetNumber(KeyStore.Width, 0);
@@ -364,11 +432,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
render() {
var bounds = this.Bounds;
- if (bounds.x === Number.MAX_VALUE) {
+ let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
+ if (bounds.x === Number.MAX_VALUE || !seldoc) {
return (null);
}
- // console.log(this._documents.length)
- // let test = this._documents[0].props.Document.Title;
+ let minimizeIcon = (
+ <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
+ {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."}
+ </div>);
+
if (this.Hidden) {
return (null);
}
@@ -414,14 +486,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
}} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
</div>
- <div id="documentDecorations-container" style={{
+ <div className="documentDecorations-container" style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px",
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
opacity: this._opacity
}}>
- <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div>
+ {minimizeIcon}
+
<input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} />
<div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss
index be3c5069a..ea401eaf9 100644
--- a/src/client/views/EditableView.scss
+++ b/src/client/views/EditableView.scss
@@ -2,5 +2,4 @@
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/Main.tsx b/src/client/views/Main.tsx
index 98ef329c7..09ef30f6b 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -27,7 +27,7 @@ import '../northstar/model/ModelExtensions';
import { HistogramOperation } from '../northstar/operations/HistogramOperation';
import '../northstar/utils/Extensions';
import { Server } from '../Server';
-import { SetupDrag } from '../util/DragManager';
+import { SetupDrag, DragManager } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
import { CollectionDockingView } from './collections/CollectionDockingView';
@@ -38,6 +38,7 @@ import "./Main.scss";
import { MainOverlayTextBox } from './MainOverlayTextBox';
import { DocumentView } from './nodes/DocumentView';
import { PreviewCursor } from './PreviewCursor';
+import { SelectionManager } from '../util/SelectionManager';
@observer
@@ -84,7 +85,11 @@ export class Main extends React.Component {
this.initEventListeners();
this.initAuthenticationRouters();
- this.initializeNorthstar();
+ try {
+ this.initializeNorthstar();
+ } catch (e) {
+
+ }
}
componentDidMount() { window.onpopstate = this.onHistory; }
@@ -107,6 +112,12 @@ export class Main extends React.Component {
// window.addEventListener("pointermove", (e) => this.reportLocation(e))
window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
+ window.addEventListener("keydown", (e) => {
+ if (e.key == "Escape") {
+ DragManager.AbortDrag();
+ SelectionManager.DeselectAll()
+ }
+ }, false); // drag event handler
// click interactions for the context menu
document.addEventListener("pointerdown", action(function (e: PointerEvent) {
if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
@@ -186,7 +197,7 @@ export class Main extends React.Component {
selectOnLoad={false}
focus={emptyDocFunction}
parentActive={returnTrue}
- onActiveChanged={emptyFunction}
+ whenActiveChanged={emptyFunction}
ContainingCollectionView={undefined} />}
</div>
}
@@ -206,9 +217,9 @@ export class Main extends React.Component {
let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
- 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, height: 200, title: "a pdf doc" }));
+ 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" }));
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index 697d68c8c..f6a746e63 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -7,6 +7,7 @@
overflow: visible;
top: 0;
left: 0;
+ pointer-events: none;
z-index: $mainTextInput-zindex;
.formattedTextBox-cont {
background-color: rgba(248, 6, 6, 0.001);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 6d43d88f0..2b72b09c6 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -21,8 +21,8 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
public static Instance: MainOverlayTextBox;
@observable public TextDoc?: Document = undefined;
public TextScroll: number = 0;
- private _textRect: any;
- private _textXf: Transform = Transform.Identity();
+ @observable _textRect: any;
+ @observable _textXf: () => Transform = () => Transform.Identity();
private _textFieldKey: Key = KeyStore.Data;
private _textColor: string | null = null;
private _textTargetDiv: HTMLDivElement | undefined;
@@ -35,15 +35,14 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
}
@action
- SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: Transform) {
+ SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: () => Transform) {
if (this._textTargetDiv) {
this._textTargetDiv.style.color = this._textColor;
}
- this.TextDoc = undefined;
this.TextDoc = textDoc;
this._textFieldKey = textFieldKey!;
- this._textXf = tx ? tx : Transform.Identity();
+ this._textXf = tx ? tx : () => Transform.Identity();
this._textTargetDiv = div;
if (div) {
this._textColor = div.style.color;
@@ -72,9 +71,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
document.removeEventListener("pointermove", this.textBoxMove);
document.removeEventListener('pointerup', this.textBoxUp);
let dragData = new DragManager.DocumentDragData([this.TextDoc!]);
- const [left, top] = this._textXf
- .inverse()
- .transformPoint(0, 0);
+ const [left, top] = this._textXf.inverse().transformPoint(0, 0);
dragData.xOffset = e.clientX - left;
dragData.yOffset = e.clientY - top;
DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
@@ -92,17 +89,15 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
render() {
if (this.TextDoc) {
- let x: number = this._textRect.x;
- let y: number = this._textRect.y;
- let w: number = this._textRect.width;
- let h: number = this._textRect.height;
- let t = this._textXf.transformPoint(0, 0);
- let s = this._textXf.transformPoint(1, 0);
- s[0] = Math.sqrt((s[0] - t[0]) * (s[0] - t[0]) + (s[1] - t[1]) * (s[1] - t[1]));
- return <div className="mainOverlayTextBox-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
- <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}>
- <FormattedTextBox fieldKey={this._textFieldKey!} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
- selectOnLoad={true} ContainingCollectionView={undefined} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+ let toScreenXf = this._textXf().inverse();
+ let pt = toScreenXf.transformPoint(0, 0);
+ let s = 1 / this._textXf().Scale;
+ return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${pt[0]}px, ${pt[1]}px) scale(${s},${s})`, width: "auto", height: "auto" }} >
+ <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
+ style={{ width: `${this.TextDoc.Width()}px`, height: `${this.TextDoc.Height()}px` }}>
+ <FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
+ selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
+ ScreenToLocalTransform={this._textXf} focus={emptyDocFunction} />
</div>
</ div>;
}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index bff56e8c3..eec01bb3f 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -8,6 +8,7 @@ import { ListField } from '../../../fields/ListField';
import { NumberField } from '../../../fields/NumberField';
import { ContextMenu } from '../ContextMenu';
import { FieldViewProps } from '../nodes/FieldView';
+import { TextField } from '../../../fields/TextField';
export enum CollectionViewType {
Invalid,
@@ -22,7 +23,7 @@ export interface CollectionRenderProps {
removeDocument: (document: Document) => boolean;
moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
active: () => boolean;
- onActiveChanged: (isActive: boolean) => void;
+ whenActiveChanged: (isActive: boolean) => void;
}
export interface CollectionViewProps extends FieldViewProps {
@@ -55,19 +56,22 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
//TODO should this be observable?
private _isChildActive = false;
- onActiveChanged = (isActive: boolean) => {
+ whenActiveChanged = (isActive: boolean) => {
this._isChildActive = isActive;
- this.props.onActiveChanged(isActive);
+ this.props.whenActiveChanged(isActive);
}
createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
- let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
- for (const doc of data) {
+ if (!(documentToAdd instanceof Document)) {
+ return false;
+ }
+ let data = documentToAdd.GetList(KeyStore.Data, [] as Document[]);
+ for (const doc of data.filter(d => d instanceof Document)) {
if (this.createsCycle(doc, containerDocument)) {
return true;
}
}
- let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
+ let annots = documentToAdd.GetList(KeyStore.Annotations, [] as Document[]);
for (const annot of annots) {
if (this.createsCycle(annot, containerDocument)) {
return true;
@@ -84,56 +88,50 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
@action.bound
addDocument(doc: Document, allowDuplicates: boolean = false): boolean {
- let props = this.props;
- var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
+ var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
- if (this.isAnnotationOverlay) {
- doc.SetOnPrototype(KeyStore.Zoom, new NumberField(this.props.Document.GetNumber(KeyStore.Scale, 1)));
- }
if (curPage >= 0) {
- doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document);
+ doc.SetOnPrototype(KeyStore.AnnotationOn, this.props.Document);
}
- 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>());
- if (!this.createsCycle(doc, props.Document)) {
+ if (this.props.Document.Get(this.props.fieldKey) instanceof Field) {
+ const value = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ if (!this.createsCycle(doc, this.props.Document)) {
if (!value.some(v => v.Id === doc.Id) || allowDuplicates) {
value.push(doc);
+ doc.SetNumber(KeyStore.ZoomBasis, this.props.Document.GetNumber(KeyStore.Scale, 1));
}
- }
- else {
- return false;
- }
- } else {
- let proto = props.Document.GetPrototype();
- if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
- const field = new ListField([doc]);
- // const script = CompileScript(`
- // if(added) {
- // console.log("added " + field.Title + " " + doc.Title);
- // } else {
- // console.log("removed " + field.Title + " " + doc.Title);
- // }
- // `, {
- // addReturn: false,
- // params: {
- // field: Document.name,
- // added: "boolean"
- // },
- // capturedVariables: {
- // doc: this.props.Document
- // }
- // });
- // if (script.compiled) {
- // field.addScript(new ScriptField(script));
- // }
- props.Document.SetOnPrototype(props.fieldKey, field);
- }
- else {
- return false;
+ return true;
}
}
- return true;
+ // bcz: What is this code trying to do?
+ // else {
+ // let proto = props.Document.GetPrototype();
+ // if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
+ // const field = new ListField([doc]);
+ // // const script = CompileScript(`
+ // // if(added) {
+ // // console.log("added " + field.Title + " " + doc.Title);
+ // // } else {
+ // // console.log("removed " + field.Title + " " + doc.Title);
+ // // }
+ // // `, {
+ // // addReturn: false,
+ // // params: {
+ // // field: Document.name,
+ // // added: "boolean"
+ // // },
+ // // capturedVariables: {
+ // // doc: this.props.Document
+ // // }
+ // // });
+ // // if (script.compiled) {
+ // // field.addScript(new ScriptField(script));
+ // // }
+ // props.Document.SetOnPrototype(props.fieldKey, field);
+ // return true;
+ // }
+ // }
+ return false;
}
@action.bound
@@ -181,7 +179,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
removeDocument: this.removeDocument,
moveDocument: this.moveDocument,
active: this.active,
- onActiveChanged: this.onActiveChanged,
+ whenActiveChanged: this.whenActiveChanged,
};
const viewtype = this.collectionViewType;
return (
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 2b96e7678..e4c647635 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -19,6 +19,7 @@ import { ServerUtils } from "../../../server/ServerUtil";
import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
import { TextField } from "../../../fields/TextField";
import { ListField } from "../../../fields/ListField";
+import { Transform } from '../../util/Transform'
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -263,7 +264,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`);
tab.element.append(counter);
counter.DashDocId = tab.contentItem.config.props.documentId;
- (tab as any).reactionDisposer = reaction(() => [f.GetT(KeyStore.LinkedFromDocs, ListField), f.GetT(KeyStore.LinkedToDocs, ListField)],
+ tab.reactionDisposer = reaction(() => [f.GetT(KeyStore.LinkedFromDocs, ListField), f.GetT(KeyStore.LinkedToDocs, ListField)],
(lists) => {
let count = (lists.length > 0 && lists[0] && lists[0]!.Data ? lists[0]!.Data.length : 0) +
(lists.length > 1 && lists[1] && lists[1]!.Data ? lists[1]!.Data.length : 0);
@@ -315,7 +316,7 @@ interface DockedFrameProps {
@observer
export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
- private _mainCont = React.createRef<HTMLDivElement>();
+ _mainCont = React.createRef<HTMLDivElement>();
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Document>;
@@ -325,38 +326,49 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
}
- private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth);
- private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight);
- private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth);
+ nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth);
+ nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight);
+ contentScaling = () => {
+ let wscale = this._panelWidth / (this.nativeWidth() ? this.nativeWidth() : this._panelWidth);
+ if (wscale * this.nativeHeight() > this._panelHeight)
+ return this._panelHeight / (this.nativeHeight() ? this.nativeHeight() : this._panelHeight);
+ return wscale;
+ }
ScreenToLocalTransform = () => {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
- return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling());
+ if (this._mainCont.current && this._mainCont.current.children) {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement);
+ scale = Utils.GetScreenTransform(this._mainCont.current!).scale;
+ return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+ }
+ return Transform.Identity();
}
+ get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
- render() {
- if (!this._document) {
- return (null);
- }
- var content =
- <div className="collectionDockingView-content" ref={this._mainCont}>
- <DocumentView key={this._document.Id} Document={this._document}
+ get content() {
+ return (
+ <div className="collectionDockingView-content" ref={this._mainCont}
+ style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
+ <DocumentView key={this._document!.Id} Document={this._document!}
addDocument={undefined}
removeDocument={undefined}
- ContentScaling={this._contentScaling}
- PanelWidth={this._nativeWidth}
- PanelHeight={this._nativeHeight}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.nativeWidth}
+ PanelHeight={this.nativeHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
isTopMost={true}
selectOnLoad={false}
parentActive={returnTrue}
- onActiveChanged={emptyFunction}
+ whenActiveChanged={emptyFunction}
focus={emptyDocFunction}
ContainingCollectionView={undefined} />
- </div>;
+ </div>);
+ }
- return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
- {({ measureRef }) => <div ref={measureRef}> {content} </div>}
- </Measure>;
+ render() {
+ return !this._document ? (null) :
+ <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
+ {({ measureRef }) => <div ref={measureRef}> {this.content} </div>}
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 40e49bb5f..cfdb3ab22 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,56 +1,6 @@
@import "../globalCssVariables";
-//options menu styling
-#schemaOptionsMenuBtn {
- position: absolute;
- height: 20px;
- width: 20px;
- border-radius: 50%;
- z-index: 21;
- right: 4px;
- top: 4px;
- pointer-events: auto;
- background-color:black;
- display:inline-block;
- padding: 0px;
- font-size: 100%;
-}
-#schema-options-header {
- text-align: center;
- padding: 0px;
- margin: 0px;
-}
-.schema-options-subHeader {
- color: $intermediate-color;
- margin-bottom: 5px;
-}
-#schemaOptionsMenuBtn:hover {
- transform: scale(1.15);
-}
-
-#preview-schema-checkbox-div {
- margin-left: 20px;
- font-size: 12px;
-}
- #options-flyout-div {
- text-align: left;
- padding:0px;
- z-index: 100;
- font-family: $sans-serif;
- padding-left: 5px;
- }
-
- #schema-col-checklist {
- overflow: scroll;
- text-align: left;
- //background-color: $light-color-secondary;
- line-height: 25px;
- max-height: 175px;
- font-family: $sans-serif;
- font-size: 12px;
- }
-
.collectionSchemaView-container {
border-width: $COLLECTION_BORDER_WIDTH;
@@ -61,18 +11,31 @@
position: absolute;
width: 100%;
height: 100%;
-
- .collectionSchemaView-content {
- position: absolute;
- height: 100%;
- width: 100%;
- overflow: auto;
+
+ .collectionSchemaView-cellContents {
+ height: $MAX_ROW_HEIGHT;
}
+
.collectionSchemaView-previewRegion {
position: relative;
background: $light-color;
float: left;
height: 100%;
+ .collectionSchemaView-previewDoc {
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ }
+ .collectionSchemaView-input {
+ position: absolute;
+ max-width: 150px;
+ width: 100%;
+ bottom: 0px;
+ }
+ .documentView-node:first-child {
+ position: relative;
+ background: $light-color;
+ }
}
.collectionSchemaView-previewHandle {
position: absolute;
@@ -146,7 +109,7 @@
}
.rt-tr-group {
direction: ltr;
- max-height: 44px;
+ max-height: $MAX_ROW_HEIGHT;
}
.rt-td {
border-width: 1px;
@@ -178,7 +141,7 @@
}
.ReactTable .rt-th,
.ReactTable .rt-td {
- max-height: 44;
+ max-height: $MAX_ROW_HEIGHT;
padding: 3px 7px;
font-size: 13px;
text-align: center;
@@ -188,13 +151,71 @@
border-bottom-style: solid;
border-bottom-width: 1;
}
+ .documentView-node-topmost {
+ text-align:left;
+ transform-origin: center top;
+ display: inline-block;
+ }
.documentView-node:first-child {
background: $light-color;
- .imageBox-cont img {
- object-fit: contain;
- }
}
}
+//options menu styling
+#schemaOptionsMenuBtn {
+ position: absolute;
+ height: 20px;
+ width: 20px;
+ border-radius: 50%;
+ z-index: 21;
+ right: 4px;
+ top: 4px;
+ pointer-events: auto;
+ background-color:black;
+ display:inline-block;
+ padding: 0px;
+ font-size: 100%;
+}
+
+ul {
+ list-style-type: disc;
+}
+
+#schema-options-header {
+ text-align: center;
+ padding: 0px;
+ margin: 0px;
+}
+.schema-options-subHeader {
+ color: $intermediate-color;
+ margin-bottom: 5px;
+}
+#schemaOptionsMenuBtn:hover {
+ transform: scale(1.15);
+}
+
+#preview-schema-checkbox-div {
+ margin-left: 20px;
+ font-size: 12px;
+}
+
+ #options-flyout-div {
+ text-align: left;
+ padding:0px;
+ z-index: 100;
+ font-family: $sans-serif;
+ padding-left: 5px;
+ }
+
+ #schema-col-checklist {
+ overflow: scroll;
+ text-align: left;
+ //background-color: $light-color-secondary;
+ line-height: 25px;
+ max-height: 175px;
+ font-family: $sans-serif;
+ font-size: 12px;
+ }
+
.Resizer {
box-sizing: border-box;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4f4068e7a..90077b053 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -4,15 +4,15 @@ import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, untracked } from "mobx";
import { observer } from "mobx-react";
-import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
+import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss'
import "react-table/react-table.css";
import { Document } from "../../../fields/Document";
import { Field, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { emptyDocFunction, emptyFunction, returnFalse, returnOne } from "../../../Utils";
+import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils";
import { Server } from "../../Server";
import { SetupDrag } from "../../util/DragManager";
import { CompileScript, ToField } from "../../util/Scripting";
@@ -34,42 +34,35 @@ import { CollectionSubView } from "./CollectionSubView";
class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggle: (key: Key) => void }> {
@observable key: Key | undefined;
- componentWillReceiveProps() {
- Server.GetField(this.props.keyId, action((field: Opt<Field>) => {
- if (field instanceof Key) {
- this.key = field;
- }
- }));
+ constructor(props: any) {
+ super(props);
+ Server.GetField(this.props.keyId, action((field: Opt<Field>) => field instanceof Key && (this.key = field)));
}
render() {
- if (this.key) {
- return (<div key={this.key.Id}>
+ return !this.key ? (null) :
+ (<div key={this.key.Id}>
<input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} />
{this.key.Name}
</div>);
- }
- return (null);
}
}
@observer
export class CollectionSchemaView extends CollectionSubView {
- private _mainCont = React.createRef<HTMLDivElement>();
+ private _mainCont?: HTMLDivElement;
private _startSplitPercent = 0;
private DIVIDER_WIDTH = 4;
@observable _columns: Array<Key> = [KeyStore.Title, KeyStore.Data, KeyStore.Author];
- @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 _columnsPercentage = 0;
@observable _keys: Key[] = [];
+ @observable _newKeyName: string = "";
@computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); }
-
+ @computed get columns() { return this.props.Document.GetList(KeyStore.ColumnsKey, [] as Key[]); }
+ @computed get borderWidth() { return COLLECTION_BORDER_WIDTH; }
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
@@ -83,7 +76,7 @@ export class CollectionSchemaView extends CollectionSubView {
ScreenToLocalTransform: Transform.Identity,
focus: emptyDocFunction,
active: returnFalse,
- onActiveChanged: emptyFunction,
+ whenActiveChanged: emptyFunction,
};
let contents = (
<FieldView {...props} />
@@ -107,11 +100,11 @@ export class CollectionSchemaView extends CollectionSubView {
return false;
};
return (
- <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
+ <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document.Id} ref={reference}>
<EditableView
display={"inline"}
contents={contents}
- height={56}
+ height={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
let field = props.Document.Get(props.fieldKey);
if (field && field instanceof Field) {
@@ -165,9 +158,9 @@ export class CollectionSchemaView extends CollectionSubView {
};
}
- @computed
- get columns() {
- return this.props.Document.GetList<Key>(KeyStore.ColumnsKey, []);
+ private createTarget = (ele: HTMLDivElement) => {
+ this._mainCont = ele;
+ super.CreateDropTarget(ele);
}
@action
@@ -187,31 +180,12 @@ export class CollectionSchemaView extends CollectionSubView {
//toggles preview side-panel of schema
@action
toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => {
- this._startSplitPercent = this.splitPercentage;
- if (this._startSplitPercent === this.splitPercentage) {
- this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
- }
- }
-
- @computed
- get findAllDocumentKeys(): { [id: string]: boolean } {
- const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []);
- let keys: { [id: string]: boolean } = {};
- if (this._optionsActivated > -1) {
- // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
- // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
- // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
- // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
- // is displayed (unlikely) it won't show up until something else changes.
- untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
- }
- this.columns.forEach(key => keys[key.Id] = true);
- return keys;
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
}
@action
onDividerMove = (e: PointerEvent): void => {
- let nativeWidth = this._mainCont.current!.getBoundingClientRect();
+ let nativeWidth = this._mainCont!.getBoundingClientRect();
this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));
}
@action
@@ -230,155 +204,149 @@ export class CollectionSchemaView extends CollectionSubView {
document.addEventListener('pointerup', this.onDividerUp);
}
- @observable _tableWidth = 0;
- @action
- setTableDimensions = (r: any) => {
- this._tableWidth = r.entry.width;
- }
- @action
- setScaling = (r: any) => {
- const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
- const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
- this._panelWidth = r.entry.width;
- this._panelHeight = r.entry.height ? r.entry.height : this._panelHeight;
- this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width);
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (this.props.isSelected())
+ e.stopPropagation();
+ else e.preventDefault();
+ }
}
- @computed
- get borderWidth() { return COLLECTION_BORDER_WIDTH; }
- getContentScaling = (): number => this._contentScaling;
- getPanelWidth = (): number => this._panelWidth;
- getPanelHeight = (): number => this._panelHeight;
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling);
- getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling);
-
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ onWheel = (e: React.WheelEvent): void => {
+ if (this.props.active()) {
e.stopPropagation();
}
}
@action
addColumn = () => {
- this.columns.push(new Key(this.newKeyName));
- this.newKeyName = "";
+ this.columns.push(new Key(this._newKeyName));
+ this._newKeyName = "";
}
- @observable
- newKeyName: string = "";
-
@action
newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.newKeyName = e.currentTarget.value;
- }
- onWheel = (e: React.WheelEvent): void => {
- if (this.props.active()) {
- e.stopPropagation();
- }
- }
-
- @observable _optionsActivated: number = 0;
- @action
- OptionsMenuDown = (e: React.PointerEvent) => {
- this._optionsActivated++;
+ this._newKeyName = e.currentTarget.value;
}
- @observable previewScript: string = "this";
+ @observable previewScript: string = "";
@action
onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.previewScript = e.currentTarget.value;
}
- render() {
- library.add(faCog);
- library.add(faPlus);
- const columns = this.columns;
- const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
+ get previewDocument(): Document | undefined {
+ const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
- //all the keys/columns that will be displayed in the schema
- const allKeys = this.findAllDocumentKeys;
- let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined;
+ return selected ? (this.previewScript ? selected.Get(new Key(this.previewScript)) as Document : selected) : undefined;
+ }
+ get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }
+ get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }
+ get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; }
+
+ private previewDocNativeWidth = () => this.previewDocument!.GetNumber(KeyStore.NativeWidth, this.previewRegionWidth);
+ private previewDocNativeHeight = () => this.previewDocument!.GetNumber(KeyStore.NativeHeight, this.previewRegionHeight);
+ private previewContentScaling = () => {
+ let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth);
+ if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight)
+ return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight);
+ return wscale;
+ }
+ private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling();
+ private previewPanelHeight = () => this.previewDocNativeHeight() * this.previewContentScaling();
+ get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; }
+ getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
+ - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset,
+ - this.borderWidth).scale(1 / this.previewContentScaling());
+ @computed
+ get previewPanel() {
// let doc = CompileScript(this.previewScript, { this: selected }, true)();
- let content = this._selectedIndex === -1 || !selected ? (null) : (
- <Measure onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="collectionSchemaView-content" ref={measureRef}>
- {doc instanceof Document ?
- <DocumentView Document={doc}
- addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
- isTopMost={false}
- selectOnLoad={false}
- ScreenToLocalTransform={this.getPreviewTransform}
- ContentScaling={this.getContentScaling}
- PanelWidth={this.getPanelWidth}
- PanelHeight={this.getPanelHeight}
- ContainingCollectionView={this.props.CollectionView}
- focus={emptyDocFunction}
- parentActive={this.props.active}
- onActiveChanged={this.props.onActiveChanged} /> : null}
- <input value={this.previewScript} onChange={this.onPreviewScriptChange}
- style={{ position: 'absolute', bottom: '0px' }} />
- </div>
- }
- </Measure>
- );
- let dividerDragger = this.splitPercentage === 0 ? (null) :
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
-
- //options button and menu
- let optionsMenu = !this.props.active() ? (null) : (<Flyout
- anchorPoint={anchorPoints.LEFT_TOP}
- content={<div>
- <div id="schema-options-header"><h5><b>Options</b></h5></div>
- <div id="options-flyout-div">
- <h6 className="schema-options-subHeader">Preview Window</h6>
- <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div>
- <h6 className="schema-options-subHeader" >Displayed Columns</h6>
- <ul id="schema-col-checklist" >
- {Array.from(Object.keys(allKeys)).map(item =>
- (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />))}
- </ul>
- <input value={this.newKeyName} onChange={this.newKeyChange} />
- <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
+ return !this.previewDocument ? (null) : (
+ <div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>
+ <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
+ <DocumentView Document={this.previewDocument} isTopMost={false} selectOnLoad={false}
+ addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ ContentScaling={this.previewContentScaling}
+ PanelWidth={this.previewPanelWidth} PanelHeight={this.previewPanelHeight}
+ ContainingCollectionView={this.props.CollectionView}
+ focus={emptyDocFunction}
+ parentActive={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ />
</div>
+ <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
+ style={{ left: `calc(50% - ${Math.min(75, this.previewPanelWidth() / 2)}px)` }} />
</div>
- }>
- <button id="schemaOptionsMenuBtn" onPointerDown={this.OptionsMenuDown}><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
- </Flyout>);
+ );
+ }
- return (
- <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont}>
- <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
- <Measure onResize={this.setTableDimensions}>
- {({ measureRef }) =>
- <div className="collectionSchemaView-tableContainer" ref={measureRef} style={{ width: `calc(100% - ${this.splitPercentage}%)` }}>
- <ReactTable
- data={children}
- pageSize={children.length}
- page={0}
- showPagination={false}
- columns={columns.map(col => ({
- Header: col.Name,
- accessor: (doc: Document) => [doc, col],
- id: col.Id
- }))}
- column={{
- ...ReactTableDefaults.column,
- Cell: this.renderCell,
+ get documentKeysCheckList() {
+ const docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ let keys: { [id: string]: boolean } = {};
+ // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+ // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
+ // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
+ // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
+ // is displayed (unlikely) it won't show up until something else changes.
+ untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
+
+ this.columns.forEach(key => keys[key.Id] = true);
+ return Array.from(Object.keys(keys)).map(item =>
+ (<KeyToggle checked={keys[item]} key={item} keyId={item} toggle={this.toggleKey} />));
+ }
- }}
- getTrProps={this.getTrProps}
- />
- </div>}
- </Measure>
- {dividerDragger}
- <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}>
- {content}
+ get tableOptionsPanel() {
+ return !this.props.active() ? (null) :
+ (<Flyout
+ anchorPoint={anchorPoints.LEFT_TOP}
+ content={<div>
+ <div id="schema-options-header"><h5><b>Options</b></h5></div>
+ <div id="options-flyout-div">
+ <h6 className="schema-options-subHeader">Preview Window</h6>
+ <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div>
+ <h6 className="schema-options-subHeader" >Displayed Columns</h6>
+ <ul id="schema-col-checklist" >
+ {this.documentKeysCheckList}
+ </ul>
+ <input value={this._newKeyName} onChange={this.newKeyChange} />
+ <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
</div>
- {optionsMenu}
</div>
- </div >
+ }>
+ <button id="schemaOptionsMenuBtn" ><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
+ </Flyout>);
+ }
+
+ @computed
+ get dividerDragger() {
+ return this.splitPercentage === 0 ? (null) :
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
+ }
+
+ render() {
+ library.add(faCog);
+ library.add(faPlus);
+ const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ return (
+ <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
+ <div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}>
+ <ReactTable data={children} page={0} pageSize={children.length} showPagination={false}
+ columns={this.columns.map(col => ({
+ Header: col.Name,
+ accessor: (doc: Document) => [doc, col],
+ id: col.Id
+ }))}
+ column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
+ getTrProps={this.getTrProps}
+ />
+ </div>
+ {this.dividerDragger}
+ {this.previewPanel}
+ {this.tableOptionsPanel}
+ </div>
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d3d69b1af..ead559bd9 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -15,7 +15,6 @@ import { ServerUtils } from "../../../server/ServerUtil";
import { Server } from "../../Server";
import { FieldViewProps } from "../nodes/FieldView";
import * as rp from 'request-promise';
-import { emptyFunction } from "../../../Utils";
import { CollectionView } from "./CollectionView";
import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
@@ -24,6 +23,8 @@ export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
removeDocument: (document: Document) => boolean;
moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
}
export interface SubCollectionViewProps extends CollectionViewProps {
@@ -42,6 +43,9 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
}
}
+ protected CreateDropTarget(ele: HTMLDivElement) {
+ this.createDropTarget(ele);
+ }
@action
protected setCursorPosition(position: [number, number]) {
@@ -155,10 +159,7 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
e.preventDefault();
if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
- console.log("not good");
- let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
- htmlDoc.SetText(KeyStore.DocumentText, text);
- this.props.addDocument(htmlDoc, false);
+ this.props.addDocument(Documents.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text }), false);
return;
}
@@ -184,36 +185,17 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
let type = item.type;
if (item.kind === "file") {
let file = item.getAsFile();
- let formData = new FormData();
-
- if (file) {
- formData.append('file', file);
- }
let dropFileName = file ? file.name : "-empty-";
+ let formData = new FormData();
+ if (file) formData.append('file', file);
- let prom = fetch(upload, {
+ promises.push(fetch(upload, {
method: 'POST',
body: formData
- }).then(async (res: Response) => {
- (await res.json()).map(action((file: any) => {
- let path = window.location.origin + file;
- let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
-
- docPromise.then(action((doc?: Document) => {
- let docs = this.props.Document.GetT(KeyStore.Data, ListField);
- if (docs !== FieldWaiting) {
- if (!docs) {
- docs = new ListField<Document>();
- this.props.Document.Set(KeyStore.Data, docs);
- }
- if (doc) {
- docs.Data.push(doc);
- }
- }
- }));
- }));
- });
- promises.push(prom);
+ }).then(async (res: Response) =>
+ (await res.json()).map(action((file: any) =>
+ this.getDocumentFromType(type, window.location.origin + file, { ...options, nativeWidth: 600, width: 300, title: dropFileName }).
+ then(doc => doc && this.props.addDocument(doc, false))))));
}
}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 973eead97..8ecc5b67b 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -50,15 +50,15 @@
.docContainer:hover {
.delete-button {
display: inline;
- width: auto;
+ // width: auto;
}
}
.delete-button {
color: $intermediate-color;
- float: right;
+ // float: right;
margin-left: 15px;
- margin-top: 3px;
+ // margin-top: 3px;
display: inline;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 51a02fc25..e0387f4b4 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,20 +1,17 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, trace } from "mobx";
+import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { FieldWaiting } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { SetupDrag, DragManager } from "../../util/DragManager";
+import { DragManager, SetupDrag } from "../../util/DragManager";
import { EditableView } from "../EditableView";
-import "./CollectionTreeView.scss";
-import { CollectionView } from "./CollectionView";
-import * as globalCssVariables from "../../views/globalCssVariables.scss";
import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionTreeView.scss";
import React = require("react");
-import { props } from 'bluebird';
export interface TreeViewProps {
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 1f69a69e8..675e720e2 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -10,6 +10,7 @@ import { CurrentUserUtils } from '../../../server/authentication/models/current_
import { KeyStore } from '../../../fields/KeyStore';
import { observer } from 'mobx-react';
import { undoBatch } from '../../util/UndoManager';
+import { trace } from 'mobx';
@observer
export class CollectionView extends React.Component<FieldViewProps> {
@@ -28,8 +29,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
return (null);
}
+ get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+
onContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // 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: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) });
ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) });
ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 081b3eb6c..20c5a84bf 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -23,10 +23,10 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let l = this.props.LinkDocs;
let a = this.props.A;
let b = this.props.B;
- let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Width, 0) / 2);
- let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.GetNumber(KeyStore.Height, 0) / 2);
- let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Width, 0) / 2);
- let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.GetNumber(KeyStore.Height, 0) / 2);
+ let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Width() / 2);
+ let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Height() / 2);
+ let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Width() / 2);
+ let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Height() / 2);
return (
<line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
style={{ strokeWidth: `${l.length * 5}` }}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index 2f684a54e..ebdb0c75c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -16,9 +16,9 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
_brushReactionDisposer?: IReactionDisposer;
componentDidMount() {
- this._brushReactionDisposer = reaction(() => this.props.Document.GetList<Document>(this.props.fieldKey, []).map(doc => doc.GetNumber(KeyStore.X, 0)),
+ this._brushReactionDisposer = reaction(() => this.props.Document.GetList(this.props.fieldKey, [] as Document[]).map(doc => doc.GetNumber(KeyStore.X, 0)),
() => {
- let views = this.props.Document.GetList<Document>(this.props.fieldKey, []);
+ let views = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc.GetText(KeyStore.BackgroundLayout, "").indexOf("istogram") !== -1);
for (let i = 0; i < views.length; i++) {
for (let j = 0; j < views.length; j++) {
let srcDoc = views[j];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 751ea8190..cf0a6de00 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -12,7 +12,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
protected getCursors(): CursorEntry[] {
let doc = this.props.Document;
let id = CurrentUserUtils.id;
- let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []);
+ let cursors = doc.GetList(KeyStore.Cursors, [] as CursorEntry[]);
let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
return id ? notMe : [];
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 26c794e91..57706b51e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,11 +1,4 @@
@import "../../globalCssVariables";
-.collectionfreeformview-measure {
- position: inherit;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
.collectionfreeformview {
position: inherit;
top: 0;
@@ -48,10 +41,11 @@
}
.formattedTextBox-cont {
background: $light-color-secondary;
+ overflow: visible;
}
opacity: 0.99;
- border-width: 0;
+ border-width: 0;
border-color: transparent;
border-style: solid;
border-radius: $border-radius;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 4db3bf81b..83b7f9be4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,10 +1,7 @@
-import { action, computed, observable, runInAction, untracked } from "mobx";
+import { action, computed, observable, trace } from "mobx";
import { observer } from "mobx-react";
-import Measure from "react-measure";
import { Document } from "../../../../fields/Document";
-import { FieldWaiting } from "../../../../fields/Field";
import { KeyStore } from "../../../../fields/KeyStore";
-import { TextField } from "../../../../fields/TextField";
import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
@@ -13,43 +10,52 @@ import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
import { InkingCanvas } from "../../InkingCanvas";
-import { Main } from "../../Main";
-import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewProps } from "../../nodes/CollectionFreeFormDocumentView";
+import { MainOverlayTextBox } from "../../MainOverlayTextBox";
+import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { DocumentViewProps } from "../../nodes/DocumentView";
-import { CollectionSubView, SubCollectionViewProps } from "../CollectionSubView";
+import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
-import { MainOverlayTextBox } from "../../MainOverlayTextBox";
+import { BooleanField } from "../../../../fields/BooleanField";
@observer
export class CollectionFreeFormView extends CollectionSubView {
- public _canvasRef = React.createRef<HTMLDivElement>();
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
+ private _lastX: number = 0;
+ private _lastY: number = 0;
+ private get _pwidth() { return this.props.PanelWidth(); }
+ private get _pheight() { return this.props.PanelHeight(); }
- 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 and receive text input
- this._selectOnLoaded = newBox.Id;
+ @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+ private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
+ private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+ private childViews = () => this.views;
+ private panX = () => this.props.Document.GetNumber(KeyStore.PanX, 0);
+ private panY = () => this.props.Document.GetNumber(KeyStore.PanY, 0);
+ private zoomScaling = () => this.props.Document.GetNumber(KeyStore.Scale, 1);
+ private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0; // shift so pan position is at center of window for non-overlay collections
+ private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
+ private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+ private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
+ private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
+ private addLiveTextBox = (newBox: Document) => {
+ this._selectOnLoaded = newBox.Id;// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newBox, false);
}
-
- public addDocument = (newBox: Document, allowDuplicates: boolean) => {
- if (this.isAnnotationOverlay) {
- newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));
- }
+ private addDocument = (newBox: Document, allowDuplicates: boolean) => {
return this.props.addDocument(this.bringToFront(newBox), false);
}
-
- public selectDocuments = (docs: Document[]) => {
+ private selectDocuments = (docs: Document[]) => {
SelectionManager.DeselectAll;
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
SelectionManager.SelectDoc(dv!, true));
}
-
public getActiveDocuments = () => {
var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
return this.props.Document.GetList(this.props.fieldKey, [] as Document[]).reduce((active, doc) => {
@@ -61,42 +67,35 @@ export class CollectionFreeFormView extends CollectionSubView {
}, [] as Document[]);
}
- @observable public DownX: number = 0;
- @observable public DownY: number = 0;
- @observable private _lastX: number = 0;
- @observable private _lastY: number = 0;
- @observable private _pwidth: number = 0;
- @observable private _pheight: 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); }
- @computed get scale(): number { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
- @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
- @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
- @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
- @computed get zoomScaling() { return this.props.Document.GetNumber(KeyStore.Scale, 1); }
- @computed get centeringShiftX() { return !this.props.Document.GetNumber(KeyStore.NativeWidth, 0) ? this._pwidth / 2 : 0; } // shift so pan position is at center of window for non-overlay collections
- @computed get centeringShiftY() { return !this.props.Document.GetNumber(KeyStore.NativeHeight, 0) ? this._pheight / 2 : 0; }// shift so pan position is at center of window for non-overlay collections
-
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
- const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);
if (de.data.droppedDocuments.length) {
- let dropX = de.data.droppedDocuments[0].GetNumber(KeyStore.X, 0);
- let dropY = de.data.droppedDocuments[0].GetNumber(KeyStore.Y, 0);
+ let dragDoc = de.data.droppedDocuments[0];
+ let zoom = dragDoc.GetNumber(KeyStore.ZoomBasis, 1);
+ let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ let x = xp - de.data.xOffset / zoom;
+ let y = yp - de.data.yOffset / zoom;
+
+ let dropX = dragDoc.GetNumber(KeyStore.X, 0);
+ let dropY = dragDoc.GetNumber(KeyStore.Y, 0);
de.data.droppedDocuments.map(d => {
- d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0) - dropX));
- d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0) - dropY));
- if (!d.GetNumber(KeyStore.Width, 0)) {
- d.SetNumber(KeyStore.Width, 300);
- }
- if (!d.GetNumber(KeyStore.Height, 0)) {
- d.SetNumber(KeyStore.Height, 300);
+ d.SetNumber(KeyStore.X, x + (d.GetNumber(KeyStore.X, 0)) - dropX);
+ d.SetNumber(KeyStore.Y, y + (d.GetNumber(KeyStore.Y, 0)) - dropY);
+ if (!d.GetBoolean(KeyStore.IsMinimized, false)) {
+ if (!d.GetNumber(KeyStore.Width, 0)) {
+ d.SetNumber(KeyStore.Width, 300);
+ }
+ if (!d.GetNumber(KeyStore.Height, 0)) {
+ let nw = d.GetNumber(KeyStore.NativeWidth, 0);
+ let nh = d.GetNumber(KeyStore.NativeHeight, 0);
+ d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300);
+ }
}
this.bringToFront(d);
});
+ SelectionManager.ReselectAll();
}
return true;
}
@@ -111,13 +110,17 @@ export class CollectionFreeFormView extends CollectionSubView {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling !== 1)) || e.button === 0) && this.props.active()) {
+ let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => {
+ var dv = DocumentManager.Instance.getDocumentView(doc);
+ return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false);
+ }, false);
+ if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) || (e.button === 0 && e.altKey)) && (childSelected || this.props.active())) {
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
- this._lastX = this.DownX = e.pageX;
- this._lastY = this.DownY = e.pageY;
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
}
}
@@ -130,38 +133,36 @@ export class CollectionFreeFormView extends CollectionSubView {
@action
onPointerMove = (e: PointerEvent): void => {
- if (!e.cancelBubble && this.props.active()) {
- 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 docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
- let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
- if (!this.isAnnotationOverlay) {
- let minx = docs.length ? docs[0].GetNumber(KeyStore.X, 0) : 0;
- let maxx = docs.length ? docs[0].GetNumber(KeyStore.Width, 0) + minx : minx;
- let miny = docs.length ? docs[0].GetNumber(KeyStore.Y, 0) : 0;
- let maxy = docs.length ? docs[0].GetNumber(KeyStore.Height, 0) + miny : miny;
- let ranges = docs.filter(doc => doc).reduce((range, doc) => {
- let x = doc.GetNumber(KeyStore.X, 0);
- let xe = x + doc.GetNumber(KeyStore.Width, 0);
- let y = doc.GetNumber(KeyStore.Y, 0);
- let ye = y + doc.GetNumber(KeyStore.Height, 0);
- return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
- [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
- }, [[minx, maxx], [miny, maxy]]);
- let panelwidth = this._pwidth / this.scale / 2;
- let panelheight = this._pheight / this.scale / 2;
- if (x - dx < ranges[0][0] - panelwidth) x = ranges[0][1] + panelwidth + dx;
- if (x - dx > ranges[0][1] + panelwidth) x = ranges[0][0] - panelwidth + dx;
- if (y - dy < ranges[1][0] - panelheight) y = ranges[1][1] + panelheight + dy;
- if (y - dy > ranges[1][1] + panelheight) y = ranges[1][0] - panelheight + dy;
- }
- this.SetPan(x - dx, y - dy);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
- e.stopPropagation();
- e.preventDefault();
+ if (!e.cancelBubble) {
+ let x = this.props.Document.GetNumber(KeyStore.PanX, 0);
+ let y = this.props.Document.GetNumber(KeyStore.PanY, 0);
+ let docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ if (!this.isAnnotationOverlay) {
+ let minx = docs.length ? docs[0].GetNumber(KeyStore.X, 0) : 0;
+ let maxx = docs.length ? docs[0].Width() + minx : minx;
+ let miny = docs.length ? docs[0].GetNumber(KeyStore.Y, 0) : 0;
+ let maxy = docs.length ? docs[0].Height() + miny : miny;
+ let ranges = docs.filter(doc => doc).reduce((range, doc) => {
+ let x = doc.GetNumber(KeyStore.X, 0);
+ let xe = x + doc.GetNumber(KeyStore.Width, 0);
+ let y = doc.GetNumber(KeyStore.Y, 0);
+ let ye = y + doc.GetNumber(KeyStore.Height, 0);
+ return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
+ [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
+ }, [[minx, maxx], [miny, maxy]]);
+ let panelwidth = this._pwidth / this.zoomScaling() / 2;
+ let panelheight = this._pheight / this.zoomScaling() / 2;
+ if (x - dx < ranges[0][0] - panelwidth) x = ranges[0][1] + panelwidth + dx;
+ if (x - dx > ranges[0][1] + panelwidth) x = ranges[0][0] - panelwidth + dx;
+ if (y - dy < ranges[1][0] - panelheight) y = ranges[1][1] + panelheight + dy;
+ if (y - dy > ranges[1][1] + panelheight) y = ranges[1][0] - panelheight + dy;
}
+ this.setPan(x - dx, y - dy);
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ e.stopPropagation();
+ e.preventDefault();
}
}
@@ -170,45 +171,45 @@ export class CollectionFreeFormView extends CollectionSubView {
// if (!this.props.active()) {
// return;
// }
+ let childSelected = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((childSelected, doc) => {
+ var dv = DocumentManager.Instance.getDocumentView(doc);
+ return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false);
+ }, false);
+ if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {
+ return;
+ }
e.stopPropagation();
- let coefficient = 1000;
+ const coefficient = 1000;
if (e.ctrlKey) {
- var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
- var nativeHeight = this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
- const coefficient = 1000;
let deltaScale = (1 - (e.deltaY / coefficient));
- this.props.Document.SetNumber(KeyStore.NativeWidth, nativeWidth * deltaScale);
- this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight * deltaScale);
+ this.props.Document.SetNumber(KeyStore.NativeWidth, this.nativeWidth * deltaScale);
+ this.props.Document.SetNumber(KeyStore.NativeHeight, this.nativeHeight * deltaScale);
e.stopPropagation();
e.preventDefault();
} else {
// if (modes[e.deltaMode] === 'pixels') coefficient = 50;
// else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
- let transform = this.getTransform();
-
let deltaScale = (1 - (e.deltaY / coefficient));
- if (deltaScale * this.zoomScaling < 1 && this.isAnnotationOverlay) {
- deltaScale = 1 / this.zoomScaling;
+ if (deltaScale < 0) deltaScale = -deltaScale;
+ if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
+ deltaScale = 1 / this.zoomScaling();
}
- let [x, y] = transform.transformPoint(e.clientX, e.clientY);
-
- let localTransform = this.getLocalTransform();
- localTransform = localTransform.inverse().scaleAbout(deltaScale, x, y);
- // console.log(localTransform)
+ let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
+ let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
- this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
- this.SetPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
+ let safeScale = Math.abs(localTransform.Scale);
+ this.props.Document.SetNumber(KeyStore.Scale, Math.abs(safeScale));
+ this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
e.stopPropagation();
}
}
@action
- private SetPan(panX: number, panY: number) {
- MainOverlayTextBox.Instance.SetTextDoc();
- var x1 = this.getLocalTransform().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));
+ setPan(panX: number, panY: number) {
+ var scale = this.getLocalTransform().inverse().Scale;
+ const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
+ const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
this.props.Document.SetNumber(KeyStore.PanX, this.isAnnotationOverlay ? newPanX : panX);
this.props.Document.SetNumber(KeyStore.PanY, this.isAnnotationOverlay ? newPanY : panY);
}
@@ -224,39 +225,18 @@ export class CollectionFreeFormView extends CollectionSubView {
@action
bringToFront(doc: Document) {
- const { fieldKey: fieldKey, Document: Document } = this.props;
-
- const value: Document[] = Document.GetList<Document>(fieldKey, []).slice();
- value.sort((doc1, doc2) => {
- if (doc1 === doc) {
- return 1;
- }
- if (doc2 === doc) {
- return -1;
- }
+ this.props.Document.GetList(this.props.fieldKey, [] as Document[]).slice().sort((doc1, doc2) => {
+ if (doc1 === doc) return 1;
+ if (doc2 === doc) return -1;
return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
- }).map((doc, index) =>
- doc.SetNumber(KeyStore.ZIndex, index + 1));
+ }).map((doc, index) => doc.SetNumber(KeyStore.ZIndex, index + 1));
return doc;
}
- @computed get backgroundLayout(): string | undefined {
- let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField);
- if (field && field !== FieldWaiting) {
- return field.Data;
- }
- }
- @computed get overlayLayout(): string | undefined {
- let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField);
- if (field && field !== FieldWaiting) {
- return field.Data;
- }
- }
-
focusDocument = (doc: Document) => {
- let x = doc.GetNumber(KeyStore.X, 0) + doc.GetNumber(KeyStore.Width, 0) / 2;
- let y = doc.GetNumber(KeyStore.Y, 0) + doc.GetNumber(KeyStore.Height, 0) / 2;
- this.SetPan(x, y);
+ this.setPan(
+ doc.GetNumber(KeyStore.X, 0) + doc.Width() / 2,
+ doc.GetNumber(KeyStore.Y, 0) + doc.Height() / 2);
this.props.focus(this.props.Document);
}
@@ -271,11 +251,11 @@ export class CollectionFreeFormView extends CollectionSubView {
selectOnLoad: document.Id === this._selectOnLoaded,
PanelWidth: document.Width,
PanelHeight: document.Height,
- ContentScaling: this.noScaling,
+ ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
focus: this.focusDocument,
parentActive: this.props.active,
- onActiveChanged: this.props.active,
+ whenActiveChanged: this.props.active,
};
}
@@ -284,79 +264,93 @@ export class CollectionFreeFormView extends CollectionSubView {
var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
var page = doc.GetNumber(KeyStore.Page, -1);
- var zoom = doc.GetNumber(KeyStore.Zoom, 1);
- var dv = DocumentManager.Instance.getDocumentView(doc);
- let zoomFade = !this.isAnnotationOverlay || (dv && SelectionManager.IsSelected(dv)) ? 1 :
- Math.max(0, 2 - (zoom < this.scale ? this.scale / zoom : zoom / this.scale));
if (page === curPage || page === -1) {
- prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} zoomFade={zoomFade} />);
+ let minim = doc.GetT(KeyStore.IsMinimized, BooleanField);
+ if (minim === undefined || (minim && !minim.Data))
+ prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
}
return prev;
}, [] as JSX.Element[]);
- setTimeout(() => { // bcz: surely there must be a better way ....
- this._selectOnLoaded = "";
- }, 600);
+ setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way ....
return docviews;
}
- @computed
- get backgroundView() {
- return !this.backgroundLayout ? (null) :
- (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
- layoutKey={KeyStore.BackgroundLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+ @action
+ onCursorMove = (e: React.PointerEvent) => {
+ super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
- @computed
- get overlayView() {
- return !this.overlayLayout ? (null) :
- (<DocumentContentsView {...this.getDocumentViewProps(this.props.Document)}
- layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+
+ render() {
+ const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
+ return (
+ <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
+ <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+ addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
+ getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
+ <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
+ zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ <CollectionFreeFormBackgroundView {...this.getDocumentViewProps(this.props.Document)} />
+ <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+ {this.childViews}
+ </InkingCanvas>
+ </CollectionFreeFormLinksView>
+ <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+ </CollectionFreeFormViewPannableContents>
+ <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} />
+ </MarqueeView>
+ </div>
+ );
}
+}
- @computed
- get borderWidth() {
- return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH;
+@observer
+class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {
+ @computed get overlayView() {
+ let overlayLayout = this.props.Document.GetText(KeyStore.OverlayLayout, "");
+ return !overlayLayout ? (null) :
+ (<DocumentContentsView {...this.props} layoutKey={KeyStore.OverlayLayout}
+ isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+ }
+ render() {
+ return this.overlayView;
}
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform());
- getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
- getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY);
- noScaling = () => 1;
- childViews = () => this.views;
+}
+@observer
+class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps> {
+ @computed get backgroundView() {
+ let backgroundLayout = this.props.Document.GetText(KeyStore.BackgroundLayout, "");
+ return !backgroundLayout ? (null) :
+ (<DocumentContentsView {...this.props} layoutKey={KeyStore.BackgroundLayout}
+ isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+ }
render() {
- const [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
- const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
- const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
- const zoom: number = this.zoomScaling;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
- const backgroundView = this.backgroundView; // needs to be a variable outside of the <Measure> otherwise, reactions won't fire
- const overlayView = this.overlayView;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
+ return this.backgroundView;
+ }
+}
- return (
- <Measure onResize={(r: any) => runInAction(() => { this._pwidth = r.entry.width; this._pheight = r.entry.height; })}>
- {({ measureRef }) => (
- <div className={`collectionfreeformview-measure`} ref={measureRef}>
- <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
- onPointerDown={this.onPointerDown} onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
- onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} ref={this.createDropTarget}>
- <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
- addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
- <div className="collectionfreeformview" ref={this._canvasRef}
- style={{ transform: `translate(${dx}px, ${dy}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
- {backgroundView}
- <CollectionFreeFormLinksView {...this.props}>
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
- {this.childViews}
- </InkingCanvas>
- </CollectionFreeFormLinksView>
- <CollectionFreeFormRemoteCursors {...this.props} />
- </div>
- {overlayView}
- </MarqueeView>
- </div>
- </div>)}
- </Measure>
- );
+interface CollectionFreeFormViewPannableContentsProps {
+ centeringShiftX: () => number;
+ centeringShiftY: () => number;
+ panX: () => number;
+ panY: () => number;
+ zoomScaling: () => number;
+}
+
+@observer
+class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
+ render() {
+ const cenx = this.props.centeringShiftX();
+ const ceny = this.props.centeringShiftY();
+ const panx = -this.props.panX();
+ const pany = -this.props.panY();
+ const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
+ return <div className="collectionfreeformview" style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+ {this.props.children}
+ </div>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index dbab790d4..318adbe85 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -11,6 +11,7 @@ import { undoBatch } from "../../../util/UndoManager";
import { InkingCanvas } from "../../InkingCanvas";
import { PreviewCursor } from "../../PreviewCursor";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
+import { MINIMIZED_ICON_SIZE } from '../../../views/globalCssVariables.scss'
import "./MarqueeView.scss";
import React = require("react");
@@ -147,26 +148,24 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
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, -1);
- d.SetText(KeyStore.Title, "" + d.GetNumber(KeyStore.Width, 0) + " " + d.GetNumber(KeyStore.Height, 0));
return d;
});
let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined;
- //setTimeout(() => {
+ let zoomBasis = this.props.container.props.Document.GetNumber(KeyStore.Scale, 1);
let newCollection = Documents.FreeformDocument(selected, {
x: bounds.left,
y: bounds.top,
panx: 0,
pany: 0,
- width: bounds.width,
- height: bounds.height,
- backgroundColor: "Transparent",
+ scale: zoomBasis,
+ width: bounds.width * zoomBasis,
+ height: bounds.height * zoomBasis,
ink: inkData ? this.marqueeInkSelect(inkData) : undefined,
title: "a nested collection"
});
this.props.addDocument(newCollection, false);
this.marqueeInkDelete(inkData);
- // }, 100);
this.cleanupInteractions();
SelectionManager.DeselectAll();
}
@@ -209,11 +208,11 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let selRect = this.Bounds;
let selection: Document[] = [];
this.props.activeDocuments().map(doc => {
- var z = doc.GetNumber(KeyStore.Zoom, 1);
+ var z = doc.GetNumber(KeyStore.ZoomBasis, 1);
var x = doc.GetNumber(KeyStore.X, 0);
var y = doc.GetNumber(KeyStore.Y, 0);
- var w = doc.GetNumber(KeyStore.Width, 0) / z;
- var h = doc.GetNumber(KeyStore.Height, 0) / z;
+ var w = doc.Width() / z;
+ var h = doc.Height() / z;
if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) {
selection.push(doc);
}
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 5c8e9c8fc..4f68b71b0 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -23,7 +23,11 @@ $mainTextInput-zindex: 999; // then text input overlay so that it's context menu
$docDecorations-zindex: 998; // then doc decorations appear over everything else
$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
$COLLECTION_BORDER_WIDTH: 1;
+$MINIMIZED_ICON_SIZE:25;
+$MAX_ROW_HEIGHT: 44px;
:export {
contextMenuZindex: $contextMenu-zindex;
COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+ MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
+ MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
index e874b815d..9788d31f7 100644
--- a/src/client/views/globalCssVariables.scss.d.ts
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -1,7 +1,9 @@
interface IGlobalScss {
contextMenuZindex: string; // context menu shows up over everything
- COLLECTION_BORDER_WIDTH: number;
+ COLLECTION_BORDER_WIDTH: string;
+ MINIMIZED_ICON_SIZE: string;
+ MAX_ROW_HEIGHT: string;
}
declare const globalCssVariables: IGlobalScss;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index b00cefbf6..12e5bdf1f 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -9,23 +9,18 @@ import React = require("react");
import { OmitKeys } from "../../../Utils";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
- zoomFade: number;
}
@observer
export class CollectionFreeFormDocumentView extends React.Component<CollectionFreeFormDocumentViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
- constructor(props: CollectionFreeFormDocumentViewProps) {
- super(props);
- }
-
@computed
get transform(): string {
- return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px) scale(${this.zoom}, ${this.zoom}) `;
+ return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
}
- @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); }
+ @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.ZoomBasis, 1); }
@computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
@computed get width(): number { return this.props.Document.Width(); }
@computed get height(): number { return this.props.Document.Height(); }
@@ -50,36 +45,49 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
}
- contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
-
+ get X() {
+ return this.props.Document.GetNumber(KeyStore.X, 0);
+ }
+ get Y() {
+ return this.props.Document.GetNumber(KeyStore.Y, 0);
+ }
getTransform = (): Transform =>
this.props.ScreenToLocalTransform()
- .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0))
+ .translate(-this.X, -this.Y)
.scale(1 / this.contentScaling()).scale(1 / this.zoom)
+ contentScaling = () => (this.nativeWidth > 0 ? this.width / this.nativeWidth : 1);
+ panelWidth = () => this.props.PanelWidth();
+ panelHeight = () => this.props.PanelHeight();
+
@computed
get docView() {
- return <DocumentView {...this.docViewProps}
+ return <DocumentView {...OmitKeys(this.props, ['zoomFade'])}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
PanelWidth={this.panelWidth}
PanelHeight={this.panelHeight}
/>;
}
- @computed
- get docViewProps(): DocumentViewProps {
- return (OmitKeys(this.props, ['zoomFade']));
- }
- panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
- panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
render() {
+ let zoomFade = 1;
+ //var zoom = doc.GetNumber(KeyStore.Zoom, 1);
+ // let transform = this.getTransform().scale(this.contentScaling()).inverse();
+ // var [sptX, sptY] = transform.transformPoint(0, 0);
+ // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
+ // let w = bptX - sptX;
+ // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
+ // let fadeUp = .75 * 1800;
+ // let fadeDown = .075 * 1800;
+ // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+
return (
<div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
- opacity: this.props.zoomFade,
+ opacity: zoomFade,
transformOrigin: "left top",
transform: this.transform,
- pointerEvents: "all",
+ pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
width: this.width,
height: this.height,
position: "absolute",
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 76f852601..07599c345 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -15,6 +15,7 @@ import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
+import { IconBox } from "./IconBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { VideoBox } from "./VideoBox";
@@ -62,7 +63,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
return <p>Error loading layout keys</p>;
}
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
bindings={this.CreateBindings()}
jsx={this.layout}
showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index a946ac1a8..7c72fb6e6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,10 +1,11 @@
@import "../globalCssVariables";
-.documentView-node {
+.documentView-node, .documentView-node-topmost {
position: inherit;
top: 0;
left:0;
- background: $light-color; //overflow: hidden;
+ // background: $light-color; //overflow: hidden;
+ transform-origin: left top;
&.minimized {
width: 30px;
@@ -12,7 +13,6 @@
}
.top {
- background: #232323;
height: 20px;
cursor: pointer;
}
@@ -28,16 +28,6 @@
height: calc(100% - 20px);
}
}
-
-.minimized-box {
- height: 10px;
- width: 10px;
- border-radius: 2px;
- background: $dark-color
-}
-
-.minimized-box:hover {
- background: $main-accent;
- transform: scale(1.15);
- cursor: pointer;
+.documentView-node-topmost {
+ background: white;
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index e851bc9c5..07a3c1819 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,12 +1,10 @@
import { action, computed, runInAction, observable } from "mobx";
import { observer } from "mobx-react";
-import { BooleanField } from "../../../fields/BooleanField";
import { Document } from "../../../fields/Document";
-import { Field, FieldWaiting, Opt } from "../../../fields/Field";
+import { Field, Opt, FieldWaiting } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { TextField } from "../../../fields/TextField";
import { ServerUtils } from "../../../server/ServerUtil";
import { emptyFunction, Utils } from "../../../Utils";
import { Documents } from "../../documents/Documents";
@@ -24,9 +22,11 @@ import { DocumentContentsView } from "./DocumentContentsView";
import { Template, Templates } from "./../Templates";
import "./DocumentView.scss";
import React = require("react");
+import { TextField } from "../../../fields/TextField";
+import { string } from "prop-types";
+import { NumberField } from "../../../fields/NumberField";
import { TemplateField } from "../../../fields/TemplateField";
-
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
Document: Document;
@@ -41,7 +41,7 @@ export interface DocumentViewProps {
focus: (doc: Document) => void;
selectOnLoad: boolean;
parentActive: () => boolean;
- onActiveChanged: (isActive: boolean) => void;
+ whenActiveChanged: (isActive: boolean) => void;
}
export interface JsxArgs extends DocumentViewProps {
Keys: { [name: string]: Key };
@@ -67,14 +67,12 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
let Keys: { [name: string]: any } = {};
let Fields: { [name: string]: any } = {};
for (const key of keys) {
- let fn = emptyFunction;
- Object.defineProperty(fn, "name", { value: key + "Key" });
- Keys[key] = fn;
+ Object.defineProperty(emptyFunction, "name", { value: key + "Key" });
+ Keys[key] = emptyFunction;
}
for (const field of fields) {
- let fn = emptyFunction;
- Object.defineProperty(fn, "name", { value: field });
- Fields[field] = fn;
+ Object.defineProperty(emptyFunction, "name", { value: field });
+ Fields[field] = emptyFunction;
}
let args: JsxArgs = {
Document: function Document() { },
@@ -87,13 +85,14 @@ export function FakeJsxArgs(keys: string[], fields: string[] = []): JsxArgs {
@observer
export class DocumentView extends React.Component<DocumentViewProps> {
- private _mainCont = React.createRef<HTMLDivElement>();
- public get ContentRef() {
- return this._mainCont;
- }
+ static _incompleteAnimations: Map<string, boolean> = new Map<string, boolean>();
private _downX: number = 0;
private _downY: number = 0;
@computed get base(): string { return this.props.Document.GetText(KeyStore.BaseLayout, "<p>Error loading base layout data</p>"); }
+ private _mainCont = React.createRef<HTMLDivElement>();
+ private _dropDisposer?: DragManager.DragDropDisposer;
+
+ public get ContentDiv() { return this._mainCont.current; }
@computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
@computed get topMost(): boolean { return this.props.isTopMost; }
@computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
@@ -105,9 +104,13 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
set templates(templates: Array<Template>) { this.props.Document.SetData(KeyStore.Templates, templates, TemplateField); }
screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
+
onPointerDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
this._downY = e.clientY;
+ if (e.button === 2 && !this.isSelected()) {
+ return;
+ }
if (e.shiftKey && e.buttons === 2) {
if (this.props.isTopMost) {
this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
@@ -116,7 +119,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
e.stopPropagation();
} else {
- if (this.active) {
+ let maxdoc = this.props.Document.GetT(KeyStore.MaximizedDoc, Document);
+ if (this.active ||
+ maxdoc instanceof Document // bcz: need a better way of allowing a document to handle pointer events when its not active (ie. be a top-level widget)
+ ) {
e.stopPropagation();
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
@@ -126,11 +132,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
- private dropDisposer?: DragManager.DragDropDisposer;
-
componentDidMount() {
if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
handlers: { drop: this.drop.bind(this) }
});
}
@@ -138,33 +142,31 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
componentDidUpdate() {
- if (this.dropDisposer) {
- this.dropDisposer();
+ if (this._dropDisposer) {
+ this._dropDisposer();
}
if (this._mainCont.current) {
- this.dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
+ this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
handlers: { drop: this.drop.bind(this) }
});
}
}
componentWillUnmount() {
- if (this.dropDisposer) {
- this.dropDisposer();
+ if (this._dropDisposer) {
+ this._dropDisposer();
}
runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1));
}
startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
if (this._mainCont.current) {
- const [left, top] = this.props
- .ScreenToLocalTransform()
- .inverse()
- .transformPoint(0, 0);
+ const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
let dragData = new DragManager.DocumentDragData([this.props.Document]);
+ const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.aliasOnDrop = dropAliasOfDraggedDoc;
- dragData.xOffset = x - left;
- dragData.yOffset = y - top;
+ dragData.xOffset = xoff;
+ dragData.yOffset = yoff;
dragData.moveDocument = this.props.moveDocument;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
@@ -179,13 +181,10 @@ export class DocumentView extends React.Component<DocumentViewProps> {
if (e.cancelBubble) {
return;
}
- if (
- Math.abs(this._downX - e.clientX) > 3 ||
- Math.abs(this._downY - e.clientY) > 3
- ) {
+ 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 || e.altKey) {
+ if (!e.altKey && (!this.topMost || e.buttons === 2)) {
this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
}
}
@@ -196,57 +195,127 @@ export class DocumentView extends React.Component<DocumentViewProps> {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
e.stopPropagation();
- if (!SelectionManager.IsSelected(this) &&
- e.button !== 2 &&
- Math.abs(e.clientX - this._downX) < 4 &&
- Math.abs(e.clientY - this._downY) < 4
- ) {
- SelectionManager.SelectDoc(this, e.ctrlKey);
+ if (!SelectionManager.IsSelected(this) && e.button !== 2 &&
+ Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
+ this.props.Document.GetTAsync(KeyStore.MaximizedDoc, Document).then(maxdoc => {
+ if (maxdoc instanceof Document) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ this.props.addDocument && this.props.addDocument(maxdoc, false);
+ this.toggleIcon();
+ } else
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ });
}
}
- stopPropogation = (e: React.SyntheticEvent) => {
+ stopPropagation = (e: React.SyntheticEvent) => {
e.stopPropagation();
}
deleteClicked = (): void => {
- if (this.props.removeDocument) {
- this.props.removeDocument(this.props.Document);
- }
+ this.props.removeDocument && this.props.removeDocument(this.props.Document);
}
fieldsClicked = (e: React.MouseEvent): void => {
- if (this.props.addDocument) {
- this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
- }
+ let kvp = Documents.KVPDocument(this.props.Document, { width: 300, height: 300 });
+ CollectionDockingView.Instance.AddRightSplit(kvp);
}
fullScreenClicked = (e: React.MouseEvent): void => {
- CollectionDockingView.Instance.OpenFullScreen(this.props.Document);
+ CollectionDockingView.Instance.OpenFullScreen((this.props.Document.GetPrototype() as Document).MakeDelegate());
ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({
- description: "Close Full Screen",
- event: this.closeFullScreenClicked
- });
+ ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
closeFullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.CloseFullScreen();
ContextMenu.Instance.clearItems();
- ContextMenu.Instance.addItem({
- description: "Full Screen",
- event: this.fullScreenClicked
- });
+ ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
+ @action createIcon = (layoutString: string): Document => {
+ let iconDoc = Documents.IconDocument(layoutString);
+ iconDoc.SetText(KeyStore.Title, "ICON" + this.props.Document.Title)
+ iconDoc.SetBoolean(KeyStore.IsMinimized, false);
+ iconDoc.SetNumber(KeyStore.NativeWidth, 0);
+ iconDoc.SetNumber(KeyStore.NativeHeight, 0);
+ iconDoc.SetNumber(KeyStore.X, this.props.Document.GetNumber(KeyStore.X, 0));
+ iconDoc.SetNumber(KeyStore.Y, this.props.Document.GetNumber(KeyStore.Y, 0) - 24);
+ iconDoc.Set(KeyStore.Prototype, this.props.Document);
+ iconDoc.Set(KeyStore.MaximizedDoc, this.props.Document);
+ this.props.Document.Set(KeyStore.MinimizedDoc, iconDoc);
+ this.props.addDocument && this.props.addDocument(iconDoc, false);
+ return iconDoc;
+ }
+
+ animateBetweenIcon(icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) {
+ setTimeout(() => {
+ let now = Date.now();
+ let progress = Math.min(1, (now - stime) / 200);
+ let pval = maximizing ?
+ [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
+ [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
+ target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress);
+ target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress);
+ target.SetNumber(KeyStore.X, pval[0]);
+ target.SetNumber(KeyStore.Y, pval[1]);
+ if (now < stime + 200) {
+ this.animateBetweenIcon(icon, targ, width, height, stime, target, maximizing);
+ }
+ else {
+ if (!maximizing) {
+ target.SetBoolean(KeyStore.IsMinimized, true);
+ target.SetNumber(KeyStore.X, targ[0]);
+ target.SetNumber(KeyStore.Y, targ[1]);
+ target.SetNumber(KeyStore.Width, width);
+ target.SetNumber(KeyStore.Height, height);
+ }
+ DocumentView._incompleteAnimations.set(target.Id, false);
+ }
+ },
+ 2);
+ }
+
@action
- public minimize = (): void => {
- this.props.Document.SetData(
- KeyStore.Minimized,
- true as boolean,
- BooleanField
- );
+ public toggleIcon = async (): Promise<void> => {
SelectionManager.DeselectAll();
+ let isMinimized: boolean | undefined;
+ let minDoc = await this.props.Document.GetTAsync(KeyStore.MinimizedDoc, Document);
+ if (!minDoc) return;
+ let minimizedDocSet = await minDoc.GetTAsync(KeyStore.LinkTags, ListField);
+ if (!minimizedDocSet) return;
+ minimizedDocSet.Data.map(async minimizedDoc => {
+ if (minimizedDoc instanceof Document) {
+ this.props.addDocument && this.props.addDocument(minimizedDoc, false);
+ let maximizedDoc = await minimizedDoc.GetTAsync(KeyStore.MaximizedDoc, Document);
+ if (maximizedDoc instanceof Document && !DocumentView._incompleteAnimations.get(maximizedDoc.Id)) {
+ DocumentView._incompleteAnimations.set(maximizedDoc.Id, true);
+ isMinimized = isMinimized === undefined ? maximizedDoc.GetBoolean(KeyStore.IsMinimized, false) : isMinimized;
+ maximizedDoc.SetBoolean(KeyStore.IsMinimized, false);
+ let minx = await minimizedDoc.GetTAsync(KeyStore.X, NumberField);
+ let miny = await minimizedDoc.GetTAsync(KeyStore.Y, NumberField);
+ let maxx = await maximizedDoc.GetTAsync(KeyStore.X, NumberField);
+ let maxy = await maximizedDoc.GetTAsync(KeyStore.Y, NumberField);
+ let maxw = await maximizedDoc.GetTAsync(KeyStore.Width, NumberField);
+ let maxh = await maximizedDoc.GetTAsync(KeyStore.Height, NumberField);
+ if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
+ maxw !== undefined && maxh !== undefined)
+ this.animateBetweenIcon(
+ [minx.Data, miny.Data], [maxx.Data, maxy.Data], maxw.Data, maxh.Data,
+ Date.now(), maximizedDoc, isMinimized);
+ }
+
+ }
+ })
+ }
+
+ @action
+ public getIconDoc = async (): Promise<Document | undefined> => {
+ return await this.props.Document.GetTAsync(KeyStore.MinimizedDoc, Document).then(async mindoc =>
+ mindoc ? mindoc :
+ await this.props.Document.GetTAsync(KeyStore.BackgroundLayout, TextField).then(async field =>
+ (field instanceof TextField) ? this.createIcon(field.Data) :
+ await this.props.Document.GetTAsync(KeyStore.Layout, TextField).then(field =>
+ (field instanceof TextField) ? this.createIcon(field.Data) : undefined)));
}
@undoBatch
@@ -261,9 +330,9 @@ export class DocumentView extends React.Component<DocumentViewProps> {
sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc =>
runInAction(() => {
let batch = UndoManager.StartBatch("document view drop");
- linkDoc.Set(KeyStore.Title, new TextField("New Link"));
- linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
- linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
+ linkDoc.SetText(KeyStore.Title, "New Link");
+ linkDoc.SetText(KeyStore.LinkDescription, "");
+ linkDoc.SetText(KeyStore.LinkTags, "Default");
let dstTarg = protoDest ? protoDest : destDoc;
let srcTarg = protoSrc ? protoSrc : sourceDoc;
@@ -294,11 +363,8 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
onDrop = (e: React.DragEvent) => {
- if (e.isDefaultPrevented()) {
- return;
- }
let text = e.dataTransfer.getData("text/plain");
- if (text && text.startsWith("<div")) {
+ if (!e.isDefaultPrevented() && 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);
@@ -347,145 +413,51 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
- let moved =
- Math.abs(this._downX - e.clientX) > 3 ||
- Math.abs(this._downY - e.clientY) > 3;
- if (moved || e.isDefaultPrevented()) {
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3 ||
+ e.isDefaultPrevented()) {
e.preventDefault();
return;
}
e.preventDefault();
- if (!this.isMinimized()) {
- ContextMenu.Instance.addItem({
- description: "Minimize",
- event: this.minimize
- });
- }
- ContextMenu.Instance.addItem({
- description: "Full Screen",
- event: this.fullScreenClicked
- });
- ContextMenu.Instance.addItem({
- description: "Fields",
- event: this.fieldsClicked
- });
- ContextMenu.Instance.addItem({
- description: "Center",
- event: () => this.props.focus(this.props.Document)
- });
- ContextMenu.Instance.addItem({
- description: "Open Right",
- event: () =>
- CollectionDockingView.Instance.AddRightSplit(this.props.Document)
- });
- ContextMenu.Instance.addItem({
- description: "Copy URL",
- event: () => {
- Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id));
- }
- });
- ContextMenu.Instance.addItem({
- description: "Copy ID",
- event: () => {
- Utils.CopyText(this.props.Document.Id);
- }
- });
+ ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
+ ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
+ ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
+ ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)) });
+ ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document.Id) });
//ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
+ ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- if (!this.topMost) {
- // DocumentViews should stop propagation of this event
- e.stopPropagation();
- }
-
- ContextMenu.Instance.addItem({
- description: "Delete",
- event: this.deleteClicked
- });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
-
- isMinimized = () => {
- let field = this.props.Document.GetT(KeyStore.Minimized, BooleanField);
- if (field && field !== FieldWaiting) {
- return field.Data;
- }
- }
-
- @action
- expand = () => {
- this.props.Document.SetData(
- KeyStore.Minimized,
- false as boolean,
- BooleanField
- );
+ if (!SelectionManager.IsSelected(this))
+ SelectionManager.SelectDoc(this, false);
}
isSelected = () => SelectionManager.IsSelected(this);
+ select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
- select = (ctrlPressed: boolean) => {
- SelectionManager.SelectDoc(this, ctrlPressed);
- }
+ @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+ @computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); }
- @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
- @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
- @computed
- get contents() {
- return (<DocumentContentsView
- {...this.props}
- isSelected={this.isSelected}
- select={this.select}
- layoutKey={KeyStore.Layout}
- />);
- }
render() {
- if (!this.props.Document) {
- return null;
- }
-
var scaling = this.props.ContentScaling();
- var nativeWidth = this.nativeWidth;
- var nativeHeight = this.nativeHeight;
-
- if (this.isMinimized()) {
- return (
- <div
- className="minimized-box"
- ref={this._mainCont}
- style={{
- transformOrigin: "left top",
- transform: `scale(${scaling} , ${scaling})`
- }}
- onClick={this.expand}
- onDrop={this.onDrop}
- onPointerDown={this.onPointerDown}
- />
- );
- } else {
- 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.contents}
- </div>
- );
- }
+ var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
+ var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
+
+ return (
+ <div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
+ ref={this._mainCont}
+ style={{
+ background: this.props.Document.GetText(KeyStore.BackgroundColor, ""),
+ width: nativeWidth, height: nativeHeight,
+ transform: `scale(${scaling}, ${scaling})`
+ }}
+ onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}
+ >
+ {this.contents}
+ </div>
+ );
}
}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ebd25f937..93e385821 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -23,6 +23,8 @@ import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../.
import { CollectionView } from "../collections/CollectionView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { IconField } from "../../../fields/IconFIeld";
+import { IconBox } from "./IconBox";
//
@@ -43,7 +45,7 @@ export interface FieldViewProps {
moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
active: () => boolean;
- onActiveChanged: (isActive: boolean) => void;
+ whenActiveChanged: (isActive: boolean) => void;
focus: (doc: Document) => void;
}
@@ -72,6 +74,9 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) {
return <ImageBox {...this.props} />;
}
+ else if (field instanceof IconField) {
+ return <IconBox {...this.props} />;
+ }
else if (field instanceof VideoField) {
return <VideoBox {...this.props} />;
}
@@ -95,7 +100,7 @@ export class FieldView extends React.Component<FieldViewProps> {
layoutKey={KeyStore.Layout}
ContainingCollectionView={this.props.ContainingCollectionView}
parentActive={this.props.active}
- onActiveChanged={this.props.onActiveChanged} />
+ whenActiveChanged={this.props.whenActiveChanged} />
);
}
else if (field instanceof ListField) {
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 3978c3d38..5eb2bf7ce 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -10,7 +10,7 @@
outline: none !important;
}
-.formattedTextBox-cont {
+.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
background: $light-color-secondary;
padding: 0.9em;
border-width: 0px;
@@ -24,6 +24,10 @@
height: 100%;
pointer-events: all;
}
+.formattedTextBox-cont-hidden {
+ overflow: hidden;
+ pointer-events: none;
+}
.menuicon {
display: inline-block;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 9f71609d5..a61f00fd0 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,22 +1,25 @@
import { action, IReactionDisposer, reaction, trace, computed } from "mobx";
import { baseKeymap } from "prosemirror-commands";
-import { history, redo, undo } from "prosemirror-history";
+import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { EditorState, Plugin, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
import { FieldWaiting, Opt } from "../../../fields/Field";
+import { KeyStore } from "../../../fields/KeyStore";
import { RichTextField } from "../../../fields/RichTextField";
+import { TextField } from "../../../fields/TextField";
+import { Document } from "../../../fields/Document";
+import buildKeymap from "../../util/ProsemirrorKeymap";
import { inpRules } from "../../util/RichTextRules";
import { schema } from "../../util/RichTextSchema";
+import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu";
import { TooltipTextMenu } from "../../util/TooltipTextMenu";
import { ContextMenu } from "../../views/ContextMenu";
-import { Main } from "../Main";
+import { MainOverlayTextBox } from "../MainOverlayTextBox";
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
-import { TextField } from "../../../fields/TextField";
-import { KeyStore } from "../../../fields/KeyStore";
-import { MainOverlayTextBox } from "../MainOverlayTextBox";
+import { SelectionManager } from "../../util/SelectionManager";
import { observer } from "mobx-react";
const { buildMenuItems } = require("prosemirror-example-setup");
const { menuBar } = require("prosemirror-menu");
@@ -49,6 +52,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
}
private _ref: React.RefObject<HTMLDivElement>;
private _editorView: Opt<EditorView>;
+ private _gotDown: boolean = false;
private _reactionDisposer: Opt<IReactionDisposer>;
private _inputReactionDisposer: Opt<IReactionDisposer>;
private _proxyReactionDisposer: Opt<IReactionDisposer>;
@@ -84,12 +88,18 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
inpRules, //these currently don't do anything, but could eventually be helpful
plugins: this.props.isOverlay ? [
history(),
- keymap({ "Mod-z": undo, "Mod-y": redo }),
+ keymap(buildKeymap(schema)),
keymap(baseKeymap),
- this.tooltipMenuPlugin()
+ this.tooltipTextMenuPlugin(),
+ // this.tooltipLinkingMenuPlugin(),
+ new Plugin({
+ props: {
+ attributes: { class: "ProseMirror-example-setup-style" }
+ }
+ })
] : [
history(),
- keymap({ "Mod-z": undo, "Mod-y": redo }),
+ keymap(buildKeymap(schema)),
keymap(baseKeymap),
]
};
@@ -100,42 +110,36 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
if (this._editorView) {
this._editorView.destroy();
}
-
- this.setupEditor(config);
+ this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
}
);
} else {
+<<<<<<< HEAD
this._proxyReactionDisposer = reaction(() => { }/*this.props.isSelected()*/,
() => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()));
+=======
+ this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
+ () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
+>>>>>>> b63bcb791013766d5d16e4f38964499268f904c4
}
+
this._reactionDisposer = reaction(
() => {
const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
return field && field !== FieldWaiting ? field.Data : undefined;
},
- field => {
- if (field && this._editorView && !this._applyingChange) {
- this._editorView.updateState(
- EditorState.fromJSON(config, JSON.parse(field))
- );
- }
- }
+ field => field && this._editorView && !this._applyingChange &&
+ this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
);
- this.setupEditor(config);
+ this.setupEditor(config, this.props.Document);
}
- private setupEditor(config: any) {
- let state: EditorState;
- let field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
- if (field && field !== FieldWaiting && field.Data) {
- state = EditorState.fromJSON(config, JSON.parse(field.Data));
- } else {
- state = EditorState.create(config);
- }
+ private setupEditor(config: any, doc?: Document) {
+ let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined;
if (this._ref.current) {
this._editorView = new EditorView(this._ref.current, {
- state,
+ state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
dispatchTransaction: this.dispatchTransaction
});
}
@@ -161,10 +165,6 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
}
}
- shouldComponentUpdate() {
- return false;
- }
-
@action
onChange(e: React.ChangeEvent<HTMLInputElement>) {
const { fieldKey, Document } = this.props;
@@ -173,13 +173,17 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
}
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ console.log("first");
e.stopPropagation();
}
if (e.button === 2) {
+ this._gotDown = true;
+ console.log("second");
e.preventDefault();
}
}
onPointerUp = (e: React.PointerEvent): void => {
+ console.log("pointer up");
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
@@ -187,7 +191,9 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
onFocused = (e: React.FocusEvent): void => {
if (!this.props.isOverlay) {
- MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform());
+ if (MainOverlayTextBox.Instance.TextDoc != this.props.Document) {
+ MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
+ }
} else {
if (this._ref.current) {
this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
@@ -199,6 +205,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
textCapability = (e: React.MouseEvent): void => { };
specificContextMenu = (e: React.MouseEvent): void => {
+ if (!this._gotDown) {
+ e.preventDefault();
+ return;
+ }
ContextMenu.Instance.addItem({
description: "Text Capability",
event: this.textCapability
@@ -224,7 +234,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
}
}
- tooltipMenuPlugin() {
+ tooltipTextMenuPlugin() {
let myprops = this.props;
return new Plugin({
view(_editorView) {
@@ -232,17 +242,36 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
}
});
}
+
+ tooltipLinkingMenuPlugin() {
+ let myprops = this.props;
+ return new Plugin({
+ view(_editorView) {
+ return new TooltipLinkingMenu(_editorView, myprops);
+ }
+ });
+ }
+
onKeyPress(e: React.KeyboardEvent) {
+ if (e.key == "Escape") {
+ SelectionManager.DeselectAll();
+ }
e.stopPropagation();
+ if (e.keyCode === 9) e.preventDefault();
// stop propagation doesn't seem to stop propagation of native keyboard events.
// so we set a flag on the native event that marks that the event's been handled.
// (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
}
render() {
+ let style = this.props.isOverlay ? "scroll" : "hidden";
return (
+<<<<<<< HEAD
<div
style={{ overflowY: /*this.props.isSelected() ||*/ this.props.isOverlay ? "scroll" : "hidden" }}
className={`formattedTextBox-cont`}
+=======
+ <div className={`formattedTextBox-cont-${style}`}
+>>>>>>> b63bcb791013766d5d16e4f38964499268f904c4
onKeyDown={this.onKeyPress}
onKeyPress={this.onKeyPress}
onFocus={this.onFocused}
@@ -251,8 +280,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
onContextMenu={this.specificContextMenu}
// tfs: do we need this event handler
onWheel={this.onPointerWheel}
- ref={this._ref}
- />
+ ref={this._ref} />
);
}
}
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
new file mode 100644
index 000000000..ce0ee2e09
--- /dev/null
+++ b/src/client/views/nodes/IconBox.scss
@@ -0,0 +1,12 @@
+
+@import "../globalCssVariables";
+.iconBox-container {
+ position: absolute;
+ left:0;
+ top:0;
+ svg {
+ width: 100% !important;
+ height: 100%;
+ background: white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
new file mode 100644
index 000000000..9c90c0a0e
--- /dev/null
+++ b/src/client/views/nodes/IconBox.tsx
@@ -0,0 +1,45 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from '../../../fields/Document';
+import { IconField } from "../../../fields/IconFIeld";
+import { KeyStore } from "../../../fields/KeyStore";
+import { SelectionManager } from "../../util/SelectionManager";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./IconBox.scss";
+
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+@observer
+export class IconBox extends React.Component<FieldViewProps> {
+ public static LayoutString() { return FieldView.LayoutString(IconBox); }
+
+ @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); }
+ @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "<p>Error loading layout data</p>" as string); }
+ @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
+
+ public static DocumentIcon(layout: string) {
+ let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ layout.indexOf("ImageBox") !== -1 ? faImage :
+ layout.indexOf("Formatted") !== -1 ? faStickyNote :
+ layout.indexOf("Video") !== -1 ? faFilm :
+ layout.indexOf("Collection") !== -1 ? faObjectGroup :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />
+ }
+
+ render() {
+ return (
+ <div className="iconBox-container">
+ {this.minimizedIcon}
+ </div>);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 487038841..f4b3837ff 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -8,6 +8,16 @@
max-height: 100%;
}
+.imageBox-dot {
+ position:absolute;
+ bottom: 10;
+ left: 0;
+ border-radius: 10px;
+ width:20px;
+ height:20px;
+ background:gray;
+}
+
.imageBox-cont img {
height: 100%;
width:100%;
@@ -18,4 +28,4 @@
border: none;
width: 100%;
height: 100%;
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 6b0a3a799..ce855384c 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,51 +1,87 @@
-import { action, observable, trace } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import { Document } from '../../../fields/Document';
import { FieldWaiting } from '../../../fields/Field';
import { ImageField } from '../../../fields/ImageField';
import { KeyStore } from '../../../fields/KeyStore';
+import { ListField } from '../../../fields/ListField';
+import { Utils } from '../../../Utils';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
-import { Utils } from '../../../Utils';
@observer
export class ImageBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(ImageBox); }
- private _ref: React.RefObject<HTMLDivElement>;
private _imgRef: React.RefObject<HTMLImageElement>;
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
@observable private _photoIndex: number = 0;
@observable private _isOpen: boolean = false;
+ private dropDisposer?: DragManager.DragDropDisposer;
constructor(props: FieldViewProps) {
super(props);
- this._ref = React.createRef();
this._imgRef = React.createRef();
- this.state = {
- photoIndex: 0,
- isOpen: false,
- };
}
@action
onLoad = (target: any) => {
var h = this._imgRef.current!.naturalHeight;
var w = this._imgRef.current!.naturalWidth;
- this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
+ if (this._photoIndex === 0) {
+ this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
+ this.props.Document.SetNumber(KeyStore.Height, this.props.Document.Width() * h / w);
+ }
}
- componentDidMount() {
+
+ protected createDropTarget = (ele: HTMLDivElement) => {
+ if (this.dropDisposer) {
+ this.dropDisposer();
+ }
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+ onDrop = (e: React.DragEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ console.log("IMPLEMENT ME PLEASE");
}
- componentWillUnmount() {
+
+ @undoBatch
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ de.data.droppedDocuments.map(action((drop: Document) => {
+ let layout = drop.GetText(KeyStore.BackgroundLayout, "");
+ if (layout.indexOf(ImageBox.name) !== -1) {
+ let imgData = this.props.Document.Get(KeyStore.Data);
+ if (imgData instanceof ImageField && imgData) {
+ this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData]));
+ }
+ let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]);
+ if (imgList) {
+ let field = drop.Get(KeyStore.Data);
+ if (field === FieldWaiting) { }
+ else if (field instanceof ImageField) imgList.push(field);
+ else if (field instanceof ListField) imgList.push(field.Data);
+ }
+ e.stopPropagation();
+ }
+ }));
+ // de.data.removeDocument() bcz: need to implement
+ }
}
onPointerDown = (e: React.PointerEvent): void => {
@@ -70,8 +106,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
e.stopPropagation();
}
- lightbox = (path: string) => {
- const images = [path];
+ lightbox = (images: string[]) => {
if (this._isOpen) {
return (<Lightbox
mainSrc={images[this._photoIndex]}
@@ -102,15 +137,35 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
}
+ @action
+ onDotDown(index: number) {
+ this._photoIndex = index;
+ this.props.Document.SetNumber(KeyStore.CurPage, index);
+ }
+
+ dots(paths: string[]) {
+ let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+ let dist = Math.min(nativeWidth / paths.length, 40);
+ let left = (nativeWidth - paths.length * dist) / 2;
+ return paths.map((p, i) =>
+ <div className="imageBox-placer" key={i} >
+ <div className="imageBox-dot" style={{ background: (i == this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
+ </div>
+ );
+ }
+
render() {
let field = this.props.Document.Get(this.props.fieldKey);
- let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
- field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
+ let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
+ if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"];
+ else if (field instanceof ImageField) paths = [field.Data.href];
+ else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href);
let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
return (
- <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this._ref} onContextMenu={this.specificContextMenu}>
- <img src={path} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
- {this.lightbox(path)}
+ <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
+ {paths.length > 1 ? this.dots(paths) : (null)}
+ {this.lightbox(paths)}
</div>);
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 29e4af160..ddbec014b 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -26,12 +26,6 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
super(props);
}
-
-
- shouldComponentUpdate() {
- return false;
- }
-
@action
onEnterKey = (e: React.KeyboardEvent): void => {
if (e.key === 'Enter') {
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 5d69f23b2..d480eb5af 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -48,7 +48,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
isTopMost: false,
selectOnLoad: false,
active: returnFalse,
- onActiveChanged: emptyFunction,
+ whenActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyDocFunction,
};
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index c73bc0c47..2ad1129a4 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -9,6 +9,12 @@
overflow: scroll;
}
+#webBox-htmlSpan {
+ position: absolute;
+ top:0;
+ left:0;
+}
+
.webBox-button {
padding : 0vw;
border: none;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 90ce72c41..1edb4d826 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -18,21 +18,40 @@ export class WebBox extends React.Component<FieldViewProps> {
@computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); }
+ _ignore = 0;
+ onPreWheel = (e: React.WheelEvent) => {
+ this._ignore = e.timeStamp;
+ }
+ onPrePointer = (e: React.PointerEvent) => {
+ this._ignore = e.timeStamp;
+ }
+ onPostPointer = (e: React.PointerEvent) => {
+ if (this._ignore !== e.timeStamp) {
+ e.stopPropagation();
+ }
+ }
+ onPostWheel = (e: React.WheelEvent) => {
+ if (this._ignore !== e.timeStamp) {
+ e.stopPropagation();
+ }
+ }
render() {
let field = this.props.Document.Get(this.props.fieldKey);
let path = field === FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
field instanceof WebField ? field.Data.href : "https://crossorigin.me/" + "https://cs.brown.edu";
- let content = this.html ?
- <span dangerouslySetInnerHTML={{ __html: this.html }}></span> :
- <div style={{ width: "100%", height: "100%", position: "absolute" }}>
- <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }}></iframe>
- {this.props.isSelected() ? (null) : <div style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ let content =
+ <div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
+ {this.html ? <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: this.html }} /> :
+ <iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }} />}
</div>;
return (
- <div className="webBox-cont" >
- {content}
- </div>);
+ <>
+ <div className="webBox-cont" >
+ {content}
+ </div>
+ {this.props.isSelected() ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ </>);
}
} \ No newline at end of file
diff --git a/src/fields/IconFIeld.ts b/src/fields/IconFIeld.ts
new file mode 100644
index 000000000..a6694cc49
--- /dev/null
+++ b/src/fields/IconFIeld.ts
@@ -0,0 +1,25 @@
+import { BasicField } from "./BasicField";
+import { FieldId } from "./Field";
+import { Types } from "../server/Message";
+
+export class IconField extends BasicField<string> {
+ constructor(data: string = "", id?: FieldId, save: boolean = true) {
+ super(data, save, id);
+ }
+
+ ToScriptString(): string {
+ return `new IconField("${this.Data}")`;
+ }
+
+ Copy() {
+ return new IconField(this.Data);
+ }
+
+ ToJson() {
+ return {
+ type: Types.Icon,
+ data: this.Data,
+ id: this.Id
+ };
+ }
+} \ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 5bd402890..e58384df8 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -15,7 +15,7 @@ export namespace KeyStore {
export const Width = new Key("Width");
export const Height = new Key("Height");
export const ZIndex = new Key("ZIndex");
- export const Zoom = new Key("Zoom");
+ export const ZoomBasis = new Key("ZoomBasis");
export const Data = new Key("Data");
export const Annotations = new Key("Annotations");
export const ViewType = new Key("ViewType");
@@ -47,14 +47,16 @@ export namespace KeyStore {
export const OptionalRightCollection = new Key("OptionalRightCollection");
export const Archives = new Key("Archives");
export const Workspaces = new Key("Workspaces");
- export const Minimized = new Key("Minimized");
+ export const IsMinimized = new Key("IsMinimized");
+ export const MinimizedDoc = new Key("MinimizedDoc");
+ export const MaximizedDoc = new Key("MaximizedDoc");
export const CopyDraggedItems = new Key("CopyDraggedItems");
export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
- Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, BaseLayout, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
+ Width, Height, ZIndex, ZoomBasis, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
- Archives, Workspaces, Minimized, CopyDraggedItems
+ Archives, Workspaces, IsMinimized, MinimizedDoc, MaximizedDoc, CopyDraggedItems
];
export function KeyLookup(keyid: string) {
for (const key of KeyList) {
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 00a30b329..854ae0168 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -14,7 +14,7 @@ export class Message<T> {
}
export enum Types {
- Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference,
+ Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
}
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index a10bdd5e6..a63fe5447 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -18,7 +18,11 @@ import { NumberField } from "./../fields/NumberField";
import { RichTextField } from "./../fields/RichTextField";
import { TextField } from "./../fields/TextField";
import { Transferable, Types } from "./Message";
+<<<<<<< HEAD
import { TemplateField } from "../fields/TemplateField";
+=======
+import { IconField } from "../fields/IconFIeld";
+>>>>>>> b63bcb791013766d5d16e4f38964499268f904c4
export class ServerUtils {
public static prepend(extension: string): string {
@@ -38,6 +42,7 @@ export class ServerUtils {
case Types.Boolean: return new BooleanField(json.data, json.id, false);
case Types.Number: return new NumberField(json.data, json.id, false);
case Types.Text: return new TextField(json.data, json.id, false);
+ case Types.Icon: return new IconField(json.data, json.id, false);
case Types.Html: return new HtmlField(json.data, json.id, false);
case Types.Web: return new WebField(new URL(json.data), json.id, false);
case Types.RichText: return new RichTextField(json.data, json.id, false);
diff --git a/src/server/authentication/models/user_model.ts b/src/server/authentication/models/user_model.ts
index d5c84c311..ee85e1c05 100644
--- a/src/server/authentication/models/user_model.ts
+++ b/src/server/authentication/models/user_model.ts
@@ -85,8 +85,7 @@ userSchema.pre("save", function save(next) {
});
const comparePassword: comparePasswordFunction = function (this: DashUserModel, candidatePassword, cb) {
- bcrypt.compare(candidatePassword, this.password, (err: mongoose.Error, isMatch: boolean) =>
- cb(err, isMatch));
+ bcrypt.compare(candidatePassword, this.password, cb);
};
userSchema.methods.comparePassword = comparePassword;
diff --git a/src/server/database.ts b/src/server/database.ts
index 7914febf8..5457e4dd5 100644
--- a/src/server/database.ts
+++ b/src/server/database.ts
@@ -59,7 +59,7 @@ export class Database {
public getDocument(id: string, fn: (result?: Transferable) => void, collectionName = Database.DocumentsCollection) {
this.db && this.db.collection(collectionName).findOne({ id: id }, (err, result) =>
- fn(result ? ({ id: result._id, type: result.type, data: result.data }) : undefined))
+ fn(result ? ({ id: result._id, type: result.type, data: result.data }) : undefined));
}
public getDocuments(ids: string[], fn: (result: Transferable[]) => void, collectionName = Database.DocumentsCollection) {
diff --git a/src/server/index.ts b/src/server/index.ts
index 3cbe1ca76..70a7d266c 100644
--- a/src/server/index.ts
+++ b/src/server/index.ts
@@ -35,7 +35,7 @@ import c = require("crypto");
const MongoStore = require('connect-mongo')(session);
const mongoose = require('mongoose');
-const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));;
+const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));
const mongoUrl = 'mongodb://localhost:27017/Dash';
mongoose.connect(mongoUrl);