aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/Utils.ts4
-rw-r--r--src/client/documents/Documents.ts29
-rw-r--r--src/client/northstar/core/filter/ValueComparision.ts6
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.scss6
-rw-r--r--src/client/northstar/dash-nodes/HistogramBox.tsx56
-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/DocumentManager.ts14
-rw-r--r--src/client/util/DragManager.ts43
-rw-r--r--src/client/util/SelectionManager.ts13
-rw-r--r--src/client/util/TooltipTextMenu.scss4
-rw-r--r--src/client/util/TooltipTextMenu.tsx148
-rw-r--r--src/client/views/.DS_Storebin8196 -> 6148 bytes
-rw-r--r--src/client/views/DocumentDecorations.scss109
-rw-r--r--src/client/views/DocumentDecorations.tsx277
-rw-r--r--src/client/views/InkingCanvas.tsx17
-rw-r--r--src/client/views/Main.scss10
-rw-r--r--src/client/views/Main.tsx50
-rw-r--r--src/client/views/MainOverlayTextBox.scss1
-rw-r--r--src/client/views/MainOverlayTextBox.tsx33
-rw-r--r--src/client/views/PreviewCursor.tsx36
-rw-r--r--src/client/views/TemplateMenu.tsx75
-rw-r--r--src/client/views/Templates.tsx66
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx109
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx74
-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.scss148
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx303
-rw-r--r--src/client/views/collections/CollectionSubView.tsx48
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx14
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx10
-rw-r--r--src/client/views/collections/CollectionView.tsx4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx41
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx17
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx122
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx139
-rw-r--r--src/client/views/globalCssVariables.scss4
-rw-r--r--src/client/views/globalCssVariables.scss.d.ts4
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx187
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx7
-rw-r--r--src/client/views/nodes/DocumentView.scss21
-rw-r--r--src/client/views/nodes/DocumentView.tsx211
-rw-r--r--src/client/views/nodes/FieldView.tsx16
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss15
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx88
-rw-r--r--src/client/views/nodes/IconBox.scss12
-rw-r--r--src/client/views/nodes/IconBox.tsx45
-rw-r--r--src/client/views/nodes/ImageBox.scss16
-rw-r--r--src/client/views/nodes/ImageBox.tsx59
-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/KeyValuePair.tsx2
-rw-r--r--src/client/views/nodes/PDFBox.scss18
-rw-r--r--src/client/views/nodes/PDFBox.tsx249
-rw-r--r--src/client/views/nodes/Sticky.tsx83
-rw-r--r--src/client/views/nodes/VideoBox.tsx27
-rw-r--r--src/client/views/nodes/WebBox.scss17
-rw-r--r--src/client/views/nodes/WebBox.tsx17
-rw-r--r--src/fields/Document.ts31
-rw-r--r--src/fields/IconFIeld.ts25
-rw-r--r--src/fields/KeyStore.ts13
-rw-r--r--src/fields/TemplateField.ts43
-rw-r--r--src/server/Message.ts4
-rw-r--r--src/server/ServerUtil.ts4
68 files changed, 2049 insertions, 1317 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index dec6245ef..8252ba011 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -1,11 +1,13 @@
import v4 = require('uuid/v4');
import v5 = require("uuid/v5");
import { Socket } from 'socket.io';
-import { Message, Types, Transferable } from './server/Message';
+import { Message } from './server/Message';
import { Document } from './fields/Document';
export class Utils {
+ public static DRAG_THRESHOLD = 4;
+
public static GenerateGuid(): string {
return v4();
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 4febfa7eb..a7514f1d6 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -32,6 +32,11 @@ 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 { IconField } from "../../fields/IconFIeld";
export interface DocumentOptions {
x?: number;
@@ -46,11 +51,15 @@ export interface DocumentOptions {
pany?: number;
page?: number;
scale?: number;
+ baseLayout?: string;
layout?: string;
+ templates?: Array<Template>;
layoutKeys?: Key[];
viewType?: number;
backgroundColor?: string;
copyDraggedItems?: boolean;
+ documentText?: string;
+ borderRounding?: number;
}
export namespace Documents {
@@ -63,6 +72,7 @@ export namespace Documents {
let videoProto: Document;
let audioProto: Document;
let pdfProto: Document;
+ let iconProto: Document;
const textProtoId = "textProto";
const histoProtoId = "histoProto";
const pdfProtoId = "pdfProto";
@@ -72,6 +82,7 @@ export namespace Documents {
const kvpProtoId = "kvpProto";
const videoProtoId = "videoProto";
const audioProtoId = "audioProto";
+ const iconProtoId = "iconProto";
export function initProtos(): Promise<void> {
return Server.GetFields([textProtoId, histoProtoId, collProtoId, pdfProtoId, imageProtoId, videoProtoId, audioProtoId, webProtoId, kvpProtoId]).then(fields => {
@@ -84,6 +95,7 @@ export namespace Documents {
videoProto = fields[videoProtoId] as Document || CreateVideoPrototype();
audioProto = fields[audioProtoId] as Document || CreateAudioPrototype();
pdfProto = fields[pdfProtoId] as Document || CreatePdfPrototype();
+ iconProto = fields[iconProtoId] as Document || CreateIconPrototype();
});
}
function assignOptions(doc: Document, options: DocumentOptions): Document {
@@ -91,13 +103,19 @@ export namespace Documents {
if (options.nativeHeight !== undefined) { doc.SetNumber(KeyStore.NativeHeight, options.nativeHeight); }
if (options.title !== undefined) { doc.SetText(KeyStore.Title, options.title); }
if (options.page !== undefined) { doc.SetNumber(KeyStore.Page, options.page); }
+ if (options.documentText !== undefined) { doc.SetText(KeyStore.DocumentText, options.documentText); }
if (options.scale !== undefined) { doc.SetNumber(KeyStore.Scale, options.scale); }
+ if (options.width !== undefined) { doc.SetNumber(KeyStore.Width, options.width); }
+ if (options.height !== undefined) { doc.SetNumber(KeyStore.Height, options.height); }
if (options.viewType !== undefined) { doc.SetNumber(KeyStore.ViewType, options.viewType); }
if (options.backgroundColor !== undefined) { doc.SetText(KeyStore.BackgroundColor, options.backgroundColor); }
if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); }
+ if (options.baseLayout !== undefined) { doc.SetText(KeyStore.BaseLayout, options.baseLayout); }
if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); }
+ if (options.templates !== undefined) { doc.Set(KeyStore.Templates, new TemplateField(options.templates)); }
if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); }
if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); }
+ if (options.borderRounding !== undefined) { doc.SetNumber(KeyStore.BorderRounding, options.borderRounding); }
return doc;
}
@@ -112,7 +130,7 @@ export namespace Documents {
}
function setupPrototypeOptions(protoId: string, title: string, layout: string, options: DocumentOptions): Document {
- return assignOptions(new Document(protoId), { ...options, title: title, layout: layout });
+ return assignOptions(new Document(protoId), { ...options, title: title, layout: layout, baseLayout: layout });
}
function SetInstanceOptions<T, U extends Field & { Data: T }>(doc: Document, options: DocumentOptions, value: [T, { new(): U }] | Document, id?: string) {
var deleg = doc.MakeDelegate(id);
@@ -130,6 +148,7 @@ export namespace Documents {
{ x: 0, y: 0, nativeWidth: 600, width: 300, layoutKeys: [KeyStore.Data, KeyStore.Annotations, KeyStore.Caption] });
imageProto.SetText(KeyStore.BackgroundLayout, ImageBox.LayoutString());
imageProto.SetNumber(KeyStore.CurPage, 0);
+ imageProto.SetData(KeyStore.LayoutFields, [KeyStore.Title], ListField);
return imageProto;
}
@@ -139,6 +158,11 @@ export namespace Documents {
histoProto.SetText(KeyStore.BackgroundLayout, HistogramBox.LayoutString());
return histoProto;
}
+ function CreateIconPrototype(): Document {
+ let iconProto = setupPrototypeOptions(iconProtoId, "ICON_PROTO", IconBox.LayoutString(),
+ { x: 0, y: 0, width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data] });
+ return iconProto;
+ }
function CreateTextPrototype(): Document {
let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(),
{ x: 0, y: 0, width: 300, height: 150, layoutKeys: [KeyStore.Data] });
@@ -203,6 +227,9 @@ export namespace Documents {
export function TextDocument(options: DocumentOptions = {}) {
return assignToDelegate(SetInstanceOptions(textProto, options, ["", TextField]).MakeDelegate(), options);
}
+ export function IconDocument(icon: string, options: DocumentOptions = {}) {
+ return assignToDelegate(SetInstanceOptions(iconProto, { width: Number(MINIMIZED_ICON_SIZE), height: Number(MINIMIZED_ICON_SIZE), layoutKeys: [KeyStore.Data], layout: IconBox.LayoutString(), ...options }, [icon, IconField]), options);
+ }
export function PdfDocument(url: string, options: DocumentOptions = {}) {
return assignToDelegate(SetInstanceOptions(pdfProto, options, [new URL(url), PDFField]).MakeDelegate(), options);
}
diff --git a/src/client/northstar/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.scss b/src/client/northstar/dash-nodes/HistogramBox.scss
index e899cf15e..06d781263 100644
--- a/src/client/northstar/dash-nodes/HistogramBox.scss
+++ b/src/client/northstar/dash-nodes/HistogramBox.scss
@@ -1,12 +1,12 @@
.histogrambox-container {
padding: 0vw;
position: absolute;
- top: 0;
- left:0;
+ top: -50%;
+ left:-50%;
text-align: center;
width: 100%;
height: 100%;
- background: black;
+ background: black;
}
.histogrambox-xaxislabel {
position:absolute;
diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx
index 3e94fed81..ac5f3c8cf 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 { FieldWaiting, Opt } from "../../../fields/Field";
import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
@@ -31,8 +30,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[] = [];
@@ -88,7 +85,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) {
@@ -118,7 +115,7 @@ export class HistogramBox extends React.Component<FieldViewProps> {
this.props.Document.GetTAsync(this.props.fieldKey, HistogramField).then((histoOp: Opt<HistogramField>) => runInAction(() => {
this.HistoOp = histoOp ? histoOp.Data : HistogramOperation.Empty;
if (this.HistoOp !== HistogramOperation.Empty) {
- reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, []), (docs: Document[]) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
+ reaction(() => this.props.Document.GetList(KeyStore.LinkedFromDocs, [] as Document[]), (docs) => this.HistoOp.Links.splice(0, this.HistoOp.Links.length, ...docs), { fireImmediately: true });
reaction(() => this.props.Document.GetList(KeyStore.BrushingDocs, []).length,
() => {
let brushingDocs = this.props.Document.GetList(KeyStore.BrushingDocs, [] as Document[]);
@@ -134,38 +131,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 : this.props.Document.GetNumber(KeyStore.Height, 0);
- var w = this.props.isTopMost ? this.PanelWidth : this.props.Document.GetNumber(KeyStore.Width, 0);
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} style={{ transform: `translate(-50%, -50%)` }}>
- <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 760106023..6a8c9d8cf 100644
--- a/src/client/northstar/operations/HistogramOperation.ts
+++ b/src/client/northstar/operations/HistogramOperation.ts
@@ -23,7 +23,7 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons
@observable public Links: Document[] = [];
@observable public BrushLinks: { l: Document, b: Document }[] = [];
@observable public BrushColors: number[] = [];
- @observable public FilterModels: FilterModel[] = [];
+ @observable public BarFilterModels: FilterModel[] = [];
@observable public Normalization: number = -1;
@observable public X: AttributeTransformationModel;
@@ -50,17 +50,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);
}
@@ -79,6 +86,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/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 3b5a5b470..56669fb79 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -38,11 +38,17 @@ export class DocumentManager {
toReturn = view;
return;
}
- let docSrc = doc.GetT(KeyStore.Prototype, Document);
- if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
- toReturn = view;
- }
});
+ if (!toReturn) {
+ DocumentManager.Instance.DocumentViews.map(view => {
+ let doc = view.props.Document;
+
+ let docSrc = doc.GetT(KeyStore.Prototype, Document);
+ if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) {
+ toReturn = view;
+ }
+ });
+ }
return toReturn;
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 4bd654e15..136852e12 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -41,11 +41,11 @@ export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
}
export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Document) {
- let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
- let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+ let srcTarg = sourceDoc.GetPrototype();
+ let draggedDocs = srcTarg ?
srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
(linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
- let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+ let draggedFromDocs = srcTarg ?
srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
(linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
draggedDocs.push(...draggedFromDocs);
@@ -158,11 +158,13 @@ export namespace DragManager {
}
export class LinkDragData {
- constructor(linkSourceDoc: Document) {
+ constructor(linkSourceDoc: Document, blacklist: Document[] = []) {
this.linkSourceDocument = linkSourceDoc;
+ this.blacklist = blacklist;
}
droppedDocuments: Document[] = [];
linkSourceDocument: Document;
+ blacklist: Document[];
[id: string]: any;
}
@@ -170,10 +172,13 @@ 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");
dragDiv.className = "dragManager-dragDiv";
+ dragDiv.style.pointerEvents = "none";
DragManager.Root().appendChild(dragDiv);
}
MainOverlayTextBox.Instance.SetTextDoc();
@@ -250,7 +255,7 @@ export namespace DragManager {
dragData.aliasOnDrop = e.ctrlKey || e.altKey;
}
if (e.shiftKey) {
- abortDrag();
+ AbortDrag();
CollectionDockingView.Instance.StartOtherDrag(docs, {
pageX: e.pageX,
pageY: e.pageY,
@@ -269,21 +274,32 @@ export namespace DragManager {
);
};
- const 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 => 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);
}
- 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);
@@ -308,11 +324,6 @@ export namespace DragManager {
}
})
);
-
- if (options) {
- options.handlers.dragComplete({});
- }
}
- DocumentDecorations.Instance.Hidden = false;
}
}
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 320553952..da66bf3fc 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,8 +1,7 @@
-import { observable, action } from "mobx";
-import { DocumentView } from "../views/nodes/DocumentView";
+import { action, observable } from "mobx";
import { Document } from "../../fields/Document";
-import { Main } from "../views/Main";
import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
+import { DocumentView } from "../views/nodes/DocumentView";
export namespace SelectionManager {
class Manager {
@@ -18,13 +17,13 @@ export namespace SelectionManager {
if (manager.SelectedDocuments.indexOf(doc) === -1) {
manager.SelectedDocuments.push(doc);
- doc.props.onActiveChanged(true);
+ doc.props.whenActiveChanged(true);
}
}
@action
DeselectAll(): void {
- manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false));
+ manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false));
manager.SelectedDocuments = [];
MainOverlayTextBox.Instance.SetTextDoc();
}
@@ -36,7 +35,7 @@ export namespace SelectionManager {
}
@action
ReselectAll2(sdocs: DocumentView[]) {
- sdocs.map(s => SelectionManager.SelectDoc(s, false));
+ sdocs.map(s => SelectionManager.SelectDoc(s, true));
}
}
@@ -64,7 +63,7 @@ export namespace SelectionManager {
export function ReselectAll() {
let sdocs = manager.ReselectAll();
- manager.ReselectAll2(sdocs);
+ setTimeout(() => manager.ReselectAll2(sdocs), 0);
}
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss
index 5c2d66480..70d9ad772 100644
--- a/src/client/util/TooltipTextMenu.scss
+++ b/src/client/util/TooltipTextMenu.scss
@@ -35,6 +35,10 @@
cursor: pointer;
position: relative;
padding-right: 15px;
+ margin: 3px;
+ background: #333333;
+ border-radius: 3px;
+ text-align: center;
}
.ProseMirror-menu-dropdown-wrap {
diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx
index a92cbd263..14af4bdfd 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,16 +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;
@@ -55,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 }) => {
@@ -76,7 +84,7 @@ export class TooltipTextMenu {
this.fontStylesToName.set(schema.marks.timesNewRoman, "Times New Roman");
this.fontStylesToName.set(schema.marks.arial, "Arial");
this.fontStylesToName.set(schema.marks.georgia, "Georgia");
- this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans");
+ this.fontStylesToName.set(schema.marks.comicSans, "Comic Sans MS");
this.fontStylesToName.set(schema.marks.tahoma, "Tahoma");
this.fontStylesToName.set(schema.marks.impact, "Impact");
this.fontStylesToName.set(schema.marks.crimson, "Crimson Text");
@@ -93,38 +101,75 @@ 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);
}
- //adds font size and font style dropdowns
- addFontDropdowns() {
+ //label of dropdown will change to given label
+ updateFontSizeDropdown(label: string) {
//filtering function - might be unecessary
let cut = (arr: MenuItem[]) => arr.filter(x => x);
+
+ //font SIZES
+ let fontSizeBtns: MenuItem[] = [];
+ this.fontSizeToNum.forEach((number, mark) => {
+ fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes));
+ });
+
+ if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); }
+ this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), {
+ label: label,
+ css: "color:white; min-width: 60px; padding-left: 5px; margin-right: 0;"
+ }) as MenuItem).render(this.view).dom;
+ this.tooltip.appendChild(this.fontSizeDom);
+ }
+
+ //label of dropdown will change to given label
+ updateFontStyleDropdown(label: string) {
+ //filtering function - might be unecessary
+ let cut = (arr: MenuItem[]) => arr.filter(x => x);
+
//font STYLES
let fontBtns: MenuItem[] = [];
this.fontStylesToName.forEach((name, mark) => {
- fontBtns.push(this.dropdownBtn(name, "font-family: " + name + ", sans-serif; width: 120px;", 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));
});
- //font size indicator
- this.fontSizeIndicator = this.icon("12", "font-size-indicator");
+ if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); }
+ this.fontStyleDom = (new Dropdown(cut(fontBtns), {
+ label: label,
+ css: "color:white; width: 125px; margin-left: -3px; padding-left: 2px;"
+ }) as MenuItem).render(this.view).dom;
- //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));
+ 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;
- //dropdown to hold font btns
- let dd_fontStyle = new Dropdown(cut(fontBtns), { label: "Font Style", css: "color:white;" }) as MenuItem;
- let dd_fontSize = new Dropdown(cut(fontSizeBtns), { label: "Font Size", css: "color:white; margin-left: -6px;" }) as MenuItem;
- this.tooltip.appendChild(dd_fontStyle.render(this.view).dom);
- this.tooltip.appendChild(this.fontSizeIndicator);
- this.tooltip.appendChild(dd_fontSize.render(this.view).dom);
- dd_fontStyle.render(this.view).dom.nodeValue = "TEST";
- console.log(dd_fontStyle.render(this.view).dom.nodeValue);
+ //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
@@ -158,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,
@@ -173,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");
@@ -246,34 +317,39 @@ export class TooltipTextMenu {
let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale;
let mid = Math.min(start.left, end.left) + width;
- this.tooltip.style.width = 220 + "px";
+ 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) {
// if we want to update something somewhere with active font name
let fontName = this.fontStylesToName.get(activeStyles[0]);
+ if (fontName) { this.updateFontStyleDropdown(fontName); }
} else if (activeStyles.length === 0) {
//crimson on default
+ this.updateFontStyleDropdown("Crimson Text");
+ } else {
+ this.updateFontStyleDropdown("Various");
}
- //update font size indicator
+ //UPDATE FONT SIZE DROPDOWN
let activeSizes = this.activeMarksOnSelection(this.fontSizes);
if (activeSizes.length === 1) { //if there's only one active font size
let size = this.fontSizeToNum.get(activeSizes[0]);
- if (size) {
- this.fontSizeIndicator.innerHTML = String(size);
- }
- //should be 14 on default
+ if (size) { this.updateFontSizeDropdown(String(size) + " pt"); }
} else if (activeSizes.length === 0) {
- this.fontSizeIndicator.innerHTML = "14";
- //multiple font sizes selected
- } else {
- this.fontSizeIndicator.innerHTML = "";
+ //should be 14 on default
+ this.updateFontSizeDropdown("14 pt");
+ } else { //multiple font sizes selected
+ this.updateFontSizeDropdown("Various");
}
}
- //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/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 c1a949639..27b94e6d2 100644
--- a/src/client/views/DocumentDecorations.scss
+++ b/src/client/views/DocumentDecorations.scss
@@ -3,18 +3,19 @@
.documentDecorations {
position: absolute;
}
-#documentDecorations-container {
+
+.documentDecorations-container {
+ z-index: $docDecorations-zindex;
position: absolute;
top: 0;
- left:0;
+ left: 0;
display: grid;
- z-index: $docDecorations-zindex;
grid-template-rows: 20px 8px 1fr 8px;
- grid-template-columns: 8px 8px 1fr 8px 8px;
+ grid-template-columns: 8px 16px 1fr 8px 8px;
pointer-events: none;
#documentDecorations-centerCont {
- 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,15 +81,22 @@
text-align: center;
cursor: pointer;
}
+
.documentDecorations-minimizeButton {
- background:$alt-accent;
+ background: $alt-accent;
opacity: 0.8;
grid-column-start: 1;
grid-column-end: 3;
pointer-events: all;
text-align: center;
cursor: pointer;
+ position: absolute;
+ left: 0px;
+ top: 0px;
+ width: $MINIMIZED_ICON_SIZE;
+ height: $MINIMIZED_ICON_SIZE;
}
+
.documentDecorations-background {
background: lightblue;
position: absolute;
@@ -100,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;
@@ -134,13 +130,14 @@
justify-content: center;
align-items: center;
}
+
.linkButton-tray {
grid-column: 1/4;
}
+
.linkButton-empty {
height: 20px;
width: 20px;
- margin-top: 10px;
border-radius: 50%;
opacity: 0.9;
pointer-events: auto;
@@ -154,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 16fac0694..18449ed32 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,11 +1,12 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, runInAction, untracked, reaction } from "mobx";
import { observer } from "mobx-react";
import { Key } from "../../fields/Key";
import { KeyStore } from "../../fields/KeyStore";
import { ListField } from "../../fields/ListField";
import { NumberField } from "../../fields/NumberField";
import { TextField } from "../../fields/TextField";
-import { emptyFunction } from "../../Utils";
+import { Document } from "../../fields/Document";
+import { emptyFunction, Utils } from "../../Utils";
import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
import { undoBatch } from "../util/UndoManager";
@@ -13,7 +14,13 @@ import './DocumentDecorations.scss';
import { MainOverlayTextBox } from "./MainOverlayTextBox";
import { DocumentView } 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 { FieldValue, Field } from "../../fields/Field";
+import { Documents } from "../documents/Documents";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -21,37 +28,36 @@ export const Flyout = higflyout.default;
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
static Instance: DocumentDecorations;
- private _resizer = "";
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>();
- //@observable private _title: string = this._documents[0].props.Document.Title;
- @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : "";
+ private _downX = 0;
+ private _downY = 0;
+ @observable private _minimizedX = 0;
+ @observable private _minimizedY = 0;
+ @observable private _title: string = "";
+ @observable private _edtingTitle = false;
@observable private _fieldKey: Key = KeyStore.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) {
@@ -59,36 +65,52 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (text[0] === '#') {
let command = text.slice(1, text.length);
this._fieldKey = new Key(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._title = this.selectionTitle;
}
else {
- if (this._documents.length > 0) {
- let field = this._documents[0].props.Document.Get(this._fieldKey);
- if (field instanceof TextField) {
- this._documents.forEach(d =>
- d.props.Document.Set(this._fieldKey, new TextField(this._title)));
- }
- else if (field instanceof NumberField) {
- this._documents.forEach(d =>
- d.props.Document.Set(this._fieldKey, new NumberField(+this._title)));
+ if (SelectionManager.SelectedDocuments().length > 0) {
+ let field = SelectionManager.SelectedDocuments()[0].props.Document.Get(this._fieldKey);
+ if (field instanceof NumberField) {
+ SelectionManager.SelectedDocuments().forEach(d =>
+ d.props.Document.SetNumber(this._fieldKey, +this._title));
+ } else if (field instanceof TextField || true) {
+ SelectionManager.SelectedDocuments().forEach(d =>
+ d.props.Document.SetText(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;
@@ -103,46 +125,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().inverse().transformPoint(0, 0);
+ const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
+ 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;
- dragData.xOffset = e.x - left;
- dragData.yOffset = e.y - top;
- let move = SelectionManager.SelectedDocuments()[0].props.moveDocument;
- dragData.moveDocument = move;
- 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._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);
@@ -175,32 +190,114 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.removeEventListener("pointerup", this.onCloseUp);
}
}
+ @action
onMinimizeDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
+ this._downX = e.pageX;
+ this._downY = e.pageY;
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ this._minimizedX = selDocPos[0] + 12;
+ this._minimizedY = selDocPos[1] + 12;
document.removeEventListener("pointermove", this.onMinimizeMove);
document.addEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
document.addEventListener("pointerup", this.onMinimizeUp);
}
}
+
+ @action
onMinimizeMove = (e: PointerEvent): void => {
e.stopPropagation();
+ if (Math.abs(e.pageX - this._downX) > Utils.DRAG_THRESHOLD ||
+ Math.abs(e.pageY - this._downY) > Utils.DRAG_THRESHOLD) {
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ let snapped = Math.abs(e.pageX - selDocPos[0]) < 20 && Math.abs(e.pageY - selDocPos[1]) < 20;
+ this._minimizedX = snapped ? selDocPos[0] + 4 : e.clientX;
+ this._minimizedY = snapped ? selDocPos[1] - 18 : e.clientY;
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+ Promise.all(selectedDocs.map(async selDoc => await this.getIconDoc(selDoc))).then(minDocSet =>
+ this.moveIconDocs(SelectionManager.SelectedDocuments())
+ );
+ this._iconifying = snapped;
+ }
}
+
+
+ @action createIcon = (docView: DocumentView, layoutString: string): Document => {
+ let doc = docView.props.Document;
+ let iconDoc = Documents.IconDocument(layoutString);
+ iconDoc.SetText(KeyStore.Title, "ICON" + doc.Title)
+ iconDoc.SetBoolean(KeyStore.IsMinimized, false);
+ iconDoc.SetNumber(KeyStore.NativeWidth, 0);
+ iconDoc.SetNumber(KeyStore.NativeHeight, 0);
+ iconDoc.SetNumber(KeyStore.X, doc.GetNumber(KeyStore.X, 0));
+ iconDoc.SetNumber(KeyStore.Y, doc.GetNumber(KeyStore.Y, 0) - 24);
+ iconDoc.Set(KeyStore.Prototype, doc);
+ iconDoc.Set(KeyStore.MaximizedDoc, doc);
+ doc.Set(KeyStore.MinimizedDoc, iconDoc);
+ docView.props.addDocument && docView.props.addDocument(iconDoc, false);
+ return iconDoc;
+ }
+ @action
+ public getIconDoc = async (docView: DocumentView): Promise<Document | undefined> => {
+ let doc = docView.props.Document;
+ let iconDoc = await doc.GetTAsync(KeyStore.MinimizedDoc, Document).then(async mindoc =>
+ mindoc ? mindoc :
+ await doc.GetTAsync(KeyStore.BackgroundLayout, TextField).then(async field =>
+ (field instanceof TextField) ? this.createIcon(docView, field.Data) :
+ await doc.GetTAsync(KeyStore.Layout, TextField).then(field =>
+ (field instanceof TextField) ? this.createIcon(docView, field.Data) : undefined)));
+ if (SelectionManager.SelectedDocuments()[0].props.addDocument !== undefined)
+ SelectionManager.SelectedDocuments()[0].props.addDocument!(iconDoc!);
+ return iconDoc;
+ }
+ @action
onMinimizeUp = (e: PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
- SelectionManager.SelectedDocuments().map(dv => dv.minimize());
document.removeEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+ Promise.all(selectedDocs.map(async selDoc => await this.getIconDoc(selDoc))).then(minDocSet => {
+ let minDocs = minDocSet.filter(minDoc => minDoc instanceof Document).map(minDoc => minDoc as Document);
+ minDocs.map(minDoc => {
+ minDoc.SetNumber(KeyStore.X, minDocs[0].GetNumber(KeyStore.X, 0));
+ minDoc.SetNumber(KeyStore.Y, minDocs[0].GetNumber(KeyStore.Y, 0));
+ minDoc.SetData(KeyStore.LinkTags, minDocs, ListField);
+ if (this._iconifying && selectedDocs[0].props.removeDocument) {
+ selectedDocs[0].props.removeDocument(minDoc);
+ (minDoc.Get(KeyStore.MaximizedDoc, false) as Document)!.Set(KeyStore.MinimizedDoc, undefined);
+ }
+ });
+ runInAction(() => this._minimizedX = this._minimizedY = 0);
+ if (!this._iconifying) selectedDocs[0].props.toggleMinimized();
+ this._iconifying = false;
+ });
}
}
+ moveIconDocs(selViews: DocumentView[], minDocSet?: FieldValue<Field>[]) {
+ selViews.map(selDoc => {
+ let minDoc = selDoc.props.Document.Get(KeyStore.MinimizedDoc);
+ if (minDoc instanceof Document) {
+ let zoom = selDoc.props.Document.GetNumber(KeyStore.ZoomBasis, 1);
+ let where = (selDoc.props.ScreenToLocalTransform()).scale(selDoc.props.ContentScaling()).scale(1 / zoom).
+ transformPoint(this._minimizedX - 12, this._minimizedY - 12);
+ minDoc.SetNumber(KeyStore.X, where[0] + selDoc.props.Document.GetNumber(KeyStore.X, 0));
+ minDoc.SetNumber(KeyStore.Y, where[1] + selDoc.props.Document.GetNumber(KeyStore.Y, 0));
+ }
+ });
+ }
+ @action
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
this._isPointerDown = true;
- this._resizer = e.currentTarget.id;
+ this._resizing = e.currentTarget.id;
+ this.Interacting = true;
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -225,7 +322,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (this._linkerButton.current !== null) {
document.removeEventListener("pointermove", this.onLinkerButtonMoved);
document.removeEventListener("pointerup", this.onLinkerButtonUp);
- let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0].props.Document);
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.GetPrototype() : undefined;
+ let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
handlers: {
dragComplete: action(emptyFunction),
@@ -269,7 +368,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let dX = 0, dY = 0, dW = 0, dH = 0;
- switch (this._resizer) {
+ switch (this._resizing) {
case "":
break;
case "documentDecorations-topLeftResizer":
@@ -339,8 +438,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
});
}
+ @action
onPointerUp = (e: PointerEvent): void => {
e.stopPropagation();
+ this._resizing = "";
+ this.Interacting = false;
+ SelectionManager.ReselectAll();
if (e.button === 0) {
e.preventDefault();
this._isPointerDown = false;
@@ -349,17 +452,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
}
- getValue = (): string => {
- if (this._title === "changed" && this._documents.length > 0) {
- let field = this._documents[0].props.Document.Get(this._fieldKey);
+ @computed
+ get selectionTitle(): string {
+ if (SelectionManager.SelectedDocuments().length === 1) {
+ let field = SelectionManager.SelectedDocuments()[0].props.Document.Get(this._fieldKey);
if (field instanceof TextField) {
return (field).GetValue();
}
else if (field instanceof NumberField) {
return (field).GetValue().toString();
}
+ } else if (SelectionManager.SelectedDocuments().length > 1) {
+ return "-multiple-";
}
- return this._title;
+ return "-unset-";
}
changeFlyoutContent = (): void => {
@@ -368,20 +474,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
// buttonOnPointerUp = (e: React.PointerEvent): void => {
// e.stopPropagation();
// }
+
render() {
var bounds = this.Bounds;
- if (bounds.x === Number.MAX_VALUE) {
- return (null);
- }
- // console.log(this._documents.length)
- // let test = this._documents[0].props.Document.Title;
- if (this.Hidden) {
- return (null);
- }
- if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) {
- console.log("DocumentDecorations: Bounds Error");
+ let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined;
+ 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 = (
+ <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
+ {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."}
+ </div>);
let linkButton = null;
if (SelectionManager.SelectedDocuments().length > 0) {
@@ -396,25 +499,42 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
<div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "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>
- <div id="documentDecorations-container" style={{
+ <div className="documentDecorations-container" style={{
width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px",
height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight) + "px",
left: bounds.x - this._resizeBorderWidth / 2,
top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight,
opacity: this._opacity
}}>
- <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div>
- <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} />
+ {minimizeIcon}
+
+ {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>
@@ -428,6 +548,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 47ee8eb85..83836aa59 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -148,14 +148,15 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
}
return paths;
}, [] as JSX.Element[]);
- return [<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)}
- </svg>,
- <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)}
- </svg>];
+ 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)}
+ </svg>,
+ <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)}
+ </svg>];
}
render() {
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4373534b2..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%;
@@ -42,7 +46,9 @@ h1 {
}
.jsx-parser {
- width:100%
+ width:100%;
+ pointer-events: none;
+ border-radius: inherit;
}
p {
@@ -53,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 a7e088ea4..15f999bda 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -16,7 +16,7 @@ import { CurrentUserUtils } from '../../server/authentication/models/current_use
import { MessageStore } from '../../server/Message';
import { RouteStore } from '../../server/RouteStore';
import { ServerUtils } from '../../server/ServerUtil';
-import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne } from '../../Utils';
+import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
import { Documents } from '../documents/Documents';
import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
@@ -26,7 +26,7 @@ import '../northstar/model/ModelExtensions';
import { HistogramOperation } from '../northstar/operations/HistogramOperation';
import '../northstar/utils/Extensions';
import { Server } from '../Server';
-import { SetupDrag } from '../util/DragManager';
+import { SetupDrag, DragManager } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
import { PresentationView } from './PresentationView';
@@ -39,6 +39,7 @@ import "./Main.scss";
import { MainOverlayTextBox } from './MainOverlayTextBox';
import { DocumentView } from './nodes/DocumentView';
import { PreviewCursor } from './PreviewCursor';
+import { SelectionManager } from '../util/SelectionManager';
@observer
@@ -115,6 +116,12 @@ export class Main extends React.Component {
// window.addEventListener("pointermove", (e) => this.reportLocation(e))
window.addEventListener("drop", (e) => e.preventDefault(), false); // drop event handler
window.addEventListener("dragover", (e) => e.preventDefault(), false); // drag event handler
+ window.addEventListener("keydown", (e) => {
+ if (e.key == "Escape") {
+ DragManager.AbortDrag();
+ SelectionManager.DeselectAll()
+ }
+ }, false); // drag event handler
// click interactions for the context menu
document.addEventListener("pointerdown", action(function (e: PointerEvent) {
if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
@@ -193,25 +200,20 @@ export class Main extends React.Component {
{({ measureRef }) =>
<div ref={measureRef} id="mainContent-div">
{!mainCont ? (null) :
- <div style={{ width: "100%", height: "100%", position: "absolute", }}>
- {pcontent}
- <DocumentView
- key="documentView"
- Document={mainCont}
- addDocument={undefined}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- ContentScaling={noScaling}
- PanelWidth={pwidthFunc}
- PanelHeight={pheightFunc}
- isTopMost={true}
- selectOnLoad={false}
- focus={emptyDocFunction}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- ContainingCollectionView={undefined} />
- </div>
- }
+ <DocumentView Document={mainCont}
+ toggleMinimized={emptyFunction}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={noScaling}
+ PanelWidth={pwidthFunc}
+ PanelHeight={pheightFunc}
+ isTopMost={true}
+ selectOnLoad={false}
+ focus={emptyDocFunction}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ ContainingCollectionView={undefined} />}
</div>
}
</Measure>;
@@ -226,13 +228,13 @@ 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(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }));
+ let addTextNode = action(() => Documents.TextDocument({ borderRounding: -1, width: 200, height: 50, title: "a text note" }));
let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true }));
- let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
+ let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, title: "video node" }));
let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
- let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
+ let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index 697d68c8c..f6a746e63 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -7,6 +7,7 @@
overflow: visible;
top: 0;
left: 0;
+ pointer-events: none;
z-index: $mainTextInput-zindex;
.formattedTextBox-cont {
background-color: rgba(248, 6, 6, 0.001);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 141b3ad74..13e661b46 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import { Document } from '../../fields/Document';
import { Key } from '../../fields/Key';
import { KeyStore } from '../../fields/KeyStore';
-import { emptyDocFunction, emptyFunction, returnTrue } from '../../Utils';
+import { emptyDocFunction, emptyFunction, returnTrue, returnZero } from '../../Utils';
import '../northstar/model/ModelExtensions';
import '../northstar/utils/Extensions';
import { DragManager } from '../util/DragManager';
@@ -21,8 +21,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
public static Instance: MainOverlayTextBox;
@observable public TextDoc?: Document = undefined;
public TextScroll: number = 0;
- private _textRect: any;
- private _textXf: Transform = Transform.Identity();
+ @observable _textXf: () => Transform = () => Transform.Identity();
private _textFieldKey: Key = KeyStore.Data;
private _textColor: string | null = null;
private _textTargetDiv: HTMLDivElement | undefined;
@@ -35,19 +34,18 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
}
@action
- SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: Transform) {
+ SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: () => Transform) {
if (this._textTargetDiv) {
this._textTargetDiv.style.color = this._textColor;
}
this.TextDoc = textDoc;
this._textFieldKey = textFieldKey!;
- this._textXf = tx ? tx : Transform.Identity();
+ this._textXf = tx ? tx : () => Transform.Identity();
this._textTargetDiv = div;
if (div) {
this._textColor = div.style.color;
div.style.color = "transparent";
- this._textRect = div.getBoundingClientRect();
this.TextScroll = div.scrollTop;
}
}
@@ -71,9 +69,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
document.removeEventListener("pointermove", this.textBoxMove);
document.removeEventListener('pointerup', this.textBoxUp);
let dragData = new DragManager.DocumentDragData([this.TextDoc!]);
- const [left, top] = this._textXf
- .inverse()
- .transformPoint(0, 0);
+ const [left, top] = this._textXf().inverse().transformPoint(0, 0);
dragData.xOffset = e.clientX - left;
dragData.yOffset = e.clientY - top;
DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
@@ -90,18 +86,15 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
}
render() {
- if (this.TextDoc) {
- let x: number = this._textRect.x;
- let y: number = this._textRect.y;
- let w: number = this._textRect.width;
- let h: number = this._textRect.height;
- let t = this._textXf.transformPoint(0, 0);
- let s = this._textXf.transformPoint(1, 0);
- s[0] = Math.sqrt((s[0] - t[0]) * (s[0] - t[0]) + (s[1] - t[1]) * (s[1] - t[1]));
- return <div className="mainOverlayTextBox-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
- <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}>
+ if (this.TextDoc && this._textTargetDiv) {
+ let textRect = this._textTargetDiv.getBoundingClientRect();
+ let s = this._textXf().Scale;
+ return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.x}px, ${textRect.y}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: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}>
<FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
- selectOnLoad={true} ContainingCollectionView={undefined} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+ selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyDocFunction} />
</div>
</ div>;
}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ff8434681..4359ba093 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -7,30 +7,48 @@ import "./PreviewCursor.scss";
@observer
export class PreviewCursor extends React.Component<{}> {
private _prompt = React.createRef<HTMLDivElement>();
+ static _onKeyPress?: (e: KeyboardEvent) => void;
+ @observable static _clickPoint = [0, 0];
+ @observable public static Visible = false;
//when focus is lost, this will remove the preview cursor
@action onBlur = (): void => {
PreviewCursor.Visible = false;
- PreviewCursor.hide();
}
- @observable static clickPoint = [0, 0];
- @observable public static Visible = false;
- @observable public static hide = () => { };
+ constructor(props: any) {
+ super(props);
+ document.addEventListener("keydown", this.onKeyPress)
+ }
+
+ @action
+ onKeyPress = (e: KeyboardEvent) => {
+ // Mixing events between React and Native is finicky. In FormattedTextBox, we set the
+ // 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.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;
+ }
+ }
+ }
@action
- public static Show(hide: any, x: number, y: number) {
- this.clickPoint = [x, y];
- this.hide = hide;
+ public static Show(x: number, y: number, onKeyPress: (e: KeyboardEvent) => void) {
+ this._clickPoint = [x, y];
+ this._onKeyPress = onKeyPress;
setTimeout(action(() => this.Visible = true), (1));
}
render() {
- if (!PreviewCursor.clickPoint) {
+ if (!PreviewCursor._clickPoint) {
return (null);
}
if (PreviewCursor.Visible && this._prompt.current) {
this._prompt.current.focus();
}
return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._prompt}
- style={{ transform: `translate(${PreviewCursor.clickPoint[0]}px, ${PreviewCursor.clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
+ style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
I
</div >;
}
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 444f6fc26..aa8fce923 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -2,12 +2,13 @@ import { action, computed } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Document } from '../../../fields/Document';
-import { Field, FieldValue, FieldWaiting } from '../../../fields/Field';
+import { FieldValue, FieldWaiting } from '../../../fields/Field';
import { KeyStore } from '../../../fields/KeyStore';
import { ListField } from '../../../fields/ListField';
import { NumberField } from '../../../fields/NumberField';
import { ContextMenu } from '../ContextMenu';
import { FieldViewProps } from '../nodes/FieldView';
+import { SelectionManager } from '../../util/SelectionManager';
export enum CollectionViewType {
Invalid,
@@ -22,7 +23,7 @@ export interface CollectionRenderProps {
removeDocument: (document: Document) => boolean;
moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
active: () => boolean;
- onActiveChanged: (isActive: boolean) => void;
+ whenActiveChanged: (isActive: boolean) => void;
}
export interface CollectionViewProps extends FieldViewProps {
@@ -55,19 +56,22 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
//TODO should this be observable?
private _isChildActive = false;
- onActiveChanged = (isActive: boolean) => {
+ whenActiveChanged = (isActive: boolean) => {
this._isChildActive = isActive;
- this.props.onActiveChanged(isActive);
+ this.props.whenActiveChanged(isActive);
}
createsCycle(documentToAdd: Document, containerDocument: Document): boolean {
- let data = documentToAdd.GetList<Document>(KeyStore.Data, []);
- for (const doc of data) {
+ if (!(documentToAdd instanceof Document)) {
+ return false;
+ }
+ let data = documentToAdd.GetList(KeyStore.Data, [] as Document[]);
+ for (const doc of data.filter(d => d instanceof Document)) {
if (this.createsCycle(doc, containerDocument)) {
return true;
}
}
- let annots = documentToAdd.GetList<Document>(KeyStore.Annotations, []);
+ let annots = documentToAdd.GetList(KeyStore.Annotations, [] as Document[]);
for (const annot of annots) {
if (this.createsCycle(annot, containerDocument)) {
return true;
@@ -84,56 +88,56 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
@action.bound
addDocument(doc: Document, allowDuplicates: boolean = false): boolean {
- let props = this.props;
- var curPage = props.Document.GetNumber(KeyStore.CurPage, -1);
+ var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
doc.SetOnPrototype(KeyStore.Page, new NumberField(curPage));
- if (this.isAnnotationOverlay) {
- doc.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));
- }
if (curPage >= 0) {
- doc.SetOnPrototype(KeyStore.AnnotationOn, props.Document);
+ doc.SetOnPrototype(KeyStore.AnnotationOn, this.props.Document);
}
- if (props.Document.Get(props.fieldKey) instanceof Field) {
- //TODO This won't create the field if it doesn't already exist
- const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>());
- if (!this.createsCycle(doc, props.Document)) {
- if (!value.some(v => v.Id === doc.Id) || allowDuplicates) {
- value.push(doc);
+ if (!this.createsCycle(doc, this.props.Document)) {
+ let value = this.props.Document.Get(this.props.fieldKey) as ListField<Document>;
+ if (value) {
+ if (!value.Data.some(v => v.Id === doc.Id) || allowDuplicates) {
+ value.Data.push(doc);
}
+ } else {
+ this.props.Document.Set(this.props.fieldKey, new ListField([doc]));
}
- else {
- return false;
- }
- } else {
- let proto = props.Document.GetPrototype();
- if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
- const field = new ListField([doc]);
- // const script = CompileScript(`
- // if(added) {
- // console.log("added " + field.Title + " " + doc.Title);
- // } else {
- // console.log("removed " + field.Title + " " + doc.Title);
- // }
- // `, {
- // addReturn: false,
- // params: {
- // field: Document.name,
- // added: "boolean"
- // },
- // capturedVariables: {
- // doc: this.props.Document
- // }
- // });
- // if (script.compiled) {
- // field.addScript(new ScriptField(script));
- // }
- props.Document.SetOnPrototype(props.fieldKey, field);
- }
- else {
- return false;
+ // 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 = this.props.Document.GetNumber(KeyStore.Scale, 1);
+ doc.SetNumber(KeyStore.ZoomBasis, zoom);
}
}
return true;
+ // bcz: What is this code trying to do?
+ // else {
+ // let proto = props.Document.GetPrototype();
+ // if (!proto || proto === FieldWaiting || !this.createsCycle(proto, doc)) {
+ // const field = new ListField([doc]);
+ // // const script = CompileScript(`
+ // // if(added) {
+ // // console.log("added " + field.Title + " " + doc.Title);
+ // // } else {
+ // // console.log("removed " + field.Title + " " + doc.Title);
+ // // }
+ // // `, {
+ // // addReturn: false,
+ // // params: {
+ // // field: Document.name,
+ // // added: "boolean"
+ // // },
+ // // capturedVariables: {
+ // // doc: this.props.Document
+ // // }
+ // // });
+ // // if (script.compiled) {
+ // // field.addScript(new ScriptField(script));
+ // // }
+ // props.Document.SetOnPrototype(props.fieldKey, field);
+ // return true;
+ // }
+ // }
+ return false;
}
@action.bound
@@ -170,6 +174,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
return true;
}
if (this.removeDocument(doc)) {
+ SelectionManager.DeselectAll();
return addDocument(doc);
}
return false;
@@ -181,11 +186,13 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
removeDocument: this.removeDocument,
moveDocument: this.moveDocument,
active: this.active,
- onActiveChanged: this.onActiveChanged,
+ whenActiveChanged: this.whenActiveChanged,
};
const viewtype = this.collectionViewType;
return (
- <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
+ <div className={this.props.className || "collectionView-cont"}
+ style={{ borderRadius: "inherit", pointerEvents: "all" }}
+ onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
{viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
</div>
);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4ea21b2f5..2a8191c3a 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -8,9 +8,9 @@ import { Document } from "../../../fields/Document";
import { KeyStore } from "../../../fields/KeyStore";
import Measure from "react-measure";
import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field";
-import { Utils, returnTrue, emptyFunction, emptyDocFunction, returnOne } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, emptyDocFunction, returnOne, returnZero } from "../../../Utils";
import { Server } from "../../Server";
-import { undoBatch } from "../../util/UndoManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
import "./CollectionDockingView.scss";
import React = require("react");
@@ -19,6 +19,7 @@ import { ServerUtils } from "../../../server/ServerUtil";
import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
import { TextField } from "../../../fields/TextField";
import { ListField } from "../../../fields/ListField";
+import { Transform } from '../../util/Transform'
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -47,7 +48,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
(window as any).React = React;
(window as any).ReactDOM = ReactDOM;
}
+ hack: boolean = false;
+ undohack: any = null;
public StartOtherDrag(dragDocs: Document[], e: any) {
+ this.hack = true;
+ this.undohack = UndoManager.StartBatch("goldenDrag");
dragDocs.map(dragDoc =>
this.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
@@ -235,6 +240,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
stateChanged = () => {
var json = JSON.stringify(this._goldenLayout.toConfig());
this.props.Document.SetText(KeyStore.Data, json);
+ if (this.undohack && !this.hack) {
+ this.undohack.end();
+ this.undohack = undefined;
+ }
+ this.hack = false;
}
itemDropped = () => {
@@ -315,7 +325,7 @@ interface DockedFrameProps {
@observer
export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
- private _mainCont = React.createRef<HTMLDivElement>();
+ _mainCont = React.createRef<HTMLDivElement>();
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Document>;
@@ -325,38 +335,56 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
Server.GetField(this.props.documentId, action((f: Opt<Field>) => this._document = f as Document));
}
- private _nativeWidth = () => this._document!.GetNumber(KeyStore.NativeWidth, this._panelWidth);
- private _nativeHeight = () => this._document!.GetNumber(KeyStore.NativeHeight, this._panelHeight);
- private _contentScaling = () => this._panelWidth / (this._nativeWidth() ? this._nativeWidth() : this._panelWidth);
+ nativeWidth = () => {
+ let pw = this._document!.GetNumber(KeyStore.NativeWidth, 0);
+ return pw ? pw : this._panelWidth;
+ }
+ nativeHeight = () => {
+ let pw = this._document!.GetNumber(KeyStore.NativeHeight, 0);
+ return pw ? pw : this._panelHeight;
+ }
+ contentScaling = () => {
+ let wscale = this._panelWidth / (this.nativeWidth() ? this.nativeWidth() : this._panelWidth);
+ if (wscale * this.nativeHeight() > this._panelHeight)
+ return this._panelHeight / (this.nativeHeight() ? this.nativeHeight() : this._panelHeight);
+ return wscale;
+ }
ScreenToLocalTransform = () => {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!);
- return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this._contentScaling());
+ if (this._mainCont.current && this._mainCont.current.children) {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current!.children[0].firstChild as HTMLElement);
+ scale = Utils.GetScreenTransform(this._mainCont.current!).scale;
+ return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+ }
+ return Transform.Identity();
}
+ get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
- render() {
- if (!this._document) {
- return (null);
- }
- var content =
- <div className="collectionDockingView-content" ref={this._mainCont}>
- <DocumentView key={this._document.Id} Document={this._document}
+ get content() {
+ return (
+ <div className="collectionDockingView-content" ref={this._mainCont}
+ style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
+ <DocumentView key={this._document!.Id} Document={this._document!}
+ toggleMinimized={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
- ContentScaling={this._contentScaling}
- PanelWidth={this._nativeWidth}
- PanelHeight={this._nativeHeight}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.nativeWidth}
+ PanelHeight={this.nativeHeight}
ScreenToLocalTransform={this.ScreenToLocalTransform}
isTopMost={true}
selectOnLoad={false}
parentActive={returnTrue}
- onActiveChanged={emptyFunction}
+ whenActiveChanged={emptyFunction}
focus={emptyDocFunction}
ContainingCollectionView={undefined} />
- </div>;
+ </div>);
+ }
- return <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
- {({ measureRef }) => <div ref={measureRef}> {content} </div>}
- </Measure>;
+ render() {
+ return !this._document ? (null) :
+ <Measure onResize={action((r: any) => { this._panelWidth = r.entry.width; this._panelHeight = r.entry.height; })}>
+ {({ measureRef }) => <div ref={measureRef}> {this.content} </div>}
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/collections/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 229bc4059..497c3ee3c 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 { KeyStore } from "../../../fields/KeyStore";
import { ContextMenu } from "../ContextMenu";
@@ -16,18 +16,44 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
public static LayoutString(fieldKey: string = "DataKey") {
return FieldView.LayoutString(CollectionPDFView, fieldKey);
}
+ @observable _inThumb = false;
private get curPage() { return this.props.Document.GetNumber(KeyStore.CurPage, -1); }
+ private set curPage(value: number) { this.props.Document.SetNumber(KeyStore.CurPage, value); }
private get numPages() { return this.props.Document.GetNumber(KeyStore.NumPages, 0); }
@action onPageBack = () => this.curPage > 1 ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage - 1) : -1;
@action onPageForward = () => this.curPage < this.numPages ? this.props.Document.SetNumber(KeyStore.CurPage, this.curPage + 1) : -1;
+ @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 = () => this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
+ nativeHeight = () => this.props.Document.GetNumber(KeyStore.NativeHeight, 0);
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>
);
}
@@ -50,7 +76,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.scss b/src/client/views/collections/CollectionSchemaView.scss
index c8bfedff4..cfdb3ab22 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -1,61 +1,6 @@
@import "../globalCssVariables";
-//options menu styling
-#schemaOptionsMenuBtn {
- position: absolute;
- height: 20px;
- width: 20px;
- border-radius: 50%;
- z-index: 21;
- right: 4px;
- top: 4px;
- pointer-events: auto;
- background-color:black;
- display:inline-block;
- padding: 0px;
- font-size: 100%;
-}
-
-ul {
- list-style-type: disc;
-}
-
-#schema-options-header {
- text-align: center;
- padding: 0px;
- margin: 0px;
-}
-.schema-options-subHeader {
- color: $intermediate-color;
- margin-bottom: 5px;
-}
-#schemaOptionsMenuBtn:hover {
- transform: scale(1.15);
-}
-
-#preview-schema-checkbox-div {
- margin-left: 20px;
- font-size: 12px;
-}
- #options-flyout-div {
- text-align: left;
- padding:0px;
- z-index: 100;
- font-family: $sans-serif;
- padding-left: 5px;
- }
-
- #schema-col-checklist {
- overflow: scroll;
- text-align: left;
- //background-color: $light-color-secondary;
- line-height: 25px;
- max-height: 175px;
- font-family: $sans-serif;
- font-size: 12px;
- }
-
.collectionSchemaView-container {
border-width: $COLLECTION_BORDER_WIDTH;
@@ -66,18 +11,31 @@ ul {
position: absolute;
width: 100%;
height: 100%;
-
- .collectionSchemaView-content {
- position: absolute;
- height: 100%;
- width: 100%;
- overflow: auto;
+
+ .collectionSchemaView-cellContents {
+ height: $MAX_ROW_HEIGHT;
}
+
.collectionSchemaView-previewRegion {
position: relative;
background: $light-color;
float: left;
height: 100%;
+ .collectionSchemaView-previewDoc {
+ height: 100%;
+ width: 100%;
+ position: absolute;
+ }
+ .collectionSchemaView-input {
+ position: absolute;
+ max-width: 150px;
+ width: 100%;
+ bottom: 0px;
+ }
+ .documentView-node:first-child {
+ position: relative;
+ background: $light-color;
+ }
}
.collectionSchemaView-previewHandle {
position: absolute;
@@ -151,7 +109,7 @@ ul {
}
.rt-tr-group {
direction: ltr;
- max-height: 44px;
+ max-height: $MAX_ROW_HEIGHT;
}
.rt-td {
border-width: 1px;
@@ -183,7 +141,7 @@ ul {
}
.ReactTable .rt-th,
.ReactTable .rt-td {
- max-height: 44;
+ max-height: $MAX_ROW_HEIGHT;
padding: 3px 7px;
font-size: 13px;
text-align: center;
@@ -193,13 +151,71 @@ ul {
border-bottom-style: solid;
border-bottom-width: 1;
}
+ .documentView-node-topmost {
+ text-align:left;
+ transform-origin: center top;
+ display: inline-block;
+ }
.documentView-node:first-child {
background: $light-color;
- .imageBox-cont img {
- object-fit: contain;
- }
}
}
+//options menu styling
+#schemaOptionsMenuBtn {
+ position: absolute;
+ height: 20px;
+ width: 20px;
+ border-radius: 50%;
+ z-index: 21;
+ right: 4px;
+ top: 4px;
+ pointer-events: auto;
+ background-color:black;
+ display:inline-block;
+ padding: 0px;
+ font-size: 100%;
+}
+
+ul {
+ list-style-type: disc;
+}
+
+#schema-options-header {
+ text-align: center;
+ padding: 0px;
+ margin: 0px;
+}
+.schema-options-subHeader {
+ color: $intermediate-color;
+ margin-bottom: 5px;
+}
+#schemaOptionsMenuBtn:hover {
+ transform: scale(1.15);
+}
+
+#preview-schema-checkbox-div {
+ margin-left: 20px;
+ font-size: 12px;
+}
+
+ #options-flyout-div {
+ text-align: left;
+ padding:0px;
+ z-index: 100;
+ font-family: $sans-serif;
+ padding-left: 5px;
+ }
+
+ #schema-col-checklist {
+ overflow: scroll;
+ text-align: left;
+ //background-color: $light-color-secondary;
+ line-height: 25px;
+ max-height: 175px;
+ font-family: $sans-serif;
+ font-size: 12px;
+ }
+
.Resizer {
box-sizing: border-box;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 4f4068e7a..b6d5f1bfa 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -4,15 +4,15 @@ import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, observable, untracked } from "mobx";
import { observer } from "mobx-react";
-import Measure from "react-measure";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
+import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss'
import "react-table/react-table.css";
import { Document } from "../../../fields/Document";
import { Field, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { emptyDocFunction, emptyFunction, returnFalse, returnOne } from "../../../Utils";
+import { emptyDocFunction, emptyFunction, returnFalse, returnZero } from "../../../Utils";
import { Server } from "../../Server";
import { SetupDrag } from "../../util/DragManager";
import { CompileScript, ToField } from "../../util/Scripting";
@@ -34,42 +34,35 @@ import { CollectionSubView } from "./CollectionSubView";
class KeyToggle extends React.Component<{ keyId: string, checked: boolean, toggle: (key: Key) => void }> {
@observable key: Key | undefined;
- componentWillReceiveProps() {
- Server.GetField(this.props.keyId, action((field: Opt<Field>) => {
- if (field instanceof Key) {
- this.key = field;
- }
- }));
+ constructor(props: any) {
+ super(props);
+ Server.GetField(this.props.keyId, action((field: Opt<Field>) => field instanceof Key && (this.key = field)));
}
render() {
- if (this.key) {
- return (<div key={this.key.Id}>
+ return !this.key ? (null) :
+ (<div key={this.key.Id}>
<input type="checkbox" checked={this.props.checked} onChange={() => this.key && this.props.toggle(this.key)} />
{this.key.Name}
</div>);
- }
- return (null);
}
}
@observer
export class CollectionSchemaView extends CollectionSubView {
- private _mainCont = React.createRef<HTMLDivElement>();
+ private _mainCont?: HTMLDivElement;
private _startSplitPercent = 0;
private DIVIDER_WIDTH = 4;
@observable _columns: Array<Key> = [KeyStore.Title, KeyStore.Data, KeyStore.Author];
- @observable _contentScaling = 1; // used to transfer the dimensions of the content pane in the DOM to the ContentScaling prop of the DocumentView
- @observable _dividerX = 0;
- @observable _panelWidth = 0;
- @observable _panelHeight = 0;
@observable _selectedIndex = 0;
@observable _columnsPercentage = 0;
@observable _keys: Key[] = [];
+ @observable _newKeyName: string = "";
@computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0); }
-
+ @computed get columns() { return this.props.Document.GetList(KeyStore.ColumnsKey, [] as Key[]); }
+ @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
@@ -83,7 +76,9 @@ export class CollectionSchemaView extends CollectionSubView {
ScreenToLocalTransform: Transform.Identity,
focus: emptyDocFunction,
active: returnFalse,
- onActiveChanged: emptyFunction,
+ whenActiveChanged: emptyFunction,
+ PanelHeight: returnZero,
+ PanelWidth: returnZero,
};
let contents = (
<FieldView {...props} />
@@ -107,11 +102,11 @@ export class CollectionSchemaView extends CollectionSubView {
return false;
};
return (
- <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "56px" }} key={props.Document.Id} ref={reference}>
+ <div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document.Id} ref={reference}>
<EditableView
display={"inline"}
contents={contents}
- height={56}
+ height={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
let field = props.Document.Get(props.fieldKey);
if (field && field instanceof Field) {
@@ -165,9 +160,9 @@ export class CollectionSchemaView extends CollectionSubView {
};
}
- @computed
- get columns() {
- return this.props.Document.GetList<Key>(KeyStore.ColumnsKey, []);
+ private createTarget = (ele: HTMLDivElement) => {
+ this._mainCont = ele;
+ super.CreateDropTarget(ele);
}
@action
@@ -187,31 +182,12 @@ export class CollectionSchemaView extends CollectionSubView {
//toggles preview side-panel of schema
@action
toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => {
- this._startSplitPercent = this.splitPercentage;
- if (this._startSplitPercent === this.splitPercentage) {
- this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
- }
- }
-
- @computed
- get findAllDocumentKeys(): { [id: string]: boolean } {
- const docs = this.props.Document.GetList<Document>(this.props.fieldKey, []);
- let keys: { [id: string]: boolean } = {};
- if (this._optionsActivated > -1) {
- // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
- // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
- // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
- // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
- // is displayed (unlikely) it won't show up until something else changes.
- untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
- }
- this.columns.forEach(key => keys[key.Id] = true);
- return keys;
+ this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, this.splitPercentage === 0 ? 33 : 0);
}
@action
onDividerMove = (e: PointerEvent): void => {
- let nativeWidth = this._mainCont.current!.getBoundingClientRect();
+ let nativeWidth = this._mainCont!.getBoundingClientRect();
this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100)));
}
@action
@@ -230,155 +206,150 @@ export class CollectionSchemaView extends CollectionSubView {
document.addEventListener('pointerup', this.onDividerUp);
}
- @observable _tableWidth = 0;
- @action
- setTableDimensions = (r: any) => {
- this._tableWidth = r.entry.width;
- }
- @action
- setScaling = (r: any) => {
- const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
- const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
- this._panelWidth = r.entry.width;
- this._panelHeight = r.entry.height ? r.entry.height : this._panelHeight;
- this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width);
+ onPointerDown = (e: React.PointerEvent): void => {
+ if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ if (this.props.isSelected())
+ e.stopPropagation();
+ else e.preventDefault();
+ }
}
- @computed
- get borderWidth() { return COLLECTION_BORDER_WIDTH; }
- getContentScaling = (): number => this._contentScaling;
- getPanelWidth = (): number => this._panelWidth;
- getPanelHeight = (): number => this._panelHeight;
- getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling);
- getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling);
-
- onPointerDown = (e: React.PointerEvent): void => {
- if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) {
+ onWheel = (e: React.WheelEvent): void => {
+ if (this.props.active()) {
e.stopPropagation();
}
}
@action
addColumn = () => {
- this.columns.push(new Key(this.newKeyName));
- this.newKeyName = "";
+ this.columns.push(new Key(this._newKeyName));
+ this._newKeyName = "";
}
- @observable
- newKeyName: string = "";
-
@action
newKeyChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.newKeyName = e.currentTarget.value;
- }
- onWheel = (e: React.WheelEvent): void => {
- if (this.props.active()) {
- e.stopPropagation();
- }
- }
-
- @observable _optionsActivated: number = 0;
- @action
- OptionsMenuDown = (e: React.PointerEvent) => {
- this._optionsActivated++;
+ this._newKeyName = e.currentTarget.value;
}
- @observable previewScript: string = "this";
+ @observable previewScript: string = "";
@action
onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
this.previewScript = e.currentTarget.value;
}
- render() {
- library.add(faCog);
- library.add(faPlus);
- const columns = this.columns;
- const children = this.props.Document.GetList<Document>(this.props.fieldKey, []);
+ get previewDocument(): Document | undefined {
+ const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
const selected = children.length > this._selectedIndex ? children[this._selectedIndex] : undefined;
- //all the keys/columns that will be displayed in the schema
- const allKeys = this.findAllDocumentKeys;
- let doc: any = selected ? selected.Get(new Key(this.previewScript)) : undefined;
+ return selected ? (this.previewScript ? selected.Get(new Key(this.previewScript)) as Document : selected) : undefined;
+ }
+ get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }
+ get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }
+ get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; }
+
+ private previewDocNativeWidth = () => this.previewDocument!.GetNumber(KeyStore.NativeWidth, this.previewRegionWidth);
+ private previewDocNativeHeight = () => this.previewDocument!.GetNumber(KeyStore.NativeHeight, this.previewRegionHeight);
+ private previewContentScaling = () => {
+ let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth);
+ if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight)
+ return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight);
+ return wscale;
+ }
+ private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling();
+ private previewPanelHeight = () => this.previewDocNativeHeight() * this.previewContentScaling();
+ get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; }
+ getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
+ - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset,
+ - this.borderWidth).scale(1 / this.previewContentScaling());
+ @computed
+ get previewPanel() {
// let doc = CompileScript(this.previewScript, { this: selected }, true)();
- let content = this._selectedIndex === -1 || !selected ? (null) : (
- <Measure onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="collectionSchemaView-content" ref={measureRef}>
- {doc instanceof Document ?
- <DocumentView Document={doc}
- addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
- isTopMost={false}
- selectOnLoad={false}
- ScreenToLocalTransform={this.getPreviewTransform}
- ContentScaling={this.getContentScaling}
- PanelWidth={this.getPanelWidth}
- PanelHeight={this.getPanelHeight}
- ContainingCollectionView={this.props.CollectionView}
- focus={emptyDocFunction}
- parentActive={this.props.active}
- onActiveChanged={this.props.onActiveChanged} /> : null}
- <input value={this.previewScript} onChange={this.onPreviewScriptChange}
- style={{ position: 'absolute', bottom: '0px' }} />
- </div>
- }
- </Measure>
- );
- let dividerDragger = this.splitPercentage === 0 ? (null) :
- <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
-
- //options button and menu
- let optionsMenu = !this.props.active() ? (null) : (<Flyout
- anchorPoint={anchorPoints.LEFT_TOP}
- content={<div>
- <div id="schema-options-header"><h5><b>Options</b></h5></div>
- <div id="options-flyout-div">
- <h6 className="schema-options-subHeader">Preview Window</h6>
- <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div>
- <h6 className="schema-options-subHeader" >Displayed Columns</h6>
- <ul id="schema-col-checklist" >
- {Array.from(Object.keys(allKeys)).map(item =>
- (<KeyToggle checked={allKeys[item]} key={item} keyId={item} toggle={this.toggleKey} />))}
- </ul>
- <input value={this.newKeyName} onChange={this.newKeyChange} />
- <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
+ return !this.previewDocument ? (null) : (
+ <div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>
+ <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
+ <DocumentView Document={this.previewDocument} isTopMost={false} selectOnLoad={false}
+ toggleMinimized={emptyFunction}
+ addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={this.getPreviewTransform}
+ ContentScaling={this.previewContentScaling}
+ PanelWidth={this.previewPanelWidth} PanelHeight={this.previewPanelHeight}
+ ContainingCollectionView={this.props.CollectionView}
+ focus={emptyDocFunction}
+ parentActive={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ />
</div>
+ <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
+ style={{ left: `calc(50% - ${Math.min(75, this.previewPanelWidth() / 2)}px)` }} />
</div>
- }>
- <button id="schemaOptionsMenuBtn" onPointerDown={this.OptionsMenuDown}><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
- </Flyout>);
+ );
+ }
- return (
- <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont}>
- <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
- <Measure onResize={this.setTableDimensions}>
- {({ measureRef }) =>
- <div className="collectionSchemaView-tableContainer" ref={measureRef} style={{ width: `calc(100% - ${this.splitPercentage}%)` }}>
- <ReactTable
- data={children}
- pageSize={children.length}
- page={0}
- showPagination={false}
- columns={columns.map(col => ({
- Header: col.Name,
- accessor: (doc: Document) => [doc, col],
- id: col.Id
- }))}
- column={{
- ...ReactTableDefaults.column,
- Cell: this.renderCell,
+ get documentKeysCheckList() {
+ const docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ let keys: { [id: string]: boolean } = {};
+ // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields.
+ // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be
+ // invalidated and re-rendered. This workaround will inquire all of the document fields before the options button is clicked.
+ // then by the time the options button is clicked, all of the fields should be in place. If a new field is added while this menu
+ // is displayed (unlikely) it won't show up until something else changes.
+ untracked(() => docs.map(doc => doc.GetAllPrototypes().map(proto => proto._proxies.forEach((val: any, key: string) => keys[key] = false))));
+
+ this.columns.forEach(key => keys[key.Id] = true);
+ return Array.from(Object.keys(keys)).map(item =>
+ (<KeyToggle checked={keys[item]} key={item} keyId={item} toggle={this.toggleKey} />));
+ }
- }}
- getTrProps={this.getTrProps}
- />
- </div>}
- </Measure>
- {dividerDragger}
- <div className="collectionSchemaView-previewRegion" style={{ width: `calc(${this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 0)}% - ${this.DIVIDER_WIDTH}px)` }}>
- {content}
+ get tableOptionsPanel() {
+ return !this.props.active() ? (null) :
+ (<Flyout
+ anchorPoint={anchorPoints.LEFT_TOP}
+ content={<div>
+ <div id="schema-options-header"><h5><b>Options</b></h5></div>
+ <div id="options-flyout-div">
+ <h6 className="schema-options-subHeader">Preview Window</h6>
+ <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div>
+ <h6 className="schema-options-subHeader" >Displayed Columns</h6>
+ <ul id="schema-col-checklist" >
+ {this.documentKeysCheckList}
+ </ul>
+ <input value={this._newKeyName} onChange={this.newKeyChange} />
+ <button onClick={this.addColumn}><FontAwesomeIcon style={{ color: "white" }} icon="plus" size="lg" /></button>
</div>
- {optionsMenu}
</div>
- </div >
+ }>
+ <button id="schemaOptionsMenuBtn" ><FontAwesomeIcon style={{ color: "white" }} icon="cog" size="sm" /></button>
+ </Flyout>);
+ }
+
+ @computed
+ get dividerDragger() {
+ return this.splitPercentage === 0 ? (null) :
+ <div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
+ }
+
+ render() {
+ library.add(faCog);
+ library.add(faPlus);
+ const children = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ return (
+ <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
+ <div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}>
+ <ReactTable data={children} page={0} pageSize={children.length} showPagination={false}
+ columns={this.columns.map(col => ({
+ Header: col.Name,
+ accessor: (doc: Document) => [doc, col],
+ id: col.Id
+ }))}
+ column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
+ getTrProps={this.getTrProps}
+ />
+ </div>
+ {this.dividerDragger}
+ {this.previewPanel}
+ {this.tableOptionsPanel}
+ </div>
);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index d3d69b1af..f9fc7be5a 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -15,7 +15,6 @@ import { ServerUtils } from "../../../server/ServerUtil";
import { Server } from "../../Server";
import { FieldViewProps } from "../nodes/FieldView";
import * as rp from 'request-promise';
-import { emptyFunction } from "../../../Utils";
import { CollectionView } from "./CollectionView";
import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
@@ -24,6 +23,8 @@ export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
removeDocument: (document: Document) => boolean;
moveDocument: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
}
export interface SubCollectionViewProps extends CollectionViewProps {
@@ -42,6 +43,9 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
}
}
+ protected CreateDropTarget(ele: HTMLDivElement) {
+ this.createDropTarget(ele);
+ }
@action
protected setCursorPosition(position: [number, number]) {
@@ -137,7 +141,7 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
return undefined;
}
ctor = Documents.WebDocument;
- options = { height: options.width, ...options, title: path };
+ options = { height: options.width, ...options, title: path, nativeWidth: undefined };
}
return ctor ? ctor(path, options) : undefined;
}
@@ -155,10 +159,7 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
e.preventDefault();
if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
- console.log("not good");
- let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
- htmlDoc.SetText(KeyStore.DocumentText, text);
- this.props.addDocument(htmlDoc, false);
+ this.props.addDocument(Documents.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text }), false);
return;
}
@@ -173,7 +174,7 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
.then(action((s: string) => rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + (str = s)))))
.then(result => {
- let type = result.headers["content-type"];
+ let type = result["content-type"];
if (type) {
this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
.then(doc => doc && this.props.addDocument(doc, false));
@@ -184,36 +185,17 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> {
let type = item.type;
if (item.kind === "file") {
let file = item.getAsFile();
- let formData = new FormData();
-
- if (file) {
- formData.append('file', file);
- }
let dropFileName = file ? file.name : "-empty-";
+ let formData = new FormData();
+ if (file) formData.append('file', file);
- let prom = fetch(upload, {
+ promises.push(fetch(upload, {
method: 'POST',
body: formData
- }).then(async (res: Response) => {
- (await res.json()).map(action((file: any) => {
- let path = window.location.origin + file;
- let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
-
- docPromise.then(action((doc?: Document) => {
- let docs = this.props.Document.GetT(KeyStore.Data, ListField);
- if (docs !== FieldWaiting) {
- if (!docs) {
- docs = new ListField<Document>();
- this.props.Document.Set(KeyStore.Data, docs);
- }
- if (doc) {
- docs.Data.push(doc);
- }
- }
- }));
- }));
- });
- promises.push(prom);
+ }).then(async (res: Response) =>
+ (await res.json()).map(action((file: any) =>
+ this.getDocumentFromType(type, window.location.origin + file, { ...options, nativeWidth: 600, width: 300, title: dropFileName }).
+ then(doc => doc && this.props.addDocument(doc, false))))));
}
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 51a02fc25..905b48db7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,20 +1,17 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, trace } from "mobx";
+import { action, observable } from "mobx";
import { observer } from "mobx-react";
import { Document } from "../../../fields/Document";
import { FieldWaiting } from "../../../fields/Field";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { SetupDrag, DragManager } from "../../util/DragManager";
+import { DragManager, SetupDrag } from "../../util/DragManager";
import { EditableView } from "../EditableView";
-import "./CollectionTreeView.scss";
-import { CollectionView } from "./CollectionView";
-import * as globalCssVariables from "../../views/globalCssVariables.scss";
import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionTreeView.scss";
import React = require("react");
-import { props } from 'bluebird';
export interface TreeViewProps {
@@ -139,7 +136,10 @@ export class CollectionTreeView extends CollectionSubView {
);
return (
- <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
+ <div id="body" className="collectionTreeView-dropTarget"
+ style={{ borderRadius: "inherit" }}
+ onWheel={(e: React.WheelEvent) => e.stopPropagation()}
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
<div className="coll-title">
<EditableView
contents={this.props.Document.Title}
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 29fb342c6..779dc8fc3 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -37,10 +37,13 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
]);
}
+ _ele: HTMLDivElement | null = null;
@action
mainCont = (ele: HTMLDivElement | null) => {
+ this._ele = ele;
if (ele) {
this._player = ele.getElementsByTagName("video")[0];
+ console.log(this._player);
if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) {
this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1);
}
@@ -57,9 +60,12 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
@action
updateTimecode = () => {
+ this._player = this._player ? this._player : this._ele ? this._ele.getElementsByTagName("video")[0] : undefined;
if (this._player) {
- if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero !== -1) {
- this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero;
+ let timecode = (this._player as any).hasOwnProperty("AHackBecauseSomethingResetsTheVideoToZero") ?
+ (this._player as any).AHackBecauseSomethingResetsTheVideoToZero : -1;
+ if (timecode !== -1 && Object) {
+ this._player.currentTime = timecode;
(this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1;
} else {
this._currentTimecode = this._player.currentTime;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index bd7a5635f..675e720e2 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -29,8 +29,10 @@ export class CollectionView extends React.Component<FieldViewProps> {
return (null);
}
+ get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
+
onContextMenu = (e: React.MouseEvent): void => {
- if (!e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document.Id !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Freeform)) });
ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Schema)) });
ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Tree)) });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.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 8868f7df0..8cd6c7624 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -5,33 +5,54 @@ import { Utils } from "../../../../Utils";
import "./CollectionFreeFormLinkView.scss";
import React = require("react");
import v5 = require("uuid/v5");
+import { InkingControl } from "../../InkingControl";
export interface CollectionFreeFormLinkViewProps {
A: Document;
B: Document;
LinkDocs: Document[];
+ addDocument: (document: Document, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Document) => boolean;
}
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
onPointerDown = (e: React.PointerEvent) => {
- this.props.LinkDocs.map(l =>
- console.log("Link:" + l.Title));
+ if (e.button === 0 && !InkingControl.Instance.selectedTool) {
+ let a = this.props.A;
+ let b = this.props.B;
+ let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Width() / 2);
+ let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Height() / 2);
+ let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Width() / 2);
+ let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Height() / 2);
+ this.props.LinkDocs.map(l => {
+ let width = l.GetNumber(KeyStore.Width, 0);
+ l.SetNumber(KeyStore.X, (x1 + x2) / 2 - width / 2);
+ l.SetNumber(KeyStore.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;
let a = this.props.A;
let b = this.props.B;
- let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Width() / 2);
- let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.Minimized, false) ? 5 : a.Height() / 2);
- let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Width() / 2);
- let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.Minimized, false) ? 5 : b.Height() / 2);
+ let x1 = a.GetNumber(KeyStore.X, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Width() / 2);
+ let y1 = a.GetNumber(KeyStore.Y, 0) + (a.GetBoolean(KeyStore.IsMinimized, false) ? 5 : a.Height() / 2);
+ let x2 = b.GetNumber(KeyStore.X, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Width() / 2);
+ let y2 = b.GetNumber(KeyStore.Y, 0) + (b.GetBoolean(KeyStore.IsMinimized, false) ? 5 : b.Height() / 2);
return (
- <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine" onPointerDown={this.onPointerDown}
- style={{ strokeWidth: `${l.length * 5}` }}
- 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 2f684a54e..b97df7556 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -16,9 +16,9 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
_brushReactionDisposer?: IReactionDisposer;
componentDidMount() {
- this._brushReactionDisposer = reaction(() => this.props.Document.GetList<Document>(this.props.fieldKey, []).map(doc => doc.GetNumber(KeyStore.X, 0)),
+ this._brushReactionDisposer = reaction(() => this.props.Document.GetList(this.props.fieldKey, [] as Document[]).map(doc => doc.GetNumber(KeyStore.X, 0)),
() => {
- let views = this.props.Document.GetList<Document>(this.props.fieldKey, []);
+ let views = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc.GetText(KeyStore.BackgroundLayout, "").indexOf("istogram") !== -1);
for (let i = 0; i < views.length; i++) {
for (let j = 0; j < views.length; j++) {
let srcDoc = views[j];
@@ -72,7 +72,15 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
let equalViews = [view];
let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document);
if (containerDoc && containerDoc instanceof Document) {
- equalViews = DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!);
+ equalViews.push(...DocumentManager.Instance.getDocumentViews(containerDoc.GetPrototype()!));
+ }
+ if (view.props.ContainingCollectionView) {
+ let collid = view.props.ContainingCollectionView.props.Document.Id;
+ this.props.Document.GetList(this.props.fieldKey, [] as Document[]).
+ 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);
}
@@ -97,7 +105,8 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
);
return drawnPairs;
}, [] as { a: Document, b: Document, l: Document[] }[]);
- 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/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 751ea8190..cf0a6de00 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -12,7 +12,7 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
protected getCursors(): CursorEntry[] {
let doc = this.props.Document;
let id = CurrentUserUtils.id;
- let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []);
+ let cursors = doc.GetList(KeyStore.Cursors, [] as CursorEntry[]);
let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
return id ? notMe : [];
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 392bd514f..67a0e532c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,11 +1,4 @@
@import "../../globalCssVariables";
-.collectionfreeformview-measure {
- position: inherit;
- top: 0;
- left: 0;
- width: 100%;
- height: 100%;
- }
.collectionfreeformview {
position: inherit;
top: 0;
@@ -13,6 +6,7 @@
width: 100%;
height: 100%;
transform-origin: left top;
+ pointer-events: none;
}
.collectionfreeformview-container {
.collectionfreeformview > .jsx-parser {
@@ -52,7 +46,7 @@
}
opacity: 0.99;
- border-width: 0;
+ border-width: 0;
border-color: transparent;
border-style: solid;
border-radius: $border-radius;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 50f0a6164..1a953006a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,5 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, computed } from "mobx";
import { observer } from "mobx-react";
-import Measure from "react-measure";
import { Document } from "../../../../fields/Document";
import { KeyStore } from "../../../../fields/KeyStore";
import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
@@ -11,7 +10,6 @@ import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
import { InkingCanvas } from "../../InkingCanvas";
-import { MainOverlayTextBox } from "../../MainOverlayTextBox";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { DocumentViewProps } from "../../nodes/DocumentView";
@@ -22,20 +20,21 @@ import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
+import { BooleanField } from "../../../../fields/BooleanField";
@observer
export class CollectionFreeFormView extends CollectionSubView {
+ public static RIGHT_BTN_DRAG = false;
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
private _lastX: number = 0;
private _lastY: number = 0;
- @observable private _pwidth: number = 0;
- @observable private _pheight: number = 0;
+ private get _pwidth() { return this.props.PanelWidth(); }
+ private get _pheight() { return this.props.PanelHeight(); }
@computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
@computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey.Id === KeyStore.Annotations.Id; } // bcz: ? Why do we need to compare Id's?
- private childViews = () => this.views;
private panX = () => this.props.Document.GetNumber(KeyStore.PanX, 0);
private panY = () => this.props.Document.GetNumber(KeyStore.PanY, 0);
private zoomScaling = () => this.props.Document.GetNumber(KeyStore.Scale, 1);
@@ -48,11 +47,11 @@ export class CollectionFreeFormView extends CollectionSubView {
this._selectOnLoaded = newBox.Id;// track the new text box so we can give it a prop that tells it to focus itself when it's displayed
this.addDocument(newBox, false);
}
+ @action
private addDocument = (newBox: Document, allowDuplicates: boolean) => {
- if (this.isAnnotationOverlay) {
- newBox.SetNumber(KeyStore.Zoom, this.props.Document.GetNumber(KeyStore.Scale, 1));
- }
- return this.props.addDocument(this.bringToFront(newBox), false);
+ this.props.addDocument(newBox, false);
+ this.bringToFront(newBox);
+ return true;
}
private selectDocuments = (docs: Document[]) => {
SelectionManager.DeselectAll;
@@ -74,8 +73,12 @@ export class CollectionFreeFormView extends CollectionSubView {
@action
drop = (e: Event, de: DragManager.DropEvent) => {
if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
- const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);
if (de.data.droppedDocuments.length) {
+ let dragDoc = de.data.droppedDocuments[0];
+ let zoom = dragDoc.GetNumber(KeyStore.ZoomBasis, 1);
+ let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ let x = xp - de.data.xOffset / zoom;
+ let y = yp - de.data.yOffset / zoom;
let dropX = de.data.droppedDocuments[0].GetNumber(KeyStore.X, 0);
let dropY = de.data.droppedDocuments[0].GetNumber(KeyStore.Y, 0);
de.data.droppedDocuments.map(d => {
@@ -85,7 +88,9 @@ export class CollectionFreeFormView extends CollectionSubView {
d.SetNumber(KeyStore.Width, 300);
}
if (!d.GetNumber(KeyStore.Height, 0)) {
- d.SetNumber(KeyStore.Height, 300);
+ let nw = d.GetNumber(KeyStore.NativeWidth, 0);
+ let nh = d.GetNumber(KeyStore.NativeHeight, 0);
+ d.SetNumber(KeyStore.Height, nw && nh ? nh / nw * d.Width() : 300);
}
this.bringToFront(d);
});
@@ -108,20 +113,20 @@ export class CollectionFreeFormView extends CollectionSubView {
var dv = DocumentManager.Instance.getDocumentView(doc);
return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false);
}, false);
- if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) || (e.button === 0 && e.altKey)) && (childSelected || this.props.active())) {
- document.removeEventListener("pointermove", this.onPointerMove);
+ if ((CollectionFreeFormView.RIGHT_BTN_DRAG &&
+ (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
+ (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.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
this._lastX = e.pageX;
this._lastY = e.pageY;
}
}
- @action
onPointerUp = (e: PointerEvent): void => {
- e.stopPropagation();
-
this.cleanupInteractions();
}
@@ -155,7 +160,7 @@ export class CollectionFreeFormView extends CollectionSubView {
this.setPan(x - dx, y - dy);
this._lastX = e.pageX;
this._lastY = e.pageY;
- e.stopPropagation();
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
}
@@ -188,18 +193,19 @@ export class CollectionFreeFormView extends CollectionSubView {
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
}
+ if (deltaScale < 0) deltaScale = -deltaScale;
let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
- this.props.Document.SetNumber(KeyStore.Scale, localTransform.Scale);
- this.setPan(-localTransform.TranslateX / localTransform.Scale, -localTransform.TranslateY / localTransform.Scale);
+ let safeScale = Math.abs(localTransform.Scale);
+ this.props.Document.SetNumber(KeyStore.Scale, Math.abs(safeScale));
+ this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
e.stopPropagation();
}
}
@action
setPan(panX: number, panY: number) {
- MainOverlayTextBox.Instance.SetTextDoc();
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
@@ -218,12 +224,13 @@ export class CollectionFreeFormView extends CollectionSubView {
@action
bringToFront(doc: Document) {
- this.props.Document.GetList<Document>(this.props.fieldKey, []).slice().sort((doc1, doc2) => {
+ let docs = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).slice();
+ docs.sort((doc1, doc2) => {
if (doc1 === doc) return 1;
if (doc2 === doc) return -1;
return doc1.GetNumber(KeyStore.ZIndex, 0) - doc2.GetNumber(KeyStore.ZIndex, 0);
}).map((doc, index) => doc.SetNumber(KeyStore.ZIndex, index + 1));
- return doc;
+ doc.SetNumber(KeyStore.ZIndex, docs.length + 1);
}
focusDocument = (doc: Document) => {
@@ -236,6 +243,7 @@ export class CollectionFreeFormView extends CollectionSubView {
getDocumentViewProps(document: Document): DocumentViewProps {
return {
Document: document,
+ toggleMinimized: emptyFunction,
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
@@ -248,18 +256,19 @@ export class CollectionFreeFormView extends CollectionSubView {
ContainingCollectionView: this.props.CollectionView,
focus: this.focusDocument,
parentActive: this.props.active,
- onActiveChanged: this.props.active,
+ whenActiveChanged: this.props.active,
};
}
@computed
get views() {
- trace();
var curPage = this.props.Document.GetNumber(KeyStore.CurPage, -1);
let docviews = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc).reduce((prev, doc) => {
var page = doc.GetNumber(KeyStore.Page, -1);
if (page === curPage || page === -1) {
- prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
+ let minim = doc.GetT(KeyStore.IsMinimized, BooleanField);
+ if (minim === undefined || (minim && !minim.Data))
+ prev.push(<CollectionFreeFormDocumentView key={doc.Id} {...this.getDocumentViewProps(doc)} />);
}
return prev;
}, [] as JSX.Element[]);
@@ -270,42 +279,37 @@ export class CollectionFreeFormView extends CollectionSubView {
}
@action
- onResize = (r: any) => {
- this._pwidth = r.entry.width;
- this._pheight = r.entry.height;
- }
- @action
onCursorMove = (e: React.PointerEvent) => {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+ private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />];
render() {
- trace();
const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
return (
- <Measure onResize={this.onResize}>
- {({ measureRef }) => (
- <div className="collectionfreeformview-measure" ref={measureRef}>
- <div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
- <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
- addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
- getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
- <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
- zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
- <CollectionFreeFormBackgroundView {...this.getDocumentViewProps(this.props.Document)} />
- <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
- {this.childViews}
- </InkingCanvas>
- </CollectionFreeFormLinksView>
- <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
- </CollectionFreeFormViewPannableContents>
- <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} />
- </MarqueeView>
- </div>
- </div>)}
- </Measure>
+ <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} 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}
+ zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ <CollectionFreeFormLinksView {...this.props} key="freeformLinks">
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+ {this.childViews}
+ </InkingCanvas>
+ </CollectionFreeFormLinksView>
+ <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
+ </CollectionFreeFormViewPannableContents>
+ <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />
+ </MarqueeView>
+ </div>
);
}
}
@@ -324,12 +328,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 = this.props.Document.GetText(KeyStore.BackgroundLayout, "");
return !backgroundLayout ? (null) :
(<DocumentContentsView {...this.props} layoutKey={KeyStore.BackgroundLayout}
- isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />);
+ isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
return this.backgroundView;
@@ -352,7 +356,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const panx = -this.props.panX();
const pany = -this.props.panY();
const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
- return <div className="collectionfreeformview" style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+ return <div className="collectionfreeformview" style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
{this.props.children}
</div>;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 96d59c831..da1170759 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -13,6 +13,7 @@ import { PreviewCursor } from "../../PreviewCursor";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
import "./MarqueeView.scss";
import React = require("react");
+import { Utils } from "../../../../Utils";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -23,6 +24,7 @@ interface MarqueeViewProps {
selectDocuments: (docs: Document[]) => void;
removeDocument: (doc: Document) => boolean;
addLiveTextDocument: (doc: Document) => void;
+ isSelected: () => boolean;
}
@observer
@@ -32,18 +34,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@observable _lastY: number = 0;
@observable _downX: number = 0;
@observable _downY: number = 0;
- @observable _used: boolean = false;
@observable _visible: boolean = false;
- _showOnUp: boolean = false;
- static DRAG_THRESHOLD = 4;
+ _commandExecuted = false;
@action
cleanupInteractions = (all: boolean = false) => {
if (all) {
- document.removeEventListener("pointermove", this.onPointerMove, true);
document.removeEventListener("pointerup", this.onPointerUp, true);
- } else {
- this._used = true;
+ document.removeEventListener("pointermove", this.onPointerMove, true);
}
document.removeEventListener("keydown", this.marqueeCommand, true);
this._visible = false;
@@ -51,34 +49,26 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@action
onKeyPress = (e: KeyboardEvent) => {
- // Mixing events between React and Native is finicky. In FormattedTextBox, we set the
- // 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) {
- //make textbox and add it to this collection
- let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
- let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" });
- this.props.addLiveTextDocument(newBox);
- PreviewCursor.Visible = false;
- e.stopPropagation();
- }
- }
- hideCursor = () => {
- document.removeEventListener("keypress", this.onKeyPress, false);
+ //make textbox and add it to this collection
+ let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY);
+ let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
+ this.props.addLiveTextDocument(newBox);
+ e.stopPropagation();
}
@action
onPointerDown = (e: React.PointerEvent): void => {
- if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) {
- this._downX = this._lastX = e.pageX;
- this._downY = this._lastY = e.pageY;
- this._used = false;
- this._showOnUp = true;
- document.removeEventListener("keypress", this.onKeyPress, false);
+ 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())) {
document.addEventListener("pointermove", this.onPointerMove, true);
document.addEventListener("pointerup", this.onPointerUp, true);
document.addEventListener("keydown", this.marqueeCommand, true);
}
+ if (e.altKey)
+ e.preventDefault();
}
@action
@@ -86,33 +76,45 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this._lastX = e.pageX;
this._lastY = e.pageY;
if (!e.cancelBubble) {
- if (Math.abs(this._downX - e.clientX) > 4 || Math.abs(this._downY - e.clientY) > 4) {
- this._showOnUp = false;
- PreviewCursor.Visible = false;
+ if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD ||
+ Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
+ if (!this._commandExecuted) {
+ this._visible = true;
+ }
+ e.stopPropagation();
+ e.preventDefault();
}
- if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey &&
- (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) {
- this._visible = true;
- }
- e.stopPropagation();
- e.preventDefault();
}
+ if (e.altKey)
+ e.preventDefault();
}
@action
onPointerUp = (e: PointerEvent): void => {
- this.cleanupInteractions(true);
- this._visible = false;
- if (this._showOnUp) {
- PreviewCursor.Show(this.hideCursor, this._downX, this._downY);
- document.addEventListener("keypress", this.onKeyPress, false);
- } else {
+ if (this._visible) {
let mselect = this.marqueeSelect();
if (!e.shiftKey) {
SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
}
this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
}
+ this.cleanupInteractions(true);
+ if (e.altKey)
+ e.preventDefault();
+ }
+
+ @action
+ onClick = (e: React.MouseEvent): void => {
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ if (this.props.isSelected()) {
+ PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress);
+ }
+ // let the DocumentView stopPropagation of this event when it selects this document
+ } else { // why do we get a click event when the cursor have moved a big distance?
+ // let's cut it off here so no one else has to deal with it.
+ e.stopPropagation();
+ }
}
intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -132,42 +134,71 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@undoBatch
@action
marqueeCommand = (e: KeyboardEvent) => {
- if (e.key === "Backspace" || e.key === "Delete") {
+ 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 = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
if (ink && ink !== FieldWaiting) {
this.marqueeInkDelete(ink.Data);
}
- this.cleanupInteractions();
+ this.cleanupInteractions(false);
+ e.stopPropagation();
}
- if (e.key === "c") {
+ 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.SetNumber(KeyStore.X, d.GetNumber(KeyStore.X, 0) - bounds.left - bounds.width / 2);
d.SetNumber(KeyStore.Y, d.GetNumber(KeyStore.Y, 0) - bounds.top - bounds.height / 2);
d.SetNumber(KeyStore.Page, -1);
- d.SetText(KeyStore.Title, "" + d.Width() + " " + d.Height());
return d;
});
let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined;
- //setTimeout(() => {
+ let zoomBasis = this.props.container.props.Document.GetNumber(KeyStore.Scale, 1);
let newCollection = Documents.FreeformDocument(selected, {
x: bounds.left,
y: bounds.top,
panx: 0,
pany: 0,
- width: bounds.width,
- height: bounds.height,
+ borderRounding: e.key === "e" ? -1 : undefined,
+ scale: zoomBasis,
+ width: bounds.width * zoomBasis,
+ height: bounds.height * zoomBasis,
ink: inkData ? this.marqueeInkSelect(inkData) : undefined,
title: "a nested collection"
});
- this.props.addDocument(newCollection, false);
+
this.marqueeInkDelete(inkData);
- // }, 100);
- this.cleanupInteractions();
+ // SelectionManager.DeselectAll();
+ if (e.key === "r") {
+ let summary = Documents.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+ summary.GetPrototype()!.CreateLink(newCollection.GetPrototype()!);
+ this.props.addLiveTextDocument(summary);
+ e.preventDefault();
+ }
+ else {
+ this.props.addDocument(newCollection, false);
+ }
+ this.cleanupInteractions(false);
+ }
+ if (e.key === "s") {
+ this._commandExecuted = true;
+ e.stopPropagation();
+ e.preventDefault();
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect();
SelectionManager.DeselectAll();
+ let summary = Documents.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+ this.props.addLiveTextDocument(summary);
+ selected.map(select => summary.GetPrototype()!.CreateLink(select.GetPrototype()!));
+
+ this.cleanupInteractions(false);
}
}
@action
@@ -208,7 +239,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let selRect = this.Bounds;
let selection: Document[] = [];
this.props.activeDocuments().map(doc => {
- var z = doc.GetNumber(KeyStore.Zoom, 1);
+ var z = doc.GetNumber(KeyStore.ZoomBasis, 1);
var x = doc.GetNumber(KeyStore.X, 0);
var y = doc.GetNumber(KeyStore.Y, 0);
var w = doc.Width() / z;
@@ -230,7 +261,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
render() {
- return <div className="marqueeView" onPointerDown={this.onPointerDown}>
+ return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this.props.children}
{!this._visible ? (null) : this.marqueeDiv}
</div>;
diff --git a/src/client/views/globalCssVariables.scss b/src/client/views/globalCssVariables.scss
index 5c8e9c8fc..4f68b71b0 100644
--- a/src/client/views/globalCssVariables.scss
+++ b/src/client/views/globalCssVariables.scss
@@ -23,7 +23,11 @@ $mainTextInput-zindex: 999; // then text input overlay so that it's context menu
$docDecorations-zindex: 998; // then doc decorations appear over everything else
$remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right?
$COLLECTION_BORDER_WIDTH: 1;
+$MINIMIZED_ICON_SIZE:25;
+$MAX_ROW_HEIGHT: 44px;
:export {
contextMenuZindex: $contextMenu-zindex;
COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH;
+ MINIMIZED_ICON_SIZE: $MINIMIZED_ICON_SIZE;
+ MAX_ROW_HEIGHT: $MAX_ROW_HEIGHT;
} \ No newline at end of file
diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts
index e874b815d..9788d31f7 100644
--- a/src/client/views/globalCssVariables.scss.d.ts
+++ b/src/client/views/globalCssVariables.scss.d.ts
@@ -1,7 +1,9 @@
interface IGlobalScss {
contextMenuZindex: string; // context menu shows up over everything
- COLLECTION_BORDER_WIDTH: number;
+ COLLECTION_BORDER_WIDTH: string;
+ MINIMIZED_ICON_SIZE: string;
+ MAX_ROW_HEIGHT: string;
}
declare const globalCssVariables: IGlobalScss;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 68e5a70fb..bad78cbd5 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,12 +1,17 @@
-import { computed, trace } from "mobx";
+import { computed, trace, action } from "mobx";
import { observer } from "mobx-react";
import { KeyStore } from "../../../fields/KeyStore";
import { NumberField } from "../../../fields/NumberField";
+import { Document } from "../../../fields/Document";
import { Transform } from "../../util/Transform";
import { DocumentView, DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
-import { OmitKeys } from "../../../Utils";
+import { OmitKeys, Utils } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+import { ListField } from "../../../fields/ListField";
+import { BooleanField } from "../../../fields/BooleanField";
+import { matchedData } from "express-validator/filter";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
}
@@ -14,49 +19,48 @@ export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
@observer
export class CollectionFreeFormDocumentView extends React.Component<CollectionFreeFormDocumentViewProps> {
private _mainCont = React.createRef<HTMLDivElement>();
+ private _downX: number = 0;
+ private _downY: number = 0;
- @computed
- get transform(): string {
- return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.props.Document.GetNumber(KeyStore.X, 0)}px, ${this.props.Document.GetNumber(KeyStore.Y, 0)}px) scale(${this.zoom}, ${this.zoom}) `;
+ @computed get transform() {
+ return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
}
-
- @computed get zoom(): number { return 1 / this.props.Document.GetNumber(KeyStore.Zoom, 1); }
- @computed get zIndex(): number { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
- @computed get width(): number { return this.props.Document.Width(); }
- @computed get height(): number { return this.props.Document.Height(); }
- @computed get nativeWidth(): number { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
- @computed get nativeHeight(): number { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
-
+ @computed get X() { return this.props.Document.GetNumber(KeyStore.X, 0); }
+ @computed get Y() { return this.props.Document.GetNumber(KeyStore.Y, 0); }
+ @computed get zoom() { return 1 / this.props.Document.GetNumber(KeyStore.ZoomBasis, 1); }
+ @computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
+ @computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
+ @computed get width() { return this.props.Document.Width(); }
+ @computed get height() { return this.props.Document.Height(); }
+ @computed get zIndex() { return this.props.Document.GetNumber(KeyStore.ZIndex, 0); }
set width(w: number) {
this.props.Document.SetData(KeyStore.Width, w, NumberField);
if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Height, this.nativeHeight / this.nativeWidth * w);
}
}
-
set height(h: number) {
this.props.Document.SetData(KeyStore.Height, h, NumberField);
if (this.nativeWidth && this.nativeHeight) {
this.props.Document.SetNumber(KeyStore.Width, this.nativeWidth / this.nativeHeight * h);
}
}
-
set zIndex(h: number) {
this.props.Document.SetData(KeyStore.ZIndex, h, NumberField);
}
- getTransform = (): Transform =>
- this.props.ScreenToLocalTransform()
- .translate(-this.props.Document.GetNumber(KeyStore.X, 0), -this.props.Document.GetNumber(KeyStore.Y, 0))
- .scale(1 / this.contentScaling()).scale(1 / this.zoom)
-
- contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
- panelWidth = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelWidth();
- panelHeight = () => this.props.Document.GetBoolean(KeyStore.Minimized, false) ? 10 : this.props.PanelHeight();
+ contentScaling = () => (this.nativeWidth > 0 ? this.width / this.nativeWidth : 1);
+ panelWidth = () => this.props.PanelWidth();
+ panelHeight = () => this.props.PanelHeight();
+ toggleMinimized = () => this.toggleIcon();
+ getTransform = (): Transform => this.props.ScreenToLocalTransform()
+ .translate(-this.X, -this.Y)
+ .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@computed
get docView() {
return <DocumentView {...OmitKeys(this.props, ['zoomFade'])}
+ toggleMinimized={this.toggleMinimized}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
PanelWidth={this.panelWidth}
@@ -64,31 +68,126 @@ export class CollectionFreeFormDocumentView extends React.Component<CollectionFr
/>;
}
+ animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, target: Document, maximizing: boolean) {
+ setTimeout(() => {
+ let now = Date.now();
+ let progress = Math.min(1, (now - stime) / 200);
+ let pval = maximizing ?
+ [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
+ [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
+ target.SetNumber(KeyStore.Width, maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress);
+ target.SetNumber(KeyStore.Height, maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress);
+ target.SetNumber(KeyStore.X, pval[0]);
+ target.SetNumber(KeyStore.Y, pval[1]);
+ if (first) {
+ target.SetBoolean(KeyStore.IsMinimized, false);
+ }
+ if (now < stime + 200) {
+ this.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing);
+ }
+ else {
+ if (!maximizing) {
+ target.SetBoolean(KeyStore.IsMinimized, true);
+ target.SetNumber(KeyStore.X, targ[0]);
+ target.SetNumber(KeyStore.Y, targ[1]);
+ target.SetNumber(KeyStore.Width, width);
+ target.SetNumber(KeyStore.Height, height);
+ }
+ (target as any).isIconAnimating = false;
+ }
+ },
+ 2);
+ }
+ @action
+ public toggleIcon = async (): Promise<void> => {
+ SelectionManager.DeselectAll();
+ let isMinimized: boolean | undefined;
+ let minimizedDocSet = await this.props.Document.GetTAsync(KeyStore.LinkTags, ListField);
+ if (!minimizedDocSet) return;
+ minimizedDocSet.Data.map(async minimizedDoc => {
+ if (minimizedDoc instanceof Document) {
+ this.props.addDocument && this.props.addDocument(minimizedDoc, false);
+ let maximizedDoc = await minimizedDoc.GetTAsync(KeyStore.MaximizedDoc, Document);
+ if (maximizedDoc instanceof Document && !(maximizedDoc as any).isIconAnimating) {
+ (maximizedDoc as any).isIconAnimating = true;
+ if (isMinimized === undefined) {
+ let maximizedDocMinimizedState = await maximizedDoc.GetTAsync(KeyStore.IsMinimized, BooleanField);
+ isMinimized = (maximizedDocMinimizedState && maximizedDocMinimizedState.Data) ? true : false;
+ }
+ let minx = await minimizedDoc.GetTAsync(KeyStore.X, NumberField);
+ let miny = await minimizedDoc.GetTAsync(KeyStore.Y, NumberField);
+ let maxx = await maximizedDoc.GetTAsync(KeyStore.X, NumberField);
+ let maxy = await maximizedDoc.GetTAsync(KeyStore.Y, NumberField);
+ let maxw = await maximizedDoc.GetTAsync(KeyStore.Width, NumberField);
+ let maxh = await maximizedDoc.GetTAsync(KeyStore.Height, NumberField);
+ if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
+ maxw !== undefined && maxh !== undefined)
+ this.animateBetweenIcon(
+ true,
+ [minx.Data, miny.Data], [maxx.Data, maxy.Data], maxw.Data, maxh.Data,
+ Date.now(), maximizedDoc, isMinimized);
+ }
+
+ }
+ })
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ e.stopPropagation();
+ }
+ onClick = (e: React.MouseEvent): void => {
+ e.stopPropagation();
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ this.props.Document.GetTAsync(KeyStore.MaximizedDoc, Document).then(maxdoc => {
+ if (maxdoc instanceof Document) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ this.props.addDocument && this.props.addDocument(maxdoc, false);
+ this.toggleIcon();
+ }
+ });
+ }
+ }
+
+ borderRounding = () => {
+ let br = this.props.Document.GetNumber(KeyStore.BorderRounding, 0);
+ return br >= 0 ? br :
+ this.props.Document.GetNumber(KeyStore.NativeWidth, 0) === 0 ?
+ Math.min(this.props.PanelWidth(), this.props.PanelHeight())
+ :
+ Math.min(this.props.Document.GetNumber(KeyStore.NativeWidth, 0), this.props.Document.GetNumber(KeyStore.NativeHeight, 0));
+ }
+
render() {
- trace();
+ let maximizedDoc = this.props.Document.GetT(KeyStore.MaximizedDoc, Document);
let zoomFade = 1;
- // //var zoom = doc.GetNumber(KeyStore.Zoom, 1);
- // let transform = (this.props.ScreenToLocalTransform().scale(this.props.ContentScaling())).inverse();
- // var [sptX, sptY] = transform.transformPoint(0, 0);
- // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
- // let w = bptX - sptX;
- // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
- // let fadeUp = .75 * 1800;
- // let fadeDown = .075 * 1800;
- // zoomFade = w < fadeDown || w > fadeUp ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+ //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1);
+ let transform = this.getTransform().scale(this.contentScaling()).inverse();
+ var [sptX, sptY] = transform.transformPoint(0, 0);
+ let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
+ let w = bptX - sptX;
+ //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
+ const screenWidth = 1800;
+ let fadeUp = .75 * screenWidth;
+ let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
+ zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0.1, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
return (
- <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
- opacity: zoomFade,
- transformOrigin: "left top",
- transform: this.transform,
- pointerEvents: "all",
- width: this.width,
- height: this.height,
- position: "absolute",
- zIndex: this.zIndex,
- backgroundColor: "transparent"
- }} >
+ <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ style={{
+ opacity: zoomFade,
+ borderRadius: `${this.borderRounding()}px`,
+ transformOrigin: "left top",
+ transform: this.transform,
+ pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
+ width: this.width,
+ height: this.height,
+ position: "absolute",
+ zIndex: this.zIndex,
+ backgroundColor: "transparent"
+ }} >
{this.docView}
</div>
);
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 76f852601..e9c46aa9d 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -15,6 +15,7 @@ import { DocumentViewProps } from "./DocumentView";
import "./DocumentView.scss";
import { FormattedTextBox } from "./FormattedTextBox";
import { ImageBox } from "./ImageBox";
+import { IconBox } from "./IconBox";
import { KeyValueBox } from "./KeyValueBox";
import { PDFBox } from "./PDFBox";
import { VideoBox } from "./VideoBox";
@@ -46,7 +47,9 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
CreateBindings(): JsxBindings {
let bindings: JsxBindings = { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive) };
- for (const key of this.layoutKeys) {
+ let keys: Key[] = [];
+ keys.push(...this.layoutKeys, KeyStore.Caption) // bcz: hack to get templates to work
+ for (const key of keys) {
bindings[key.Name + "Key"] = key; // this maps string values of the form <keyname>Key to an actual key Kestore.keyname e.g, "DataKey" => KeyStore.Data
}
for (const key of this.layoutFields) {
@@ -62,7 +65,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
return <p>Error loading layout keys</p>;
}
return <JsxParser
- components={{ FormattedTextBox, ImageBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
+ components={{ FormattedTextBox, ImageBox, IconBox, CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, CollectionPDFView, CollectionVideoView, WebBox, KeyValueBox, PDFBox, VideoBox, AudioBox, HistogramBox }}
bindings={this.CreateBindings()}
jsx={this.layout}
showWarnings={true}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index 6383ac3f6..7c72fb6e6 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -1,10 +1,10 @@
@import "../globalCssVariables";
-.documentView-node, .documentView-node-topMost {
+.documentView-node, .documentView-node-topmost {
position: inherit;
top: 0;
left:0;
- background: $light-color; //overflow: hidden;
+ // background: $light-color; //overflow: hidden;
transform-origin: left top;
&.minimized {
@@ -13,7 +13,6 @@
}
.top {
- background: #232323;
height: 20px;
cursor: pointer;
}
@@ -29,20 +28,6 @@
height: calc(100% - 20px);
}
}
-.documentView-node-topMost {
+.documentView-node-topmost {
background: white;
-}
-
-.minimized-box {
- height: 10px;
- width: 10px;
- border-radius: 2px;
- background: $dark-color;
- transform-origin: left top;
-}
-
-.minimized-box:hover {
- background: $main-accent;
- transform: scale(1.15);
- cursor: pointer;
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 9c991113c..40e3c66ae 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,12 +1,11 @@
import { action, computed, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { BooleanField } from "../../../fields/BooleanField";
import { Document } from "../../../fields/Document";
import { Field, FieldWaiting, Opt } from "../../../fields/Field";
import { Key } from "../../../fields/Key";
import { KeyStore } from "../../../fields/KeyStore";
import { ListField } from "../../../fields/ListField";
-import { TextField } from "../../../fields/TextField";
+import { TemplateField } from "../../../fields/TemplateField";
import { ServerUtils } from "../../../server/ServerUtil";
import { emptyFunction, Utils } from "../../../Utils";
import { Documents } from "../../documents/Documents";
@@ -20,11 +19,15 @@ 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 { PresentationView } from "../PresentationView";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { TextField } from "../../../fields/TextField";
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView | CollectionPDFView | CollectionVideoView>;
@@ -40,7 +43,8 @@ export interface DocumentViewProps {
focus: (doc: Document) => void;
selectOnLoad: boolean;
parentActive: () => boolean;
- onActiveChanged: (isActive: boolean) => void;
+ whenActiveChanged: (isActive: boolean) => void;
+ toggleMinimized: () => void;
}
export interface JsxArgs extends DocumentViewProps {
Keys: { [name: string]: Key };
@@ -95,40 +99,23 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@computed get layout(): string { return this.props.Document.GetText(KeyStore.Layout, "<p>Error loading layout data</p>"); }
@computed get layoutKeys(): Key[] { return this.props.Document.GetData(KeyStore.LayoutKeys, ListField, new Array<Key>()); }
@computed get layoutFields(): Key[] { return this.props.Document.GetData(KeyStore.LayoutFields, ListField, new Array<Key>()); }
-
- onPointerDown = (e: React.PointerEvent): void => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- if (e.button === 2 && !this.isSelected()) {
- return;
- }
- if (e.shiftKey && e.buttons === 2) {
- if (this.props.isTopMost) {
- this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
- } else {
- CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
- }
- e.stopPropagation();
- } else {
- if (this.active) {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointerup", this.onPointerUp);
- }
- }
+ @computed get templates(): Array<Template> {
+ let field = this.props.Document.GetT(KeyStore.Templates, TemplateField);
+ return !field || field === FieldWaiting ? [] : field.Data;
}
+ set templates(templates: Array<Template>) { this.props.Document.SetData(KeyStore.Templates, templates, TemplateField); }
+ screenRect = (): ClientRect | DOMRect => this._mainCont.current ? this._mainCont.current.getBoundingClientRect() : new DOMRect();
+ @action
componentDidMount() {
if (this._mainCont.current) {
this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
handlers: { drop: this.drop.bind(this) }
});
}
- runInAction(() => DocumentManager.Instance.DocumentViews.push(this));
+ DocumentManager.Instance.DocumentViews.push(this);
}
-
+ @action
componentDidUpdate() {
if (this._dropDisposer) {
this._dropDisposer();
@@ -139,21 +126,26 @@ export class DocumentView extends React.Component<DocumentViewProps> {
});
}
}
-
+ @action
componentWillUnmount() {
if (this._dropDisposer) {
this._dropDisposer();
}
- runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1));
+ DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+ }
+
+ stopPropagation = (e: React.SyntheticEvent) => {
+ e.stopPropagation();
}
startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
if (this._mainCont.current) {
- const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
let dragData = new DragManager.DocumentDragData([this.props.Document]);
+ const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.aliasOnDrop = dropAliasOfDraggedDoc;
- dragData.xOffset = x - left;
- dragData.yOffset = y - top;
+ dragData.xOffset = xoff;
+ dragData.yOffset = yoff;
dragData.moveDocument = this.props.moveDocument;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
@@ -164,41 +156,58 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) {
+ onClick = (e: React.MouseEvent): void => {
+ if (CurrentUserUtils.MainDocId != this.props.Document.Id &&
+ (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
return;
}
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ if (e.shiftKey && e.buttons === 1) {
+ if (this.props.isTopMost) {
+ this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
+ } else {
+ CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
+ }
+ e.stopPropagation();
+ } else if (this.active) {
document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- if (!this.topMost || e.buttons === 2 || e.altKey) {
- this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+ document.addEventListener("pointerup", this.onPointerUp);
+ e.preventDefault();
+ }
+ }
+ onPointerMove = (e: PointerEvent): void => {
+ if (!e.cancelBubble) {
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
+ this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+ }
}
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
}
- e.stopPropagation();
- e.preventDefault();
}
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
- if (!SelectionManager.IsSelected(this) && e.button !== 2 &&
- Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
- }
- stopPropagation = (e: React.SyntheticEvent) => {
- e.stopPropagation();
}
deleteClicked = (): void => {
this.props.removeDocument && this.props.removeDocument(this.props.Document);
}
-
fieldsClicked = (e: React.MouseEvent): void => {
- if (this.props.addDocument) {
- this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
- }
+ let kvp = Documents.KVPDocument(this.props.Document, { width: 300, height: 300 });
+ CollectionDockingView.Instance.AddRightSplit(kvp);
}
fullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.OpenFullScreen((this.props.Document.GetPrototype() as Document).MakeDelegate());
@@ -206,7 +215,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
-
closeFullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.CloseFullScreen();
ContextMenu.Instance.clearItems();
@@ -214,56 +222,22 @@ export class DocumentView extends React.Component<DocumentViewProps> {
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
- @action
- public minimize = (): void => {
- this.props.Document.SetBoolean(KeyStore.Minimized, true);
- SelectionManager.DeselectAll();
- }
-
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.LinkDragData) {
- let sourceDoc: Document = de.data.linkSourceDocument;
- let destDoc: Document = this.props.Document;
- let linkDoc: Document = new Document();
+ let sourceDoc = de.data.linkSourceDocument;
+ let destDoc = this.props.Document;
destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest =>
sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc =>
- runInAction(() => {
- let batch = UndoManager.StartBatch("document view drop");
- linkDoc.SetText(KeyStore.Title, "New Link");
- linkDoc.SetText(KeyStore.LinkDescription, "");
- linkDoc.SetText(KeyStore.LinkTags, "Default");
-
- let dstTarg = protoDest ? protoDest : destDoc;
- let srcTarg = protoSrc ? protoSrc : sourceDoc;
- linkDoc.Set(KeyStore.LinkedToDocs, dstTarg);
- linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg);
- const prom1 = new Promise(resolve => dstTarg.GetOrCreateAsync(
- KeyStore.LinkedFromDocs,
- ListField,
- field => {
- (field as ListField<Document>).Data.push(linkDoc);
- resolve();
- }
- ));
- const prom2 = new Promise(resolve => srcTarg.GetOrCreateAsync(
- KeyStore.LinkedToDocs,
- ListField,
- field => {
- (field as ListField<Document>).Data.push(linkDoc);
- resolve();
- }
- ));
- Promise.all([prom1, prom2]).finally(() => batch.end());
- })
- )
+ (protoSrc ? protoSrc : sourceDoc).CreateLink(protoDest ? protoDest : destDoc))
);
e.stopPropagation();
}
}
+ @action
onDrop = (e: React.DragEvent) => {
let text = e.dataTransfer.getData("text/plain");
if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
@@ -275,6 +249,46 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
}
+ updateLayout = async () => {
+ const baseLayout = await this.props.Document.GetTAsync(KeyStore.BaseLayout, TextField);
+ if (baseLayout) {
+ let base = baseLayout.Data;
+ let layout = baseLayout.Data;
+
+ this.templates.forEach(template => {
+ let temp = template.Layout;
+ layout = temp.replace("{layout}", base);
+ base = layout;
+ });
+
+ this.props.Document.SetText(KeyStore.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();
@@ -285,7 +299,6 @@ export class DocumentView extends React.Component<DocumentViewProps> {
}
e.preventDefault();
- !this.isMinimized() && ContextMenu.Instance.addItem({ description: "Minimize", event: this.minimize });
ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
@@ -300,15 +313,12 @@ export class DocumentView extends React.Component<DocumentViewProps> {
e.stopPropagation();
}
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
- SelectionManager.SelectDoc(this, e.ctrlKey);
+ if (!SelectionManager.IsSelected(this))
+ SelectionManager.SelectDoc(this, false);
}
- @action
- expand = () => this.props.Document.SetBoolean(KeyStore.Minimized, false)
- isMinimized = () => this.props.Document.GetBoolean(KeyStore.Minimized, false);
isSelected = () => SelectionManager.IsSelected(this);
select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
-
@computed get nativeWidth() { return this.props.Document.GetNumber(KeyStore.NativeWidth, 0); }
@computed get nativeHeight() { return this.props.Document.GetNumber(KeyStore.NativeHeight, 0); }
@computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />); }
@@ -318,19 +328,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
- if (this.isMinimized()) {
- return <div className="minimized-box" ref={this._mainCont} onClick={this.expand} onDrop={this.onDrop}
- style={{ transform: `scale(${scaling} , ${scaling})` }} onPointerDown={this.onPointerDown} />;
- }
return (
<div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
+ borderRadius: "inherit",
background: this.props.Document.GetText(KeyStore.BackgroundColor, ""),
width: nativeWidth, height: nativeHeight,
transform: `scale(${scaling}, ${scaling})`
}}
- onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}
+ onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
>
{this.contents}
</div>
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index ebd25f937..9e175b0d1 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -19,10 +19,12 @@ import { ListField } from "../../../fields/ListField";
import { DocumentContentsView } from "./DocumentContentsView";
import { Transform } from "../../util/Transform";
import { KeyStore } from "../../../fields/KeyStore";
-import { returnFalse, emptyDocFunction, emptyFunction, returnOne } from "../../../Utils";
+import { returnFalse, emptyDocFunction, emptyFunction, returnOne, returnZero } from "../../../Utils";
import { CollectionView } from "../collections/CollectionView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
+import { IconField } from "../../../fields/IconFIeld";
+import { IconBox } from "./IconBox";
//
@@ -43,8 +45,10 @@ export interface FieldViewProps {
moveDocument?: (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
active: () => boolean;
- onActiveChanged: (isActive: boolean) => void;
+ whenActiveChanged: (isActive: boolean) => void;
focus: (doc: Document) => void;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
}
@observer
@@ -72,6 +76,9 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) {
return <ImageBox {...this.props} />;
}
+ else if (field instanceof IconField) {
+ return <IconBox {...this.props} />;
+ }
else if (field instanceof VideoField) {
return <VideoBox {...this.props} />;
}
@@ -90,12 +97,13 @@ export class FieldView extends React.Component<FieldViewProps> {
isTopMost={true} //TODO Why is this top most?
selectOnLoad={false}
focus={emptyDocFunction}
- isSelected={returnFalse}
+ isSelected={this.props.isSelected}
select={returnFalse}
layoutKey={KeyStore.Layout}
ContainingCollectionView={this.props.ContainingCollectionView}
parentActive={this.props.active}
- onActiveChanged={this.props.onActiveChanged} />
+ toggleMinimized={emptyFunction}
+ whenActiveChanged={this.props.whenActiveChanged} />
);
}
else if (field instanceof ListField) {
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 5eb2bf7ce..727d3c0b2 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -10,11 +10,11 @@
outline: none !important;
}
-.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
+.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
background: $light-color-secondary;
- padding: 0.9em;
+ padding: 0;
border-width: 0px;
- border-radius: $border-radius;
+ border-radius: inherit;
border-color: $intermediate-color;
box-sizing: border-box;
border-style: solid;
@@ -24,10 +24,19 @@
height: 100%;
pointer-events: all;
}
+
.formattedTextBox-cont-hidden {
overflow: hidden;
pointer-events: none;
}
+.formattedTextBox-inner-rounded {
+ height: calc(100% - 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 bff8ca7a4..41ee24498 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, reaction } 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";
@@ -19,8 +19,9 @@ import { MainOverlayTextBox } from "../MainOverlayTextBox";
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
-const { buildMenuItems } = require("prosemirror-example-setup");
-const { menuBar } = require("prosemirror-menu");
+import { SelectionManager } from "../../util/SelectionManager";
+import { observer } from "mobx-react";
+import { InkingControl } from "../InkingControl";
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
@@ -43,11 +44,13 @@ export interface FormattedTextBoxOverlay {
isOverlay?: boolean;
}
+@observer
export class FormattedTextBox extends React.Component<(FieldViewProps & FormattedTextBoxOverlay)> {
public static LayoutString(fieldStr: string = "DataKey") {
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>;
@@ -58,14 +61,16 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
super(props);
this._ref = React.createRef();
+ this._proseRef = React.createRef();
this.onChange = this.onChange.bind(this);
}
_applyingChange: boolean = false;
+ _lastState: any = undefined;
dispatchTransaction = (tx: Transaction) => {
if (this._editorView) {
- const state = this._editorView.state.apply(tx);
+ const state = this._lastState = this._editorView.state.apply(tx);
this._editorView.updateState(state);
this._applyingChange = true;
this.props.Document.SetDataOnPrototype(
@@ -75,7 +80,11 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
);
this.props.Document.SetDataOnPrototype(KeyStore.DocumentText, state.doc.textBetween(0, state.doc.content.size, "\n\n"), TextField);
this._applyingChange = false;
- // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField);
+ if (this.props.Document.Title.startsWith("-") && this._editorView) {
+ let str = this._editorView.state.doc.textContent;
+ let titlestr = str.substr(0, Math.min(40, str.length));
+ this.props.Document.SetText(KeyStore.Title, "-" + titlestr + (str.length > 40 ? "..." : ""));
+ };
}
}
@@ -84,10 +93,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
schema,
inpRules, //these currently don't do anything, but could eventually be helpful
plugins: this.props.isOverlay ? [
+ this.tooltipTextMenuPlugin(),
history(),
keymap(buildKeymap(schema)),
keymap(baseKeymap),
- this.tooltipTextMenuPlugin(),
// this.tooltipLinkingMenuPlugin(),
new Plugin({
props: {
@@ -107,34 +116,26 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
if (this._editorView) {
this._editorView.destroy();
}
- this.setupEditor(config, MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
+ this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
}
);
} else {
this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
- () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()));
+ () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
}
+
this._reactionDisposer = reaction(
() => {
const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
return field && field !== FieldWaiting ? field.Data : undefined;
},
- field => {
- if (field && this._editorView && !this._applyingChange) {
- this._editorView.updateState(
- EditorState.fromJSON(config, JSON.parse(field))
- );
- }
- }
+ field => field && this._editorView && !this._applyingChange &&
+ this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
);
this.setupEditor(config, this.props.Document);
}
- shouldComponentUpdate() {
- return false;
- }
-
private setupEditor(config: any, doc?: Document) {
let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined;
if (this._ref.current) {
@@ -171,19 +172,21 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
Document.SetOnPrototype(fieldKey, new RichTextField(e.target.value));
// doc.SetData(fieldKey, e.target.value, RichTextField);
}
+ @action
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();
}
@@ -191,7 +194,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
onFocused = (e: React.FocusEvent): void => {
if (!this.props.isOverlay) {
- MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform());
+ MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
} else {
if (this._ref.current) {
this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
@@ -232,15 +235,21 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
}
}
+ onClick = (e: React.MouseEvent): void => {
+ 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({
@@ -250,27 +259,40 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte
});
}
- onKeyPress(e: React.KeyboardEvent) {
+ @action
+ onKeyPress = (e: React.KeyboardEvent) => {
+ if (e.key == "Escape") {
+ SelectionManager.DeselectAll();
+ }
e.stopPropagation();
- if (e.keyCode === 9) e.preventDefault();
+ if (e.key === "Tab") e.preventDefault();
// stop propagation doesn't seem to stop propagation of native keyboard events.
// so we set a flag on the native event that marks that the event's been handled.
- // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+ (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
}
render() {
- let style = this.props.isSelected() || this.props.isOverlay ? "scroll" : "hidden";
+ let style = this.props.isOverlay ? "-scroll" : "-hidden";
+ let rounded = this.props.Document.GetNumber(KeyStore.BorderRounding, 0) < 0 ? "-rounded" : "";
+ let color = this.props.Document.GetText(KeyStore.BackgroundColor, "");
+ let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
return (
- <div className={`formattedTextBox-cont-${style}`}
+ <div className={`formattedTextBox-cont${style}`} ref={this._ref}
+ style={{
+ pointerEvents: interactive ? "all" : "none",
+ background: color,
+ }}
onKeyDown={this.onKeyPress}
onKeyPress={this.onKeyPress}
onFocus={this.onFocused}
+ onClick={this.onClick}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
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.scss b/src/client/views/nodes/IconBox.scss
new file mode 100644
index 000000000..ce0ee2e09
--- /dev/null
+++ b/src/client/views/nodes/IconBox.scss
@@ -0,0 +1,12 @@
+
+@import "../globalCssVariables";
+.iconBox-container {
+ position: absolute;
+ left:0;
+ top:0;
+ svg {
+ width: 100% !important;
+ height: 100%;
+ background: white;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
new file mode 100644
index 000000000..9c90c0a0e
--- /dev/null
+++ b/src/client/views/nodes/IconBox.tsx
@@ -0,0 +1,45 @@
+import React = require("react");
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faCaretUp, faFilePdf, faFilm, faImage, faObjectGroup, faStickyNote } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import { Document } from '../../../fields/Document';
+import { IconField } from "../../../fields/IconFIeld";
+import { KeyStore } from "../../../fields/KeyStore";
+import { SelectionManager } from "../../util/SelectionManager";
+import { FieldView, FieldViewProps } from './FieldView';
+import "./IconBox.scss";
+
+
+library.add(faCaretUp);
+library.add(faObjectGroup);
+library.add(faStickyNote);
+library.add(faFilePdf);
+library.add(faFilm);
+
+@observer
+export class IconBox extends React.Component<FieldViewProps> {
+ public static LayoutString() { return FieldView.LayoutString(IconBox); }
+
+ @computed get maximized() { return this.props.Document.GetT(KeyStore.MaximizedDoc, Document); }
+ @computed get layout(): string { return this.props.Document.GetData(this.props.fieldKey, IconField, "<p>Error loading layout data</p>" as string); }
+ @computed get minimizedIcon() { return IconBox.DocumentIcon(this.layout); }
+
+ public static DocumentIcon(layout: string) {
+ let button = layout.indexOf("PDFBox") !== -1 ? faFilePdf :
+ layout.indexOf("ImageBox") !== -1 ? faImage :
+ layout.indexOf("Formatted") !== -1 ? faStickyNote :
+ layout.indexOf("Video") !== -1 ? faFilm :
+ layout.indexOf("Collection") !== -1 ? faObjectGroup :
+ faCaretUp;
+ return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />
+ }
+
+ render() {
+ return (
+ <div className="iconBox-container">
+ {this.minimizedIcon}
+ </div>);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 487038841..9fe211df0 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -6,6 +6,20 @@
height: auto;
max-width: 100%;
max-height: 100%;
+ pointer-events: none;
+}
+.imageBox-cont-interactive {
+ pointer-events: all;
+}
+
+.imageBox-dot {
+ position:absolute;
+ bottom: 10;
+ left: 0;
+ border-radius: 10px;
+ width:20px;
+ height:20px;
+ background:gray;
}
.imageBox-cont img {
@@ -18,4 +32,4 @@
border: none;
width: 100%;
height: 100%;
-}
+} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index edd7f55fc..2cbb0fa90 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,21 +1,22 @@
-import { action, observable, trace } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
+import { Document } from '../../../fields/Document';
import { FieldWaiting } from '../../../fields/Field';
import { ImageField } from '../../../fields/ImageField';
import { KeyStore } from '../../../fields/KeyStore';
+import { ListField } from '../../../fields/ListField';
+import { Utils } from '../../../Utils';
+import { DragManager } from '../../util/DragManager';
+import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../../views/ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
-import { Utils } from '../../../Utils';
-import { ListField } from '../../../fields/ListField';
-import { DragManager } from '../../util/DragManager';
-import { undoBatch } from '../../util/UndoManager';
-import { TextField } from '../../../fields/TextField';
-import { Document } from '../../../fields/Document';
+import { InkingControl } from '../InkingControl';
+import { NumberField } from '../../../fields/NumberField';
@observer
export class ImageBox extends React.Component<FieldViewProps> {
@@ -33,21 +34,19 @@ export class ImageBox extends React.Component<FieldViewProps> {
super(props);
this._imgRef = React.createRef();
- this.state = {
- photoIndex: 0,
- isOpen: false,
- };
}
@action
onLoad = (target: any) => {
var h = this._imgRef.current!.naturalHeight;
var w = this._imgRef.current!.naturalWidth;
- this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
+ if (this._photoIndex === 0) {
+ this.props.Document.SetNumber(KeyStore.NativeHeight, this.props.Document.GetNumber(KeyStore.NativeWidth, 0) * h / w);
+ this.props.Document.GetTAsync(KeyStore.Width, NumberField, field =>
+ field && this.props.Document.SetNumber(KeyStore.Height, field.Data * h / w));
+ }
}
- componentDidMount() {
- }
protected createDropTarget = (ele: HTMLDivElement) => {
if (this.dropDisposer) {
@@ -57,8 +56,10 @@ export class ImageBox extends React.Component<FieldViewProps> {
this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
}
}
-
- componentWillUnmount() {
+ onDrop = (e: React.DragEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ console.log("IMPLEMENT ME PLEASE");
}
@@ -70,7 +71,7 @@ export class ImageBox extends React.Component<FieldViewProps> {
if (layout.indexOf(ImageBox.name) !== -1) {
let imgData = this.props.Document.Get(KeyStore.Data);
if (imgData instanceof ImageField && imgData) {
- this.props.Document.Set(KeyStore.Data, new ListField([imgData]));
+ this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData]));
}
let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]);
if (imgList) {
@@ -89,7 +90,6 @@ export class ImageBox extends React.Component<FieldViewProps> {
onPointerDown = (e: React.PointerEvent): void => {
if (Date.now() - this._lastTap < 300) {
if (e.buttons === 1) {
- e.stopPropagation();
this._downX = e.clientX;
this._downY = e.clientY;
document.removeEventListener("pointerup", this.onPointerUp);
@@ -139,6 +139,23 @@ export class ImageBox extends React.Component<FieldViewProps> {
}
}
+ @action
+ onDotDown(index: number) {
+ this._photoIndex = index;
+ this.props.Document.SetNumber(KeyStore.CurPage, index);
+ }
+
+ dots(paths: string[]) {
+ let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+ let dist = Math.min(nativeWidth / paths.length, 40);
+ let left = (nativeWidth - paths.length * dist) / 2;
+ return paths.map((p, i) =>
+ <div className="imageBox-placer" key={i} >
+ <div className="imageBox-dot" style={{ background: (i == this._photoIndex ? "black" : "gray"), transform: `translate(${i * dist + left}px, 0px)` }} onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); this.onDotDown(i); }} />
+ </div>
+ );
+ }
+
render() {
let field = this.props.Document.Get(this.props.fieldKey);
let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
@@ -146,9 +163,11 @@ export class ImageBox extends React.Component<FieldViewProps> {
else if (field instanceof ImageField) paths = [field.Data.href];
else if (field instanceof ListField) paths = field.Data.filter(val => val as ImageField).map(p => (p as ImageField).Data.href);
let nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 1);
+ let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
return (
- <div className="imageBox-cont" onPointerDown={this.onPointerDown} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
- <img src={paths[0]} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
+ <div className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
+ {paths.length > 1 ? this.dots(paths) : (null)}
{this.lightbox(paths)}
</div>);
}
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/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 5d69f23b2..d480eb5af 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -48,7 +48,7 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
isTopMost: false,
selectOnLoad: false,
active: returnFalse,
- onActiveChanged: emptyFunction,
+ whenActiveChanged: emptyFunction,
ScreenToLocalTransform: Transform.Identity,
focus: emptyDocFunction,
};
diff --git a/src/client/views/nodes/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 81ceb37f6..226dfba11 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";
@@ -14,11 +14,10 @@ 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 { InkingControl } from "../InkingControl";
/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -40,15 +39,6 @@ import { SelectionManager } from "../../util/SelectionManager";
* 4) another method: click on highlight first and then drag on your desired text
* 5) To make another highlight, you need to reclick on the button
*
- * Draw:
- * 1) click draw and select color. then just draw like there's no tomorrow.
- * 2) once you finish drawing your masterpiece, just reclick on the draw button to end your drawing session.
- *
- * Pagination:
- * 1) click on arrows. You'll notice that stickies will stay in those page. But... highlights won't.
- * 2) to test this out, make few area/stickies and then click on next page then come back. You'll see that they are all saved.
- *
- *
* written by: Andrew Kim
*/
@observer
@@ -56,30 +46,9 @@ export class PDFBox extends React.Component<FieldViewProps> {
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
@@ -112,43 +81,6 @@ export class PDFBox extends React.Component<FieldViewProps> {
}
/**
- * selection tool used for area highlighting (stickies). Kinda temporary
- */
- selectionTool = () => {
- this._toolOn = true;
- }
- /**
- * when user draws on the canvas. When mouse pointer is down
- */
- drawDown = (e: PointerEvent) => {
- this.initX = e.offsetX;
- this.initY = e.offsetY;
- this._pdfContext.beginPath();
- this._pdfContext.lineTo(this.initX, this.initY);
- this._pdfContext.strokeStyle = this._currColor;
- this._pdfCanvas.addEventListener("pointermove", this.drawMove);
- this._pdfCanvas.addEventListener("pointerup", this.drawUp);
-
- }
- //when user drags
- drawMove = (e: PointerEvent): void => {
- //x and y mouse movement
- let x = this.initX += e.movementX,
- y = this.initY += e.movementY;
- //connects the point
- this._pdfContext.lineTo(x, y);
- this._pdfContext.stroke();
- }
-
- drawUp = (e: PointerEvent) => {
- this._pdfContext.closePath();
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- }
-
-
- /**
* highlighting helper function
*/
makeEditableAndHighlight = (colour: string) => {
@@ -183,7 +115,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
child.id = "highlighted";
//@ts-ignore
obj.spans.push(child);
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
});
}
@@ -206,7 +138,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
child.id = "highlighted";
//@ts-ignore
temp.spans.push(child);
- child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
+ // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler
}
});
@@ -272,11 +204,20 @@ export class PDFBox extends React.Component<FieldViewProps> {
* controls the area highlighting (stickies) Kinda temporary
*/
onPointerDown = (e: React.PointerEvent) => {
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- this.initX = mouse.offsetX;
- this.initY = mouse.offsetY;
-
+ if (this.props.isSelected() && !InkingControl.Instance.selectedTool && e.buttons === 1) {
+ if (e.altKey) {
+ this._alt = true;
+ } else {
+ if (e.metaKey)
+ e.stopPropagation();
+ }
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ if (this.props.isSelected() && e.buttons === 2) {
+ this._alt = true;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
}
}
@@ -284,96 +225,15 @@ export class PDFBox extends React.Component<FieldViewProps> {
* controls area highlighting and partially highlighting. Kinda temporary
*/
@action
- onPointerUp = (e: React.PointerEvent) => {
- if (this._highlightToolOn) {
+ onPointerUp = (e: PointerEvent) => {
+ this._alt = false;
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (this.props.isSelected()) {
this.highlight("rgba(76, 175, 80, 0.3)"); //highlights to this default color.
- this._highlightToolOn = false;
- }
- if (this._toolOn) {
- let mouse = e.nativeEvent;
- let finalX = mouse.offsetX;
- let finalY = mouse.offsetY;
- let width = Math.abs(finalX - this.initX); //width
- let height = Math.abs(finalY - this.initY); //height
-
- //these two if statements are bidirectional dragging. You can drag from any point to another point and generate sticky
- if (finalX < this.initX) {
- this.initX = finalX;
- }
- if (finalY < this.initY) {
- this.initY = finalY;
- }
-
- if (this._mainDiv.current) {
- let sticky = <Sticky key={Utils.GenerateGuid()} Height={height} Width={width} X={this.initX} Y={this.initY} />;
- this._pageInfo.area.push(sticky);
- }
- this._toolOn = false;
}
this._interactive = true;
}
- /**
- * starts drawing the line when user presses down.
- */
- onDraw = () => {
- if (this._currTool !== null) {
- this._currTool.style.backgroundColor = "grey";
- }
-
- if (this._drawTool.current) {
- this._currTool = this._drawTool.current;
- if (this._drawToolOn) {
- this._drawToolOn = false;
- this._pdfCanvas.removeEventListener("pointerdown", this.drawDown);
- this._pdfCanvas.removeEventListener("pointerup", this.drawUp);
- this._pdfCanvas.removeEventListener("pointermove", this.drawMove);
- this._drawTool.current.style.backgroundColor = "grey";
- } else {
- this._drawToolOn = true;
- this._pdfCanvas.addEventListener("pointerdown", this.drawDown);
- this._drawTool.current.style.backgroundColor = "cyan";
- }
- }
- }
-
-
- /**
- * for changing color (for ink/pen)
- */
- onColorChange = (e: React.PointerEvent) => {
- if (e.currentTarget.innerHTML === "Red") {
- this._currColor = "red";
- } else if (e.currentTarget.innerHTML === "Blue") {
- this._currColor = "blue";
- } else if (e.currentTarget.innerHTML === "Green") {
- this._currColor = "green";
- } else if (e.currentTarget.innerHTML === "Black") {
- this._currColor = "black";
- }
-
- }
-
-
- /**
- * For highlighting (text drag highlighting)
- */
- onHighlight = () => {
- this._drawToolOn = false;
- if (this._currTool !== null) {
- this._currTool.style.backgroundColor = "grey";
- }
- if (this._highlightTool.current) {
- this._currTool = this._drawTool.current;
- if (this._highlightToolOn) {
- this._highlightToolOn = false;
- this._highlightTool.current.style.backgroundColor = "grey";
- } else {
- this._highlightToolOn = true;
- this._highlightTool.current.style.backgroundColor = "orange";
- }
- }
- }
@action
@@ -397,22 +257,6 @@ export class PDFBox extends React.Component<FieldViewProps> {
@action
onLoaded = (page: any) => {
- if (this._mainDiv.current) {
- this._mainDiv.current.childNodes.forEach((element) => {
- if (element.nodeName === "DIV") {
- element.childNodes[0].childNodes.forEach((e) => {
-
- if (e instanceof HTMLCanvasElement) {
- this._pdfCanvas = e;
- this._pdfContext = e.getContext("2d");
-
- }
-
- });
- }
- });
- }
-
// bcz: the number of pages should really be set when the document is imported.
this.props.Document.SetNumber(KeyStore.NumPages, page._transport.numPages);
if (this._perPageInfo.length === 0) { //Makes sure it only runs once
@@ -424,7 +268,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
@action
setScaling = (r: any) => {
// bcz: the nativeHeight should really be set when the document is imported.
- // also, the native dimensions could be different for different pages of the PDF
+ // also, the native dimensions could be different for different pages of the canvas
// so this design is flawed.
var nativeWidth = this.props.Document.GetNumber(KeyStore.NativeWidth, 0);
if (!this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) {
@@ -433,22 +277,27 @@ export class PDFBox extends React.Component<FieldViewProps> {
this.props.Document.SetNumber(KeyStore.NativeHeight, nativeHeight);
}
}
-
+ renderHeight = 2400;
+ @computed
+ get pdfPage() {
+ return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />
+ }
@computed
get pdfContent() {
- let page = this.curPage;
- const renderHeight = 2400;
let pdfUrl = this.props.Document.GetT(this.props.fieldKey, PDFField);
- let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
+ let xf = this.props.Document.GetNumber(KeyStore.NativeHeight, 0) / this.renderHeight;
+ let body = (this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) ?
+ this.pdfPage :
+ <Measure onResize={this.setScaling}>
+ {({ measureRef }) =>
+ <div className="pdfBox-page" ref={measureRef}>
+ {this.pdfPage}
+ </div>
+ }
+ </Measure>;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : ""}>
- <Measure onResize={this.setScaling}>
- {({ measureRef }) =>
- <div className="pdfBox-page" ref={measureRef}>
- <Page height={renderHeight} pageNumber={page} onLoadSuccess={this.onLoaded} />
- </div>
- }
- </Measure>
+ <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}>
+ {body}
</Document>
</div >;
}
@@ -478,10 +327,24 @@ export class PDFBox extends React.Component<FieldViewProps> {
}
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 9d7c2bc56..b34f1dc08 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,18 +1,16 @@
import React = require("react");
+import { action, computed, IReactionDisposer, trace } from "mobx";
import { observer } from "mobx-react";
+import Measure from "react-measure";
import { FieldWaiting, Opt } from '../../../fields/Field';
+import { KeyStore } from "../../../fields/KeyStore";
import { VideoField } from '../../../fields/VideoField';
import { FieldView, FieldViewProps } from './FieldView';
import "./VideoBox.scss";
-import Measure from "react-measure";
-import { action, trace, observable, IReactionDisposer, computed, reaction } from "mobx";
-import { KeyStore } from "../../../fields/KeyStore";
-import { number } from "prop-types";
@observer
export class VideoBox extends React.Component<FieldViewProps> {
- private _reactionDisposer: Opt<IReactionDisposer>;
private _videoRef = React.createRef<HTMLVideoElement>();
public static LayoutString() { return FieldView.LayoutString(VideoBox); }
@@ -54,25 +52,26 @@ export class VideoBox extends React.Component<FieldViewProps> {
(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 = this.props.Document.GetT(this.props.fieldKey, VideoField);
if (!field || field === FieldWaiting) {
return <div>Loading</div>;
}
- let path = field.Data.href;
- trace();
- return (
+ return (this.props.Document.GetNumber(KeyStore.NativeHeight, 0)) ?
+ this.videoContent(field.data.href) :
<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>
+ {this.videoContent(field.data.href)}
</div>
}
- </Measure>
- );
+ </Measure>;
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss
index 2ad1129a4..eb09b0693 100644
--- a/src/client/views/nodes/WebBox.scss
+++ b/src/client/views/nodes/WebBox.scss
@@ -1,12 +1,19 @@
-.webBox-cont {
+.webBox-cont, .webBox-cont-interactive{
padding: 0vw;
position: absolute;
top: 0;
left:0;
width: 100%;
height: 100%;
- overflow: scroll;
+ overflow: auto;
+ pointer-events: none ;
+}
+.webBox-cont-interactive {
+ pointer-events: all;
+ span {
+ user-select: text !important;
+ }
}
#webBox-htmlSpan {
@@ -15,6 +22,12 @@
left:0;
}
+.webBox-overlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+}
+
.webBox-button {
padding : 0vw;
border: none;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 1edb4d826..a7c6fda8b 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -2,20 +2,18 @@ import "./WebBox.scss";
import React = require("react");
import { WebField } from '../../../fields/WebField';
import { FieldViewProps, FieldView } from './FieldView';
-import { FieldWaiting } from '../../../fields/Field';
+import { FieldWaiting, Opt } from '../../../fields/Field';
import { observer } from "mobx-react";
-import { computed } from 'mobx';
+import { computed, reaction, IReactionDisposer } from 'mobx';
import { KeyStore } from '../../../fields/KeyStore';
+import { DocumentDecorations } from "../DocumentDecorations";
+import { InkingControl } from "../InkingControl";
@observer
export class WebBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(WebBox); }
- constructor(props: FieldViewProps) {
- super(props);
- }
-
@computed get html(): string { return this.props.Document.GetHtml(KeyStore.Data, ""); }
_ignore = 0;
@@ -46,12 +44,15 @@ export class WebBox extends React.Component<FieldViewProps> {
<iframe src={path} style={{ position: "absolute", width: "100%", height: "100%" }} />}
</div>;
+ let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+
+ let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : "");
return (
<>
- <div className="webBox-cont" >
+ <div className={classname} >
{content}
</div>
- {this.props.isSelected() ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ {!frozen ? (null) : <div className="webBox-overlay" onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} />}
</>);
}
} \ No newline at end of file
diff --git a/src/fields/Document.ts b/src/fields/Document.ts
index 7cf784f0e..2797efc09 100644
--- a/src/fields/Document.ts
+++ b/src/fields/Document.ts
@@ -13,6 +13,7 @@ import { BooleanField } from "./BooleanField";
import { allLimit } from "async";
import { prototype } from "nodemailer/lib/smtp-pool";
import { HistogramField } from "../client/northstar/dash-fields/HistogramField";
+import { Documents } from "../client/documents/Documents";
export class Document extends Field {
//TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field
@@ -371,6 +372,36 @@ export class Document extends Field {
return alias;
}
+ @action
+ CreateLink(dstTarg: Document) {
+ let batch = UndoManager.StartBatch("document view drop");
+ let linkDoc: Document = Documents.TextDocument({ width: 100, height: 25, title: "-link-" });
+ linkDoc.SetText(KeyStore.LinkDescription, "");
+ linkDoc.SetText(KeyStore.LinkTags, "Default");
+
+ let srcTarg = this;
+ linkDoc.Set(KeyStore.LinkedToDocs, dstTarg);
+ linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg);
+ const prom1 = new Promise(resolve => dstTarg.GetOrCreateAsync(
+ KeyStore.LinkedFromDocs,
+ ListField,
+ field => {
+ (field as ListField<Document>).Data.push(linkDoc);
+ resolve();
+ }
+ ));
+ const prom2 = new Promise(resolve => srcTarg.GetOrCreateAsync(
+ KeyStore.LinkedToDocs,
+ ListField,
+ field => {
+ (field as ListField<Document>).Data.push(linkDoc);
+ resolve();
+ }
+ ));
+ Promise.all([prom1, prom2]).finally(() => batch.end());
+ return linkDoc;
+ }
+
MakeDelegate(id?: string): Document {
let delegate = new Document(id);
diff --git a/src/fields/IconFIeld.ts b/src/fields/IconFIeld.ts
new file mode 100644
index 000000000..a6694cc49
--- /dev/null
+++ b/src/fields/IconFIeld.ts
@@ -0,0 +1,25 @@
+import { BasicField } from "./BasicField";
+import { FieldId } from "./Field";
+import { Types } from "../server/Message";
+
+export class IconField extends BasicField<string> {
+ constructor(data: string = "", id?: FieldId, save: boolean = true) {
+ super(data, save, id);
+ }
+
+ ToScriptString(): string {
+ return `new IconField("${this.Data}")`;
+ }
+
+ Copy() {
+ return new IconField(this.Data);
+ }
+
+ ToJson() {
+ return {
+ type: Types.Icon,
+ data: this.Data,
+ id: this.Id
+ };
+ }
+} \ No newline at end of file
diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts
index 1fb65965b..069611991 100644
--- a/src/fields/KeyStore.ts
+++ b/src/fields/KeyStore.ts
@@ -15,11 +15,13 @@ export namespace KeyStore {
export const Width = new Key("Width");
export const Height = new Key("Height");
export const ZIndex = new Key("ZIndex");
- export const Zoom = new Key("Zoom");
+ export const ZoomBasis = new Key("ZoomBasis");
export const Data = new Key("Data");
export const Annotations = new Key("Annotations");
export const ViewType = new Key("ViewType");
+ export const BaseLayout = new Key("BaseLayout");
export const Layout = new Key("Layout");
+ export const Templates = new Key("Templates");
export const BackgroundColor = new Key("BackgroundColor");
export const BackgroundLayout = new Key("BackgroundLayout");
export const OverlayLayout = new Key("OverlayLayout");
@@ -47,14 +49,17 @@ export namespace KeyStore {
export const OptionalRightCollection = new Key("OptionalRightCollection");
export const Archives = new Key("Archives");
export const Workspaces = new Key("Workspaces");
- export const Minimized = new Key("Minimized");
+ export const IsMinimized = new Key("IsMinimized");
+ export const MinimizedDoc = new Key("MinimizedDoc");
+ export const MaximizedDoc = new Key("MaximizedDoc");
export const CopyDraggedItems = new Key("CopyDraggedItems");
+ export const BorderRounding = new Key("BorderRounding");
export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight,
- Width, Height, ZIndex, Zoom, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
+ Width, Height, ZIndex, ZoomBasis, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys,
LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs,
LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection,
- Archives, Workspaces, Minimized, CopyDraggedItems
+ Archives, Workspaces, IsMinimized, MinimizedDoc, MaximizedDoc, CopyDraggedItems, BorderRounding
];
export function KeyLookup(keyid: string) {
for (const key of KeyList) {
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/server/Message.ts b/src/server/Message.ts
index bbe4ffcad..854ae0168 100644
--- a/src/server/Message.ts
+++ b/src/server/Message.ts
@@ -14,8 +14,8 @@ export class Message<T> {
}
export enum Types {
- Number, List, Key, Image, Web, Document, Text, RichText, DocumentReference,
- Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script,
+ Number, List, Key, Image, Web, Document, Text, Icon, RichText, DocumentReference,
+ Html, Video, Audio, Ink, PDF, Tuple, HistogramOp, Boolean, Script, Templates
}
export interface Transferable {
diff --git a/src/server/ServerUtil.ts b/src/server/ServerUtil.ts
index 818230c1a..eb5749dff 100644
--- a/src/server/ServerUtil.ts
+++ b/src/server/ServerUtil.ts
@@ -18,6 +18,8 @@ import { NumberField } from "./../fields/NumberField";
import { RichTextField } from "./../fields/RichTextField";
import { TextField } from "./../fields/TextField";
import { Transferable, Types } from "./Message";
+import { TemplateField } from "../fields/TemplateField";
+import { IconField } from "../fields/IconFIeld";
export class ServerUtils {
public static prepend(extension: string): string {
@@ -37,6 +39,7 @@ export class ServerUtils {
case Types.Boolean: return new BooleanField(json.data, json.id, false);
case Types.Number: return new NumberField(json.data, json.id, false);
case Types.Text: return new TextField(json.data, json.id, false);
+ case Types.Icon: return new IconField(json.data, json.id, false);
case Types.Html: return new HtmlField(json.data, json.id, false);
case Types.Web: return new WebField(new URL(json.data), json.id, false);
case Types.RichText: return new RichTextField(json.data, json.id, false);
@@ -50,6 +53,7 @@ export class ServerUtils {
case Types.Video: return new VideoField(new URL(json.data), json.id, false);
case Types.Tuple: return new TupleField(json.data, json.id, false);
case Types.Ink: return InkField.FromJson(json.id, json.data);
+ case Types.Templates: return TemplateField.FromJson(json.id, json.data);
case Types.Document: return Document.FromJson(json.data, json.id, false);
default:
throw Error(