aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/documents/Documents.ts14
-rw-r--r--src/client/northstar/core/filter/ValueComparision.ts6
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.tsx54
-rw-r--r--src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx8
-rw-r--r--src/client/northstar/operations/HistogramOperation.ts34
-rw-r--r--src/client/util/DragManager.ts32
-rw-r--r--src/client/util/TooltipTextMenu.tsx81
-rw-r--r--src/client/util/UndoManager.ts4
-rw-r--r--src/client/views/.DS_Storebin8196 -> 6148 bytes
-rw-r--r--src/client/views/DocumentDecorations.scss100
-rw-r--r--src/client/views/DocumentDecorations.tsx188
-rw-r--r--src/client/views/InkingCanvas.tsx18
-rw-r--r--src/client/views/InkingStroke.scss3
-rw-r--r--src/client/views/InkingStroke.tsx8
-rw-r--r--src/client/views/Main.scss6
-rw-r--r--src/client/views/Main.tsx2
-rw-r--r--src/client/views/MainOverlayTextBox.tsx13
-rw-r--r--src/client/views/PreviewCursor.tsx9
-rw-r--r--src/client/views/TemplateMenu.tsx75
-rw-r--r--src/client/views/Templates.tsx66
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx5
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx1
-rw-r--r--src/client/views/collections/CollectionPDFView.scss26
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx34
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx35
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx11
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx46
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx30
-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.tsx61
-rw-r--r--src/client/views/nodes/FieldView.tsx2
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss11
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx48
-rw-r--r--src/client/views/nodes/IconBox.tsx2
-rw-r--r--src/client/views/nodes/KeyValueBox.scss1
-rw-r--r--src/client/views/nodes/KeyValuePair.scss1
-rw-r--r--src/client/views/nodes/PDFBox.scss18
-rw-r--r--src/client/views/nodes/PDFBox.tsx247
-rw-r--r--src/client/views/nodes/Sticky.tsx83
-rw-r--r--src/client/views/nodes/VideoBox.tsx21
-rw-r--r--src/client/views/nodes/WebBox.scss17
-rw-r--r--src/client/views/nodes/WebBox.tsx6
-rw-r--r--src/debug/Test.tsx8
-rw-r--r--src/fields/TemplateField.ts43
-rw-r--r--src/new_fields/Doc.ts16
-rw-r--r--src/new_fields/IconField.ts6
-rw-r--r--src/new_fields/List.ts192
-rw-r--r--src/new_fields/util.ts8
-rw-r--r--src/server/Message.ts2
52 files changed, 1105 insertions, 660 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 2a9687bda..964faa8db 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -18,6 +18,8 @@ import { action } from "mobx";
import { ColumnAttributeModel } from "../northstar/core/attribute/AttributeModel";
import { AttributeTransformationModel } from "../northstar/core/attribute/AttributeTransformationModel";
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 { Field, Doc, Opt } from "../../new_fields/Doc";
@@ -44,7 +46,9 @@ export interface DocumentOptions {
panY?: number;
page?: number;
scale?: number;
+ baseLayout?: string;
layout?: string;
+ //templates?: Array<Template>;
viewType?: number;
backgroundColor?: string;
copyDraggedItems?: boolean;
@@ -52,6 +56,7 @@ export interface DocumentOptions {
curPage?: number;
documentText?: string;
borderRounding?: number;
+ schemaColumns?: List<string>;
// [key: string]: Opt<Field>;
}
const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
@@ -94,7 +99,7 @@ export namespace Docs {
}
function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Doc {
- return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout });
+ return Doc.assign(new Doc(protoId, true), { ...options, title: title, layout: layout, baseLayout: layout });
}
function SetInstanceOptions<U extends Field>(doc: Doc, options: DocumentOptions, value: U) {
const deleg = Doc.MakeDelegate(doc);
@@ -120,6 +125,7 @@ export namespace Docs {
function CreateIconPrototype(): Doc {
let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
{ x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE) });
+ console.log("iconpr" + iconProto.layout)
return iconProto;
}
function CreateTextPrototype(): Doc {
@@ -234,13 +240,13 @@ export namespace Docs {
if (!makePrototype) {
return SetInstanceOptions(collProto, { ...options, viewType: CollectionViewType.Freeform }, new List(documents));
}
- return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Freeform });
+ return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["schemaColumns"]), ...options, viewType: CollectionViewType.Freeform });
}
export function SchemaDocument(documents: Array<Doc>, options: DocumentOptions) {
- return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Schema });
+ return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["schemaColumns"]), ...options, viewType: CollectionViewType.Schema });
}
export function TreeDocument(documents: Array<Doc>, options: DocumentOptions) {
- return CreateInstance(collProto, new List(documents), { ...options, viewType: CollectionViewType.Tree });
+ return CreateInstance(collProto, new List(documents), { schemaColumns: new List(["schemaColumns"]), ...options, viewType: CollectionViewType.Tree });
}
export function DockDocument(config: string, options: DocumentOptions) {
return CreateInstance(collProto, config, { ...options, viewType: CollectionViewType.Docking });
diff --git a/src/client/northstar/core/filter/ValueComparision.ts b/src/client/northstar/core/filter/ValueComparision.ts
index 80b1242a9..65687a82b 100644
--- a/src/client/northstar/core/filter/ValueComparision.ts
+++ b/src/client/northstar/core/filter/ValueComparision.ts
@@ -62,13 +62,13 @@ export class ValueComparison {
var rawName = this.attributeModel.CodeName;
switch (this.Predicate) {
case Predicate.STARTS_WITH:
- ret += rawName + " !== null && " + rawName + ".StartsWith(" + val + ") ";
+ ret += rawName + " != null && " + rawName + ".StartsWith(" + val + ") ";
return ret;
case Predicate.ENDS_WITH:
- ret += rawName + " !== null && " + rawName + ".EndsWith(" + val + ") ";
+ ret += rawName + " != null && " + rawName + ".EndsWith(" + val + ") ";
return ret;
case Predicate.CONTAINS:
- ret += rawName + " !== null && " + rawName + ".Contains(" + val + ") ";
+ ret += rawName + " != null && " + rawName + ".Contains(" + val + ") ";
return ret;
default:
ret += rawName + " " + op + " " + val + " ";
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 19d108676..765ecf8f0 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.tsx
+++ b/src/client/northstar/dash-nodes/HistogramBox.tsx
@@ -1,7 +1,6 @@
import React = require("react");
import { action, computed, observable, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
-import Measure from "react-measure";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { ChartType, VisualBinRange } from '../../northstar/model/binRanges/VisualBinRange';
import { VisualBinRangeHelper } from "../../northstar/model/binRanges/VisualBinRangeHelper";
@@ -32,8 +31,6 @@ export class HistogramBox extends React.Component<FieldViewProps> {
private _dropXDisposer?: DragManager.DragDropDisposer;
private _dropYDisposer?: DragManager.DragDropDisposer;
- @observable public PanelWidth: number = 100;
- @observable public PanelHeight: number = 100;
@observable public HistoOp: HistogramOperation = HistogramOperation.Empty;
@observable public VisualBinRanges: VisualBinRange[] = [];
@observable public ValueRange: number[] = [];
@@ -89,7 +86,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
}
reaction(() => CurrentUserUtils.NorthstarDBCatalog, (catalog?: Catalog) => this.activateHistogramOperation(catalog), { fireImmediately: true });
reaction(() => [this.VisualBinRanges && this.VisualBinRanges.slice()], () => this.SizeConverter.SetVisualBinRanges(this.VisualBinRanges));
- reaction(() => [this.PanelHeight, this.PanelWidth], () => this.SizeConverter.SetIsSmall(this.PanelWidth < 40 && this.PanelHeight < 40));
+ reaction(() => [this.props.PanelWidth(), this.props.PanelHeight()], (size: number[]) => this.SizeConverter.SetIsSmall(size[0] < 40 && size[1] < 40));
reaction(() => this.HistogramResult ? this.HistogramResult.binRanges : undefined,
(binRanges: BinRange[] | undefined) => {
if (binRanges) {
@@ -138,38 +135,39 @@ export class HistogramBox extends React.Component<FieldViewProps> {
});
}
}
+
+ @action
+ private onScrollWheel = (e: React.WheelEvent) => {
+ this.HistoOp.DrillDown(e.deltaY > 0);
+ e.stopPropagation();
+ }
+
render() {
let labelY = this.HistoOp && this.HistoOp.Y ? this.HistoOp.Y.PresentedName : "<...>";
let labelX = this.HistoOp && this.HistoOp.X ? this.HistoOp.X.PresentedName : "<...>";
- var h = this.props.isTopMost ? this.PanelHeight : NumCast(this.props.Document.height);
- var w = this.props.isTopMost ? this.PanelWidth : NumCast(this.props.Document.width);
let loff = this.SizeConverter.LeftOffset;
let toff = this.SizeConverter.TopOffset;
let roff = this.SizeConverter.RightOffset;
let boff = this.SizeConverter.BottomOffset;
return (
- <Measure onResize={(r: any) => runInAction(() => { this.PanelWidth = r.entry.width; this.PanelHeight = r.entry.height; })}>
- {({ measureRef }) =>
- <div className="histogrambox-container" ref={measureRef}>
- <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
- <span className="histogrambox-yaxislabel-text">
- {labelY}
- </span>
- </div>
- <div className="histogrambox-primitives" style={{
- transform: `translate(${loff + 25}px, ${toff}px)`,
- width: `calc(100% - ${loff + roff + 25}px)`,
- height: `calc(100% - ${toff + boff}px)`,
- }}>
- <HistogramLabelPrimitives HistoBox={this} />
- <HistogramBoxPrimitives HistoBox={this} />
- </div>
- <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >
- {labelX}
- </div>
- </div>
- }
- </Measure>
+ <div className="histogrambox-container" onWheel={this.onScrollWheel}>
+ <div className="histogrambox-yaxislabel" onPointerDown={this.yLabelPointerDown} ref={this._dropYRef} >
+ <span className="histogrambox-yaxislabel-text">
+ {labelY}
+ </span>
+ </div>
+ <div className="histogrambox-primitives" style={{
+ transform: `translate(${loff + 25}px, ${toff}px)`,
+ width: `calc(100% - ${loff + roff + 25}px)`,
+ height: `calc(100% - ${toff + boff}px)`,
+ }}>
+ <HistogramLabelPrimitives HistoBox={this} />
+ <HistogramBoxPrimitives HistoBox={this} />
+ </div>
+ <div className="histogrambox-xaxislabel" onPointerDown={this.xLabelPointerDown} ref={this._dropXRef} >
+ {labelX}
+ </div>
+ </div>
);
}
}
diff --git a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
index 5785fe838..62aebd3c6 100644
--- a/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
+++ b/src/client/northstar/dash-nodes/HistogramLabelPrimitives.tsx
@@ -12,7 +12,7 @@ import { HistogramPrimitivesProps } from "./HistogramBoxPrimitives";
@observer
export class HistogramLabelPrimitives extends React.Component<HistogramPrimitivesProps> {
componentDidMount() {
- reaction(() => [this.props.HistoBox.PanelWidth, this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
+ reaction(() => [this.props.HistoBox.props.PanelWidth(), this.props.HistoBox.SizeConverter.LeftOffset, this.props.HistoBox.VisualBinRanges.length],
(fields) => HistogramLabelPrimitives.computeLabelAngle(fields[0], fields[1], this.props.HistoBox), { fireImmediately: true });
}
@@ -35,7 +35,7 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
if (!vb.length || !sc.Initialized) {
return (null);
}
- let dim = (axis === 0 ? this.props.HistoBox.PanelWidth : this.props.HistoBox.PanelHeight) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
+ let dim = (axis === 0 ? this.props.HistoBox.props.PanelWidth() : this.props.HistoBox.props.PanelHeight()) / ((axis === 0 && vb[axis] instanceof NominalVisualBinRange) ?
(12 + 5) : // (<number>FontStyles.AxisLabel.fontSize + 5)));
sc.MaxLabelSizes[axis].coords[axis] + 5);
@@ -49,12 +49,12 @@ export class HistogramLabelPrimitives extends React.Component<HistogramPrimitive
let yStart = (axis === 1 ? r.yFrom - textHeight / 2 : r.yFrom);
if (axis === 0 && vb[axis] instanceof NominalVisualBinRange) {
- let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.PanelWidth;
+ let space = (r.xTo - r.xFrom) / sc.RenderDimension * this.props.HistoBox.props.PanelWidth();
xStart += Math.max(textWidth / 2, (1 - textWidth / space) * textWidth / 2) - textHeight / 2;
}
let xPercent = axis === 1 ? `${xStart}px` : `${xStart / sc.RenderDimension * 100}%`;
- let yPercent = axis === 0 ? `${this.props.HistoBox.PanelHeight - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
+ let yPercent = axis === 0 ? `${this.props.HistoBox.props.PanelHeight() - sc.BottomOffset - textHeight}px` : `${yStart / sc.RenderDimension * 100}%`;
prims.push(
<div className="histogramLabelPrimitives-placer" key={DashUtils.GenerateGuid()} style={{ transform: `translate(${xPercent}, ${yPercent})` }}>
diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts
index b6672eca3..5c9c832c0 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -22,7 +22,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
@observable public Links: Doc[] = [];
@observable public BrushLinks: { l: Doc, b: Doc }[] = [];
@observable public BrushColors: number[] = [];
- @observable public FilterModels: FilterModel[] = [];
+ @observable public BarFilterModels: FilterModel[] = [];
@observable public Normalization: number = -1;
@observable public X: AttributeTransformationModel;
@@ -49,17 +49,24 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
throw new Error("Method not implemented.");
}
+
+ @computed public get FilterModels() {
+ return this.BarFilterModels;
+ }
@action
public AddFilterModels(filterModels: FilterModel[]): void {
- filterModels.filter(f => f !== null).forEach(fm => this.FilterModels.push(fm));
+ filterModels.filter(f => f !== null).forEach(fm => this.BarFilterModels.push(fm));
}
@action
public RemoveFilterModels(filterModels: FilterModel[]): void {
- ArrayUtil.RemoveMany(this.FilterModels, filterModels);
+ ArrayUtil.RemoveMany(this.BarFilterModels, filterModels);
}
@computed
public get FilterString(): string {
+ if (this.OverridingFilters.length > 0) {
+ return "(" + this.OverridingFilters.filter(fm => fm != null).map(fm => fm.ToPythonString()).join(" || ") + ")";
+ }
let filterModels: FilterModel[] = [];
return FilterModel.GetFilterModelsRecursive(this, new Set<IBaseFilterProvider>(), filterModels, true);
}
@@ -78,6 +85,27 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
return brushes;
}
+ _stackedFilters: (FilterModel[])[] = [];
+ @action
+ public DrillDown(up: boolean) {
+ if (!up) {
+ if (!this.BarFilterModels.length)
+ return;
+ this._stackedFilters.push(this.BarFilterModels.map(f => f));
+ this.OverridingFilters.length = 0;
+ this.OverridingFilters.push(...this._stackedFilters[this._stackedFilters.length - 1]);
+ this.BarFilterModels.map(fm => fm).map(fm => this.RemoveFilterModels([fm]));
+ //this.updateHistogram();
+ } else {
+ this.OverridingFilters.length = 0;
+ if (this._stackedFilters.length) {
+ this.OverridingFilters.push(...this._stackedFilters.pop()!);
+ }
+ // else
+ // this.updateHistogram();
+ }
+ }
+
private getAggregateParameters(histoX: AttributeTransformationModel, histoY: AttributeTransformationModel, histoValue: AttributeTransformationModel) {
let allAttributes = new Array<AttributeTransformationModel>(histoX, histoY, histoValue);
allAttributes = ArrayUtil.Distinct(allAttributes.filter(a => a.AggregateFunction !== AggregateFunction.None));
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index e65f2b9ed..a149da52f 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -172,6 +172,8 @@ export namespace DragManager {
StartDrag([ele], dragData, downX, downY, options);
}
+ export let AbortDrag: () => void = emptyFunction;
+
function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
if (!dragDiv) {
dragDiv = document.createElement("div");
@@ -272,23 +274,32 @@ export namespace DragManager {
);
};
- AbortDrag = () => {
+ let hideDragElements = () => {
+ dragElements.map(dragElement => dragElement.parentNode == dragDiv && dragDiv.removeChild(dragElement));
+ eles.map(ele => (ele.hidden = false));
+ };
+ let endDrag = () => {
document.removeEventListener("pointermove", moveHandler, true);
document.removeEventListener("pointerup", upHandler);
- dragElements.map(dragElement => { if (dragElement.parentNode === dragDiv) dragDiv.removeChild(dragElement); });
- eles.map(ele => (ele.hidden = false));
+ if (options) {
+ options.handlers.dragComplete({});
+ }
+ }
+
+ AbortDrag = () => {
+ hideDragElements();
+ endDrag();
};
const upHandler = (e: PointerEvent) => {
- AbortDrag();
- FinishDrag(eles, e, dragData, options, finishDrag);
+ hideDragElements();
+ dispatchDrag(eles, e, dragData, options, finishDrag);
+ endDrag();
};
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) {
+ function dispatchDrag(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;
if (parent) parent.removeChild(dragEle);
@@ -313,11 +324,6 @@ export namespace DragManager {
}
})
);
-
- if (options) {
- options.handlers.dragComplete({});
- }
}
- DocumentDecorations.Instance.Hidden = false;
}
}
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index 1b6647003..68a73375e 100644
--- a/src/client/util/TooltipTextMenu.tsx
+++ b/src/client/util/TooltipTextMenu.tsx
@@ -11,7 +11,8 @@ 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 { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list';
+import { liftTarget } from 'prosemirror-transform';
import {
faListUl,
} from '@fortawesome/free-solid-svg-icons';
@@ -24,19 +25,22 @@ 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;
+ public tooltip: HTMLElement;
private num_icons = 0;
private view: EditorView;
private fontStyles: MarkType[];
private fontSizes: MarkType[];
+ private listTypes: NodeType[];
private editorProps: FieldViewProps;
private state: EditorState;
private fontSizeToNum: Map<MarkType, number>;
private fontStylesToName: Map<MarkType, string>;
+ private listTypeToIcon: Map<NodeType, string>;
private fontSizeIndicator: HTMLSpanElement = document.createElement("span");
//dropdown doms
private fontSizeDom?: Node;
private fontStyleDom?: Node;
+ private listTypeBtnDom?: Node;
constructor(view: EditorView, editorProps: FieldViewProps) {
this.view = view;
@@ -58,8 +62,9 @@ export class TooltipTextMenu {
{ command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") },
{ command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") },
{ command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") },
- { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
- { command: lift, dom: this.icon("<", "lift") },
+ // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") },
+ // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") },
+ // { command: lift, dom: this.icon("<", "lift") },
];
//add menu items
items.forEach(({ dom, command }) => {
@@ -96,7 +101,11 @@ export class TooltipTextMenu {
this.fontSizeToNum.set(schema.marks.p72, 72);
this.fontSizes = Array.from(this.fontSizeToNum.keys());
- //this.addFontDropdowns();
+ //list types
+ this.listTypeToIcon = new Map();
+ this.listTypeToIcon.set(schema.nodes.bullet_list, ":");
+ this.listTypeToIcon.set(schema.nodes.ordered_list, "1)");
+ this.listTypes = Array.from(this.listTypeToIcon.keys());
this.update(view, undefined);
}
@@ -109,7 +118,7 @@ export class TooltipTextMenu {
//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));
+ fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
});
if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
@@ -128,7 +137,7 @@ export class TooltipTextMenu {
//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));
+ fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles));
});
if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
@@ -140,6 +149,29 @@ export class TooltipTextMenu {
this.tooltip.appendChild(this.fontStyleDom);
}
+ //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown
+ updateListItemDropdown(label: string, listTypeBtn: Node) {
+ //remove old btn
+ if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); }
+
+ //Make a dropdown of all list types
+ let toAdd: MenuItem[] = [];
+ this.listTypeToIcon.forEach((icon, type) => {
+ toAdd.push(this.dropdownNodeBtn(icon, "width: 40px;", type, this.view, this.listTypes, this.changeToNodeType));
+ });
+ //option to remove the list formatting
+ toAdd.push(this.dropdownNodeBtn("X", "width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType));
+
+ listTypeBtn = (new Dropdown(toAdd, {
+ label: label,
+ css: "color:white; width: 40px;"
+ }) as MenuItem).render(this.view).dom;
+
+ //add this new button and return it
+ this.tooltip.appendChild(listTypeBtn);
+ return listTypeBtn;
+ }
+
//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;
@@ -171,9 +203,18 @@ export class TooltipTextMenu {
return toggleMark(markType)(view.state, view.dispatch, view);
}
- //makes a button for the drop down
+ //remove all node typeand apply the passed-in one to the selected text
+ changeToNodeType(nodeType: NodeType | undefined, view: EditorView, allNodes: NodeType[]) {
+ //remove old
+ liftListItem(schema.nodes.list_item)(view.state, view.dispatch);
+ if (nodeType) { //add new
+ wrapInList(nodeType)(view.state, view.dispatch);
+ }
+ }
+
+ //makes a button for the drop down FOR MARKS
//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[]) {
+ dropdownMarkBtn(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,
@@ -186,6 +227,23 @@ export class TooltipTextMenu {
}
});
}
+
+ //makes a button for the drop down FOR NODE TYPES
+ //css is the style you want applied to the button
+ dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) {
+ return new MenuItem({
+ title: "",
+ label: label,
+ execEvent: "",
+ class: "menuicon",
+ css: css,
+ enable(state) { return true; },
+ run() {
+ changeToNodeInGroup(nodeType, view, groupNodes);
+ }
+ });
+ }
+
// Helper function to create menu icons
icon(text: string, name: string) {
let span = document.createElement("span");
@@ -262,6 +320,9 @@ export class TooltipTextMenu {
this.tooltip.style.width = 225 + "px";
this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px";
+ //UPDATE LIST ITEM DROPDOWN
+ this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!);
+
//UPDATE FONT STYLE DROPDOWN
let activeStyles = this.activeMarksOnSelection(this.fontStyles);
if (activeStyles.length === 1) {
@@ -288,7 +349,7 @@ export class TooltipTextMenu {
}
}
- //finds all active marks on selection
+ //finds all active marks on selection in given group
activeMarksOnSelection(markGroup: MarkType[]) {
//current selection
let { empty, $cursor, ranges } = this.view.state.selection as TextSelection;
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index f7c3e5a7b..0b5280c4a 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -141,10 +141,10 @@ export namespace UndoManager {
});
//TODO Make this return the return value
- export function RunInBatch(fn: () => void, batchName: string) {
+ export function RunInBatch<T>(fn: () => T, batchName: string) {
let batch = StartBatch(batchName);
try {
- runInAction(fn);
+ return runInAction(fn);
} finally {
batch.end();
}
diff --git a/src/client/views/.DS_Store b/src/client/views/.DS_Store
index 0964d5ff3..5008ddfcf 100644
--- a/src/client/views/.DS_Store
+++ b/src/client/views/.DS_Store
Binary files differ
diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss
index f78bf9ff8..0dfa6f0ff 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -3,18 +3,19 @@
.documentDecorations {
position: absolute;
}
+
.documentDecorations-container {
z-index: $docDecorations-zindex;
position: absolute;
top: 0;
- left:0;
+ left: 0;
display: grid;
grid-template-rows: 20px 8px 1fr 8px;
grid-template-columns: 8px 16px 1fr 8px 8px;
pointer-events: none;
#documentDecorations-centerCont {
- grid-column:3;
+ grid-column: 3;
background: none;
}
@@ -39,8 +40,8 @@
#documentDecorations-bottomRightResizer,
#documentDecorations-topRightResizer,
#documentDecorations-rightResizer {
- grid-column-start:5;
- grid-column-end:7;
+ grid-column-start: 5;
+ grid-column-end: 7;
}
#documentDecorations-topLeftResizer,
@@ -63,16 +64,16 @@
cursor: ew-resize;
}
.title{
- width:100%;
background: lightblue;
- grid-column-start:3;
+ grid-column-start: 3;
grid-column-end: 4;
pointer-events: auto;
+ overflow: hidden;
}
}
.documentDecorations-closeButton {
- background:$alt-accent;
+ background: $alt-accent;
opacity: 0.8;
grid-column-start: 4;
grid-column-end: 6;
@@ -80,8 +81,9 @@
text-align: center;
cursor: pointer;
}
+
.documentDecorations-minimizeButton {
- background:$alt-accent;
+ background: $alt-accent;
opacity: 0.8;
grid-column-start: 1;
grid-column-end: 3;
@@ -94,6 +96,7 @@
width: $MINIMIZED_ICON_SIZE;
height: $MINIMIZED_ICON_SIZE;
}
+
.documentDecorations-background {
background: lightblue;
position: absolute;
@@ -105,21 +108,9 @@
margin-left: 25px;
}
-.linkButton-empty:hover {
- background: $main-accent;
- transform: scale(1.05);
- cursor: pointer;
-}
-
-.linkButton-nonempty:hover {
- background: $main-accent;
- transform: scale(1.05);
- cursor: pointer;
-}
-
.linkButton-linker {
- position:absolute;
- bottom:0px;
+ position: absolute;
+ bottom: 0px;
left: 0px;
height: 20px;
width: 20px;
@@ -139,13 +130,14 @@
justify-content: center;
align-items: center;
}
+
.linkButton-tray {
grid-column: 1/4;
}
-.linkButton-empty {
+
+.linkButton-empty, .linkButton-nonempty {
height: 20px;
width: 20px;
- margin-top: 10px;
border-radius: 50%;
opacity: 0.9;
pointer-events: auto;
@@ -159,23 +151,51 @@
display: flex;
justify-content: center;
align-items: center;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ cursor: pointer;
+ }
}
-.linkButton-nonempty {
- height: 20px;
- width: 20px;
- margin-top: 10px;
- border-radius: 50%;
- opacity: 0.9;
+.templating-menu {
+ position: absolute;
+ bottom: 0;
+ left: 50px;
pointer-events: auto;
- background-color: $dark-color;
- color: $light-color;
- text-transform: uppercase;
- letter-spacing: 2px;
- font-size: 75%;
- transition: transform 0.2s;
- text-align: center;
- display: flex;
- justify-content: center;
- align-items: center;
+
+ .templating-button {
+ width: 20px;
+ height: 20px;
+ border-radius: 50%;
+ opacity: 0.9;
+ background-color: $dark-color;
+ color: $light-color;
+ text-align: center;
+ cursor: pointer;
+
+ &:hover {
+ background: $main-accent;
+ transform: scale(1.05);
+ }
+ }
+
+ #template-list {
+ position: absolute;
+ top: 0;
+ left: 30px;
+ width: 150px;
+ line-height: 25px;
+ max-height: 175px;
+ font-family: $sans-serif;
+ font-size: 12px;
+ background-color: $light-color-secondary;
+ padding: 2px 12px;
+ list-style: none;
+
+ input {
+ margin-right: 10px;
+ }
+ }
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 6e361d76a..084220f76 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, runInAction } from "mobx";
+import { action, computed, observable, runInAction, untracked, reaction } from "mobx";
import { observer } from "mobx-react";
import { emptyFunction, Utils } from "../../Utils";
import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
@@ -8,7 +8,9 @@ import './DocumentDecorations.scss';
import { MainOverlayTextBox } from "./MainOverlayTextBox";
import { DocumentView, PositionDocument } from "./nodes/DocumentView";
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 { Cast, FieldValue, NumCast, StrCast } from "../../new_fields/Types";
@@ -26,9 +28,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _isPointerDown = false;
private _resizing = "";
private keyinput: React.RefObject<HTMLInputElement>;
- private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
private _resizeBorderWidth = 16;
- private _linkBoxHeight = 30;
+ private _linkBoxHeight = 20;
private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
private _linkerButton = React.createRef<HTMLDivElement>();
@@ -36,67 +37,76 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
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 ? Cast(this._documents[0].props.Document.title, "string", "") : "";
- @observable private _fieldKey: string = "title";
+ @observable private _title: string = "";
+ @observable private _edtingTitle = false;
+ @observable private _fieldKey = "title";
@observable private _hidden = false;
@observable private _opacity = 1;
- @observable private _dragging = false;
@observable private _iconifying = false;
@observable public Interacting = false;
-
constructor(props: Readonly<{}>) {
super(props);
DocumentDecorations.Instance = this;
- this.handleChange = this.handleChange.bind(this);
this.keyinput = React.createRef();
+ reaction(() => SelectionManager.SelectedDocuments().slice(), (docs) => docs.length === 0 && (this._edtingTitle = false));
}
- @action
- handleChange = (event: any) => {
- this._title = event.target.value;
- }
-
- @action
- enterPressed = (e: any) => {
+ @action titleChanged = (event: any) => { this._title = event.target.value; }
+ @action titleBlur = () => { this._edtingTitle = false; }
+ @action titleEntered = (e: any) => {
var key = e.keyCode || e.which;
// enter pressed
if (key === 13) {
var text = e.target.value;
if (text[0] === '#') {
- let command = text.slice(1, text.length);
- this._fieldKey = command;
- // if (command === "Title" || command === "title") {
- // this._fieldKey = KeyStore.Title;
- // }
- // else if (command === "Width" || command === "width") {
- // this._fieldKey = KeyStore.Width;
- // }
- this._title = "changed";
- // TODO: Change field with switch statement
+ this._fieldKey = text.slice(1, text.length);
+ this._title = this.selectionTitle;
}
else {
- if (this._documents.length > 0) {
- let field = this._documents[0].props.Document[this._fieldKey];
- if (typeof field === "string") {
- this._documents.forEach(d =>
- d.props.Document[this._fieldKey] = this._title);
- }
- else if (typeof field === "number") {
- this._documents.forEach(d =>
+ if (SelectionManager.SelectedDocuments().length > 0) {
+ let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
+ if (typeof field === "number") {
+ SelectionManager.SelectedDocuments().forEach(d =>
d.props.Document[this._fieldKey] = +this._title);
+ } else {
+ SelectionManager.SelectedDocuments().forEach(d =>
+ d.props.Document[this._fieldKey] = this._title);
}
- this._title = "changed";
}
}
e.target.blur();
}
}
+ @action onTitleDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ e.stopPropagation();
+ this.onBackgroundDown(e);
+ document.removeEventListener("pointermove", this.onTitleMove);
+ document.removeEventListener("pointerup", this.onTitleUp);
+ document.addEventListener("pointermove", this.onTitleMove);
+ document.addEventListener("pointerup", this.onTitleUp);
+ }
+ @action onTitleMove = (e: PointerEvent): void => {
+ if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) {
+ this.Interacting = true;
+ }
+ if (this.Interacting) this.onBackgroundMove(e);
+ e.stopPropagation();
+ }
+ @action onTitleUp = (e: PointerEvent): void => {
+ if (Math.abs(e.clientX - this._downX) < 4 || Math.abs(e.clientY - this._downY) < 4) {
+ this._title = this.selectionTitle;
+ this._edtingTitle = true;
+ }
+ document.removeEventListener("pointermove", this.onTitleMove);
+ document.removeEventListener("pointerup", this.onTitleUp);
+ this.onBackgroundUp(e);
+ }
@computed
get Bounds(): { x: number, y: number, b: number, r: number } {
- this._documents = SelectionManager.SelectedDocuments();
return SelectionManager.SelectedDocuments().reduce((bounds, documentView) => {
if (documentView.props.isTopMost) {
return bounds;
@@ -111,47 +121,39 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE });
}
-
- @computed
- public get Hidden() { return this._hidden; }
- public set Hidden(value: boolean) { this._hidden = value; }
-
- _lastDrag: number[] = [0, 0];
onBackgroundDown = (e: React.PointerEvent): void => {
document.removeEventListener("pointermove", this.onBackgroundMove);
- document.addEventListener("pointermove", this.onBackgroundMove);
document.removeEventListener("pointerup", this.onBackgroundUp);
+ document.addEventListener("pointermove", this.onBackgroundMove);
document.addEventListener("pointerup", this.onBackgroundUp);
- this._lastDrag = [e.clientX, e.clientY];
e.stopPropagation();
- if (e.currentTarget.localName !== "input") {
- e.preventDefault();
- }
+ e.preventDefault();
}
@action
onBackgroundMove = (e: PointerEvent): void => {
let dragDocView = SelectionManager.SelectedDocuments()[0];
const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
- let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
+ let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
dragData.xOffset = xoff;
dragData.yOffset = yoff;
dragData.aliasOnDrop = false;
- let move = SelectionManager.SelectedDocuments()[0].props.moveDocument;
- dragData.moveDocument = move;
- this.Interacting = this._dragging = true;
+ dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument;
+ this.Interacting = true;
+ this._hidden = true;
document.removeEventListener("pointermove", this.onBackgroundMove);
document.removeEventListener("pointerup", this.onBackgroundUp);
+ document.removeEventListener("pointermove", this.onTitleMove);
+ document.removeEventListener("pointerup", this.onTitleUp);
DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
- handlers: {
- dragComplete: action(() => this.Interacting = this._dragging = false),
- },
+ handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
hideSource: true
});
e.stopPropagation();
}
+ @action
onBackgroundUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onBackgroundMove);
document.removeEventListener("pointerup", this.onBackgroundUp);
@@ -229,26 +231,30 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
iconDoc.nativeHeight = 0;
iconDoc.x = NumCast(doc.x);
iconDoc.y = NumCast(doc.y) - 24;
- iconDoc.proto = doc;
iconDoc.maximizedDoc = doc;
doc.minimizedDoc = iconDoc;
+ console.log("Layout " + iconDoc.layout)
docView.props.addDocument && docView.props.addDocument(iconDoc, false);
return iconDoc;
}
@action
public getIconDoc = async (docView: DocumentView): Promise<Doc | undefined> => {
let doc = docView.props.Document;
-
- const minDoc = await Cast(doc.minimizedDoc, Doc);
- if (minDoc) return minDoc;
-
- const field = StrCast(doc.backgroundLayout, undefined);
- if (field !== undefined) return this.createIcon(docView, field);
-
- const layout = StrCast(doc.layout, undefined);
- if (layout !== undefined) return this.createIcon(docView, field);
-
- return undefined;
+ let iconDoc: Doc | undefined = await Cast(doc.minimizedDoc, Doc);
+ if (!iconDoc) {
+ const field = StrCast(doc.backgroundLayout, undefined);
+ if (field !== undefined) {
+ iconDoc = this.createIcon(docView, field);
+ } else {
+ const layout = StrCast(doc.layout, undefined);
+ if (layout !== undefined) {
+ iconDoc = this.createIcon(docView, field);
+ }
+ }
+ }
+ if (SelectionManager.SelectedDocuments()[0].props.addDocument !== undefined)
+ SelectionManager.SelectedDocuments()[0].props.addDocument!(iconDoc!);
+ return iconDoc;
}
@action
onMinimizeUp = (e: PointerEvent): void => {
@@ -262,7 +268,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
minDocs.map(minDoc => {
minDoc.x = NumCast(minDocs[0].x);
minDoc.y = NumCast(minDocs[0].y);
- minDoc.linkTags = new List(minDocs);
+ minDoc.linkTags = new List(minDocs.filter(d => d != minDoc));
if (this._iconifying && selectedDocs[0].props.removeDocument) {
selectedDocs[0].props.removeDocument(minDoc);
(minDoc.maximizedDoc as Doc).minimizedDoc = undefined;
@@ -450,17 +456,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
}
- getValue = (): string => {
- if (this._documents.length > 0) {
- let field = this._documents[0].props.Document[this._fieldKey];
+ @computed
+ get selectionTitle(): string {
+ if (SelectionManager.SelectedDocuments().length === 1) {
+ let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
if (typeof field === "string") {
return field;
}
else if (typeof field === "number") {
return field.toString();
}
+ } else if (SelectionManager.SelectedDocuments().length > 1) {
+ return "-multiple-";
}
- return this._title;
+ return "-unset-";
}
changeFlyoutContent = (): void => {
@@ -469,10 +478,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
// buttonOnPointerUp = (e: React.PointerEvent): void => {
// e.stopPropagation();
// }
+
render() {
var bounds = this.Bounds;
let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
- if (bounds.x === Number.MAX_VALUE || !seldoc) {
+ if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden || isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
return (null);
}
let minimizeIcon = (
@@ -480,14 +490,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
{SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
</div>);
- if (this.Hidden) {
- return (null);
- }
- if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
- console.log("DocumentDecorations: Bounds Error");
- return (null);
- }
-
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
let selFirst = SelectionManager.SelectedDocuments()[0];
@@ -498,17 +500,30 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
anchorPoint={anchorPoints.RIGHT_TOP}
content={<LinkMenu docView={selFirst}
changeFlyout={this.changeFlyoutContent} />}>
- {/* <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div> */}
- <div className={"linkButton-empty"} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
- </Flyout>);
+ <div className={"linkButton-" + (linkCount ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
+ </Flyout >);
}
+
+ let templates: Map<Template, boolean> = new Map();
+ let doc = SelectionManager.SelectedDocuments()[0];
+ Array.from(Object.values(Templates.TemplateList)).map(template => {
+ let docTemps = doc.templates;
+ let checked = false;
+ docTemps.forEach(temp => {
+ if (template.Name === temp.Name) {
+ checked = true;
+ }
+ });
+ templates.set(template, checked);
+ });
+
return (<div className="documentDecorations">
<div className="documentDecorations-background" style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px",
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2,
- pointerEvents: this._dragging ? "none" : "all",
+ pointerEvents: this.Interacting ? "none" : "all",
zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0,
}} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
</div>
@@ -521,7 +536,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}}>
{minimizeIcon}
- <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} />
+ {this._edtingTitle ?
+ <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this._title} onBlur={this.titleBlur} onChange={this.titleChanged} onKeyPress={this.titleEntered} /> :
+ <div className="title" onPointerDown={this.onTitleDown} ><span>{`${this.selectionTitle}`}</span></div>}
<div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div>
<div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
@@ -535,6 +552,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div title="View Links" className="linkFlyout" ref={this._linkButton}> {linkButton} </div>
<div className="linkButton-linker" ref={this._linkerButton} onPointerDown={this.onLinkerButtonDown}>∞</div>
+ <TemplateMenu doc={doc} templates={templates} />
</div >
</div>
);
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 1e26893c5..1c0d13545 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -139,21 +139,25 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
let curPage = NumCast(this.props.Document.curPage, -1);
let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
if (strokeData.page === -1 || strokeData.page === curPage) {
- paths.push(<InkingStroke key={id} id={id} line={strokeData.pathData}
+ paths.push(<InkingStroke key={id} id={id}
+ line={strokeData.pathData}
+ count={strokeData.pathData.length}
offsetX={this.maxCanvasDim - this.inkMidX}
offsetY={this.maxCanvasDim - this.inkMidY}
- color={strokeData.color} width={strokeData.width}
- tool={strokeData.tool} deleteCallback={this.removeLine} />);
+ color={strokeData.color}
+ width={strokeData.width}
+ tool={strokeData.tool}
+ deleteCallback={this.removeLine} />);
}
return paths;
}, [] as JSX.Element[]);
- return [<svg className={`inkingCanvas-paths-markers`} key="Markers"
+ return [<svg className={`inkingCanvas-paths-ink`} key="Pens"
style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
- {paths.filter(path => path.props.tool === InkTool.Highlighter)}
+ {paths.filter(path => path.props.tool !== InkTool.Highlighter)}
</svg>,
- <svg className={`inkingCanvas-paths-ink`} key="Pens"
+ <svg className={`inkingCanvas-paths-markers`} key="Markers"
style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
- {paths.filter(path => path.props.tool !== InkTool.Highlighter)}
+ {paths.filter(path => path.props.tool === InkTool.Highlighter)}
</svg>];
}
diff --git a/src/client/views/InkingStroke.scss b/src/client/views/InkingStroke.scss
new file mode 100644
index 000000000..cdbfdcff3
--- /dev/null
+++ b/src/client/views/InkingStroke.scss
@@ -0,0 +1,3 @@
+.inkingStroke-marker {
+ mix-blend-mode: multiply
+} \ No newline at end of file
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 616299146..37b1f5899 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,14 +1,16 @@
import { observer } from "mobx-react";
-import { observable } from "mobx";
+import { observable, trace } from "mobx";
import { InkingControl } from "./InkingControl";
import React = require("react");
import { InkTool } from "../../new_fields/InkField";
+import "./InkingStroke.scss";
interface StrokeProps {
offsetX: number;
offsetY: number;
id: string;
+ count: number;
line: Array<{ x: number, y: number }>;
color: string;
width: string;
@@ -48,10 +50,12 @@ export class InkingStroke extends React.Component<StrokeProps> {
render() {
let pathStyle = this.createStyle();
let pathData = this.parseData(this.props.line);
+ let pathlength = this.props.count; // bcz: this is needed to force reactions to the line data changes
+ let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ? "all" : "none";
return (
- <path d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
+ <path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }} strokeLinejoin="round" strokeLinecap="round"
onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />
);
}
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 2f899ff28..89fdf595a 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -12,6 +12,10 @@ body {
left:0;
}
+div {
+ user-select: none;
+}
+
#dash-title {
position: absolute;
right: 46.5%;
@@ -55,7 +59,7 @@ p {
::-webkit-scrollbar {
-webkit-appearance: none;
height: 5px;
- width: 5px;
+ width: 10px;
}
::-webkit-scrollbar-thumb {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index adc15a4bc..3f8314d5f 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -216,7 +216,7 @@ export class Main extends React.Component {
let audiourl = "http://techslides.com/demos/samples/sample.mp3";
let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
- let addTextNode = action(() => Docs.TextDocument({ width: 200, height: 200, title: "a text note" }));
+ let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
let addSchemaNode = action(() => Docs.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index a7ced36bf..de8b3707d 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -20,7 +20,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
public static Instance: MainOverlayTextBox;
@observable public TextDoc?: Doc = undefined;
public TextScroll: number = 0;
- @observable _textRect: any;
@observable _textXf: () => Transform = () => Transform.Identity();
private _textFieldKey: string = "data";
private _textColor: string | null = null;
@@ -46,7 +45,6 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
if (div) {
this._textColor = div.style.color;
div.style.color = "transparent";
- this._textRect = div.getBoundingClientRect();
this.TextScroll = div.scrollTop;
}
}
@@ -87,13 +85,12 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
}
render() {
- if (this.TextDoc) {
- 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" }} >
+ if (this.TextDoc && this._textTargetDiv) {
+ let textRect = this._textTargetDiv.getBoundingClientRect();
+ let s = this._textXf().Scale;
+ return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} >
<div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
- style={{ width: `${NumCast(this.TextDoc.width)}px`, height: `${NumCast(this.TextDoc.height)}px` }}>
+ style={{ width: `${NumCast(this.TextDoc.width) * s}px`, height: `${NumCast(this.TextDoc.height) * s}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} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} />
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 744ec0535..4359ba093 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -26,12 +26,11 @@ export class PreviewCursor extends React.Component<{}> {
// DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore
// the keyPress here.
//if not these keys, make a textbox if preview cursor is active!
- if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
- PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
- PreviewCursor.Visible = false;
- } else if (e.ctrlKey) {
- if (e.key == "v") {
+ if (e.key.startsWith("F") && !e.key.endsWith("F")) {
+ } else if (e.key != "Escape" && e.key != "Alt" && e.key != "Shift" && e.key != "Meta" && e.key != "Control" && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) {
+ if ((!e.ctrlKey && !e.metaKey) || e.key === "v") {
PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
+ PreviewCursor.Visible = false;
}
}
}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
new file mode 100644
index 000000000..8eb2fc6c6
--- /dev/null
+++ b/src/client/views/TemplateMenu.tsx
@@ -0,0 +1,75 @@
+import { observable, computed, action } from "mobx";
+import React = require("react");
+import { observer } from "mobx-react";
+import './DocumentDecorations.scss';
+import { Template } from "./Templates";
+import { DocumentView } from "./nodes/DocumentView";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+@observer
+class TemplateToggle extends React.Component<{ template: Template, checked: boolean, toggle: (event: React.ChangeEvent<HTMLInputElement>, template: Template) => void }> {
+ render() {
+ if (this.props.template) {
+ return (
+ <li>
+ <input type="checkbox" checked={this.props.checked} onChange={(event) => this.props.toggle(event, this.props.template)} />
+ {this.props.template.Name}
+ </li>
+ );
+ } else {
+ return (null);
+ }
+ }
+}
+
+export interface TemplateMenuProps {
+ doc: DocumentView;
+ templates: Map<Template, boolean>;
+}
+
+@observer
+export class TemplateMenu extends React.Component<TemplateMenuProps> {
+
+ @observable private _hidden: boolean = true;
+ @observable private _templates: Map<Template, boolean> = this.props.templates;
+
+
+ @action
+ toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
+ if (event.target.checked) {
+ this.props.doc.addTemplate(template);
+ this._templates.set(template, true);
+ } else {
+ this.props.doc.removeTemplate(template);
+ this._templates.set(template, false);
+ }
+ }
+
+ @action
+ componentWillReceiveProps(nextProps: TemplateMenuProps) {
+ this._templates = nextProps.templates;
+ }
+
+ @action
+ toggleTemplateActivity = (): void => {
+ this._hidden = !this._hidden;
+ }
+
+ render() {
+ let templateMenu: Array<JSX.Element> = [];
+ this._templates.forEach((checked, template) => {
+ templateMenu.push(<TemplateToggle key={template.Name} template={template} checked={checked} toggle={this.toggleTemplate} />);
+ });
+
+ return (
+ <div className="templating-menu" >
+ <div className="templating-button" onClick={() => this.toggleTemplateActivity()}>+</div>
+ <ul id="template-list" style={{ display: this._hidden ? "none" : "block" }}>
+ {templateMenu}
+ </ul>
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
new file mode 100644
index 000000000..fef69392d
--- /dev/null
+++ b/src/client/views/Templates.tsx
@@ -0,0 +1,66 @@
+import React = require("react");
+
+export enum TemplatePosition {
+ InnerTop,
+ InnerBottom,
+ InnerRight,
+ InnerLeft,
+ OutterTop,
+ OutterBottom,
+ OutterRight,
+ OutterLeft,
+}
+
+export class Template {
+ constructor(name: string, position: TemplatePosition, layout: string) {
+ this._name = name;
+ this._position = position;
+ this._layout = layout;
+ }
+
+ private _name: string;
+ private _position: TemplatePosition;
+ private _layout: string;
+
+ get Name(): string {
+ return this._name;
+ }
+
+ get Position(): TemplatePosition {
+ return this._position;
+ }
+
+ get Layout(): string {
+ return this._layout;
+ }
+}
+
+export namespace Templates {
+ // export const BasicLayout = new Template("Basic layout", "{layout}");
+
+ export const OuterCaption = new Template("Outer caption", TemplatePosition.OutterBottom,
+ `<div><div style="margin:auto; height:calc(100%); width:100%;">{layout}</div><div style="height:(100% + 50px); width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={CaptionKey} /></div></div>`
+ );
+
+ export const InnerCaption = new Template("Inner caption", TemplatePosition.InnerBottom,
+ `<div><div style="margin:auto; height:calc(100% - 50px); width:100%;">{layout}</div><div style="height:50px; width:100%; position:absolute"><FormattedTextBox {...props} fieldKey={CaptionKey}/></div></div>`
+ );
+
+ export const SideCaption = new Template("Side caption", TemplatePosition.OutterRight,
+ `<div><div style="margin:auto; height:100%; width:100%;">{layout}</div><div style="height:100%; width:300px; position:absolute; top: 0; right: -300px;"><FormattedTextBox {...props} fieldKey={CaptionKey}/></div> </div>`
+ );
+
+ export const Title = new Template("Title", TemplatePosition.InnerTop,
+ `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div><div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; padding:2px 10px">{Title}</div></div>`
+ );
+
+ export const TemplateList: Template[] = [Title, OuterCaption, InnerCaption, SideCaption];
+
+ export function sortTemplates(a: Template, b: Template) {
+ if (a.Position < b.Position) { return -1; }
+ if (a.Position > b.Position) { return 1; }
+ return 0;
+ }
+
+}
+
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index ed761d3f3..76adfcdcd 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -8,6 +8,7 @@ import { Doc, FieldResult, Opt } from '../../../new_fields/Doc';
import { listSpec } from '../../../new_fields/Schema';
import { List } from '../../../new_fields/List';
import { Id } from '../../../new_fields/RefField';
+import { SelectionManager } from '../../util/SelectionManager';
export enum CollectionViewType {
Invalid,
@@ -104,8 +105,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
// set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
if (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid) {
let zoom = NumCast(this.props.Document.scale, 1);
- let screen = this.props.ScreenToLocalTransform().inverse().Scale / (this.props as any).ContentScaling() * zoom;
- doc.zoomBasis = screen;
+ doc.zoomBasis = zoom;
}
}
return true;
@@ -145,6 +145,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
return true;
}
if (this.removeDocument(doc)) {
+ SelectionManager.DeselectAll();
return addDocument(doc);
}
return false;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 1574562c6..958335425 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -241,6 +241,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.props.Document.data = json;
if (this.undohack && !this.hack) {
this.undohack.end();
+ this.undohack = undefined;
}
this.hack = false;
}
diff --git a/src/client/views/collections/CollectionPDFView.scss b/src/client/views/collections/CollectionPDFView.scss
index 0eca3f1cd..f6fb79582 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -1,20 +1,39 @@
.collectionPdfView-buttonTray {
- top : 25px;
+ top : 15px;
left : 20px;
position: relative;
transform-origin: left top;
position: absolute;
}
+.collectionPdfView-thumb {
+ width:25px;
+ height:25px;
+ transform-origin: left top;
+ position: absolute;
+ background: darkgray;
+}
+.collectionPdfView-slider {
+ width:25px;
+ height:25px;
+ transform-origin: left top;
+ position: absolute;
+ background: lightgray;
+}
.collectionPdfView-cont{
width: 100%;
height: 100%;
position: absolute;
top: 0;
left:0;
-
+}
+.collectionPdfView-cont-dragging {
+ span {
+ user-select: none;
+ }
}
.collectionPdfView-backward {
color : white;
+ font-size: 24px;
top :0px;
left : 0px;
position: absolute;
@@ -22,8 +41,9 @@
}
.collectionPdfView-forward {
color : white;
+ font-size: 24px;
top :0px;
- left : 35px;
+ left : 45px;
position: absolute;
background-color: rgba(50, 50, 50, 0.2);
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 5a1af354a..b3762206a 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,4 +1,4 @@
-import { action } from "mobx";
+import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { ContextMenu } from "../ContextMenu";
import "./CollectionPDFView.scss";
@@ -17,18 +17,44 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
public static LayoutString(fieldKey: string = "data") {
return FieldView.LayoutString(CollectionPDFView, fieldKey);
}
+ @observable _inThumb = false;
+ private set curPage(value: number) { this.props.Document.curPage = value; }
private get curPage() { return NumCast(this.props.Document.curPage, -1); }
private get numPages() { return NumCast(this.props.Document.numPages); }
@action onPageBack = () => this.curPage > 1 ? (this.props.Document.curPage = this.curPage - 1) : -1;
@action onPageForward = () => this.curPage < this.numPages ? (this.props.Document.curPage = this.curPage + 1) : -1;
+ @action
+ onThumbDown = (e: React.PointerEvent) => {
+ document.addEventListener("pointermove", this.onThumbMove, false);
+ document.addEventListener("pointerup", this.onThumbUp, false);
+ e.stopPropagation();
+ this._inThumb = true;
+ }
+ @action
+ onThumbMove = (e: PointerEvent) => {
+ let pso = (e.clientY - (e as any).target.parentElement.getBoundingClientRect().top) / (e as any).target.parentElement.getBoundingClientRect().height;
+ this.curPage = Math.trunc(Math.min(this.numPages, pso * this.numPages + 1));
+ e.stopPropagation();
+ }
+ @action
+ onThumbUp = (e: PointerEvent) => {
+ this._inThumb = false;
+ document.removeEventListener("pointermove", this.onThumbMove);
+ document.removeEventListener("pointerup", this.onThumbUp);
+ }
+ nativeWidth = () => NumCast(this.props.Document.nativeWidth);
+ nativeHeight = () => NumCast(this.props.Document.nativeHeight);
private get uIButtons() {
- let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+ let ratio = (this.curPage - 1) / this.numPages * 100;
return (
- <div className="collectionPdfView-buttonTray" key="tray" style={{ transform: `scale(${scaling}, ${scaling})` }}>
+ <div className="collectionPdfView-buttonTray" key="tray" style={{ height: "100%" }}>
<button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
<button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
+ <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} >
+ <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} />
+ </div>
</div>
);
}
@@ -51,7 +77,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
render() {
return (
- <CollectionBaseView {...this.props} className="collectionPdfView-cont" onContextMenu={this.onContextMenu}>
+ <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}>
{this.subView}
</CollectionBaseView>
);
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 58d20819b..6add6ada8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked } from "mobx";
+import { action, computed, observable, untracked, runInAction } from "mobx";
import { observer } from "mobx-react";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
@@ -58,7 +58,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@observable _newKeyName: string = "";
@computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); }
- @computed get columns() { return Cast(this.props.Document.columns, listSpec("string"), []); }
+ @computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); }
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
renderCell = (rowProps: CellInfo) => {
@@ -156,9 +156,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
toggleKey = (key: string) => {
- let list = Cast(this.props.Document.columns, listSpec("string"));
+ let list = Cast(this.props.Document.schemaColumns, listSpec("string"));
if (list === undefined) {
- this.props.Document.columns = list = new List<string>([key]);
+ this.props.Document.schemaColumns = list = new List<string>([key]);
} else {
const index = list.indexOf(key);
if (index === -1) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 3b2f79be1..3e8a8a442 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -3,4 +3,10 @@
stroke-width: 3;
transform: translate(10000px,10000px);
pointer-events: all;
+}
+.collectionfreeformlinkview-linkCircle {
+ stroke: black;
+ stroke-width: 3;
+ transform: translate(10000px,10000px);
+ pointer-events: all;
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index d4987fc18..3b700b053 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -4,20 +4,37 @@ import "./CollectionFreeFormLinkView.scss";
import React = require("react");
import v5 = require("uuid/v5");
import { StrCast, NumCast, BoolCast } from "../../../../new_fields/Types";
-import { Doc } from "../../../../new_fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
+import { InkingControl } from "../../InkingControl";
export interface CollectionFreeFormLinkViewProps {
A: Doc;
B: Doc;
LinkDocs: Doc[];
+ addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Doc) => boolean;
}
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
onPointerDown = (e: React.PointerEvent) => {
- this.props.LinkDocs.map(l =>
- console.log("Link:" + StrCast(l.title)));
+ if (e.button === 0 && !InkingControl.Instance.selectedTool) {
+ let a = this.props.A;
+ let b = this.props.B;
+ let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : a[WidthSym]() / 2);
+ let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2);
+ let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2);
+ let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2);
+ this.props.LinkDocs.map(l => {
+ let width = l[WidthSym]();
+ l.x = (x1 + x2) / 2 - width / 2;
+ l.y = (y1 + y2) / 2 + 10;
+ if (!this.props.removeDocument(l)) this.props.addDocument(l, false);
+ });
+ e.stopPropagation();
+ e.preventDefault();
+ }
}
render() {
let l = this.props.LinkDocs;
@@ -28,10 +45,14 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2);
let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2);
return (
- <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
- style={{ strokeWidth: `${l.length * 5}` }}
- x1={`${x1}`} y1={`${y1}`}
- x2={`${x2}`} y2={`${y2}`} />
+ <>
+ <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
+ style={{ strokeWidth: `${l.length * 5}` }}
+ x1={`${x1}`} y1={`${y1}`}
+ x2={`${x2}`} y2={`${y2}`} />
+ <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
+ cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={10} onPointerDown={this.onPointerDown} />
+ </>
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index f693d55e8..4a75fccff 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -84,6 +84,14 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
if (containerDoc) {
equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.proto!);
}
+ if (view.props.ContainingCollectionView) {
+ let collid = view.props.ContainingCollectionView.props.Document[Id];
+ Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []).
+ filter(child =>
+ child[Id] === collid).map(view =>
+ DocumentManager.Instance.getDocumentViews(view).map(view =>
+ equalViews.push(view)));
+ }
return equalViews.filter(sv => sv.props.ContainingCollectionView && sv.props.ContainingCollectionView.props.Document === this.props.Document);
}
@@ -107,7 +115,8 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
);
return drawnPairs;
}, [] as { a: Doc, b: Doc, l: Doc[] }[]);
- return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l} />);
+ return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l}
+ removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />);
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dcded7648..e54553d2b 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from "mobx";
+import { action, computed, trace } from "mobx";
import { observer } from "mobx-react";
import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
import { DocumentManager } from "../../../util/DocumentManager";
@@ -19,10 +19,9 @@ import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
-import { Doc } from "../../../../new_fields/Doc";
+import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types";
import { pageSchema } from "../../nodes/ImageBox";
-import { List } from "../../../../new_fields/List";
import { Id } from "../../../../new_fields/RefField";
export const panZoomSchema = createSchema({
@@ -98,7 +97,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
if (!NumCast(d.height)) {
let nw = NumCast(d.nativeWidth);
let nh = NumCast(d.nativeHeight);
- d.height = nw && nh ? nh / nw * NumCast(d.Width) : 300;
+ d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
}
this.bringToFront(d);
});
@@ -110,12 +109,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
@action
- cleanupInteractions = () => {
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- }
-
- @action
onPointerDown = (e: React.PointerEvent): void => {
let childSelected = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), [] as Doc[]).filter(doc => doc).reduce((childSelected, doc) => {
var dv = DocumentManager.Instance.getDocumentView(doc);
@@ -126,7 +119,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
(e.button === 0 && e.altKey)) && (childSelected || this.props.active()))) ||
(!CollectionFreeFormView.RIGHT_BTN_DRAG &&
((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && (childSelected || this.props.active())))) {
- this.cleanupInteractions();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
this._lastX = e.pageX;
@@ -135,7 +129,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
onPointerUp = (e: PointerEvent): void => {
- this.cleanupInteractions();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
}
@action
@@ -243,11 +238,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
focusDocument = (doc: Doc) => {
this.setPan(
- Cast(doc.x, "number", 0) + Cast(doc.width, "number", 0) / 2,
- Cast(doc.y, "number", 0) + Cast(doc.height, "number", 0) / 2);
+ NumCast(doc.x) + NumCast(doc.width) / 2,
+ NumCast(doc.y) + NumCast(doc.height) / 2);
this.props.focus(this.props.Document);
}
+
getDocumentViewProps(document: Doc): DocumentViewProps {
return {
Document: document,
@@ -258,8 +254,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
ScreenToLocalTransform: this.getTransform,
isTopMost: false,
selectOnLoad: document[Id] === this._selectOnLoaded,
- PanelWidth: () => Cast(document.width, "number", 0),//TODO Types These are inline functions
- PanelHeight: () => Cast(document.height, "number", 0),
+ PanelWidth: document[WidthSym],
+ PanelHeight: document[HeightSym],
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
focus: this.focusDocument,
@@ -268,10 +264,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
};
}
- @computed
+ @computed.struct
get views() {
let curPage = FieldValue(this.Document.curPage, -1);
let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => {
+ if (!FieldValue(doc)) return prev;
var page = Cast(doc.page, "number", -1);
if (page === curPage || page === -1) {
let minim = Cast(doc.isMinimized, "boolean");
@@ -292,19 +289,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
- private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.getDocumentViewProps(this.props.Document)} />];
+ private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />];
render() {
const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
return (
<div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
style={{ borderRadius: "inherit" }}
onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
- {/* <svg viewBox="0 0 180 18" style={{ top: "50%", opacity: 0.05, position: "absolute" }}>
- <text y="15" >
- {this.props.Document.Title}
- </text>
- </svg> */}
- <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
+ <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
@@ -316,7 +308,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
</CollectionFreeFormLinksView>
{/* <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> */}
</CollectionFreeFormViewPannableContents>
- <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} />
+ <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />
</MarqueeView>
</div>
);
@@ -337,12 +329,12 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps> {
}
@observer
-class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps> {
+class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
@computed get backgroundView() {
let backgroundLayout = Cast(this.props.Document.backgroundLayout, "string", "");
return !backgroundLayout ? (null) :
(<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
- isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+ isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
return this.backgroundView;
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 644a8784c..85a12defa 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -23,6 +23,7 @@ interface MarqueeViewProps {
selectDocuments: (docs: Doc[]) => void;
removeDocument: (doc: Doc) => boolean;
addLiveTextDocument: (doc: Doc) => void;
+ isSelected: () => boolean;
}
@observer
@@ -33,12 +34,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@observable _downX: number = 0;
@observable _downY: number = 0;
@observable _visible: boolean = false;
+ _commandExecuted = false;
@action
cleanupInteractions = (all: boolean = false) => {
if (all) {
- document.removeEventListener("pointermove", this.onPointerMove, true);
document.removeEventListener("pointerup", this.onPointerUp, true);
+ document.removeEventListener("pointermove", this.onPointerMove, true);
}
document.removeEventListener("keydown", this.marqueeCommand, true);
this._visible = false;
@@ -56,6 +58,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.pageX;
this._downY = this._lastY = e.pageY;
+ this._commandExecuted = false;
PreviewCursor.Visible = false;
if ((CollectionFreeFormView.RIGHT_BTN_DRAG && e.button === 0 && !e.altKey && !e.metaKey && this.props.container.props.active()) ||
(!CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && this.props.container.props.active())) {
@@ -75,7 +78,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
if (!e.cancelBubble) {
if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD ||
Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
- this._visible = true;
+ if (!this._commandExecuted) {
+ this._visible = true;
+ }
e.stopPropagation();
e.preventDefault();
}
@@ -129,22 +134,27 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@undoBatch
@action
marqueeCommand = (e: KeyboardEvent) => {
- if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
+ if (this._commandExecuted) {
+ return;
+ }
+ if (e.key === "Backspace" || e.key === "Delete" || e.key == "d") {
+ this._commandExecuted = true;
this.marqueeSelect().map(d => this.props.removeDocument(d));
let ink = Cast(this.props.container.props.Document.ink, InkField);
if (ink) {
this.marqueeInkDelete(ink.inkData);
}
- this.cleanupInteractions(true);
+ this.cleanupInteractions(false);
e.stopPropagation();
}
if (e.key === "c" || e.key === "r" || e.key === "e") {
+ this._commandExecuted = true;
e.stopPropagation();
let bounds = this.Bounds;
let selected = this.marqueeSelect().map(d => {
this.props.removeDocument(d);
- d.x = NumCast(d.X) - bounds.left - bounds.width / 2;
- d.y = NumCast(d.Y) - bounds.top - bounds.height / 2;
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
d.page = -1;
return d;
});
@@ -157,7 +167,6 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
panX: 0,
panY: 0,
borderRounding: e.key === "e" ? -1 : undefined,
- backgroundColor: selected.length ? "white" : "",
scale: zoomBasis,
width: bounds.width * zoomBasis,
height: bounds.height * zoomBasis,
@@ -166,7 +175,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
});
this.marqueeInkDelete(inkData);
- SelectionManager.DeselectAll();
+ // SelectionManager.DeselectAll();
if (e.key === "r") {
let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
Doc.MakeLink(summary.proto!, newCollection.proto!);
@@ -176,9 +185,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
else {
this.props.addDocument(newCollection, false);
}
- this.cleanupInteractions(true);
+ this.cleanupInteractions(false);
}
if (e.key === "s") {
+ this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
let bounds = this.Bounds;
@@ -188,7 +198,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.props.addLiveTextDocument(summary);
selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
- this.cleanupInteractions(true);
+ this.cleanupInteractions(false);
}
}
@action
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 8766eb7ea..56c2a80fa 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -103,7 +103,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
target.width = width;
target.height = height;
}
- (target as any).isIconAnimating = false;
+ target.isIconAnimating = false;
}
},
2);
@@ -114,34 +114,34 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
let isMinimized: boolean | undefined;
let minimizedDocSet = Cast(this.props.Document.linkTags, listSpec(Doc));
if (!minimizedDocSet) return;
- minimizedDocSet.map(async minimizedDoc => {
- if (minimizedDoc instanceof Document) {
- this.props.addDocument && this.props.addDocument(minimizedDoc, false);
- let maximizedDoc = await Cast(minimizedDoc.maximizedDoc, Doc);
- if (maximizedDoc && !(maximizedDoc as any).isIconAnimating) {
- (maximizedDoc as any).isIconAnimating = true;
- if (isMinimized === undefined) {
- let maximizedDocMinimizedState = Cast(maximizedDoc.isMinimized, "boolean");
- isMinimized = (maximizedDocMinimizedState) ? true : false;
- }
- let minx = NumCast(minimizedDoc.x, undefined);
- let miny = NumCast(minimizedDoc.y, undefined);
- let maxx = NumCast(maximizedDoc.x, undefined);
- let maxy = NumCast(maximizedDoc.y, undefined);
- let maxw = NumCast(maximizedDoc.width, undefined);
- let maxh = NumCast(maximizedDoc.height, undefined);
- if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
- maxw !== undefined && maxh !== undefined) {
- this.animateBetweenIcon(true, [minx, miny], [maxx, maxy], maxw, maxh, Date.now(), maximizedDoc, isMinimized);
- }
+ let docs = minimizedDocSet.map(d => d);
+ docs.push(this.props.Document);
+ docs.map(async minimizedDoc => {
+ this.props.addDocument && this.props.addDocument(minimizedDoc, false);
+ let maximizedDoc = await Cast(minimizedDoc.maximizedDoc, Doc);
+ if (maximizedDoc && !maximizedDoc.isIconAnimating) {
+ maximizedDoc.isIconAnimating = true;
+ if (isMinimized === undefined) {
+ let maximizedDocMinimizedState = Cast(maximizedDoc.isMinimized, "boolean");
+ isMinimized = (maximizedDocMinimizedState) ? true : false;
+ }
+ let minx = NumCast(minimizedDoc.x, undefined);
+ let miny = NumCast(minimizedDoc.y, undefined);
+ let maxx = NumCast(maximizedDoc.x, undefined);
+ let maxy = NumCast(maximizedDoc.y, undefined);
+ let maxw = NumCast(maximizedDoc.width, undefined);
+ let maxh = NumCast(maximizedDoc.height, undefined);
+ if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
+ maxw !== undefined && maxh !== undefined) {
+ this.animateBetweenIcon(true, [minx, miny], [maxx, maxy], maxw, maxh, Date.now(), maximizedDoc, isMinimized);
}
-
}
})
}
onPointerDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
this._downY = e.clientY;
+ e.stopPropagation();
}
onClick = async (e: React.MouseEvent) => {
e.stopPropagation();
@@ -175,7 +175,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
const screenWidth = 1800;
let fadeUp = .75 * screenWidth;
let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
- zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+ zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
return (
<div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index c90ca6d17..794442469 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -37,8 +37,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
@computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", "<p>Error loading layout data</p>"); }
CreateBindings(): JsxBindings {
- let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
- return bindings;
+ return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
}
render() {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 3814eeb9c..eab068355 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -12,13 +12,14 @@ import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
+import { Template, Templates } from "./../Templates";
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import React = require("react");
import { Opt, Doc } from "../../../new_fields/Doc";
import { DocComponent } from "../DocComponent";
import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, PromiseValue } from "../../../new_fields/Types";
+import { FieldValue, Cast, PromiseValue, StrCast } from "../../../new_fields/Types";
import { List } from "../../../new_fields/List";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
@@ -87,6 +88,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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 templates(): Array<Template> {
+ return new Array<Template>();
+ // let field = this.props.Document[KeyStore.Templates];
+ // return !field || field === FieldWaiting ? [] : field.Data;
+ }
+ set templates(templates: Array<Template>) { /* this.props.Document.templates = templates;*/ }
+ screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
@action
componentDidMount() {
@@ -151,7 +159,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
return;
}
- if (e.shiftKey && e.buttons === 2) {
+ if (e.shiftKey && e.buttons === 1) {
if (this.props.isTopMost) {
this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
} else {
@@ -233,6 +241,46 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
+ updateLayout = async () => {
+ const baseLayout = await StrCast(this.props.Document.baseLayout);
+ if (baseLayout) {
+ let base = baseLayout;
+ let layout = baseLayout;
+
+ this.templates.forEach(template => {
+ let temp = template.Layout;
+ layout = temp.replace("{layout}", base);
+ base = layout;
+ });
+
+ this.props.Document.layout = layout;
+ }
+ }
+
+ @action
+ addTemplate = (template: Template) => {
+ let templates = this.templates;
+ templates.push(template);
+ templates = templates.splice(0, templates.length).sort(Templates.sortTemplates);
+ this.templates = templates;
+ this.updateLayout();
+ }
+
+ @action
+ removeTemplate = (template: Template) => {
+ let templates = this.templates;
+ for (let i = 0; i < templates.length; i++) {
+ let temp = templates[i];
+ if (temp.Name === template.Name) {
+ templates.splice(i, 1);
+ break;
+ }
+ }
+ templates = templates.splice(0, templates.length).sort(Templates.sortTemplates);
+ this.templates = templates;
+ this.updateLayout();
+ }
+
@action
onContextMenu = (e: React.MouseEvent): void => {
e.stopPropagation();
@@ -260,8 +308,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
isSelected = () => SelectionManager.IsSelected(this);
select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
- @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
- @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
+ @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
@computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); }
render() {
@@ -274,8 +322,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ref={this._mainCont}
style={{
borderRadius: "inherit",
- background: FieldValue(this.Document.backgroundColor) || "",
- width: nativeWidth, height: nativeHeight,
+ background: this.Document.backgroundColor || "",
+ width: nativeWidth,
+ height: nativeHeight,
transform: `scale(${scaling}, ${scaling})`
}}
onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index df76f7cea..c04b91c21 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -89,7 +89,7 @@ export class FieldView extends React.Component<FieldViewProps> {
isTopMost={true} //TODO Why is this top most?
selectOnLoad={false}
focus={emptyFunction}
- isSelected={returnFalse}
+ isSelected={this.props.isSelected}
select={returnFalse}
layoutKey={"layout"}
ContainingCollectionView={this.props.ContainingCollectionView}
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index f4f37250f..727d3c0b2 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -10,7 +10,7 @@
outline: none !important;
}
-.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
+.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
background: $light-color-secondary;
padding: 0;
border-width: 0px;
@@ -24,10 +24,19 @@
height: 100%;
pointer-events: all;
}
+
.formattedTextBox-cont-hidden {
overflow: hidden;
pointer-events: none;
}
+.formattedTextBox-inner-rounded {
+ height: calc(100% - 40px);
+ width: calc(100% - 40px);
+ position: absolute;
+ overflow: scroll;
+ top: 20;
+ left: 20;
+}
.menuicon {
display: inline-block;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index c4c720ca9..455672472 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, reaction, trace, computed } from "mobx";
+import { action, IReactionDisposer, reaction, trace, computed, _allowStateChangesInsideComputed } from "mobx";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
@@ -20,7 +20,7 @@ import { createSchema, makeInterface } from "../../../new_fields/Schema";
import { Opt, Doc } from "../../../new_fields/Doc";
import { observer } from "mobx-react";
import { InkingControl } from "../InkingControl";
-import { StrCast, Cast } from "../../../new_fields/Types";
+import { StrCast, Cast, NumCast } from "../../../new_fields/Types";
import { RichTextField } from "../../../new_fields/RichTextField";
import { Id } from "../../../new_fields/RefField";
const { buildMenuItems } = require("prosemirror-example-setup");
@@ -60,6 +60,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
return FieldView.LayoutString(FormattedTextBox, fieldStr);
}
private _ref: React.RefObject<HTMLDivElement>;
+ private _proseRef: React.RefObject<HTMLDivElement>;
private _editorView: Opt<EditorView>;
private _gotDown: boolean = false;
private _reactionDisposer: Opt<IReactionDisposer>;
@@ -70,19 +71,26 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
super(props);
this._ref = React.createRef();
+ this._proseRef = React.createRef();
}
_applyingChange: boolean = false;
+ _lastState: any = undefined;
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
- const state = this._editorView.state.apply(tx);
+ const state = this._lastState = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this._applyingChange = true;
Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON())));
Doc.SetOnPrototype(this.props.Document, "documentText", state.doc.textBetween(0, state.doc.content.size, "\n\n"));
this._applyingChange = false;
- // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+ let title = StrCast(this.props.Document.title);
+ if (title && title.startsWith("-") && this._editorView) {
+ let str = this._editorView.state.doc.textContent;
+ let titlestr = str.substr(0, Math.min(40, str.length));
+ this.props.Document.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+ };
}
}
@@ -91,10 +99,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
schema,
inpRules, //these currently don't do anything, but could eventually be helpful
plugins: this.props.isOverlay ? [
+ this.tooltipTextMenuPlugin(),
history(),
keymap(buildKeymap(schema)),
keymap(baseKeymap),
- this.tooltipTextMenuPlugin(),
// this.tooltipLinkingMenuPlugin(),
new Plugin({
props: {
@@ -165,18 +173,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
- console.log("first");
+ if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
e.stopPropagation();
+ if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
+ this._toolTipTextMenu.tooltip.style.opacity = "0";
}
- if (e.button === 2) {
+ if (e.button === 2 || (e.button === 0 && e.ctrlKey)) {
this._gotDown = true;
- console.log("second");
e.preventDefault();
}
}
onPointerUp = (e: React.PointerEvent): void => {
- console.log("pointer up");
+ if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip)
+ this._toolTipTextMenu.tooltip.style.opacity = "1";
if (e.buttons === 1 && this.props.isSelected() && !e.altKey) {
e.stopPropagation();
}
@@ -184,9 +193,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onFocused = (e: React.FocusEvent): void => {
if (!this.props.isOverlay) {
- if (MainOverlayTextBox.Instance.TextDoc !== this.props.Document) {
- MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
- }
+ MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
} else {
if (this._ref.current) {
this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
@@ -228,18 +235,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
onClick = (e: React.MouseEvent): void => {
- this._ref.current!.focus();
+ this._proseRef.current!.focus();
}
tooltipTextMenuPlugin() {
let myprops = this.props;
+ let self = this;
return new Plugin({
view(_editorView) {
- return new TooltipTextMenu(_editorView, myprops);
+ return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops);
}
});
}
+ _toolTipTextMenu: TooltipTextMenu | undefined = undefined;
tooltipLinkingMenuPlugin() {
let myprops = this.props;
return new Plugin({
@@ -254,7 +263,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
SelectionManager.DeselectAll();
}
e.stopPropagation();
- if (e.keyCode === 9) e.preventDefault();
+ if (e.key === "Tab") e.preventDefault();
// stop propagation doesn't seem to stop propagation of native keyboard events.
// so we set a flag on the native event that marks that the event's been handled.
(e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
@@ -266,10 +275,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
render() {
let style = this.props.isOverlay ? "scroll" : "hidden";
+ let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : "";
let color = StrCast(this.props.Document.backgroundColor);
let interactive = InkingControl.Instance.selectedTool ? "" : "interactive";
return (
- <div className={`formattedTextBox-cont-${style}`}
+ <div className={`formattedTextBox-cont${style}`} ref={this._ref}
style={{
pointerEvents: interactive ? "all" : "none",
background: color,
@@ -283,7 +293,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onContextMenu={this.specificContextMenu}
// tfs: do we need this event handler
onWheel={this.onPointerWheel}
- ref={this._ref} />
+ >
+ <div className={`formattedTextBox-inner${rounded}`} ref={this._proseRef} />
+ </div>
);
}
}
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index f7cceb3d4..7a0c49735 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -23,7 +23,7 @@ export class IconBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(IconBox); }
@computed get maximized() { return Cast(this.props.Document.maximizedDoc, Doc); }
- @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.layout : "<p>Error loading layout data</p>"; }
+ @computed get layout(): string { const field = Cast(this.props.Document[this.props.fieldKey], IconField); return field ? field.icon : "<p>Error loading icon data</p>"; }
@computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
public static DocumentIcon(layout: string) {
diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss
index 6ebd73f2c..20cae03d4 100644
--- a/src/client/views/nodes/KeyValueBox.scss
+++ b/src/client/views/nodes/KeyValueBox.scss
@@ -8,6 +8,7 @@
border-radius: $border-radius;
box-sizing: border-box;
display: inline-block;
+ pointer-events: all;
.imageBox-cont img {
width: auto;
}
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index 01701e02c..ff6885965 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -25,4 +25,5 @@
}
.keyValuePair-td-value {
display:inline-block;
+ overflow: scroll;
} \ No newline at end of file
diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss
index 830dfe6c6..3760e378a 100644
--- a/src/client/views/nodes/PDFBox.scss
+++ b/src/client/views/nodes/PDFBox.scss
@@ -4,6 +4,9 @@
top: 0;
left:0;
}
+.react-pdf__Page__textContent span {
+ user-select: text;
+}
.react-pdf__Document {
position: absolute;
}
@@ -12,6 +15,21 @@
top: 0;
left:0;
z-index: 25;
+ pointer-events: all;
+}
+.pdfButton {
+ pointer-events: all;
+ width: 100px;
+ height:100px;
+}
+.pdfBox-cont {
+ pointer-events: none ;
+ span {
+ pointer-events: none !important;
+ }
+}
+.pdfBox-cont-interactive {
+ pointer-events: all;
}
.pdfBox-contentContainer {
position: absolute;
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index 9f0849492..eb45ea273 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
import * as htmlToImage from "html-to-image";
-import { action, computed, IReactionDisposer, observable, reaction } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import Measure from "react-measure";
@@ -10,18 +10,17 @@ import { RouteStore } from "../../../server/RouteStore";
import { Utils } from '../../../Utils';
import { Annotation } from './Annotation';
import { FieldView, FieldViewProps } from './FieldView';
-import "./ImageBox.scss";
import "./PDFBox.scss";
-import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
import React = require("react");
import { SelectionManager } from "../../util/SelectionManager";
-import { Cast, FieldValue } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { Opt } from "../../../new_fields/Doc";
import { DocComponent } from "../DocComponent";
import { makeInterface } from "../../../new_fields/Schema";
import { positionSchema } from "./DocumentView";
import { pageSchema } from "./ImageBox";
import { ImageField, PdfField } from "../../../new_fields/URLField";
+import { InkingControl } from "../InkingControl";
/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -43,15 +42,6 @@ import { ImageField, PdfField } from "../../../new_fields/URLField";
* 4) another method: click on highlight first and then drag on your desired text
* 5) To make another highlight, you need to reclick on the button
*
- * Draw:
- * 1) click draw and select color. then just draw like there's no tomorrow.
- * 2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session.
- *
- * Pagination:
- * 1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't.
- * 2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved.
- *
- *
* written by: Andrew Kim
*/
@@ -63,30 +53,9 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
public static LayoutString() { return FieldView.LayoutString(PDFBox); }
private _mainDiv = React.createRef<HTMLDivElement>();
- private _pdf = React.createRef<HTMLCanvasElement>();
@observable private _renderAsSvg = true;
- //very useful for keeping track of X and y position throughout the PDF Canvas
- private initX: number = 0;
- private initY: number = 0;
-
- //checks if tool is on
- private _toolOn: boolean = false; //checks if tool is on
- private _pdfContext: any = null; //gets pdf context
- private bool: Boolean = false; //general boolean debounce
- private currSpan: any;//keeps track of current span (for highlighting)
-
- private _currTool: any; //keeps track of current tool button reference
- private _drawToolOn: boolean = false; //boolean that keeps track of the drawing tool
- private _drawTool = React.createRef<HTMLButtonElement>();//drawing tool button reference
-
- private _colorTool = React.createRef<HTMLButtonElement>(); //color button reference
- private _currColor: string = "black"; //current color that user selected (for ink/pen)
-
- private _highlightTool = React.createRef<HTMLButtonElement>(); //highlighter button reference
- private _highlightToolOn: boolean = false;
- private _pdfCanvas: any;
private _reactionDisposer: Opt<IReactionDisposer>;
@observable private _perPageInfo: Object[] = []; //stores pageInfo
@@ -119,43 +88,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
/**
- * selection tool used for area highlighting (stickies). Kinda temporary
- */
- selectionTool = () => {
- this._toolOn = true;
- }
- /**
- * when user draws on the canvas. When mouse pointer is down
- */
- drawDown = (e: PointerEvent) => {
- this.initX = e.offsetX;
- this.initY = e.offsetY;
- this._pdfContext.beginPath();
- this._pdfContext.lineTo(this.initX, this.initY);
- this._pdfContext.strokeStyle = this._currColor;
- this._pdfCanvas.addEventListener("pointermove", this.drawMove);
- this._pdfCanvas.addEventListener("pointerup", this.drawUp);
-
- }
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = this.initX += e.movementX,
- y = this.initY += e.movementY;
- //connects the point
- this._pdfContext.lineTo(x, y);
- this._pdfContext.stroke();
- }
-
- drawUp = (e: PointerEvent) => {
- this._pdfContext.closePath();
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- }
-
-
- /**
* highlighting helper function
*/
makeEditableAndHighlight = (colour: string) => {
@@ -190,7 +122,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
child.id = "highlighted";
//@ts-ignore
obj.spans.push(child);
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
});
}
@@ -213,7 +145,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
child.id = "highlighted";
//@ts-ignore
temp.spans.push(child);
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
});
@@ -279,11 +211,20 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
* controls the area highlighting (stickies) Kinda temporary
*/
onPointerDown = (e: React.PointerEvent) => {
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
-
+ if (this.props.isSelected() && !InkingControl.Instance.selectedTool && e.buttons === 1) {
+ if (e.altKey) {
+ this._alt = true;
+ } else {
+ if (e.metaKey)
+ e.stopPropagation();
+ }
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ if (this.props.isSelected() && e.buttons === 2) {
+ this._alt = true;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
}
}
@@ -291,96 +232,15 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
* controls area highlighting and partially highlighting. Kinda temporary
*/
@action
- onPointerUp = (e: React.PointerEvent) => {
- if (this._highlightToolOn) {
+ onPointerUp = (e: PointerEvent) => {
+ this._alt = false;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (this.props.isSelected()) {
this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color.
- this._highlightToolOn = false;
- }
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- let finalX = mouse.offsetX;
- let finalY = mouse.offsetY;
- let width = Math.abs(finalX - this.initX); //width
- let height = Math.abs(finalY - this.initY); //height
-
- //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky
- if (finalX < this.initX) {
- this.initX = finalX;
- }
- if (finalY < this.initY) {
- this.initY = finalY;
- }
-
- if (this._mainDiv.current) {
- let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />;
- this._pageInfo.area.push(sticky);
- }
- this._toolOn = false;
}
this._interactive = true;
}
- /**
- * starts drawing the line when user presses down.
- */
- onDraw = () => {
- if (this._currTool !== null) {
- this._currTool.style.backgroundColor = "grey";
- }
-
- if (this._drawTool.current) {
- this._currTool = this._drawTool.current;
- if (this._drawToolOn) {
- this._drawToolOn = false;
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.removeEventListener("pointerup", this.drawUp);
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._drawTool.current.style.backgroundColor = "grey";
- } else {
- this._drawToolOn = true;
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- this._drawTool.current.style.backgroundColor = "cyan";
- }
- }
- }
-
-
- /**
- * for changing color (for ink/pen)
- */
- onColorChange = (e: React.PointerEvent) => {
- if (e.currentTarget.innerHTML === "Red") {
- this._currColor = "red";
- } else if (e.currentTarget.innerHTML === "Blue") {
- this._currColor = "blue";
- } else if (e.currentTarget.innerHTML === "Green") {
- this._currColor = "green";
- } else if (e.currentTarget.innerHTML === "Black") {
- this._currColor = "black";
- }
-
- }
-
-
- /**
- * For highlighting (text drag highlighting)
- */
- onHighlight = () => {
- this._drawToolOn = false;
- if (this._currTool !== null) {
- this._currTool.style.backgroundColor = "grey";
- }
- if (this._highlightTool.current) {
- this._currTool = this._drawTool.current;
- if (this._highlightToolOn) {
- this._highlightToolOn = false;
- this._highlightTool.current.style.backgroundColor = "grey";
- } else {
- this._highlightToolOn = true;
- this._highlightTool.current.style.backgroundColor = "orange";
- }
- }
- }
@action
@@ -403,22 +263,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
@action
onLoaded = (page: any) => {
- if (this._mainDiv.current) {
- this._mainDiv.current.childNodes.forEach((element) => {
- if (element.nodeName === "DIV") {
- element.childNodes[0].childNodes.forEach((e) => {
-
- if (e instanceof HTMLCanvasElement) {
- this._pdfCanvas = e;
- this._pdfContext = e.getContext("2d");
-
- }
-
- });
- }
- });
- }
-
// bcz: the number of pages should really be set when the document is imported.
this.props.Document.numPages = page._transport.numPages;
if (this._perPageInfo.length === 0) { //Makes sure it only runs once
@@ -430,7 +274,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
@action
setScaling = (r: any) => {
// bcz: the nativeHeight should really be set when the document is imported.
- // also, the native dimensions could be different for different pages of the PDF
+ // also, the native dimensions could be different for different pages of the canvas
// so this design is flawed.
var nativeWidth = FieldValue(this.Document.nativeWidth, 0);
if (!FieldValue(this.Document.nativeHeight, 0)) {
@@ -439,22 +283,29 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
this.props.Document.nativeHeight = nativeHeight;
}
}
-
+ renderHeight = 2400;
+ @computed
+ get pdfPage() {
+ return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />
+ }
@computed
get pdfContent() {
let page = this.curPage;
const renderHeight = 2400;
let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
let xf = FieldValue(this.Document.nativeHeight, 0) / renderHeight;
+ let body = NumCast(this.props.Document.nativeHeight) ?
+ this.pdfPage :
+ <Measure onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <div className="pdfBox-page" ref={measureRef}>
+ {this.pdfPage}
+ </div>
+ }
+ </Measure>;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl!.url.href}`} renderMode={this._renderAsSvg ? "svg" : ""}>
- <Measure onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="pdfBox-page" ref={measureRef}>
- <Page height={renderHeight} pageNumber={page} onLoadSuccess={this.onLoaded} />
- </div>
- }
- </Measure>
+ <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}>
+ {body}
</Document>
</div >;
}
@@ -484,10 +335,24 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
return (null);
}
-
+ @observable _alt = false;
+ @action
+ onKeyDown = (e: React.KeyboardEvent) => {
+ if (e.key === "Alt") {
+ this._alt = true;
+ }
+ }
+ @action
+ onKeyUp = (e: React.KeyboardEvent) => {
+ if (e.key === "Alt") {
+ this._alt = false;
+ }
+ }
render() {
+ trace();
+ let classname = "pdfBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : "");
return (
- <div className="pdfBox-cont" ref={this._mainDiv} onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} >
+ <div className={classname} tabIndex={0} ref={this._mainDiv} onPointerDown={this.onPointerDown} onKeyDown={this.onKeyDown} onKeyUp={this.onKeyUp} >
{this.pdfRenderer}
</div >
);
diff --git a/src/client/views/nodes/Sticky.tsx b/src/client/views/nodes/Sticky.tsx
deleted file mode 100644
index 11719831b..000000000
--- a/src/client/views/nodes/Sticky.tsx
+++ /dev/null
@@ -1,83 +0,0 @@
-import "react-image-lightbox/style.css"; // This only needs to be imported once in your app
-import React = require("react");
-import { observer } from "mobx-react";
-import "react-pdf/dist/Page/AnnotationLayer.css";
-
-interface IProps {
- Height: number;
- Width: number;
- X: number;
- Y: number;
-}
-
-/**
- * Sticky, also known as area highlighting, is used to highlight large selection of the PDF file.
- * Improvements that could be made: maybe store line array and store that somewhere for future rerendering.
- *
- * Written By: Andrew Kim
- */
-@observer
-export class Sticky extends React.Component<IProps> {
- private initX: number = 0;
- private initY: number = 0;
-
- private _ref = React.createRef<HTMLCanvasElement>();
- private ctx: any; //context that keeps track of sticky canvas
-
- /**
- * drawing. Registers the first point that user clicks when mouse button is pressed down on canvas
- */
- drawDown = (e: React.PointerEvent) => {
- if (this._ref.current) {
- this.ctx = this._ref.current.getContext("2d");
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
- this.ctx.beginPath();
- this.ctx.lineTo(this.initX, this.initY);
- this.ctx.strokeStyle = "black";
- document.addEventListener("pointermove", this.drawMove);
- document.addEventListener("pointerup", this.drawUp);
- }
- }
-
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = (this.initX += e.movementX),
- y = (this.initY += e.movementY);
- //connects the point
- this.ctx.lineTo(x, y);
- this.ctx.stroke();
- }
-
- /**
- * when user lifts the mouse, the drawing ends
- */
- drawUp = (e: PointerEvent) => {
- this.ctx.closePath();
- console.log(this.ctx);
- document.removeEventListener("pointermove", this.drawMove);
- }
-
- render() {
- return (
- <div onPointerDown={this.drawDown}>
- <canvas
- ref={this._ref}
- height={this.props.Height}
- width={this.props.Width}
- style={{
- position: "absolute",
- top: "20px",
- left: "0px",
- zIndex: 1,
- background: "yellow",
- transform: `translate(${this.props.X}px, ${this.props.Y}px)`,
- opacity: 0.4
- }}
- />
- </div>
- );
- }
-}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 86276660a..184a89dbc 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -7,7 +7,7 @@ import { DocComponent } from "../DocComponent";
import { positionSchema } from "./DocumentView";
import { makeInterface } from "../../../new_fields/Schema";
import { pageSchema } from "./ImageBox";
-import { Cast, FieldValue } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
import Measure from "react-measure";
import "./VideoBox.scss";
@@ -59,24 +59,27 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
(vref as any).AHackBecauseSomethingResetsTheVideoToZero = this.curPage;
}
}
+ videoContent(path: string) {
+ return <video className="videobox-cont" ref={this.setVideoRef}>
+ <source src={path} type="video/mp4" />
+ Not supported.
+ </video>;
+ }
render() {
let field = FieldValue(Cast(this.Document[this.props.fieldKey], VideoField));
if (!field) {
return <div>Loading</div>;
}
- let path = field.url.href;
- return (
+ let content = this.videoContent(field.url.href);
+ return NumCast(this.props.Document.nativeHeight) ?
+ content :
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div style={{ width: "100%", height: "auto" }} ref={measureRef}>
- <video className="videobox-cont" ref={this.setVideoRef}>
- <source src={path} type="video/mp4" />
- Not supported.
- </video>
+ {content}
</div>
}
- </Measure>
- );
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 2ad1129a4..eb09b0693 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,12 +1,19 @@
-.webBox-cont {
+.webBox-cont, .webBox-cont-interactive{
padding: 0vw;
position: absolute;
top: 0;
left:0;
width: 100%;
height: 100%;
- overflow: scroll;
+ overflow: auto;
+ pointer-events: none ;
+}
+.webBox-cont-interactive {
+ pointer-events: all;
+ span {
+ user-select: text !important;
+ }
}
#webBox-htmlSpan {
@@ -15,6 +22,12 @@
left:0;
}
+.webBox-overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+}
+
.webBox-button {
padding : 0vw;
border: none;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index c12fd5655..2239a8e38 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -6,6 +6,7 @@ import { WebField } from "../../../new_fields/URLField";
import { observer } from "mobx-react";
import { computed, reaction, IReactionDisposer } from 'mobx';
import { DocumentDecorations } from "../DocumentDecorations";
+import { InkingControl } from "../InkingControl";
@observer
export class WebBox extends React.Component<FieldViewProps> {
@@ -46,12 +47,13 @@ export class WebBox extends React.Component<FieldViewProps> {
let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+ let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
<>
- <div className="webBox-cont" >
+ <div className={classname} >
{content}
</div>
- {!frozen ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
</>);
}
} \ No newline at end of file
diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx
index 7415d4b28..04ef00722 100644
--- a/src/debug/Test.tsx
+++ b/src/debug/Test.tsx
@@ -61,14 +61,12 @@ class Test extends React.Component {
assert(test2.testDoc === undefined);
test2.url = 35;
assert(test2.url === 35);
- const l = new List<number>();
+ const l = new List<Doc>();
//TODO push, and other array functions don't go through the proxy
- l.push(1);
+ l.push(doc2);
//TODO currently length, and any other string fields will get serialized
- l.length = 3;
- l[2] = 5;
+ doc.list = l;
console.log(l.slice());
- console.log(SerializationHelper.Serialize(l));
}
render() {
diff --git a/src/fields/TemplateField.ts b/src/fields/TemplateField.ts
new file mode 100644
index 000000000..72ae13c2e
--- /dev/null
+++ b/src/fields/TemplateField.ts
@@ -0,0 +1,43 @@
+import { BasicField } from "./BasicField";
+import { Types } from "../server/Message";
+import { FieldId } from "./Field";
+import { Template, TemplatePosition } from "../client/views/Templates";
+
+
+export class TemplateField extends BasicField<Array<Template>> {
+ constructor(data: Array<Template> = [], id?: FieldId, save: boolean = true) {
+ super(data, save, id);
+ }
+
+ ToScriptString(): string {
+ return `new TemplateField("${this.Data}")`;
+ }
+
+ Copy() {
+ return new TemplateField(this.Data);
+ }
+
+ ToJson() {
+ let templates: Array<{ name: string, position: TemplatePosition, layout: string }> = [];
+ this.Data.forEach(template => {
+ templates.push({ name: template.Name, layout: template.Layout, position: template.Position });
+ });
+ return {
+ type: Types.Templates,
+ data: templates,
+ id: this.Id,
+ };
+ }
+
+ UpdateFromServer(data: any) {
+ this.data = new Array(data);
+ }
+
+ static FromJson(id: string, data: any): TemplateField {
+ let templates: Array<Template> = [];
+ data.forEach((template: { name: string, position: number, layout: string }) => {
+ templates.push(new Template(template.name, template.position, template.layout));
+ });
+ return new TemplateField(templates, id, false);
+ }
+} \ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index d15b6309d..0e5105761 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -3,8 +3,8 @@ import { serializable, primitive, map, alias, list } from "serializr";
import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper";
import { Utils } from "../Utils";
import { DocServer } from "../client/DocServer";
-import { setter, getter, getField, updateFunction } from "./util";
-import { Cast, ToConstructor, PromiseValue, FieldValue } from "./Types";
+import { setter, getter, getField, updateFunction, deleteProperty } from "./util";
+import { Cast, ToConstructor, PromiseValue, FieldValue, NumCast } from "./Types";
import { UndoManager, undoBatch } from "../client/util/UndoManager";
import { listSpec } from "./Schema";
import { List } from "./List";
@@ -25,6 +25,8 @@ export type FieldResult<T extends Field = Field> = Opt<T> | FieldWaiting<Extract
export const Update = Symbol("Update");
export const Self = Symbol("Self");
+export const WidthSym = Symbol("Width");
+export const HeightSym = Symbol("Height");
@Deserializable("doc").withFields(["id"])
export class Doc extends RefField {
@@ -34,7 +36,7 @@ export class Doc extends RefField {
set: setter,
get: getter,
ownKeys: target => Object.keys(target.__fields),
- deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
+ deleteProperty: deleteProperty,
defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
});
if (!id || forceSave) {
@@ -70,6 +72,8 @@ export class Doc extends RefField {
}
private [Self] = this;
+ public [WidthSym] = () => { return NumCast(this.__fields.width); } // bcz: is this the right way to access width/height? it didn't work with : this.width
+ public [HeightSym] = () => { return NumCast(this.__fields.height); }
}
export namespace Doc {
@@ -151,8 +155,8 @@ export namespace Doc {
}
export function MakeLink(source: Doc, target: Doc): Doc {
- let linkDoc = new Doc;
- UndoManager.RunInBatch(() => {
+ return UndoManager.RunInBatch(() => {
+ let linkDoc = new Doc;
linkDoc.title = "New Link";
linkDoc.linkDescription = "";
linkDoc.linkTags = "Default";
@@ -171,8 +175,8 @@ export namespace Doc {
source.linkedToDocs = linkedTo = new List<Doc>();
}
linkedTo.push(linkDoc);
+ return linkDoc;
}, "make link");
- return linkDoc;
}
export function MakeDelegate(doc: Doc): Doc;
diff --git a/src/new_fields/IconField.ts b/src/new_fields/IconField.ts
index 46f111f8e..c79a2f79a 100644
--- a/src/new_fields/IconField.ts
+++ b/src/new_fields/IconField.ts
@@ -5,10 +5,10 @@ import { ObjectField } from "./ObjectField";
@Deserializable("icon")
export class IconField extends ObjectField {
@serializable(primitive())
- readonly layout: string;
+ readonly icon: string;
- constructor(layout: string) {
+ constructor(icon: string) {
super();
- this.layout = layout;
+ this.icon = icon;
}
}
diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts
index e4a80f7a1..ec1bf44a9 100644
--- a/src/new_fields/List.ts
+++ b/src/new_fields/List.ts
@@ -1,21 +1,173 @@
import { Deserializable, autoObject } from "../client/util/SerializationHelper";
import { Field, Update, Self } from "./Doc";
-import { setter, getter } from "./util";
+import { setter, getter, deleteProperty } from "./util";
import { serializable, alias, list } from "serializr";
import { observable, observe, IArrayChange, IArraySplice, IObservableArray, Lambda, reaction } from "mobx";
import { ObjectField, OnUpdate } from "./ObjectField";
+import { RefField } from "./RefField";
+import { ProxyField } from "./Proxy";
const listHandlers: any = {
- push(...items: any[]) {
- // console.log("push");
- // console.log(...items);
- return this[Self].__fields.push(...items);
+ /// Mutator methods
+ copyWithin() {
+ throw new Error("copyWithin not supported yet");
+ },
+ fill(value: any, start?: number, end?: number) {
+ if (value instanceof RefField) {
+ throw new Error("fill with RefFields not supported yet");
+ }
+ const res = this[Self].__fields.fill(value, start, end);
+ this[Update]();
+ return res;
},
pop(): any {
- return this[Self].__fields.pop();
+ const field = toRealField(this[Self].__fields.pop());
+ this[Update]();
+ return field;
+ },
+ push(...items: any[]) {
+ items = items.map(toObjectField);
+ const res = this[Self].__fields.push(...items);
+ this[Update]();
+ return res;
+ },
+ reverse() {
+ const res = this[Self].__fields.reverse();
+ this[Update]();
+ return res;
+ },
+ shift() {
+ const res = toRealField(this[Self].__fields.shift());
+ this[Update]();
+ return res;
+ },
+ sort(cmpFunc: any) {
+ const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined);
+ this[Update]();
+ return res;
+ },
+ splice(start: number, deleteCount: number, ...items: any[]) {
+ items = items.map(toObjectField);
+ const res = this[Self].__fields.splice(start, deleteCount, ...items);
+ this[Update]();
+ return res.map(toRealField);
+ },
+ unshift(...items: any[]) {
+ items = items.map(toObjectField);
+ const res = this[Self].__fields.unshift(...items);
+ this[Update]();
+ return res;
+
+ },
+ /// Accessor methods
+ concat(...items: any[]) {
+ return this[Self].__fields.map(toRealField).concat(...items);
+ },
+ includes(valueToFind: any, fromIndex: number) {
+ const fields = this[Self].__fields;
+ if (valueToFind instanceof RefField) {
+ return fields.map(toRealField).includes(valueToFind, fromIndex);
+ } else {
+ return fields.includes(valueToFind, fromIndex);
+ }
+ },
+ indexOf(valueToFind: any, fromIndex: number) {
+ const fields = this[Self].__fields;
+ if (valueToFind instanceof RefField) {
+ return fields.map(toRealField).indexOf(valueToFind, fromIndex);
+ } else {
+ return fields.indexOf(valueToFind, fromIndex);
+ }
+ },
+ join(separator: any) {
+ return this[Self].__fields.map(toRealField).join(separator);
+ },
+ lastIndexOf(valueToFind: any, fromIndex: number) {
+ const fields = this[Self].__fields;
+ if (valueToFind instanceof RefField) {
+ return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex);
+ } else {
+ return fields.lastIndexOf(valueToFind, fromIndex);
+ }
+ },
+ slice(begin: number, end: number) {
+ return this[Self].__fields.slice(begin, end).map(toRealField);
+ },
+
+ /// Iteration methods
+ entries() {
+ return this[Self].__fields.map(toRealField).entries();
+ },
+ every(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).every(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ filter(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).filter(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ find(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).find(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ findIndex(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).findIndex(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ forEach(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).forEach(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ map(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).map(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ reduce(callback: any, initialValue: any) {
+ return this[Self].__fields.map(toRealField).reduce(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ reduceRight(callback: any, initialValue: any) {
+ return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue);
+ },
+ some(callback: any, thisArg: any) {
+ return this[Self].__fields.map(toRealField).some(callback, thisArg);
+ // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway.
+ // If we don't want to support the array parameter, we should use this version instead
+ // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg);
+ },
+ values() {
+ return this[Self].__fields.map(toRealField).values();
+ },
+ [Symbol.iterator]() {
+ return this[Self].__fields.map(toRealField).values();
}
};
+function toObjectField(field: Field) {
+ return field instanceof RefField ? new ProxyField(field) : field;
+}
+
+function toRealField(field: Field) {
+ return field instanceof ProxyField ? field.value() : field;
+}
+
function listGetter(target: any, prop: string | number | symbol, receiver: any): any {
if (listHandlers.hasOwnProperty(prop)) {
return listHandlers[prop];
@@ -38,36 +190,15 @@ interface ListIndexUpdate<T> {
type ListUpdate<T> = ListSpliceUpdate<T> | ListIndexUpdate<T>;
-const ObserveDisposer = Symbol("Observe Disposer");
-
-function listObserver<T extends Field>(this: ListImpl<T>, change: IArrayChange<T> | IArraySplice<T>) {
- if (change.type === "splice") {
- this[Update]({
- index: change.index,
- removedCount: change.removedCount,
- added: change.added,
- type: change.type
- });
- } else {
- //This should already be handled by the getter for the Proxy
- // this[Update]({
- // index: change.index,
- // newValue: change.newValue,
- // type: change.type
- // });
- }
-}
-
@Deserializable("list")
class ListImpl<T extends Field> extends ObjectField {
constructor(fields: T[] = []) {
super();
this.___fields = fields;
- this[ObserveDisposer] = observe(this.__fields as IObservableArray<T>, listObserver.bind(this));
const list = new Proxy<this>(this, {
set: setter,
- get: getter,
- deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); },
+ get: listGetter,
+ deleteProperty: deleteProperty,
defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); },
});
return list;
@@ -82,8 +213,6 @@ class ListImpl<T extends Field> extends ObjectField {
private set __fields(value) {
this.___fields = value;
- this[ObserveDisposer]();
- this[ObserveDisposer] = observe(this.__fields as IObservableArray<T>, listObserver.bind(this));
}
// @serializable(alias("fields", list(autoObject())))
@@ -97,7 +226,6 @@ class ListImpl<T extends Field> extends ObjectField {
update && update();
}
- private [ObserveDisposer]: Lambda;
private [Self] = this;
}
export type List<T extends Field> = ListImpl<T> & T[];
diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts
index 511820115..128817ab8 100644
--- a/src/new_fields/util.ts
+++ b/src/new_fields/util.ts
@@ -78,6 +78,14 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea
return field;
}
+export function deleteProperty(target: any, prop: string | number | symbol) {
+ if (typeof prop === "symbol") {
+ delete target[prop];
+ return true;
+ }
+ throw new Error("Currently properties can't be deleted from documents, assign to undefined instead");
+}
+
export function updateFunction(target: any, prop: any, value: any) {
return (diff?: any) => {
if (!diff) diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } };
diff --git a/src/server/Message.ts b/src/server/Message.ts
index 81da44f72..e9a8b0f0c 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -15,7 +15,7 @@ export class Message<T> {
export enum Types {
Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
- Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script,
+ Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
}
export interface Transferable {