aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx92
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx119
2 files changed, 107 insertions, 104 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dc86f38b6..9cb8443f4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -20,10 +20,11 @@ import React = require("react");
import v5 = require("uuid/v5");
import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
-import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types";
+import { FieldValue, Cast, NumCast, BoolCast } from "../../../../new_fields/Types";
import { pageSchema } from "../../nodes/ImageBox";
import { Id } from "../../../../new_fields/RefField";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { HistoryUtil } from "../../../util/History";
export const panZoomSchema = createSchema({
panX: "number",
@@ -36,23 +37,22 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema)
@observer
export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
- 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;
private get _pwidth() { return this.props.PanelWidth(); }
private get _pheight() { return this.props.PanelHeight(); }
- @computed get nativeWidth() { return NumCast(this.Document.nativeWidth, 0); }
- @computed get nativeHeight() { return NumCast(this.Document.nativeHeight, 0); }
+ @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+ public get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
- private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
- private panX = () => FieldValue(this.Document.panX, 0);
- private panY = () => FieldValue(this.Document.panY, 0);
- private zoomScaling = () => FieldValue(this.Document.scale, 1);
+ private panX = () => this.Document.panX || 0;
+ private panY = () => this.Document.panY || 0;
+ private zoomScaling = () => this.Document.scale || 1;
private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0; // shift so pan position is at center of window for non-overlay collections
private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
- private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+ private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
private addLiveTextBox = (newBox: Doc) => {
@@ -65,13 +65,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return true;
}
private selectDocuments = (docs: Doc[]) => {
- SelectionManager.DeselectAll;
+ SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
SelectionManager.SelectDoc(dv!, true));
}
public getActiveDocuments = () => {
const curPage = FieldValue(this.Document.curPage, -1);
- return this.children.filter(doc => {
+ return this.childDocs.filter(doc => {
var page = NumCast(doc.page, -1);
return page === curPage || page === -1;
});
@@ -89,7 +89,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let y = yp - de.data.yOffset / zoom;
let dropX = NumCast(de.data.droppedDocuments[0].x);
let dropY = NumCast(de.data.droppedDocuments[0].y);
- de.data.droppedDocuments.map(d => {
+ de.data.droppedDocuments.forEach(d => {
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
if (!NumCast(d.width)) {
@@ -111,11 +111,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if ((CollectionFreeFormView.RIGHT_BTN_DRAG &&
- (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
- (e.button === 0 && e.altKey)) && this.props.active())) ||
- (!CollectionFreeFormView.RIGHT_BTN_DRAG &&
- ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) {
+ if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
@@ -133,20 +129,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble) {
- let x = NumCast(this.props.Document.panX);
- let y = NumCast(this.props.Document.panY);
- let docs = this.children || [];
+ let x = this.Document.panX || 0;
+ let y = this.Document.panY || 0;
+ let docs = this.childDocs || [];
let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
if (!this.isAnnotationOverlay) {
let minx = docs.length ? NumCast(docs[0].x) : 0;
- let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
+ let maxx = docs.length ? NumCast(docs[0].width) / NumCast(docs[0].zoomBasis) + minx : minx;
let miny = docs.length ? NumCast(docs[0].y) : 0;
- let maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+ let maxy = docs.length ? NumCast(docs[0].height) / NumCast(docs[0].zoomBasis) + miny : miny;
let ranges = docs.filter(doc => doc).reduce((range, doc) => {
let x = NumCast(doc.x);
- let xe = x + NumCast(doc.width);
+ let xe = x + NumCast(doc.width) / NumCast(doc.zoomBasis);
let y = NumCast(doc.y);
- let ye = y + NumCast(doc.height);
+ let ye = y + NumCast(doc.height) / NumCast(doc.zoomBasis);
return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
[range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
}, [[minx, maxx], [miny, maxy]]);
@@ -179,7 +175,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// if (!this.props.active()) {
// return;
// }
- let childSelected = this.children.some(doc => {
+ let childSelected = this.childDocs.some(doc => {
var dv = DocumentManager.Instance.getDocumentView(doc);
return dv && SelectionManager.IsSelected(dv) ? true : false;
});
@@ -210,7 +206,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
- let safeScale = Math.abs(localTransform.Scale);
+ let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
this.props.Document.scale = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
e.stopPropagation();
@@ -219,6 +215,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
setPan(panX: number, panY: number) {
+ this.panDisposer && clearTimeout(this.panDisposer);
+ this.props.Document.panTransformType = "None";
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));
@@ -236,7 +234,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
bringToFront = (doc: Doc) => {
- const docs = this.children;
+ const docs = this.childDocs;
docs.slice().sort((doc1, doc2) => {
if (doc1 === doc) return 1;
if (doc2 === doc) return -1;
@@ -245,16 +243,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
doc.zIndex = docs.length + 1;
}
+ panDisposer?: NodeJS.Timeout;
focusDocument = (doc: Doc) => {
+ const panX = this.Document.panX;
+ const panY = this.Document.panY;
+ const id = this.Document[Id];
+ const state = HistoryUtil.getState();
+ // TODO This technically isn't correct if type !== "doc", as
+ // currently nothing is done, but we should probably push a new state
+ if (state.type === "doc" && panX !== undefined && panY !== undefined) {
+ const init = state.initializers[id];
+ if (!init) {
+ state.initializers[id] = {
+ panX, panY
+ };
+ HistoryUtil.pushState(state);
+ } else if (init.panX !== panX || init.panY !== panY) {
+ init.panX = panX;
+ init.panY = panY;
+ HistoryUtil.pushState(state);
+ }
+ }
SelectionManager.DeselectAll();
+ const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
+ const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
+ const newState = HistoryUtil.getState();
+ newState.initializers[id] = { panX: newPanX, panY: newPanY };
+ HistoryUtil.pushState(newState);
+ this.setPan(newPanX, newPanY);
this.props.Document.panTransformType = "Ease";
- this.setPan(
- NumCast(doc.x) + NumCast(doc.width) / 2,
- NumCast(doc.y) + NumCast(doc.height) / 2);
this.props.focus(this.props.Document);
- if (this.props.Document.panTransformType === "Ease") {
- setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false
- }
+ this.panDisposer = setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false
}
@@ -276,17 +295,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
parentActive: this.props.active,
whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
+ addDocTab: this.props.addDocTab,
};
}
@computed.struct
get views() {
let curPage = FieldValue(this.Document.curPage, -1);
- let docviews = this.children.reduce((prev, doc) => {
+ let docviews = this.childDocs.reduce((prev, doc) => {
if (!(doc instanceof Doc)) return prev;
var page = NumCast(doc.page, -1);
if (page === curPage || page === -1) {
- let minim = Cast(doc.isMinimized, "boolean");
+ let minim = BoolCast(doc.isMinimized, false);
if (minim === undefined || !minim) {
prev.push(<CollectionFreeFormDocumentView key={doc[Id]} {...this.getDocumentViewProps(doc)} />);
}
@@ -350,7 +370,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
- return this.backgroundView;
+ return this.props.Document.backgroundLayout ? this.backgroundView : (null);
}
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 080c484f4..4587c2227 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -17,6 +17,9 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
import { ImageField } from "../../../../new_fields/URLField";
import { Template, Templates } from "../../Templates";
+import { Gateway } from "../../../northstar/manager/Gateway";
+import { DocServer } from "../../../DocServer";
+import { Id } from "../../../../new_fields/RefField";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -60,7 +63,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
e.preventDefault();
(async () => {
let text: string = await navigator.clipboard.readText();
- let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
+ let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
for (let i = 0; i < ns.length - 1; i++) {
while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
@@ -77,9 +80,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
this.props.addDocument(newBox, false);
y += 40 * this.props.getTransform().Scale;
- })
+ });
})();
- } else if (e.key === "t" && e.ctrlKey) {
+ } else if (e.key === "b" && e.ctrlKey) {
//heuristically converts pasted text into a table.
// assumes each entry is separated by a tab
// skips all rows until it gets to a row with more than one entry
@@ -90,9 +93,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
e.preventDefault();
(async () => {
let text: string = await navigator.clipboard.readText();
- let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
- while (ns.length > 0 && ns[0].split("\t").length < 2)
+ let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+ while (ns.length > 0 && ns[0].split("\t").length < 2) {
ns.splice(0, 1);
+ }
if (ns.length > 0) {
let columns = ns[0].split("\t");
let docList: Doc[] = [];
@@ -104,18 +108,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
continue;
}
let doc = new Doc();
- columns.forEach((col, i) => {
- console.log(values[i] + " " + Number(values[i]).toString());
- doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined);
- });
+ columns.forEach((col, i) => doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
if (groupAttr) {
- doc["_group"] = groupAttr;
+ doc._group = groupAttr;
}
doc.title = i.toString();
docList.push(doc);
}
- let newCol = Docs.SchemaDocument(docList, { x: x, y: y, title: "-dropped table-", width: 300, height: 100 });
- newCol.proto!.schemaColumns = new List<string>([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)]);
+ let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+
this.props.addDocument(newCol, false);
}
})();
@@ -131,17 +132,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
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())) {
+ if (e.button === 2 || (e.button === 0 && e.altKey)) {
+ if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
document.addEventListener("pointermove", this.onPointerMove, true);
document.addEventListener("pointerup", this.onPointerUp, true);
document.addEventListener("keydown", this.marqueeCommand, true);
- // bcz: do we need this? it kills the context menu on the main collection
+ if (e.altKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ // bcz: do we need this? it kills the context menu on the main collection if !altKey
// e.stopPropagation();
}
- if (e.altKey) {
- e.preventDefault();
- }
}
@action
@@ -207,11 +209,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@undoBatch
@action
marqueeCommand = async (e: KeyboardEvent) => {
- if (this._commandExecuted) {
+ if (this._commandExecuted || (e as any).propagationIsStopped) {
return;
}
if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
this._commandExecuted = true;
+ e.stopPropagation();
+ (e as any).propagationIsStopped = true;
this.marqueeSelect().map(d => this.props.removeDocument(d));
let ink = Cast(this.props.container.props.Document.ink, InkField);
if (ink) {
@@ -221,26 +225,21 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.cleanupInteractions(false);
e.stopPropagation();
}
- if (e.key === "c" || e.key === "r" || e.key === "s" || e.key === "e" || e.key === "p") {
+ if (e.key === "c" || e.key === "s" || e.key === "e" || e.key === "p") {
this._commandExecuted = true;
e.stopPropagation();
+ (e as any).propagationIsStopped = true;
let bounds = this.Bounds;
- let selected = this.marqueeSelect().map(d => {
- if (e.key === "s") {
- let dCopy = Doc.MakeCopy(d);
- dCopy.x = NumCast(d.x) - bounds.left - bounds.width / 2;
- dCopy.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- dCopy.page = -1;
- return dCopy;
- }
- else if (e.key !== "r") {
+ let selected = this.marqueeSelect();
+ if (e.key === "c") {
+ selected.map(d => {
this.props.removeDocument(d);
d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
d.page = -1;
- }
- return d;
- });
+ return d;
+ });
+ }
let ink = Cast(this.props.container.props.Document.ink, InkField);
let inkData = ink ? ink.inkData : undefined;
let zoomBasis = NumCast(this.props.container.props.Document.scale, 1);
@@ -250,39 +249,36 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
panX: 0,
panY: 0,
borderRounding: e.key === "e" ? -1 : undefined,
+ backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
scale: zoomBasis,
width: bounds.width * zoomBasis,
height: bounds.height * zoomBasis,
ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,
- title: "a nested collection",
+ title: e.key === "s" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection",
});
-
this.marqueeInkDelete(inkData);
- // SelectionManager.DeselectAll();
- if (e.key === "s" || e.key === "r" || e.key === "p") {
- e.preventDefault();
- let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
- let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
- let dataUrl = await htmlToImage.toPng(this._mainCont.current!, { width: bounds.width, height: bounds.height, quality: 1 });
- summary.proto!.thumbnail = new ImageField(new URL(dataUrl));
+ if (e.key === "s" || e.key === "p") {
- summary.proto!.templates = new List<string>([Templates.ImageOverlay(Math.min(50, bounds.width), bounds.height * Math.min(50, bounds.width) / bounds.width, "thumbnail")]);
- if (e.key === "s" || e.key === "p") {
- summary.proto!.maximizeOnRight = true;
+ htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 1 }).then((dataUrl) => {
+ selected.map(d => {
+ this.props.removeDocument(d);
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+ d.page = -1;
+ return d;
+ });
+ let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+ summary.proto!.thumbnail = new ImageField(new URL(dataUrl));
+ summary.proto!.templates = new List<string>([Templates.ImageOverlay(Math.min(50, bounds.width), bounds.height * Math.min(50, bounds.width) / bounds.width, "thumbnail")]);
newCollection.proto!.summaryDoc = summary;
selected = [newCollection];
- }
- summary.proto!.summarizedDocs = new List<Doc>(selected);
- //summary.proto!.isButton = true;
- selected.map(summarizedDoc => {
- let maxx = NumCast(summarizedDoc.x, undefined);
- let maxy = NumCast(summarizedDoc.y, undefined);
- let maxw = NumCast(summarizedDoc.width, undefined);
- let maxh = NumCast(summarizedDoc.height, undefined);
- summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
+ newCollection.x = bounds.left + bounds.width;
+ //this.props.addDocument(newCollection, false);
+ summary.proto!.summarizedDocs = new List<Doc>(selected);
+ summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
+ this.props.addLiveTextDocument(summary);
});
- this.props.addLiveTextDocument(summary);
}
else {
this.props.addDocument(newCollection, false);
@@ -290,20 +286,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.props.selectDocuments([newCollection]);
}
this.cleanupInteractions(false);
- } else
- if (e.key === "s") {
- // this._commandExecuted = true;
- // e.stopPropagation();
- // e.preventDefault();
- // let bounds = this.Bounds;
- // let selected = this.marqueeSelect();
- // SelectionManager.DeselectAll();
- // let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
- // this.props.addLiveTextDocument(summary);
- // selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
-
- // this.cleanupInteractions(false);
- }
+ }
}
@action
marqueeInkSelect(ink: Map<any, any>) {