aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/documents/DocumentTypes.ts3
-rw-r--r--src/client/documents/Documents.ts15
-rw-r--r--src/client/util/InteractionUtils.ts111
-rw-r--r--src/client/util/SelectionManager.ts1
-rw-r--r--src/client/views/AntimodeMenu.scss29
-rw-r--r--src/client/views/AntimodeMenu.tsx128
-rw-r--r--src/client/views/DocComponent.tsx3
-rw-r--r--src/client/views/DocumentDecorations.tsx7
-rw-r--r--src/client/views/InkSelectDecorations.scss5
-rw-r--r--src/client/views/InkSelectDecorations.tsx59
-rw-r--r--src/client/views/InkingCanvas.tsx111
-rw-r--r--src/client/views/InkingStroke.tsx106
-rw-r--r--src/client/views/Main.scss2
-rw-r--r--src/client/views/MainView.scss26
-rw-r--r--src/client/views/MainView.tsx34
-rw-r--r--src/client/views/Touchable.tsx94
-rw-r--r--src/client/views/collections/CollectionDockingView.scss70
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx22
-rw-r--r--src/client/views/collections/CollectionStaffView.scss13
-rw-r--r--src/client/views/collections/CollectionStaffView.tsx65
-rw-r--r--src/client/views/collections/CollectionView.tsx7
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss4
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx282
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx46
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx308
-rw-r--r--src/client/views/nodes/ButtonBox.scss13
-rw-r--r--src/client/views/nodes/ButtonBox.tsx2
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.scss5
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx4
-rw-r--r--src/client/views/pdf/PDFMenu.scss40
-rw-r--r--src/client/views/pdf/PDFMenu.tsx125
-rw-r--r--src/new_fields/InkField.ts20
-rw-r--r--src/new_fields/documentSchemas.ts1
-rw-r--r--src/server/authentication/models/current_user_utils.ts8
36 files changed, 1283 insertions, 492 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 7bb025e49..12acda4bb 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -303,6 +303,8 @@ export function returnEmptyString() { return ""; }
export function emptyFunction() { }
+export function unimplementedFunction() { throw new Error("This function is not implemented, but should be."); }
+
export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
export type Predicate<K, V> = (entry: [K, V]) => boolean;
diff --git a/src/client/documents/DocumentTypes.ts b/src/client/documents/DocumentTypes.ts
index 12501065a..f6dd0c346 100644
--- a/src/client/documents/DocumentTypes.ts
+++ b/src/client/documents/DocumentTypes.ts
@@ -24,5 +24,6 @@ export enum DocumentType {
QUERY = "search",
COLOR = "color",
DOCULINK = "doculink",
- PDFANNO = "pdfanno"
+ PDFANNO = "pdfanno",
+ INK = "ink"
} \ No newline at end of file
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 1a9d67d83..268a14aca 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -48,6 +48,8 @@ import { PresElementBox } from "../views/presentationview/PresElementBox";
import { QueryBox } from "../views/nodes/QueryBox";
import { ColorBox } from "../views/nodes/ColorBox";
import { DocuLinkBox } from "../views/nodes/DocuLinkBox";
+import { InkingStroke } from "../views/InkingStroke";
+import { InkField } from "../../new_fields/InkField";
var requestImageSize = require('../util/request-image-size');
var path = require('path');
@@ -108,6 +110,8 @@ export interface DocumentOptions {
sourcePanel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script
targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script
dropConverter?: ScriptField; // script to run when documents are dropped on this Document.
+ strokeWidth?: number;
+ color?: string;
// [key: string]: Opt<Field>;
}
@@ -210,6 +214,9 @@ export namespace Docs {
[DocumentType.PRESELEMENT, {
layout: { view: PresElementBox, dataField: data }
}],
+ [DocumentType.INK, {
+ layout: { view: InkingStroke, dataField: data }
+ }]
]);
// All document prototypes are initialized with at least these values
@@ -415,6 +422,14 @@ export namespace Docs {
return InstanceFromProto(Prototypes.get(DocumentType.TEXT), "", options);
}
+ export function InkDocument(color: string, tool: number, strokeWidth: number, points: { x: number, y: number }[], options: DocumentOptions = {}) {
+ let doc = InstanceFromProto(Prototypes.get(DocumentType.INK), new InkField(points), options);
+ doc.color = color;
+ doc.strokeWidth = strokeWidth;
+ doc.tool = tool;
+ return doc;
+ }
+
export function IconDocument(icon: string, options: DocumentOptions = {}) {
return InstanceFromProto(Prototypes.get(DocumentType.ICON), new IconField(icon), options);
}
diff --git a/src/client/util/InteractionUtils.ts b/src/client/util/InteractionUtils.ts
new file mode 100644
index 000000000..e58635a6f
--- /dev/null
+++ b/src/client/util/InteractionUtils.ts
@@ -0,0 +1,111 @@
+export namespace InteractionUtils {
+ export const MOUSE = "mouse";
+ export const TOUCH = "touch";
+
+ export function IsType(e: PointerEvent | React.PointerEvent, type: string): boolean {
+ return e.pointerType === type;
+ }
+
+ export function TwoPointEuclidist(pt1: React.Touch, pt2: React.Touch): number {
+ return Math.sqrt(Math.pow(pt1.clientX - pt2.clientX, 2) + Math.pow(pt1.clientY - pt2.clientY, 2));
+ }
+
+ /**
+ * Returns the centroid of an n-arbitrary long list of points (takes the average the x and y components of each point)
+ * @param pts - n-arbitrary long list of points
+ */
+ export function CenterPoint(pts: React.Touch[]): { X: number, Y: number } {
+ let centerX = pts.map(pt => pt.clientX).reduce((a, b) => a + b, 0) / pts.length;
+ let centerY = pts.map(pt => pt.clientY).reduce((a, b) => a + b, 0) / pts.length;
+ return { X: centerX, Y: centerY };
+ }
+
+ /**
+ * Returns -1 if pinching out, 0 if not pinching, and 1 if pinching in
+ * @param pt1 - new point that corresponds to oldPoint1
+ * @param pt2 - new point that corresponds to oldPoint2
+ * @param oldPoint1 - previous point 1
+ * @param oldPoint2 - previous point 2
+ */
+ export function Pinching(pt1: React.Touch, pt2: React.Touch, oldPoint1: React.Touch, oldPoint2: React.Touch): number {
+ let threshold = 4;
+ let oldDist = TwoPointEuclidist(oldPoint1, oldPoint2);
+ let newDist = TwoPointEuclidist(pt1, pt2);
+
+ /** if they have the same sign, then we are either pinching in or out.
+ * threshold it by 10 (it has to be pinching by at least threshold to be a valid pinch)
+ * so that it can still pan without freaking out
+ */
+ if (Math.sign(oldDist) === Math.sign(newDist) && Math.abs(oldDist - newDist) > threshold) {
+ return Math.sign(oldDist - newDist);
+ }
+ return 0;
+ }
+
+ /**
+ * Returns the type of Touch Interaction from a list of points.
+ * Also returns any data that is associated with a Touch Interaction
+ * @param pts - List of points
+ */
+ // export function InterpretPointers(pts: React.Touch[]): { type: Opt<TouchInteraction>, data?: any } {
+ // const leniency = 200;
+ // switch (pts.length) {
+ // case 1:
+ // return { type: OneFinger };
+ // case 2:
+ // return { type: TwoSeperateFingers };
+ // case 3:
+ // let pt1 = pts[0];
+ // let pt2 = pts[1];
+ // let pt3 = pts[2];
+ // if (pt1 && pt2 && pt3) {
+ // let dist12 = TwoPointEuclidist(pt1, pt2);
+ // let dist23 = TwoPointEuclidist(pt2, pt3);
+ // let dist13 = TwoPointEuclidist(pt1, pt3);
+ // console.log(`distances: ${dist12}, ${dist23}, ${dist13}`);
+ // let dist12close = dist12 < leniency;
+ // let dist23close = dist23 < leniency;
+ // let dist13close = dist13 < leniency;
+ // let xor2313 = dist23close ? !dist13close : dist13close;
+ // let xor = dist12close ? !xor2313 : xor2313;
+ // // three input xor because javascript doesn't have logical xor's
+ // if (xor) {
+ // let points: number[] = [];
+ // let min = Math.min(dist12, dist23, dist13);
+ // switch (min) {
+ // case dist12:
+ // points = [0, 1, 2];
+ // break;
+ // case dist23:
+ // points = [1, 2, 0];
+ // break;
+ // case dist13:
+ // points = [0, 2, 1];
+ // break;
+ // }
+ // return { type: TwoToOneFingers, data: points };
+ // }
+ // else {
+ // return { type: ThreeSeperateFingers, data: null };
+ // }
+ // }
+ // default:
+ // return { type: undefined };
+ // }
+ // }
+
+ export function IsDragging(oldTouches: Map<number, React.Touch>, newTouches: TouchList, leniency: number): boolean {
+ for (let i = 0; i < newTouches.length; i++) {
+ let touch = newTouches.item(i);
+ if (touch) {
+ let oldTouch = oldTouches.get(touch.identifier);
+ if (oldTouch) {
+ if (TwoPointEuclidist(touch, oldTouch) >= leniency) {
+ return true;
+ }
+ }
+ }
+ }
+ return false;
+ }
+} \ No newline at end of file
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index 2d717ca57..ca61f9014 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -3,7 +3,6 @@ import { Doc, Opt } from "../../new_fields/Doc";
import { DocumentView } from "../views/nodes/DocumentView";
import { FormattedTextBox } from "../views/nodes/FormattedTextBox";
import { NumCast, StrCast } from "../../new_fields/Types";
-import { InkingControl } from "../views/InkingControl";
export namespace SelectionManager {
diff --git a/src/client/views/AntimodeMenu.scss b/src/client/views/AntimodeMenu.scss
new file mode 100644
index 000000000..f3da5f284
--- /dev/null
+++ b/src/client/views/AntimodeMenu.scss
@@ -0,0 +1,29 @@
+.antimodeMenu-cont {
+ position: absolute;
+ z-index: 10000;
+ height: 35px;
+ background: #323232;
+ box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
+ border-radius: 0px 6px 6px 6px;
+ overflow: hidden;
+ display: flex;
+
+ .antimodeMenu-button {
+ background-color: transparent;
+ width: 35px;
+ height: 35px;
+ }
+
+ .antimodeMenu-button:hover {
+ background-color: #121212;
+ }
+
+ .antimodeMenu-dragger {
+ height: 100%;
+ transition: width .2s;
+ background-image: url("https://logodix.com/logo/1020374.png");
+ background-size: 90% 100%;
+ background-repeat: no-repeat;
+ background-position: left center;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx
new file mode 100644
index 000000000..408df8bc2
--- /dev/null
+++ b/src/client/views/AntimodeMenu.tsx
@@ -0,0 +1,128 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import { observable, action } from "mobx";
+import "./AntimodeMenu.scss";
+
+/**
+ * This is an abstract class that serves as the base for a PDF-style or Marquee-style
+ * menu. To use this class, look at PDFMenu.tsx or MarqueeOptionsMenu.tsx for an example.
+ */
+export default abstract class AntimodeMenu extends React.Component {
+ protected _offsetY: number = 0;
+ protected _offsetX: number = 0;
+ protected _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
+ protected _dragging: boolean = false;
+
+ @observable protected _top: number = -300;
+ @observable protected _left: number = -300;
+ @observable protected _opacity: number = 1;
+ @observable protected _transition: string = "opacity 0.5s";
+ @observable protected _transitionDelay: string = "";
+
+ @observable public Pinned: boolean = false;
+
+ @action
+ /**
+ * @param x
+ * @param y
+ * @param forceJump: If the menu is pinned down, do you want to force it to jump to the new location?
+ * Called when you want the menu to show up at a location
+ */
+ public jumpTo = (x: number, y: number, forceJump: boolean = false) => {
+ if (!this.Pinned || forceJump) {
+ this._transition = this._transitionDelay = "";
+ this._opacity = 1;
+ this._left = x;
+ this._top = y;
+ }
+ }
+
+ @action
+ /**
+ * @param forceOut: Do you want the menu to disappear immediately or to slowly fadeout?
+ * Called when you want the menu to disappear
+ */
+ public fadeOut = (forceOut: boolean) => {
+ if (!this.Pinned) {
+ if (this._opacity === 0.2) {
+ this._transition = "opacity 0.1s";
+ this._transitionDelay = "";
+ this._opacity = 0;
+ this._left = this._top = -300;
+ }
+
+ if (forceOut) {
+ this._transition = "";
+ this._transitionDelay = "";
+ this._opacity = 0;
+ this._left = this._top = -300;
+ }
+ }
+ }
+
+ @action
+ protected pointerLeave = (e: React.PointerEvent) => {
+ if (!this.Pinned) {
+ this._transition = "opacity 0.5s";
+ this._transitionDelay = "1s";
+ this._opacity = 0.2;
+ setTimeout(() => this.fadeOut(false), 3000);
+ }
+ }
+
+ @action
+ protected pointerEntered = (e: React.PointerEvent) => {
+ this._transition = "opacity 0.1s";
+ this._transitionDelay = "";
+ this._opacity = 1;
+ }
+
+ @action
+ protected togglePin = (e: React.MouseEvent) => {
+ this.Pinned = !this.Pinned;
+ }
+
+ protected dragStart = (e: React.PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragging);
+ document.addEventListener("pointermove", this.dragging);
+ document.removeEventListener("pointerup", this.dragEnd);
+ document.addEventListener("pointerup", this.dragEnd);
+
+ this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX;
+ this._offsetY = e.nativeEvent.offsetY;
+
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ @action
+ protected dragging = (e: PointerEvent) => {
+ this._left = e.pageX - this._offsetX;
+ this._top = e.pageY - this._offsetY;
+
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ protected dragEnd = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", this.dragging);
+ document.removeEventListener("pointerup", this.dragEnd);
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ protected handleContextMenu = (e: React.MouseEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ protected getElement(buttons: JSX.Element[]) {
+ return (
+ <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
+ style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
+ {buttons}
+ <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
+ </div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 92c947fe6..b59bd4f1d 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -1,5 +1,6 @@
import * as React from 'react';
import { Doc } from '../../new_fields/Doc';
+import { Touchable } from './Touchable';
import { computed, action } from 'mobx';
import { Cast } from '../../new_fields/Types';
import { listSpec } from '../../new_fields/Schema';
@@ -13,7 +14,7 @@ interface DocComponentProps {
Document: Doc;
}
export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: Doc) => T) {
- class Component extends React.Component<P> {
+ class Component extends Touchable<P> {
//TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then
@computed get Document(): T { return schemaCtor(this.props.Document); }
@computed get layoutDoc() { return PositionDocument(Doc.Layout(this.props.Document)); }
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 890c32bcb..210fa40dc 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -24,6 +24,7 @@ import { DocumentView } from "./nodes/DocumentView";
import { FieldView } from "./nodes/FieldView";
import { IconBox } from "./nodes/IconBox";
import React = require("react");
+import { PointData } from '../../new_fields/InkField';
import { DocumentType } from '../documents/DocumentTypes';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
@@ -203,7 +204,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
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, {
+ DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(documentView => documentView.ContentDiv!), dragData, e.x, e.y, {
handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) },
hideSource: true
});
@@ -528,7 +529,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@computed
get selectionTitle(): string {
if (SelectionManager.SelectedDocuments().length === 1) {
- let field = SelectionManager.SelectedDocuments()[0].props.Document[this._fieldKey];
+ let selected = SelectionManager.SelectedDocuments()[0];
+ let field = selected.props.Document[this._fieldKey];
if (typeof field === "string") {
return field;
}
@@ -560,6 +562,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
let minimizeIcon = (
<div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
+ {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/}
{SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
</div>);
diff --git a/src/client/views/InkSelectDecorations.scss b/src/client/views/InkSelectDecorations.scss
new file mode 100644
index 000000000..daff58fd6
--- /dev/null
+++ b/src/client/views/InkSelectDecorations.scss
@@ -0,0 +1,5 @@
+.inkSelectDecorations {
+ position: absolute;
+ border: black 1px solid;
+ z-index: 9001;
+} \ No newline at end of file
diff --git a/src/client/views/InkSelectDecorations.tsx b/src/client/views/InkSelectDecorations.tsx
new file mode 100644
index 000000000..95ccc1777
--- /dev/null
+++ b/src/client/views/InkSelectDecorations.tsx
@@ -0,0 +1,59 @@
+import React = require("react");
+import { Touchable } from "./Touchable";
+import { PointData } from "../../new_fields/InkField";
+import { observer } from "mobx-react";
+import { computed, observable, action, runInAction } from "mobx";
+import "./InkSelectDecorations.scss"
+
+@observer
+export default class InkSelectDecorations extends Touchable {
+ static Instance: InkSelectDecorations;
+
+ @observable private _selectedInkNodes: Map<any, any> = new Map();
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ InkSelectDecorations.Instance = this;
+ }
+
+ @action
+ public SetSelected = (inkNodes: Map<any, any>, keepOld: boolean = false) => {
+ if (!keepOld) {
+ this._selectedInkNodes = new Map();
+ }
+ inkNodes.forEach((value: any, key: any) => {
+ runInAction(() => this._selectedInkNodes.set(key, value));
+ });
+ }
+
+ @computed
+ get Bounds(): { x: number, y: number, b: number, r: number } {
+ let left = Number.MAX_VALUE;
+ let top = Number.MAX_VALUE;
+ let right = -Number.MAX_VALUE;
+ let bottom = -Number.MAX_VALUE;
+ this._selectedInkNodes.forEach((value: PointData, key: string) => {
+ // value.pathData.map(val => {
+ // left = Math.min(val.x, left);
+ // top = Math.min(val.y, top);
+ // right = Math.max(val.x, right);
+ // bottom = Math.max(val.y, bottom);
+ // });
+ });
+ return { x: left, y: top, b: bottom, r: right };
+ }
+
+ render() {
+ let bounds = this.Bounds;
+ return (
+ <div style={{
+ top: bounds.y, left: bounds.x,
+ height: bounds.b - bounds.y,
+ width: bounds.r - bounds.x
+ }}>
+
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx
index 0037b95d0..e5253c377 100644
--- a/src/client/views/InkingCanvas.tsx
+++ b/src/client/views/InkingCanvas.tsx
@@ -7,9 +7,11 @@ import { InkingControl } from "./InkingControl";
import { InkingStroke } from "./InkingStroke";
import React = require("react");
import { UndoManager } from "../util/UndoManager";
-import { StrokeData, InkField, InkTool } from "../../new_fields/InkField";
+import { PointData, InkField, InkTool } from "../../new_fields/InkField";
import { Doc } from "../../new_fields/Doc";
import { Cast, PromiseValue, NumCast } from "../../new_fields/Types";
+import { Touchable } from "./Touchable";
+import { InteractionUtils } from "../util/InteractionUtils";
interface InkCanvasProps {
getScreenTransform: () => Transform;
@@ -20,19 +22,19 @@ interface InkCanvasProps {
}
@observer
-export class InkingCanvas extends React.Component<InkCanvasProps> {
+export class InkingCanvas extends Touchable<InkCanvasProps> {
maxCanvasDim = 8192 / 2; // 1/2 of the maximum canvas dimension for Chrome
@observable inkMidX: number = 0;
@observable inkMidY: number = 0;
- private previousState?: Map<string, StrokeData>;
+ private previousState?: Map<string, PointData>;
private _currentStrokeId: string = "";
- public static IntersectStrokeRect(stroke: StrokeData, selRect: { left: number, top: number, width: number, height: number }): boolean {
+ public static IntersectStrokeRect(stroke: PointData, selRect: { left: number, top: number, width: number, height: number }): boolean {
return stroke.pathData.reduce((inside: boolean, val) => inside ||
(selRect.left < val.x && selRect.left + selRect.width > val.x &&
selRect.top < val.y && selRect.top + selRect.height > val.y)
, false);
}
- public static StrokeRect(stroke: StrokeData): { left: number, top: number, right: number, bottom: number } {
+ public static StrokeRect(stroke: PointData): { left: number, top: number, right: number, bottom: number } {
return stroke.pathData.reduce((bounds: { left: number, top: number, right: number, bottom: number }, val) =>
({
left: Math.min(bounds.left, val.x), top: Math.min(bounds.top, val.y),
@@ -56,12 +58,12 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
}
@computed
- get inkData(): Map<string, StrokeData> {
+ get inkData(): Map<string, PointData> {
let map = Cast(this.props.AnnotationDocument[this.props.inkFieldKey], InkField);
return !map ? new Map : new Map(map.inkData);
}
- set inkData(value: Map<string, StrokeData>) {
+ set inkData(value: Map<string, PointData>) {
this.props.AnnotationDocument[this.props.inkFieldKey] = new InkField(value);
}
@@ -95,6 +97,18 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
}
@action
+ handle1PointerMove = (e: TouchEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ let pointer = e.targetTouches.item(0);
+ if (pointer) {
+ this.handleMove(pointer.clientX, pointer.clientY);
+ }
+ }
+
+ handle2PointersMove = () => { }
+
+ @action
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove, true);
document.removeEventListener("pointerup", this.onPointerUp, true);
@@ -117,21 +131,28 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
batch.end();
}
- @action
- onPointerMove = (e: PointerEvent): void => {
- e.stopPropagation();
- e.preventDefault();
+ handleMove = (x: number, y: number) => {
if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
let data = this.inkData; // add points to new line as it is being drawn
let strokeData = data.get(this._currentStrokeId);
if (strokeData) {
- strokeData.pathData.push(this.relativeCoordinatesForEvent(e.clientX, e.clientY));
+ strokeData.pathData.push(this.relativeCoordinatesForEvent(x, y));
data.set(this._currentStrokeId, strokeData);
}
this.inkData = data;
}
}
+ @action
+ onPointerMove = (e: PointerEvent): void => {
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) {
+ return;
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ this.handleMove(e.clientX, e.clientY);
+ }
+
relativeCoordinatesForEvent = (ex: number, ey: number): { x: number, y: number } => {
let [x, y] = this.props.getScreenTransform().transformPoint(ex, ey);
return { x, y };
@@ -149,37 +170,37 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
this.inkData = data;
}
- @computed
- get drawnPaths() {
- let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
- let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
- if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
- paths.push(<InkingStroke key={id} id={id}
- line={strokeData.pathData}
- count={strokeData.pathData.length}
- offsetX={this.maxCanvasDim - this.inkMidX}
- offsetY={this.maxCanvasDim - this.inkMidY}
- color={strokeData.color}
- width={strokeData.width}
- tool={strokeData.tool}
- creationTime={strokeData.creationTime}
- deleteCallback={this.removeLine} />);
- }
- return paths;
- }, [] as JSX.Element[]);
- let markerPaths = paths.filter(path => path.props.tool === InkTool.Highlighter);
- let penPaths = paths.filter(path => path.props.tool !== InkTool.Highlighter);
- return [!penPaths.length ? (null) :
- <svg className={`inkingCanvas-paths-ink`} key="Pens"
- style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
- {penPaths}
- </svg>,
- !markerPaths.length ? (null) :
- <svg className={`inkingCanvas-paths-markers`} key="Markers"
- style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
- {markerPaths}
- </svg>];
- }
+ // @computed
+ // get drawnPaths() {
+ // let curTimecode = NumCast(this.props.Document.currentTimecode, -1);
+ // let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => {
+ // if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) {
+ // paths.push(<InkingStroke key={id} id={id}
+ // line={strokeData.pathData}
+ // count={strokeData.pathData.length}
+ // offsetX={this.maxCanvasDim - this.inkMidX}
+ // offsetY={this.maxCanvasDim - this.inkMidY}
+ // color={strokeData.color}
+ // width={strokeData.width}
+ // tool={strokeData.tool}
+ // creationTime={strokeData.creationTime}
+ // deleteCallback={this.removeLine} />);
+ // }
+ // return paths;
+ // }, [] as JSX.Element[]);
+ // let markerPaths = paths.filter(path => path.props.tool === InkTool.Highlighter);
+ // let penPaths = paths.filter(path => path.props.tool !== InkTool.Highlighter);
+ // return [!penPaths.length ? (null) :
+ // <svg className={`inkingCanvas-paths-ink`} key="Pens"
+ // style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }} >
+ // {penPaths}
+ // </svg>,
+ // !markerPaths.length ? (null) :
+ // <svg className={`inkingCanvas-paths-markers`} key="Markers"
+ // style={{ left: `${this.inkMidX - this.maxCanvasDim}px`, top: `${this.inkMidY - this.maxCanvasDim}px` }}>
+ // {markerPaths}
+ // </svg>];
+ // }
render() {
let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect";
@@ -187,9 +208,9 @@ export class InkingCanvas extends React.Component<InkCanvasProps> {
InkingControl.Instance.selectedTool === InkTool.Scrubber ? "pointer" : "default") : undefined;
return (
<div className="inkingCanvas">
- <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} style={{ cursor: cursor }} />
+ <div className={`inkingCanvas-${svgCanvasStyle}`} onPointerDown={this.onPointerDown} onTouchStart={this.onTouchStart} style={{ cursor: cursor }} />
{this.props.children()}
- {this.drawnPaths}
+ {/* {this.drawnPaths} */}
</div >
);
}
diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx
index 332c22512..411b0d3a0 100644
--- a/src/client/views/InkingStroke.tsx
+++ b/src/client/views/InkingStroke.tsx
@@ -1,70 +1,66 @@
import { observer } from "mobx-react";
-import { observable, trace, runInAction } from "mobx";
+import { observable, trace, runInAction, computed } from "mobx";
import { InkingControl } from "./InkingControl";
import React = require("react");
-import { InkTool } from "../../new_fields/InkField";
+import { InkTool, InkField, InkData } from "../../new_fields/InkField";
import "./InkingStroke.scss";
import { AudioBox } from "./nodes/AudioBox";
+import { Doc, FieldResult } from "../../new_fields/Doc";
+import { createSchema, makeInterface, listSpec } from "../../new_fields/Schema";
+import { documentSchema } from "../../new_fields/documentSchemas";
+import { DocExtendableComponent } from "./DocComponent";
+import { FieldViewProps, FieldView } from "./nodes/FieldView";
+import { Transform } from "../util/Transform";
+import { Cast, FieldValue } from "../../new_fields/Types";
+import { List } from "../../new_fields/List";
+type InkDocument = makeInterface<[typeof documentSchema]>;
+const InkDocument = makeInterface(documentSchema);
-interface StrokeProps {
- offsetX: number;
- offsetY: number;
- id: string;
- count: number;
- line: Array<{ x: number, y: number }>;
- color: string;
- width: string;
- tool: InkTool;
- creationTime: number;
- deleteCallback: (index: string) => void;
+export function CreatePolyline(points: { x: number, y: number }[], left: number, top: number, color?: string, width?: number) {
+ let pts = points.reduce((acc: string, pt: { x: number, y: number }) => acc + `${pt.x - left},${pt.y - top} `, "");
+ return (
+ <polyline
+ points={pts}
+ style={{
+ fill: "none",
+ stroke: color ?? InkingControl.Instance.selectedColor,
+ strokeWidth: width ?? InkingControl.Instance.selectedWidth
+ }}
+ />
+ );
}
@observer
-export class InkingStroke extends React.Component<StrokeProps> {
+export class InkingStroke extends DocExtendableComponent<FieldViewProps, InkDocument>(InkDocument) {
+ public static LayoutString(fieldStr: string) { return FieldView.LayoutString(InkingStroke, fieldStr); }
- @observable private _strokeTool: InkTool = this.props.tool;
- @observable private _strokeColor: string = this.props.color;
- @observable private _strokeWidth: string = this.props.width;
-
- deleteStroke = (e: React.PointerEvent): void => {
- if (InkingControl.Instance.selectedTool === InkTool.Eraser && e.buttons === 1) {
- this.props.deleteCallback(this.props.id);
- e.stopPropagation();
- e.preventDefault();
- }
- if (InkingControl.Instance.selectedTool === InkTool.Scrubber && e.buttons === 1) {
- AudioBox.SetScrubTime(this.props.creationTime);
- e.stopPropagation();
- e.preventDefault();
- }
- }
-
- parseData = (line: Array<{ x: number, y: number }>): string => {
- return !line.length ? "" : "M " + line.map(p => (p.x + this.props.offsetX) + " " + (p.y + this.props.offsetY)).join(" L ");
- }
-
- createStyle() {
- switch (this._strokeTool) {
- // add more tool styles here
- default:
- return {
- fill: "none",
- stroke: this._strokeColor,
- strokeWidth: this._strokeWidth + "px",
- };
- }
- }
+ @computed get PanelWidth() { return this.props.PanelWidth(); }
+ @computed get PanelHeight() { return this.props.PanelHeight(); }
render() {
- let pathStyle = this.createStyle();
- let pathData = this.parseData(this.props.line);
- let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
- let marker = this.props.tool === InkTool.Highlighter ? "-marker" : "";
-
- let pointerEvents: any = InkingControl.Instance.selectedTool === InkTool.Eraser ||
- InkingControl.Instance.selectedTool === InkTool.Scrubber ? "all" : "none";
- return (<path className={`inkingStroke${marker}`} d={pathData} style={{ ...pathStyle, pointerEvents: pointerEvents }}
- strokeLinejoin="round" strokeLinecap="round" onPointerOver={this.deleteStroke} onPointerDown={this.deleteStroke} />);
+ // let pathData = this.parseData(this.props.line);
+ let data: InkData = Cast(this.Document.data, InkField) ?.inkData ?? [];
+ let xs = data.map(p => p.x);
+ let ys = data.map(p => p.y);
+ let left = Math.min(...xs);
+ let top = Math.min(...ys);
+ let right = Math.max(...xs);
+ let bottom = Math.max(...ys);
+ let points = CreatePolyline(data, 0, 0, this.Document.color, this.Document.strokeWidth);
+ let width = right - left;
+ let height = bottom - top;
+ let scaleX = this.PanelWidth / width;
+ let scaleY = this.PanelHeight / height;
+ // let pathlength = this.props.count; // bcz: this is needed to force reactions to the line's data changes
+ return (
+ <svg width={width} height={height} style={{
+ transformOrigin: "top left",
+ transform: `translate(${left}px, ${top}px) scale(${scaleX}, ${scaleY})`,
+ mixBlendMode: this.Document.tool === InkTool.Highlighter ? "multiply" : "unset"
+ }}>
+ {points}
+ </svg>
+ );
}
} \ No newline at end of file
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 134a4ac85..9cbe6e144 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -38,7 +38,7 @@ p {
::-webkit-scrollbar {
-webkit-appearance: none;
height: 8px;
- width: 8px;
+ width: 20px;
}
::-webkit-scrollbar-thumb {
diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss
index a858a73c7..b3fff081e 100644
--- a/src/client/views/MainView.scss
+++ b/src/client/views/MainView.scss
@@ -4,8 +4,9 @@
.mainView-tabButtons {
position: relative;
- width:100%;
+ width: 100%;
}
+
// add nodes menu. Note that the + button is actually an input label, not an actual button.
.mainView-docButtons {
position: absolute;
@@ -22,21 +23,25 @@
overflow: auto;
z-index: 1;
}
+
.mainView-mainContent {
- width:100%;
- height:100%;
- position:absolute;
+ width: 100%;
+ height: 100%;
+ position: absolute;
}
-.mainView-flyoutContainer{
- display:flex;
+
+.mainView-flyoutContainer {
+ display: flex;
flex-direction: column;
position: absolute;
- width:100%;
- height:100%;
+ width: 100%;
+ height: 100%;
+
.documentView-node-topmost {
background: lightgrey;
}
}
+
.mainView-mainDiv {
width: 100%;
height: 100%;
@@ -62,8 +67,8 @@
.mainView-expandFlyoutButton {
position: absolute;
- top: 5px;
- right: 5px;
+ top: 100px;
+ right: 30px;
cursor: pointer;
}
@@ -75,6 +80,7 @@
border-radius: 5px;
position: absolute;
z-index: 1;
+ touch-action: none;
}
.mainView-workspace {
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 83dbb433b..162d0c08a 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -1,7 +1,7 @@
import { library } from '@fortawesome/fontawesome-svg-core';
import {
faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight,
- faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone
+ faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter, faMicrophone, faCompressArrowsAlt
} from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, computed, configure, observable, reaction, runInAction } from 'mobx';
@@ -36,6 +36,8 @@ import { DocumentView } from './nodes/DocumentView';
import { OverlayView } from './OverlayView';
import PDFMenu from './pdf/PDFMenu';
import { PreviewCursor } from './PreviewCursor';
+import MarqueeOptionsMenu from './collections/collectionFreeForm/MarqueeOptionsMenu';
+import InkSelectDecorations from './InkSelectDecorations';
import { Scripting } from '../util/Scripting';
import { AudioBox } from './nodes/AudioBox';
@@ -116,6 +118,7 @@ export class MainView extends React.Component {
library.add(faMusic);
library.add(faTree);
library.add(faPlay);
+ library.add(faCompressArrowsAlt);
library.add(faPause);
library.add(faClone);
library.add(faCut);
@@ -130,6 +133,7 @@ export class MainView extends React.Component {
library.add(faBolt);
library.add(faChevronRight);
library.add(faEllipsisV);
+ library.add(faMusic);
this.initEventListeners();
this.initAuthenticationRouters();
}
@@ -296,13 +300,15 @@ export class MainView extends React.Component {
}
onPointerDown = (e: React.PointerEvent) => {
- this._flyoutSizeOnDown = e.clientX;
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
- e.preventDefault();
+ if (this._flyoutTranslate) {
+ this._flyoutSizeOnDown = e.clientX;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointerup", this.onPointerUp);
+ e.stopPropagation();
+ e.preventDefault();
+ }
}
@action
@@ -421,10 +427,10 @@ export class MainView extends React.Component {
style={{ cursor: "ew-resize", left: `${(this.flyoutWidth * (this._flyoutTranslate ? 1 : 0)) - 10}px`, backgroundColor: `${StrCast(sidebar.backgroundColor, "lightGray")}` }}
onPointerDown={this.onPointerDown} onPointerOver={this.pointerOverDragger}>
<span title="library View Dragger" style={{
- width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "5vw",
- height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "30vh",
- position: "absolute",
- top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "-10vh"
+ width: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "3vw",
+ height: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "100%" : "100vh",
+ position: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "absolute" : "fixed",
+ top: (this.flyoutWidth !== 0 && this._flyoutTranslate) ? "" : "0"
}} />
</div>
<div className="mainView-libraryFlyout" style={{
@@ -503,13 +509,15 @@ export class MainView extends React.Component {
<SharingManager />
<GoogleAuthenticationManager />
<DocumentDecorations />
+ <InkSelectDecorations />
{this.mainContent}
<PreviewCursor />
<ContextMenu />
{this.docButtons}
<PDFMenu />
+ <MarqueeOptionsMenu />
<OverlayView />
</div >);
}
}
-Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); }); \ No newline at end of file
+Scripting.addGlobal(function freezeSidebar() { MainView.expandFlyout(); });
diff --git a/src/client/views/Touchable.tsx b/src/client/views/Touchable.tsx
new file mode 100644
index 000000000..26779b168
--- /dev/null
+++ b/src/client/views/Touchable.tsx
@@ -0,0 +1,94 @@
+import * as React from 'react';
+import { action } from 'mobx';
+import { InteractionUtils } from '../util/InteractionUtils';
+
+export abstract class Touchable<T = {}> extends React.Component<T> {
+ protected _touchDrag: boolean = false;
+ protected prevPoints: Map<number, React.Touch> = new Map<number, React.Touch>();
+
+ public FirstX: number = 0;
+ public FirstY: number = 0;
+ public SecondX: number = 0;
+ public SecondY: number = 0;
+
+ /**
+ * When a touch even starts, we keep track of each touch that is associated with that event
+ */
+ @action
+ protected onTouchStart = (e: React.TouchEvent): void => {
+ for (let i = 0; i < e.targetTouches.length; i++) {
+ let pt = e.targetTouches.item(i);
+ this.prevPoints.set(pt.identifier, pt);
+ }
+
+ switch (e.targetTouches.length) {
+ case 1:
+ this.handle1PointerDown();
+ break;
+ case 2:
+ this.handle2PointersDown(e);
+ }
+
+ document.removeEventListener("touchmove", this.onTouch);
+ document.addEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ document.addEventListener("touchend", this.onTouchEnd);
+ }
+
+ /**
+ * Handle touch move event
+ */
+ @action
+ protected onTouch = (e: TouchEvent): void => {
+ // if we're not actually moving a lot, don't consider it as dragging yet
+ if (!InteractionUtils.IsDragging(this.prevPoints, e.targetTouches, 5) && !this._touchDrag) return;
+ this._touchDrag = true;
+ switch (e.targetTouches.length) {
+ case 1:
+ this.handle1PointerMove(e)
+ break;
+ case 2:
+ this.handle2PointersMove(e);
+ break;
+ }
+ }
+
+ @action
+ protected onTouchEnd = (e: TouchEvent): void => {
+ this._touchDrag = false;
+ e.stopPropagation();
+
+ // remove all the touches associated with the event
+ for (let i = 0; i < e.targetTouches.length; i++) {
+ let pt = e.targetTouches.item(i);
+ if (pt) {
+ if (this.prevPoints.has(pt.identifier)) {
+ this.prevPoints.delete(pt.identifier);
+ }
+ }
+ }
+
+ if (e.targetTouches.length === 0) {
+ this.prevPoints.clear();
+ }
+ this.cleanUpInteractions();
+ }
+
+ cleanUpInteractions = (): void => {
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ }
+
+ handle1PointerMove = (e: TouchEvent): any => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ handle2PointersMove = (e: TouchEvent): any => {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ handle1PointerDown = (): any => { };
+ handle2PointersDown = (e: React.TouchEvent): any => { };
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 12f54d69d..bcdc9c97e 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,12 +1,13 @@
@import "../../views/globalCssVariables.scss";
-.lm_active .messageCounter{
- color:white;
+.lm_active .messageCounter {
+ color: white;
background: #999999;
}
+
.messageCounter {
- width:18px;
- height:20px;
+ width: 18px;
+ height: 20px;
text-align: center;
border-radius: 20px;
margin-left: 5px;
@@ -18,25 +19,33 @@
.collectiondockingview-container {
width: 100%;
- height:100%;
+ height: 100%;
border-style: solid;
border-width: $COLLECTION_BORDER_WIDTH;
position: absolute;
top: 0;
left: 0;
overflow: hidden;
+
+ .collectionDockingView-dragAsDocument {
+ touch-action: none;
+ }
+
.lm_content {
background: white;
}
+
.lm_controls>li {
opacity: 0.6;
transform: scale(1.2);
}
+
.lm_maximised .lm_controls .lm_maximise {
opacity: 1;
transform: scale(0.8);
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAkAAAAJCAYAAADgkQYQAAAAKElEQVR4nGP8////fwYCgImQAgYGBgYWKM2IR81/okwajIpgvsMbVgAwgQYRVakEKQAAAABJRU5ErkJggg==) !important;
}
+
.flexlayout__layout {
left: 0;
top: 0;
@@ -45,17 +54,21 @@
position: absolute;
overflow: hidden;
}
+
.flexlayout__splitter {
background-color: black;
}
+
.flexlayout__splitter:hover {
background-color: #333;
}
+
.flexlayout__splitter_drag {
border-radius: 5px;
background-color: #444;
z-index: 1000;
}
+
.flexlayout__outline_rect {
position: absolute;
cursor: move;
@@ -65,6 +78,7 @@
z-index: 1000;
box-sizing: border-box;
}
+
.flexlayout__outline_rect_edge {
cursor: move;
border: 2px solid #b7d1b5;
@@ -73,12 +87,14 @@
z-index: 1000;
box-sizing: border-box;
}
+
.flexlayout__edge_rect {
position: absolute;
z-index: 1000;
box-shadow: inset 0 0 5px rgba(0, 0, 0, .2);
background-color: lightgray;
}
+
.flexlayout__drag_rect {
position: absolute;
cursor: move;
@@ -97,11 +113,13 @@
padding: 10px;
word-wrap: break-word;
}
+
.flexlayout__tabset {
overflow: hidden;
background-color: #222;
box-sizing: border-box;
}
+
.flexlayout__tab {
overflow: auto;
position: absolute;
@@ -109,6 +127,7 @@
background-color: #222;
color: black;
}
+
.flexlayout__tab_button {
cursor: pointer;
padding: 2px 8px 3px 8px;
@@ -120,28 +139,35 @@
vertical-align: top;
box-sizing: border-box;
}
+
.flexlayout__tab_button--selected {
color: #ddd;
background-color: #222;
}
+
.flexlayout__tab_button--unselected {
color: gray;
}
+
.flexlayout__tab_button_leading {
display: inline-block;
}
+
.flexlayout__tab_button_content {
display: inline-block;
}
+
.flexlayout__tab_button_textbox {
float: left;
border: none;
color: lightgreen;
background-color: #222;
}
+
.flexlayout__tab_button_textbox:focus {
outline: none;
}
+
.flexlayout__tab_button_trailing {
display: inline-block;
margin-left: 5px;
@@ -149,10 +175,12 @@
width: 8px;
height: 8px;
}
+
.flexlayout__tab_button:hover .flexlayout__tab_button_trailing,
.flexlayout__tab_button--selected .flexlayout__tab_button_trailing {
background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
}
+
.flexlayout__tab_button_overflow {
float: left;
width: 20px;
@@ -165,6 +193,7 @@
font-family: Arial, sans-serif;
background: transparent url("../../../../node_modules/flexlayout-react/images/more.png") no-repeat left;
}
+
.flexlayout__tabset_header {
position: absolute;
left: 0;
@@ -175,6 +204,7 @@
/*box-shadow: inset 0px 0px 3px 0px rgba(136, 136, 136, 0.54);*/
box-sizing: border-box;
}
+
.flexlayout__tab_header_inner {
position: absolute;
left: 0;
@@ -182,6 +212,7 @@
bottom: 0;
width: 10000px;
}
+
.flexlayout__tab_header_outer {
background-color: black;
position: absolute;
@@ -191,12 +222,15 @@
/*height: 100px;*/
overflow: hidden;
}
+
.flexlayout__tabset-selected {
background-image: linear-gradient(#111, #444);
}
+
.flexlayout__tabset-maximized {
background-image: linear-gradient(#666, #333);
}
+
.flexlayout__tab_toolbar {
position: absolute;
display: flex;
@@ -206,6 +240,7 @@
bottom: 0;
right: 0;
}
+
.flexlayout__tab_toolbar_button-min {
width: 20px;
height: 20px;
@@ -213,6 +248,7 @@
outline-width: 0;
background: transparent url("../../../../node_modules/flexlayout-react/images/maximize.png") no-repeat center;
}
+
.flexlayout__tab_toolbar_button-max {
width: 20px;
height: 20px;
@@ -220,14 +256,18 @@
outline-width: 0;
background: transparent url("../../../../node_modules/flexlayout-react/images/restore.png") no-repeat center;
}
+
.flexlayout__popup_menu {}
+
.flexlayout__popup_menu_item {
padding: 2px 10px 2px 10px;
color: #ddd;
}
+
.flexlayout__popup_menu_item:hover {
background-color: #444444;
}
+
.flexlayout__popup_menu_container {
box-shadow: inset 0 0 5px rgba(0, 0, 0, .15);
border: 1px solid #555;
@@ -236,33 +276,39 @@
position: absolute;
z-index: 1000;
}
+
.flexlayout__border_top {
background-color: black;
border-bottom: 1px solid #ddd;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_bottom {
background-color: black;
border-top: 1px solid #333;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_left {
background-color: black;
border-right: 1px solid #333;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_right {
background-color: black;
border-left: 1px solid #333;
box-sizing: border-box;
overflow: hidden;
}
+
.flexlayout__border_inner_bottom {
display: flex;
}
+
.flexlayout__border_inner_left {
position: absolute;
white-space: nowrap;
@@ -270,6 +316,7 @@
transform-origin: top right;
transform: rotate(-90deg);
}
+
.flexlayout__border_inner_right {
position: absolute;
white-space: nowrap;
@@ -277,6 +324,7 @@
transform-origin: top left;
transform: rotate(90deg);
}
+
.flexlayout__border_button {
background-color: #222;
color: white;
@@ -288,29 +336,36 @@
vertical-align: top;
box-sizing: border-box;
}
+
.flexlayout__border_button--selected {
color: #ddd;
background-color: #222;
}
+
.flexlayout__border_button--unselected {
color: gray;
}
+
.flexlayout__border_button_leading {
float: left;
display: inline;
}
+
.flexlayout__border_button_content {
display: inline-block;
}
+
.flexlayout__border_button_textbox {
float: left;
border: none;
color: green;
background-color: #ddd;
}
+
.flexlayout__border_button_textbox:focus {
outline: none;
}
+
.flexlayout__border_button_trailing {
display: inline-block;
margin-left: 5px;
@@ -318,10 +373,12 @@
width: 8px;
height: 8px;
}
+
.flexlayout__border_button:hover .flexlayout__border_button_trailing,
.flexlayout__border_button--selected .flexlayout__border_button_trailing {
background: transparent url("../../../../node_modules/flexlayout-react/images/close_white.png") no-repeat center;
}
+
.flexlayout__border_toolbar_left {
position: absolute;
display: flex;
@@ -331,6 +388,7 @@
left: 0;
right: 0;
}
+
.flexlayout__border_toolbar_right {
position: absolute;
display: flex;
@@ -340,6 +398,7 @@
left: 0;
right: 0;
}
+
.flexlayout__border_toolbar_top {
position: absolute;
display: flex;
@@ -349,6 +408,7 @@
bottom: 0;
right: 0;
}
+
.flexlayout__border_toolbar_bottom {
position: absolute;
display: flex;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 42d372f4a..3ff99b9f4 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -426,15 +426,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
tab.setActive(true);
};
- ReactDOM.render(<span title="Drag as document" onPointerDown={
- e => {
- e.preventDefault();
- e.stopPropagation();
- DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
- handlers: { dragComplete: emptyFunction },
- hideSource: false
- });
- }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+ ReactDOM.render(<span title="Drag as document"
+ className="collectionDockingView-dragAsDocument"
+ onPointerDown={
+ e => {
+ e.preventDefault();
+ e.stopPropagation();
+ DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc]), e.clientX, e.clientY, {
+ handlers: { dragComplete: emptyFunction },
+ hideSource: false
+ });
+ }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
ReactDOM.render(<ButtonSelector Document={doc} Stack={stack} />, gearSpan);
// ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, data, where) => {
// where === "onRight" ? CollectionDockingView.AddRightSplit(doc, dataDoc) : CollectionDockingView.Instance.AddTab(stack, doc, dataDoc);
@@ -615,7 +617,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
}
- get layoutDoc() { return this._document && Doc.Layout(this._document);}
+ get layoutDoc() { return this._document && Doc.Layout(this._document); }
panelWidth = () => this.layoutDoc && this.layoutDoc.maxWidth ? Math.min(Math.max(NumCast(this.layoutDoc.width), NumCast(this.layoutDoc.nativeWidth)), this._panelWidth) : this._panelWidth;
panelHeight = () => this._panelHeight;
diff --git a/src/client/views/collections/CollectionStaffView.scss b/src/client/views/collections/CollectionStaffView.scss
new file mode 100644
index 000000000..493a5f670
--- /dev/null
+++ b/src/client/views/collections/CollectionStaffView.scss
@@ -0,0 +1,13 @@
+.collectionStaffView {
+ .collectionStaffView-staff {
+ width: 100%;
+ margin-top: 100px;
+ margin-bottom: 100px;
+ }
+
+ .collectionStaffView-line {
+ margin: 10px;
+ height: 2px;
+ background: black;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStaffView.tsx b/src/client/views/collections/CollectionStaffView.tsx
new file mode 100644
index 000000000..5b1224b96
--- /dev/null
+++ b/src/client/views/collections/CollectionStaffView.tsx
@@ -0,0 +1,65 @@
+import { CollectionSubView } from "./CollectionSubView";
+import { InkingCanvas } from "../InkingCanvas";
+import { Transform } from "../../util/Transform";
+import React = require("react")
+import { computed, action, IReactionDisposer, reaction, runInAction, observable } from "mobx";
+import { Doc, HeightSym } from "../../../new_fields/Doc";
+import { NumCast } from "../../../new_fields/Types";
+import "./CollectionStaffView.scss";
+import { observer } from "mobx-react";
+
+@observer
+export class CollectionStaffView extends CollectionSubView(doc => doc) {
+ private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(0, -this._mainCont.current!.scrollTop);
+ private _mainCont = React.createRef<HTMLDivElement>();
+ private _reactionDisposer: IReactionDisposer | undefined;
+ @observable private _staves = NumCast(this.props.Document.staves);
+
+ componentDidMount = () => {
+ this._reactionDisposer = reaction(
+ () => NumCast(this.props.Document.staves),
+ (staves) => runInAction(() => this._staves = staves)
+ );
+
+ this.props.Document.staves = 5;
+ }
+
+ @computed get fieldExtensionDoc() {
+ return Doc.fieldExtensionDoc(this.props.DataDoc || this.props.Document, this.props.fieldKey);
+ }
+
+ @computed get addStaffButton() {
+ return <div onPointerDown={this.addStaff}>+</div>;
+ }
+
+ @computed get staves() {
+ let staves = [];
+ for (let i = 0; i < this._staves; i++) {
+ let rows = [];
+ for (let j = 0; j < 5; j++) {
+ rows.push(<div key={`staff-${i}-${j}`} className="collectionStaffView-line"></div>)
+ }
+ staves.push(<div key={`staff-${i}`} className="collectionStaffView-staff">
+ {rows}
+ </div>);
+ }
+ return staves;
+ }
+
+ @action
+ addStaff = (e: React.PointerEvent) => {
+ this.props.Document.staves = this._staves + 1;
+ }
+
+ render() {
+ return (
+ <div className="collectionStaffView" ref={this._mainCont}>
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.fieldExtensionDoc} inkFieldKey={"ink"} >
+ {() => []}
+ </InkingCanvas>
+ {this.staves}
+ {this.addStaffButton}
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8d5694bf0..4e3c7986d 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -17,6 +17,7 @@ import { CollectionTreeView } from "./CollectionTreeView";
import { CollectionViewBaseChrome } from './CollectionViewChromes';
import { ImageUtils } from '../../util/Import & Export/ImageUtils';
import { CollectionLinearView } from '../CollectionLinearView';
+import { CollectionStaffView } from './CollectionStaffView';
import { DocumentType } from '../../documents/DocumentTypes';
import { ImageField } from '../../../new_fields/URLField';
import { DocListCast } from '../../../new_fields/Doc';
@@ -30,6 +31,7 @@ import { DocumentManager } from '../../util/DocumentManager';
import { SelectionManager } from '../../util/SelectionManager';
import './CollectionView.scss';
import { FieldViewProps, FieldView } from '../nodes/FieldView';
+import { Touchable } from '../Touchable';
library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy);
export enum CollectionViewType {
@@ -42,6 +44,7 @@ export enum CollectionViewType {
Masonry,
Pivot,
Linear,
+ Staff
}
export namespace CollectionViewType {
@@ -69,7 +72,7 @@ export interface CollectionRenderProps {
}
@observer
-export class CollectionView extends React.Component<FieldViewProps> {
+export class CollectionView extends Touchable<FieldViewProps> {
public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); }
private _reactionDisposer: IReactionDisposer | undefined;
@@ -165,6 +168,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />);
case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />);
case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />);
+ case CollectionViewType.Staff: return (<CollectionStaffView chromeCollapsed={true} key="collview" {...props} ChromeHeight={this.chromeHeight} CollectionView={this} />)
case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); }
case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); }
case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); }
@@ -205,6 +209,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
this.props.Document.autoHeight = true;
}, icon: "ellipsis-v"
});
+ subItems.push({ description: "Staff", event: () => this.props.Document.viewType = CollectionViewType.Staff, icon: "music" });
subItems.push({ description: "Masonry", event: () => this.props.Document.viewType = CollectionViewType.Masonry, icon: "columns" });
subItems.push({ description: "Pivot", event: () => this.props.Document.viewType = CollectionViewType.Pivot, icon: "columns" });
switch (this.props.Document.viewType) {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index bb1a12f88..db36c4391 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -17,6 +17,7 @@
width: 100%;
height: 100%;
transform-origin: left top;
+ touch-action: none;
}
.collectionFreeform-customText {
@@ -25,6 +26,9 @@
}
.collectionfreeformview-container {
+ // touch action none means that the browser will handle none of the touch actions. this allows us to implement our own actions.
+ touch-action: none;
+
.collectionfreeformview>.jsx-parser {
position: inherit;
height: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 0c5f4ec80..61d097c62 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -5,7 +5,7 @@ import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
-import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { InkField, PointData, InkTool } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
import { BoolCast, Cast, DateCast, NumCast, StrCast } from "../../../../new_fields/Types";
@@ -35,7 +35,12 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { InteractionUtils } from "../../../util/InteractionUtils";
+import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import PDFMenu from "../../pdf/PDFMenu";
import { documentSchema, positionSchema } from "../../../../new_fields/documentSchemas";
+import { InkingControl } from "../../InkingControl";
+import { InkingStroke, CreatePolyline } from "../../InkingStroke";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -259,6 +264,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return clusterColor;
}
+ @observable private _points: { x: number, y: number }[] = [];
+
@action
onPointerDown = (e: React.PointerEvent): void => {
if (e.nativeEvent.cancelBubble) return;
@@ -268,69 +275,205 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointerup", this.onPointerUp);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
+ this._lastX = e.pageX;
+ this._lastY = e.pageY;
+ }
+ else {
+ e.stopPropagation();
+ e.preventDefault();
+
+ if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+ let point = this.getTransform().transformPoint(e.pageX, e.pageY);
+ this._points.push({ x: point[0], y: point[1] });
+ }
+ }
}
}
+ @action
onPointerUp = (e: PointerEvent): void => {
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) return;
+
+ if (this._points.length > 1) {
+ let B = this.svgBounds;
+ let points = this._points.map(p => ({ x: p.x - B.left, y: p.y - B.top }));
+ let inkDoc = Docs.Create.InkDocument(InkingControl.Instance.selectedColor, InkingControl.Instance.selectedTool, parseInt(InkingControl.Instance.selectedWidth), points, { width: B.width, height: B.height, x: B.left, y: B.top });
+ this.addDocument(inkDoc);
+ this._points = [];
+ }
+
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ }
+
+ @action
+ pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => {
+ // I think it makes sense for the marquee menu to go away when panned. -syip2
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+
+ let x = this.Document.panX || 0;
+ let y = this.Document.panY || 0;
+ let docs = this.childLayoutPairs.map(pair => pair.layout);
+ let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
+ if (!this.isAnnotationOverlay) {
+ PDFMenu.Instance.fadeOut(true);
+ let minx = docs.length ? NumCast(docs[0].x) : 0;
+ let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
+ let miny = docs.length ? NumCast(docs[0].y) : 0;
+ let maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+ let ranges = docs.filter(doc => doc).reduce((range, doc) => {
+ let layoutDoc = Doc.Layout(doc);
+ let x = NumCast(doc.x);
+ let xe = x + NumCast(layoutDoc.width);
+ let y = NumCast(doc.y);
+ let ye = y + NumCast(layoutDoc.height);
+ return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
+ [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
+ }, [[minx, maxx], [miny, maxy]]);
+ let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField);
+ if (ink && ink.inkData) {
+ // ink.inkData.forEach((value: PointData, key: string) => {
+ // let bounds = InkingCanvas.StrokeRect(value);
+ // ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
+ // ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
+ // });
+ }
+
+ let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
+ let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
+ this.props.PanelHeight() / this.zoomScaling() * cscale);
+ if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
+ if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
+ if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
+ if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;
+ }
+ this.setPan(x - dx, y - dy);
+ this._lastX = e.clientX;
+ this._lastY = e.clientY;
}
@action
onPointerMove = (e: PointerEvent): void => {
- if (!e.cancelBubble) {
- if (this._hitCluster && this.tryDragCluster(e)) {
- 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();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- return;
+ if (InteractionUtils.IsType(e, InteractionUtils.TOUCH)) {
+ if (this.props.active()) {
+ e.stopPropagation();
}
- let x = this.Document.panX || 0;
- let y = this.Document.panY || 0;
- let docs = this.childLayoutPairs.map(pair => pair.layout);
- 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(Doc.Layout(docs[0]).width) + minx : minx;
- let miny = docs.length ? NumCast(docs[0].y) : 0;
- let maxy = docs.length ? NumCast(Doc.Layout(docs[0]).height) + miny : miny;
- let ranges = docs.filter(doc => doc).reduce((range, doc) => {
- let layoutDoc = Doc.Layout(doc);
- let x = NumCast(doc.x);
- let xe = x + NumCast(layoutDoc.width);
- let y = NumCast(doc.y);
- let ye = y + NumCast(layoutDoc.height);
- return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
- [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
- }, [[minx, maxx], [miny, maxy]]);
- let ink = this.extensionDoc && Cast(this.extensionDoc.ink, InkField);
- if (ink && ink.inkData) {
- ink.inkData.forEach((value: StrokeData, key: string) => {
- let bounds = InkingCanvas.StrokeRect(value);
- ranges[0] = [Math.min(ranges[0][0], bounds.left), Math.max(ranges[0][1], bounds.right)];
- ranges[1] = [Math.min(ranges[1][0], bounds.top), Math.max(ranges[1][1], bounds.bottom)];
- });
+ return;
+ }
+ if (!e.cancelBubble) {
+ if (InkingControl.Instance.selectedTool === InkTool.None) {
+ if (this._hitCluster && this.tryDragCluster(e)) {
+ 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();
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ return;
}
-
- let cscale = this.props.ContainingCollectionDoc ? NumCast(this.props.ContainingCollectionDoc.scale) : 1;
- let panelDim = this.props.ScreenToLocalTransform().transformDirection(this.props.PanelWidth() / this.zoomScaling() * cscale,
- this.props.PanelHeight() / this.zoomScaling() * cscale);
- if (ranges[0][0] - dx > (this.panX() + panelDim[0] / 2)) x = ranges[0][1] + panelDim[0] / 2;
- if (ranges[0][1] - dx < (this.panX() - panelDim[0] / 2)) x = ranges[0][0] - panelDim[0] / 2;
- if (ranges[1][0] - dy > (this.panY() + panelDim[1] / 2)) y = ranges[1][1] + panelDim[1] / 2;
- if (ranges[1][1] - dy < (this.panY() - panelDim[1] / 2)) y = ranges[1][0] - panelDim[1] / 2;
+ this.pan(e);
+ }
+ else if (InkingControl.Instance.selectedTool !== InkTool.Eraser && InkingControl.Instance.selectedTool !== InkTool.Scrubber) {
+ let point = this.getTransform().transformPoint(e.clientX, e.clientY);
+ this._points.push({ x: point[0], y: point[1] });
}
- this.setPan(x - dx, y - dy);
- this._lastX = e.pageX;
- this._lastY = e.pageY;
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();
}
}
+ handle1PointerMove = (e: TouchEvent) => {
+ // panning a workspace
+ if (!e.cancelBubble && this.props.active()) {
+ let pt = e.targetTouches.item(0);
+ if (pt) {
+ this.pan(pt);
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ }
+
+ handle2PointersMove = (e: TouchEvent) => {
+ // pinch zooming
+ if (!e.cancelBubble) {
+ let pt1: Touch | null = e.targetTouches.item(0);
+ let pt2: Touch | null = e.targetTouches.item(1);
+ if (!pt1 || !pt2) return;
+
+ if (this.prevPoints.size === 2) {
+ let oldPoint1 = this.prevPoints.get(pt1.identifier);
+ let oldPoint2 = this.prevPoints.get(pt2.identifier);
+ if (oldPoint1 && oldPoint2) {
+ let dir = InteractionUtils.Pinching(pt1, pt2, oldPoint1, oldPoint2);
+
+ // if zooming, zoom
+ if (dir !== 0) {
+ let d1 = Math.sqrt(Math.pow(pt1.clientX - oldPoint1.clientX, 2) + Math.pow(pt1.clientY - oldPoint1.clientY, 2));
+ let d2 = Math.sqrt(Math.pow(pt2.clientX - oldPoint2.clientX, 2) + Math.pow(pt2.clientY - oldPoint2.clientY, 2));
+ let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+
+ // calculate the raw delta value
+ let rawDelta = (dir * (d1 + d2));
+
+ // this floors and ceils the delta value to prevent jitteriness
+ let delta = Math.sign(rawDelta) * Math.min(Math.abs(rawDelta), 16);
+ this.zoom(centerX, centerY, delta);
+ this.prevPoints.set(pt1.identifier, pt1);
+ this.prevPoints.set(pt2.identifier, pt2);
+ }
+ // this is not zooming. derive some form of panning from it.
+ else {
+ // use the centerx and centery as the "new mouse position"
+ let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+ this.pan({ clientX: centerX, clientY: centerY });
+ this._lastX = centerX;
+ this._lastY = centerY;
+ }
+ }
+ }
+ }
+ e.stopPropagation();
+ e.preventDefault();
+ }
+
+ handle2PointersDown = (e: React.TouchEvent) => {
+ let pt1: React.Touch | null = e.targetTouches.item(0);
+ let pt2: React.Touch | null = e.targetTouches.item(1);
+ if (!pt1 || !pt2) return;
+
+ let centerX = Math.min(pt1.clientX, pt2.clientX) + Math.abs(pt2.clientX - pt1.clientX) / 2;
+ let centerY = Math.min(pt1.clientY, pt2.clientY) + Math.abs(pt2.clientY - pt1.clientY) / 2;
+ this._lastX = centerX;
+ this._lastY = centerY;
+ }
+
+ cleanUpInteractions = () => {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.removeEventListener("touchmove", this.onTouch);
+ document.removeEventListener("touchend", this.onTouchEnd);
+ }
+
+ @action
+ zoom = (pointX: number, pointY: number, deltaY: number): void => {
+ console.log(deltaY);
+ let deltaScale = deltaY > 0 ? (1 / 1.1) : 1.1;
+ if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
+ deltaScale = 1 / this.zoomScaling();
+ }
+ if (deltaScale < 0) deltaScale = -deltaScale;
+ let [x, y] = this.getTransform().transformPoint(pointX, pointY);
+ let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
+
+ 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);
+ }
+
@action
onPointerWheel = (e: React.WheelEvent): void => {
if (this.props.Document.lockedTransform || this.props.Document.inOverlay) return;
@@ -339,17 +482,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
else if (this.props.active()) {
e.stopPropagation();
- let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1;
- 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);
-
- 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);
+ this.zoom(e.clientX, e.clientY, e.deltaY);
}
}
@@ -669,6 +802,31 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
...this.views,
];
}
+
+ @computed get svgBounds() {
+ let xs = this._points.map(p => p.x);
+ let ys = this._points.map(p => p.y);
+ let right = Math.max(...xs);
+ let left = Math.min(...xs);
+ let bottom = Math.max(...ys);
+ let top = Math.min(...ys);
+ return { right: right, left: left, bottom: bottom, top: top, width: right - left, height: bottom - top };
+ }
+
+ @computed get currentStroke() {
+ if (this._points.length <= 1) {
+ return (null);
+ }
+
+ let B = this.svgBounds;
+
+ return (
+ <svg width={B.width} height={B.height} style={{ transform: `translate(${B.left}px, ${B.top}px)` }}>
+ {CreatePolyline(this._points, B.left, B.top)}
+ </svg>
+ );
+ }
+
render() {
// update the actual dimensions of the collection so that they can inquired (e.g., by a minimap)
this.Document.fitX = this.contentBounds && this.contentBounds.x;
@@ -680,15 +838,17 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return !this.extensionDoc ? (null) :
<div className={"collectionfreeformview-container"} ref={this.createDropTarget} onWheel={this.onPointerWheel}
style={{ pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, height: this.isAnnotationOverlay ? (this.props.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu}>
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onTouchStart={this.onTouchStart}>
<MarqueeView {...this.props} extensionDoc={this.extensionDoc} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument}
addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
{!this.extensionDoc ? (null) :
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.extensionDoc} inkFieldKey={"ink"} >
- {this.childViews}
- </InkingCanvas>}
+ // <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} AnnotationDocument={this.extensionDoc} inkFieldKey={"ink"} >
+ this.childViews()
+ // </InkingCanvas>
+ }
+ {this.currentStroke}
<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
</CollectionFreeFormViewPannableContents>
</MarqueeView>
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
new file mode 100644
index 000000000..91fcad4be
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx
@@ -0,0 +1,46 @@
+import React = require("react")
+import AntimodeMenu from "../../AntimodeMenu";
+import { observer } from "mobx-react";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { unimplementedFunction } from "../../../../Utils";
+
+@observer
+export default class MarqueeOptionsMenu extends AntimodeMenu {
+ static Instance: MarqueeOptionsMenu;
+
+ public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction;
+ public showMarquee: () => void = unimplementedFunction;
+ public hideMarquee: () => void = unimplementedFunction;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+
+ MarqueeOptionsMenu.Instance = this;
+ }
+
+ render() {
+ let buttons = [
+ <button
+ className="antimodeMenu-button"
+ title="Create a Collection"
+ onPointerDown={this.createCollection}>
+ <FontAwesomeIcon icon="object-group" size="lg" />
+ </button>,
+ <button
+ className="antimodeMenu-button"
+ title="Summarize Documents"
+ onPointerDown={this.summarize}>
+ <FontAwesomeIcon icon="compress-arrows-alt" size="lg" />
+ </button>,
+ <button
+ className="antimodeMenu-button"
+ title="Delete Documents"
+ onPointerDown={this.delete}>
+ <FontAwesomeIcon icon="trash-alt" size="lg" />
+ </button>,
+ ]
+ return this.getElement(buttons);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 44b6fe030..b5f6f095e 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../../new_fields/Doc";
-import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { InkField, PointData } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
import { listSpec } from "../../../../new_fields/Schema";
import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField";
@@ -19,6 +19,8 @@ import { CollectionViewType } from "../CollectionView";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
import "./MarqueeView.scss";
import React = require("react");
+import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
+import InkSelectDecorations from "../../InkSelectDecorations";
import { SubCollectionViewProps } from "../CollectionSubView";
interface MarqueeViewProps {
@@ -26,7 +28,7 @@ interface MarqueeViewProps {
getTransform: () => Transform;
addDocument: (doc: Doc) => boolean;
activeDocuments: () => Doc[];
- selectDocuments: (docs: Doc[]) => void;
+ selectDocuments: (docs: Doc[], ink: { Document: Doc, Ink: Map<any, any> }[]) => void;
removeDocument: (doc: Doc) => boolean;
addLiveTextDocument: (doc: Doc) => void;
isSelected: () => boolean;
@@ -51,13 +53,15 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
}
@action
- cleanupInteractions = (all: boolean = false) => {
+ cleanupInteractions = (all: boolean = false, hideMarquee: boolean = true) => {
if (all) {
document.removeEventListener("pointerup", this.onPointerUp, true);
document.removeEventListener("pointermove", this.onPointerMove, true);
}
document.removeEventListener("keydown", this.marqueeCommand, true);
- this._visible = false;
+ if (hideMarquee) {
+ this._visible = false;
+ }
}
@undoBatch
@@ -188,15 +192,33 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
@action
onPointerUp = (e: PointerEvent): void => {
- if (!this.props.active()) this.props.selectDocuments([this.props.Document]);
+ if (!this.props.active()) this.props.selectDocuments([this.props.Document], []);
if (this._visible) {
let mselect = this.marqueeSelect();
if (!e.shiftKey) {
SelectionManager.DeselectAll(mselect.length ? undefined : this.props.Document);
}
- this.props.selectDocuments(mselect.length ? mselect : [this.props.Document]);
+ // let inkselect = this.ink ? this.marqueeInkSelect(this.ink.inkData) : new Map();
+ // let inks = inkselect.size ? [{ Document: this.inkDoc, Ink: inkselect }] : [];
+ let docs = mselect.length ? mselect : [this.props.Document];
+ this.props.selectDocuments(docs, []);
}
- this.cleanupInteractions(true);
+ if (!this._commandExecuted && (Math.abs(this.Bounds.height * this.Bounds.width) > 100)) {
+ MarqueeOptionsMenu.Instance.createCollection = this.collection;
+ MarqueeOptionsMenu.Instance.delete = this.delete;
+ MarqueeOptionsMenu.Instance.summarize = this.summary;
+ MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee;
+ MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee;
+ MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY);
+ }
+ this.cleanupInteractions(true, this._commandExecuted);
+
+ let hideMarquee = () => {
+ this.hideMarquee();
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ document.removeEventListener("pointerdown", hideMarquee);
+ };
+ document.addEventListener("pointerdown", hideMarquee);
if (e.altKey) {
e.preventDefault();
@@ -246,6 +268,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
}
+ get inkDoc() {
+ return this.props.extensionDoc;
+ }
+
get ink() { // ink will be stored on the extension doc for the field (fieldKey) where the container's data is stored.
return this.props.extensionDoc && Cast(this.props.extensionDoc.ink, InkField);
}
@@ -254,6 +280,120 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this.props.extensionDoc && (this.props.extensionDoc.ink = value);
}
+ @action
+ showMarquee = () => {
+ this._visible = true;
+ }
+
+ @action
+ hideMarquee = () => {
+ this._visible = false;
+ }
+
+ @action
+ delete = () => {
+ this.marqueeSelect(false).map(d => this.props.removeDocument(d));
+ if (this.ink) {
+ // this.marqueeInkDelete(this.ink.inkData);
+ }
+ SelectionManager.DeselectAll();
+ this.cleanupInteractions(false);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ }
+
+ getCollection = (selected: Doc[]) => {
+ let bounds = this.Bounds;
+ let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
+ "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
+ let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string"));
+ if (!colorPalette) this.props.Document.colorPalette = new List<string>(defaultPalette);
+ let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]);
+ let usedPaletted = new Map<string, number>();
+ [...this.props.activeDocuments(), this.props.Document].map(child => {
+ let bg = StrCast(Doc.Layout(child).backgroundColor);
+ if (palette.indexOf(bg) !== -1) {
+ palette.splice(palette.indexOf(bg), 1);
+ if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
+ else usedPaletted.set(bg, 1);
+ }
+ });
+ usedPaletted.delete("#f1efeb");
+ usedPaletted.delete("white");
+ usedPaletted.delete("rgba(255,255,255,1)");
+ let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
+ let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
+ let inkData = this.ink ? this.ink.inkData : undefined;
+ let newCollection = Docs.Create.FreeformDocument(selected, {
+ x: bounds.left,
+ y: bounds.top,
+ panX: 0,
+ panY: 0,
+ backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
+ defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
+ width: bounds.width,
+ height: bounds.height,
+ title: "a nested collection",
+ });
+ let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
+ // dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
+ // this.marqueeInkDelete(inkData);
+ this.hideMarquee();
+ return newCollection;
+ }
+
+ @action
+ collection = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect(false);
+ if (e instanceof KeyboardEvent ? e.key === "c" : true) {
+ 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.displayTimecode = undefined;
+ return d;
+ });
+ }
+ let newCollection = this.getCollection(selected);
+ this.props.addDocument(newCollection);
+ this.props.selectDocuments([newCollection], []);
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ this.hideMarquee();
+ }
+
+ @action
+ summary = (e: KeyboardEvent | React.PointerEvent | undefined) => {
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect(false);
+ let newCollection = this.getCollection(selected);
+
+ 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;
+ });
+ newCollection.chromeStatus = "disabled";
+ let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
+ Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
+ newCollection.x = bounds.left + bounds.width;
+ Doc.GetProto(newCollection).summaryDoc = summary;
+ Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
+ if (e instanceof KeyboardEvent ? e.key === "s" : true) { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
+ let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
+ container.viewType = CollectionViewType.Stacking;
+ container.autoHeight = true;
+ Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight"
+ this.props.addLiveTextDocument(container);
+ } else if (e instanceof KeyboardEvent ? e.key === "S" : false) { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
+ Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight"
+ this.props.addLiveTextDocument(summary);
+ }
+ MarqueeOptionsMenu.Instance.fadeOut(true);
+ }
+
@undoBatch
@action
marqueeCommand = async (e: KeyboardEvent) => {
@@ -264,12 +404,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._commandExecuted = true;
e.stopPropagation();
(e as any).propagationIsStopped = true;
- this.marqueeSelect(false).map(d => this.props.removeDocument(d));
- if (this.ink) {
- this.marqueeInkDelete(this.ink.inkData);
- }
- SelectionManager.DeselectAll();
- this.cleanupInteractions(false);
+ this.delete();
e.stopPropagation();
}
if (e.key === "c" || e.key === "s" || e.key === "S") {
@@ -277,117 +412,55 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
e.stopPropagation();
e.preventDefault();
(e as any).propagationIsStopped = true;
- let bounds = this.Bounds;
- let selected = this.marqueeSelect(false);
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.displayTimecode = undefined;
- return d;
- });
+ this.collection(e);
}
- let defaultPalette = ["rgb(114,229,239)", "rgb(255,246,209)", "rgb(255,188,156)", "rgb(247,220,96)", "rgb(122,176,238)",
- "rgb(209,150,226)", "rgb(127,235,144)", "rgb(252,188,189)", "rgb(247,175,81)",];
- let colorPalette = Cast(this.props.Document.colorPalette, listSpec("string"));
- if (!colorPalette) this.props.Document.colorPalette = new List<string>(defaultPalette);
- let palette = Array.from(Cast(this.props.Document.colorPalette, listSpec("string")) as string[]);
- let usedPaletted = new Map<string, number>();
- [...this.props.activeDocuments(), this.props.Document].map(child => {
- let bg = StrCast(Doc.Layout(child).backgroundColor);
- if (palette.indexOf(bg) !== -1) {
- palette.splice(palette.indexOf(bg), 1);
- if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1);
- else usedPaletted.set(bg, 1);
- }
- });
- usedPaletted.delete("#f1efeb");
- usedPaletted.delete("white");
- usedPaletted.delete("rgba(255,255,255,1)");
- let usedSequnce = Array.from(usedPaletted.keys()).sort((a, b) => usedPaletted.get(a)! < usedPaletted.get(b)! ? -1 : usedPaletted.get(a)! > usedPaletted.get(b)! ? 1 : 0);
- let chosenColor = (usedPaletted.size === 0) ? "white" : palette.length ? palette[0] : usedSequnce[0];
- let inkData = this.ink ? this.ink.inkData : undefined;
- let newCollection = Docs.Create.FreeformDocument(selected, {
- x: bounds.left,
- y: bounds.top,
- panX: 0,
- panY: 0,
- backgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
- defaultBackgroundColor: this.props.isAnnotationOverlay ? undefined : chosenColor,
- width: bounds.width,
- height: bounds.height,
- title: "a nested collection",
- });
- let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data");
- dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
- this.marqueeInkDelete(inkData);
if (e.key === "s" || e.key === "S") {
- 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;
- });
- newCollection.chromeStatus = "disabled";
- let summary = Docs.Create.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, autoHeight: true, backgroundColor: "#e2ad32" /* yellow */, title: "-summary-" });
- Doc.GetProto(summary).summarizedDocs = new List<Doc>([newCollection]);
- newCollection.x = bounds.left + bounds.width;
- Doc.GetProto(newCollection).summaryDoc = summary;
- Doc.GetProto(newCollection).title = ComputedField.MakeFunction(`summaryTitle(this);`);
- if (e.key === "s") { // summary is wrapped in an expand/collapse container that also contains the summarized documents in a free form view.
- let container = Docs.Create.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, chromeStatus: "disabled", title: "-summary-" });
- container.viewType = CollectionViewType.Stacking;
- container.autoHeight = true;
- Doc.GetProto(summary).maximizeLocation = "inPlace"; // or "onRight"
- this.props.addLiveTextDocument(container);
- } else if (e.key === "S") { // the summary stands alone, but is linked to a collection of the summarized documents - set the OnCLick behavior to link follow to access them
- Doc.GetProto(summary).maximizeLocation = "inTab"; // or "inPlace", or "onRight"
- this.props.addLiveTextDocument(summary);
- }
- }
- else {
- this.props.addDocument(newCollection);
- this.props.selectDocuments([newCollection]);
+ this.summary(e);
}
this.cleanupInteractions(false);
}
}
- @action
- marqueeInkSelect(ink: Map<any, any>) {
- let idata = new Map();
- let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
- let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
- ink.forEach((value: StrokeData, key: string, map: any) => {
- if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
- idata.set(key,
- {
- pathData: value.pathData.map(val => ({ x: val.x + centerShiftX, y: val.y + centerShiftY })),
- color: value.color,
- width: value.width,
- tool: value.tool,
- page: -1
- });
- }
- });
- return idata;
- }
+ // @action
+ // marqueeInkSelect(ink: Map<any, any>) {
+ // let idata = new Map();
+ // let centerShiftX = 0 - (this.Bounds.left + this.Bounds.width / 2); // moves each point by the offset that shifts the selection's center to the origin.
+ // let centerShiftY = 0 - (this.Bounds.top + this.Bounds.height / 2);
+ // ink.forEach((value: PointData, key: string, map: any) => {
+ // if (InkingCanvas.IntersectStrokeRect(value, this.Bounds)) {
+ // // let transform = this.props.container.props.ScreenToLocalTransform().scale(this.props.container.props.ContentScaling());
+ // idata.set(key,
+ // {
+ // pathData: value.pathData.map(val => {
+ // let tVal = this.props.getTransform().inverse().transformPoint(val.x, val.y);
+ // return { x: tVal[0], y: tVal[1] };
+ // // return { x: val.x + centerShiftX, y: val.y + centerShiftY }
+ // }),
+ // color: value.color,
+ // width: value.width,
+ // tool: value.tool,
+ // page: -1
+ // });
+ // }
+ // });
+ // // InkSelectDecorations.Instance.SetSelected(idata);
+ // return idata;
+ // }
- @action
- marqueeInkDelete(ink?: Map<any, any>) {
- // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
- // ink.forEach((value: StrokeData, key: string, map: any) =>
- // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
+ // @action
+ // marqueeInkDelete(ink?: Map<any, any>) {
+ // // bcz: this appears to work but when you restart all the deleted strokes come back -- InkField isn't observing its changes so they aren't written to the DB.
+ // // ink.forEach((value: StrokeData, key: string, map: any) =>
+ // // InkingCanvas.IntersectStrokeRect(value, this.Bounds) && ink.delete(key));
- if (ink) {
- let idata = new Map();
- ink.forEach((value: StrokeData, key: string, map: any) =>
- !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
- this.ink = new InkField(idata);
- }
- }
+ // if (ink) {
+ // let idata = new Map();
+ // ink.forEach((value: PointData, key: string, map: any) =>
+ // !InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
+ // this.ink = new InkField(idata);
+ // }
+ // }
marqueeSelect(selectBackgrounds: boolean = true) {
let selRect = this.Bounds;
@@ -438,8 +511,13 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
get marqueeDiv() {
let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0];
let v = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ /**
+ * @RE - The commented out span below
+ * This contains the "C for collection, ..." text on marquees.
+ * Commented out by syip2 when the marquee menu was added.
+ */
return <div className="marquee" style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, width: `${Math.abs(v[0])}`, height: `${Math.abs(v[1])}`, zIndex: 2000 }} >
- <span className="marquee-legend" />
+ {/* <span className="marquee-legend" /> */}
</div>;
}
diff --git a/src/client/views/nodes/ButtonBox.scss b/src/client/views/nodes/ButtonBox.scss
index e8a3d1479..7c3825978 100644
--- a/src/client/views/nodes/ButtonBox.scss
+++ b/src/client/views/nodes/ButtonBox.scss
@@ -3,7 +3,7 @@
height: 100%;
pointer-events: all;
border-radius: inherit;
- display:flex;
+ display: flex;
flex-direction: column;
}
@@ -15,19 +15,22 @@
display: table;
overflow: hidden;
text-overflow: ellipsis;
+ letter-spacing: 2px;
+ text-transform: uppercase;
}
+
.buttonBox-mainButtonCenter {
height: 100%;
- display:table-cell;
+ display: table-cell;
vertical-align: middle;
}
.buttonBox-params {
- display:flex;
- flex-direction: row;
+ display: flex;
+ flex-direction: row;
}
.buttonBox-missingParam {
- width:100%;
+ width: 100%;
background: lightgray;
} \ No newline at end of file
diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx
index beb2b30fd..659ba154a 100644
--- a/src/client/views/nodes/ButtonBox.tsx
+++ b/src/client/views/nodes/ButtonBox.tsx
@@ -79,7 +79,7 @@ export class ButtonBox extends DocComponent<FieldViewProps, ButtonDocument>(Butt
return (
<div className="buttonBox-outerDiv" ref={this.createDropTarget} onContextMenu={this.specificContextMenu}
style={{ boxShadow: this.Document.opacity === 0 ? undefined : StrCast(this.Document.boxShadow, "") }}>
- <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black" }} >
+ <div className="buttonBox-mainButton" style={{ background: this.Document.backgroundColor || "", color: this.Document.color || "black", fontSize: this.Document.fontSize }} >
<div className="buttonBox-mainButtonCenter">
{(this.Document.text || this.Document.title)}
</div>
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.scss b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
index af9232c2f..da287649e 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.scss
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.scss
@@ -2,6 +2,7 @@
transform-origin: left top;
position: absolute;
background-color: transparent;
- top:0;
- left:0;
+ touch-action: manipulation;
+ top: 0;
+ left: 0;
} \ No newline at end of file
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 779d25cdd..12ae5b6e5 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -30,6 +30,7 @@ import { DocuLinkBox } from "./DocuLinkBox";
import { PresElementBox } from "../presentationview/PresElementBox";
import { VideoBox } from "./VideoBox";
import { WebBox } from "./WebBox";
+import { InkingStroke } from "../InkingStroke";
import React = require("react");
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -97,7 +98,8 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
components={{
FormattedTextBox, ImageBox, IconBox, DirectoryImportBox, FontIconBox: FontIconBox, ButtonBox, FieldView,
CollectionFreeFormView, CollectionDockingView, CollectionSchemaView, CollectionView, WebBox, KeyValueBox,
- PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox, ColorBox, DocuLinkBox
+ PDFBox, VideoBox, AudioBox, HistogramBox, PresBox, YoutubeBox, LinkFollowBox, PresElementBox, QueryBox,
+ ColorBox, DocuLinkBox, InkingStroke
}}
bindings={this.CreateBindings()}
jsx={this.layout}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 7132f181e..5b3eea280 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -208,6 +208,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
document.addEventListener("pointerup", this.onPointerUp);
if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); }
}
+
onPointerMove = (e: PointerEvent): void => {
if ((e as any).formattedHandled) { e.stopPropagation(); return; }
if (e.cancelBubble && this.active) {
@@ -225,6 +226,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
e.preventDefault();
}
}
+
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -577,7 +579,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
linkEndpoint = (linkDoc: Doc) => Doc.LinkEndpoint(linkDoc, this.props.Document);
- // used to decide whether a link document should be created or not.
+ // used to decide whether a link document should be created or not.
// if it's a tempoarl link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
// would be good to generalize this some way.
isNonTemporalLink = (linkDoc: Doc) => {
diff --git a/src/client/views/pdf/PDFMenu.scss b/src/client/views/pdf/PDFMenu.scss
index 44e075153..3c08ba80d 100644
--- a/src/client/views/pdf/PDFMenu.scss
+++ b/src/client/views/pdf/PDFMenu.scss
@@ -1,36 +1,6 @@
-.pdfMenu-cont {
- position: absolute;
- z-index: 10000;
- height: 35px;
- background: #323232;
- box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25);
- border-radius: 0px 6px 6px 6px;
- overflow: hidden;
- display: flex;
-
- .pdfMenu-button {
- background-color: transparent;
- width: 35px;
- height: 35px;
- }
-
- .pdfMenu-button:hover {
- background-color: #d4d4d4;
- }
-
- .pdfMenu-dragger {
- height: 100%;
- transition: width .2s;
- background-image: url("https://logodix.com/logo/1020374.png");
- background-size: 90% 100%;
- background-repeat: no-repeat;
- background-position: left center;
- }
-
- .pdfMenu-addTag {
- display: grid;
- width: 200px;
- padding: 5px;
- grid-template-columns: 90px 20px 90px;
- }
+.pdfMenu-addTag {
+ display: grid;
+ width: 200px;
+ padding: 5px;
+ grid-template-columns: 90px 20px 90px;
} \ No newline at end of file
diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx
index 517a99a68..c64741769 100644
--- a/src/client/views/pdf/PDFMenu.tsx
+++ b/src/client/views/pdf/PDFMenu.tsx
@@ -3,39 +3,30 @@ import "./PDFMenu.scss";
import { observable, action, } from "mobx";
import { observer } from "mobx-react";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { emptyFunction, returnFalse } from "../../../Utils";
+import { unimplementedFunction, returnFalse } from "../../../Utils";
+import AntimodeMenu from "../AntimodeMenu";
import { Doc, Opt } from "../../../new_fields/Doc";
@observer
-export default class PDFMenu extends React.Component {
+export default class PDFMenu extends AntimodeMenu {
static Instance: PDFMenu;
- private _offsetY: number = 0;
- private _offsetX: number = 0;
- private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _commentCont = React.createRef<HTMLButtonElement>();
private _snippetButton: React.RefObject<HTMLButtonElement> = React.createRef();
- private _dragging: boolean = false;
- @observable private _top: number = -300;
- @observable private _left: number = -300;
- @observable private _opacity: number = 1;
- @observable private _transition: string = "opacity 0.5s";
- @observable private _transitionDelay: string = "";
@observable private _keyValue: string = "";
@observable private _valueValue: string = "";
@observable private _added: boolean = false;
@observable public Highlighting: boolean = false;
@observable public Status: "pdf" | "annotation" | "snippet" | "" = "";
- @observable public Pinned: boolean = false;
- public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction;
+ public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
- public Delete: () => void = emptyFunction;
- public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction;
+ public Delete: () => void = unimplementedFunction;
+ public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = unimplementedFunction;
public AddTag: (key: string, value: string) => boolean = returnFalse;
- public PinToPres: () => void = emptyFunction;
+ public PinToPres: () => void = unimplementedFunction;
public Marquee: { left: number; top: number; width: number; height: number; } | undefined;
constructor(props: Readonly<{}>) {
@@ -73,86 +64,11 @@ export default class PDFMenu extends React.Component {
}
@action
- jumpTo = (x: number, y: number, forceJump: boolean = false) => {
- if (!this.Pinned || forceJump) {
- this._transition = this._transitionDelay = "";
- this._opacity = 1;
- this._left = x;
- this._top = y;
- }
- }
-
- @action
- fadeOut = (forceOut: boolean) => {
- if (!this.Pinned) {
- if (this._opacity === 0.2) {
- this._transition = "opacity 0.1s";
- this._transitionDelay = "";
- this._opacity = 0;
- this._left = this._top = -300;
- }
-
- if (forceOut) {
- this._transition = "";
- this._transitionDelay = "";
- this._opacity = 0;
- this._left = this._top = -300;
- }
- }
- }
-
- @action
- pointerLeave = (e: React.PointerEvent) => {
- if (!this.Pinned) {
- this._transition = "opacity 0.5s";
- this._transitionDelay = "1s";
- this._opacity = 0.2;
- setTimeout(() => this.fadeOut(false), 3000);
- }
- }
-
- @action
- pointerEntered = (e: React.PointerEvent) => {
- this._transition = "opacity 0.1s";
- this._transitionDelay = "";
- this._opacity = 1;
- }
-
- @action
togglePin = (e: React.MouseEvent) => {
this.Pinned = !this.Pinned;
!this.Pinned && (this.Highlighting = false);
}
- dragStart = (e: React.PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.addEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
- document.addEventListener("pointerup", this.dragEnd);
-
- this._offsetX = this._mainCont.current!.getBoundingClientRect().width - e.nativeEvent.offsetX;
- this._offsetY = e.nativeEvent.offsetY;
-
- e.stopPropagation();
- e.preventDefault();
- }
-
- @action
- dragging = (e: PointerEvent) => {
- this._left = e.pageX - this._offsetX;
- this._top = e.pageY - this._offsetY;
-
- e.stopPropagation();
- e.preventDefault();
- }
-
- dragEnd = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.dragging);
- document.removeEventListener("pointerup", this.dragEnd);
- e.stopPropagation();
- e.preventDefault();
- }
-
@action
highlightClicked = (e: React.MouseEvent) => {
if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { // yellowish highlight color for a marker type highlight
@@ -164,11 +80,6 @@ export default class PDFMenu extends React.Component {
this.Delete();
}
- handleContextMenu = (e: React.MouseEvent) => {
- e.stopPropagation();
- e.preventDefault();
- }
-
snippetStart = (e: React.PointerEvent) => {
document.removeEventListener("pointermove", this.snippetDrag);
document.addEventListener("pointermove", this.snippetDrag);
@@ -219,33 +130,27 @@ export default class PDFMenu extends React.Component {
render() {
let buttons = this.Status === "pdf" || this.Status === "snippet" ?
[
- <button key="1" className="pdfMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
+ <button key="1" className="antimodeMenu-button" title="Click to Highlight" onClick={this.highlightClicked} style={this.Highlighting ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="highlighter" size="lg" style={{ transition: "transform 0.1s", transform: this.Highlighting ? "" : "rotate(-45deg)" }} /></button>,
- <button key="2" className="pdfMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
+ <button key="2" className="antimodeMenu-button" title="Drag to Annotate" ref={this._commentCont} onPointerDown={this.pointerDown}>
<FontAwesomeIcon icon="comment-alt" size="lg" /></button>,
- <button key="3" className="pdfMenu-button" title="Drag to Snippetize Selection" style={{ display: this.Status === "snippet" ? "" : "none" }} onPointerDown={this.snippetStart} ref={this._snippetButton}>
+ <button key="3" className="antimodeMenu-button" title="Drag to Snippetize Selection" style={{ display: this.Status === "snippet" ? "" : "none" }} onPointerDown={this.snippetStart} ref={this._snippetButton}>
<FontAwesomeIcon icon="cut" size="lg" /></button>,
- <button key="4" className="pdfMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
+ <button key="4" className="antimodeMenu-button" title="Pin Menu" onClick={this.togglePin} style={this.Pinned ? { backgroundColor: "#121212" } : {}}>
<FontAwesomeIcon icon="thumbtack" size="lg" style={{ transition: "transform 0.1s", transform: this.Pinned ? "rotate(45deg)" : "" }} /> </button>
] : [
- <button key="5" className="pdfMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
+ <button key="5" className="antimodeMenu-button" title="Delete Anchor" onPointerDown={this.deleteClicked}>
<FontAwesomeIcon icon="trash-alt" size="lg" /></button>,
- <button key="6" className="pdfMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}>
+ <button key="6" className="antimodeMenu-button" title="Pin to Presentation" onPointerDown={this.PinToPres}>
<FontAwesomeIcon icon="map-pin" size="lg" /></button>,
<div key="7" className="pdfMenu-addTag" >
<input onChange={this.keyChanged} placeholder="Key" style={{ gridColumn: 1 }} />
<input onChange={this.valueChanged} placeholder="Value" style={{ gridColumn: 3 }} />
</div>,
- <button key="8" className="pdfMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
+ <button key="8" className="antimodeMenu-button" title={`Add tag: ${this._keyValue} with value: ${this._valueValue}`} onPointerDown={this.addTag}>
<FontAwesomeIcon style={{ transition: "all .2s" }} color={this._added ? "#42f560" : "white"} icon="check" size="lg" /></button>,
];
- return (
- <div className="pdfMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu}
- style={{ left: this._left, top: this._top, opacity: this._opacity, transition: this._transition, transitionDelay: this._transitionDelay }}>
- {buttons}
- <div className="pdfMenu-dragger" onPointerDown={this.dragStart} style={{ width: this.Pinned ? "20px" : "0px" }} />
- </div >
- );
+ return this.getElement(buttons);
}
} \ No newline at end of file
diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts
index d94834e91..2d8bb582a 100644
--- a/src/new_fields/InkField.ts
+++ b/src/new_fields/InkField.ts
@@ -12,16 +12,12 @@ export enum InkTool {
Scrubber
}
-export interface StrokeData {
- pathData: Array<{ x: number, y: number }>;
- color: string;
- width: string;
- tool: InkTool;
- displayTimecode: number;
- creationTime: number;
+export interface PointData {
+ x: number;
+ y: number;
}
-export type InkData = Map<string, StrokeData>;
+export type InkData = Array<PointData>;
const pointSchema = createSimpleSchema({
x: true, y: true
@@ -34,16 +30,16 @@ const strokeDataSchema = createSimpleSchema({
@Deserializable("ink")
export class InkField extends ObjectField {
- @serializable(map(object(strokeDataSchema)))
+ @serializable(list(object(strokeDataSchema)))
readonly inkData: InkData;
- constructor(data?: InkData) {
+ constructor(data: InkData) {
super();
- this.inkData = data || new Map;
+ this.inkData = data;
}
[Copy]() {
- return new InkField(DeepCopy(this.inkData));
+ return new InkField(this.inkData);
}
[ToScriptString]() {
diff --git a/src/new_fields/documentSchemas.ts b/src/new_fields/documentSchemas.ts
index fa47374f1..fb650eefd 100644
--- a/src/new_fields/documentSchemas.ts
+++ b/src/new_fields/documentSchemas.ts
@@ -44,6 +44,7 @@ export const documentSchema = createSchema({
isAnimating: "boolean", // whether the document is in the midst of animating between two layouts (used by icons to de/iconify documents).
animateToDimensions: listSpec("number"), // layout information about the target rectangle a document is animating towards
scrollToLinkID: "string", // id of link being traversed. allows this doc to scroll/highlight/etc its link anchor. scrollToLinkID should be set to undefined by this doc after it sets up its scroll,etc.
+ strokeWidth: "number",
});
export const positionSchema = createSchema({
diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts
index 466e2d707..a1f1294e6 100644
--- a/src/server/authentication/models/current_user_utils.ts
+++ b/src/server/authentication/models/current_user_utils.ts
@@ -82,7 +82,7 @@ export class CurrentUserUtils {
});
return Docs.Create.ButtonDocument({
- width: 35, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Tools", targetContainer: sidebarContainer,
+ width: 35, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Tools", fontSize: 10, targetContainer: sidebarContainer,
sourcePanel: Docs.Create.StackingDocument([dragCreators, color], {
width: 500, height: 800, lockedPosition: true, chromeStatus: "disabled", title: "tools stack"
}),
@@ -107,19 +107,19 @@ export class CurrentUserUtils {
});
return Docs.Create.ButtonDocument({
- width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library",
+ width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Library", fontSize: 10,
sourcePanel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], {
title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true
}),
targetContainer: sidebarContainer,
- onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel")
+ onClick: ScriptField.MakeScript("this.targetContainer.proto = this.sourcePanel;")
});
}
// setup the Search button which will display the search panel.
static setupSearchPanel(sidebarContainer: Doc) {
return Docs.Create.ButtonDocument({
- width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Search",
+ width: 50, height: 35, backgroundColor: "#222222", color: "lightgrey", title: "Search", fontSize: 10,
sourcePanel: Docs.Create.QueryDocument({
title: "search stack", ignoreClick: true
}),