diff options
Diffstat (limited to 'src')
20 files changed, 754 insertions, 176 deletions
diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index 8ded43468..dec8724c6 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -1,6 +1,6 @@ import * as io from 'socket.io-client'; import { MessageStore, YoutubeQueryTypes, GestureContent, MobileInkOverlayContent, UpdateMobileInkOverlayPositionContent, MobileDocumentUploadContent } from "./../server/Message"; -import { Opt, Doc, fetchProto, FieldsSym } from '../fields/Doc'; +import { Opt, Doc, fetchProto, FieldsSym, UpdatingFromServer } from '../fields/Doc'; import { Utils, emptyFunction } from '../Utils'; import { SerializationHelper } from './util/SerializationHelper'; import { RefField } from '../fields/RefField'; @@ -228,6 +228,7 @@ export namespace DocServer { // deserialize const field = await SerializationHelper.Deserialize(fieldJson); if (force && field instanceof Doc && cached instanceof Doc) { + cached[UpdatingFromServer] = true; Array.from(Object.keys(field)).forEach(key => { const fieldval = field[key]; if (fieldval instanceof ObjectField) { @@ -235,6 +236,8 @@ export namespace DocServer { } cached[key] = field[key]; }); + cached[UpdatingFromServer] = false; + return cached; } else if (field !== undefined) { _cache[id] = field; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1a5d4f3cb..f46b38883 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -726,11 +726,11 @@ export namespace Docs { } export function FreeformDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Freeform }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Freeform }, id); } export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", hideFilterView: true, forceActive: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { @@ -754,11 +754,11 @@ export namespace Docs { } export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Tree }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Tree }, id); } export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Stacking }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Stacking }, id); } export function MulticolumnDocument(documents: Array<Doc>, options: DocumentOptions) { @@ -770,7 +770,7 @@ export namespace Docs { export function MasonryDocument(documents: Array<Doc>, options: DocumentOptions) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Masonry }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Masonry }); } export function LabelDocument(options?: DocumentOptions) { diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 8b3614ea7..04a750f93 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -99,6 +99,15 @@ export namespace InteractionUtils { if (shape) { //if any of the shape are true pts = makePolygon(shape, points); } + else if (points.length >= 5 && points[3].X === points[4].X) { + for (var i = 0; i < points.length - 3; i += 4) { + const array = [[points[i].X, points[i].Y], [points[i + 1].X, points[i + 1].Y], [points[i + 2].X, points[i + 2].Y], [points[i + 3].X, points[i + 3].Y]]; + for (var t = 0; t < 1; t += 0.01) { + const point = beziercurve(t, array); + pts.push({ X: point[0], Y: point[1] }); + } + } + } else if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y === points[0].Y) { //pointer is up (first and last points are the same) const newPoints = points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); @@ -118,6 +127,12 @@ export namespace InteractionUtils { pts.pop(); } } + if (isNaN(scalex)) { + scalex = 1; + } + if (isNaN(scaley)) { + scaley = 1; + } const strpts = pts.reduce((acc: string, pt: { X: number, Y: number }) => acc + `${(pt.X - left - width / 2) * scalex + width / 2}, ${(pt.Y - top - width / 2) * scaley + width / 2} `, ""); @@ -136,7 +151,6 @@ export namespace InteractionUtils { <polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} /> </marker>} </defs>} - <polyline points={strpts} style={{ @@ -157,17 +171,6 @@ export namespace InteractionUtils { </svg>); } - // export function makeArrow() { - // return ( - // InkOptionsMenu.Instance.getColors().map(color => { - // const id1 = "arrowStartTest" + color; - // <marker id={id1} orient="auto" overflow="visible" refX="0" refY="1" markerWidth="10" markerHeight="7"> - // <polygon points="0 0, 3 1, 0 2" fill={"#" + color} /> - // </marker>; - // }) - // ); - // } - export function makePolygon(shape: string, points: { X: number, Y: number }[]) { if (points.length > 1 && points[points.length - 1].X === points[0].X && points[points.length - 1].Y + 1 === points[0].Y) { //pointer is up (first and last points are the same) @@ -217,10 +220,28 @@ export namespace InteractionUtils { points.push({ X: left, Y: top }); return points; case "triangle": + // points.push({ X: left, Y: bottom }); + // points.push({ X: right, Y: bottom }); + // points.push({ X: (right + left) / 2, Y: top }); + // points.push({ X: left, Y: bottom }); + + points.push({ X: left, Y: bottom }); points.push({ X: left, Y: bottom }); + points.push({ X: right, Y: bottom }); + points.push({ X: right, Y: bottom }); + points.push({ X: right, Y: bottom }); + points.push({ X: right, Y: bottom }); + + points.push({ X: (right + left) / 2, Y: top }); + points.push({ X: (right + left) / 2, Y: top }); points.push({ X: (right + left) / 2, Y: top }); + points.push({ X: (right + left) / 2, Y: top }); + + points.push({ X: left, Y: bottom }); points.push({ X: left, Y: bottom }); + + return points; case "circle": const centerX = (right + left) / 2; @@ -256,6 +277,7 @@ export namespace InteractionUtils { // points.push({ X: x2, Y: y2 }); // return points; case "line": + points.push({ X: left, Y: top }); points.push({ X: right, Y: bottom }); return points; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 85dd40173..f7bd6b358 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -21,7 +21,7 @@ import e = require('express'); import { CollectionDockingView } from './collections/CollectionDockingView'; import { SnappingManager } from '../util/SnappingManager'; import { HtmlField } from '../../fields/HtmlField'; -import { InkData, InkField, InkTool } from "../../fields/InkField"; +import { InkField } from "../../fields/InkField"; import { Tooltip } from '@material-ui/core'; library.add(faCaretUp); @@ -60,6 +60,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> private _prevX = 0; private _prevY = 0; private _centerPoints: { X: number, Y: number }[] = []; + private _inkDocs: { x: number, y: number, width: number, height: number }[] = []; @observable private _accumulatedTitle = ""; @observable private _titleControlString: string = "#title"; @@ -310,8 +311,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> const right = Math.max(...xs); const bottom = Math.max(...ys); - doc._height = (bottom - top) * element.props.ScreenToLocalTransform().Scale; - doc._width = (right - left) * element.props.ScreenToLocalTransform().Scale; + // doc._height = (bottom - top) * element.props.ScreenToLocalTransform().Scale; + // doc._width = (right - left) * element.props.ScreenToLocalTransform().Scale; + doc._height = (bottom - top); + doc._width = (right - left); } index++; @@ -330,6 +333,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> _dragHeights = new Map<Doc, number>(); @action onPointerDown = (e: React.PointerEvent): void => { + + this._inkDocs = []; + SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => { + const doc = Document(element.rootDoc); + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height) { + this._inkDocs.push({ x: doc.x, y: doc.y, width: doc._width, height: doc._height }); + } + + })); + setupMoveUpEvents(this, e, this.onPointerMove, this.onPointerUp, (e) => { }); if (e.button === 0) { this._resizeHdlId = e.currentTarget.id; @@ -506,6 +519,27 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> (e.button === 0) && this._resizeUndo?.end(); this._resizeUndo = undefined; SnappingManager.clearSnapLines(); + + + //need to change points for resize, or else rotation/control points will fail. + SelectionManager.SelectedDocuments().forEach(action((element: DocumentView, index) => { + const doc = Document(element.rootDoc); + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { + const newPoints: { X: number, Y: number }[] = []; + for (var i = 0; i < ink.length; i++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = (doc.x - this._inkDocs[index].x) + (ink[i].X * doc._width) / this._inkDocs[index].width; + const newY = (doc.y - this._inkDocs[index].y) + (ink[i].Y * doc._height) / this._inkDocs[index].height; + newPoints.push({ X: newX, Y: newY }); + } + doc.data = new InkField(newPoints); + + } + + } + })); } @computed @@ -596,6 +630,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (bounds.y > bounds.b) { bounds.y = bounds.b - (this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight); } + var offset = 0; + //make offset larger for ink to edit points + if (seldoc.rootDoc.type === DocumentType.INK) { + offset = 20; + } return (<div className="documentDecorations" style={{ background: darkScheme }} > <div className="documentDecorations-background" style={{ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", @@ -608,10 +647,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> </div> {bounds.r - bounds.x < 15 && bounds.b - bounds.y < 15 ? (null) : <> <div className="documentDecorations-container" key="container" ref={this.setTextBar} style={{ - width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", - height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight) + "px", - left: bounds.x - this._resizeBorderWidth / 2, - top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, + width: (bounds.r - bounds.x + this._resizeBorderWidth + offset) + "px", + height: (bounds.b - bounds.y + this._resizeBorderWidth + this._titleHeight + offset) + "px", + left: bounds.x - this._resizeBorderWidth / 2 - offset / 2, + top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight - offset / 2, }}> {maximizeIcon} {titleArea} diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index ed77da8ca..7c0a8635e 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -22,6 +22,7 @@ import { RadialMenu } from "./nodes/RadialMenu"; import HorizontalPalette from "./Palette"; import { Touchable } from "./Touchable"; import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; +import * as fitCurve from 'fit-curve'; import { CollectionFreeFormViewChrome } from "./collections/CollectionMenu"; @observer @@ -630,6 +631,22 @@ export default class GestureOverlay extends Touchable { // if no gesture (or if the gesture was unsuccessful), "dry" the stroke into an ink document if (!actionPerformed) { + const newPoints = this._points.reduce((p, pts) => { p.push([pts.X, pts.Y]); return p; }, [] as number[][]); + newPoints.pop(); + const controlPoints: { X: number, Y: number }[] = []; + + const bezierCurves = fitCurve(newPoints, 10); + for (const curve of bezierCurves) { + + controlPoints.push({ X: curve[0][0], Y: curve[0][1] }); + controlPoints.push({ X: curve[1][0], Y: curve[1][1] }); + controlPoints.push({ X: curve[2][0], Y: curve[2][1] }); + controlPoints.push({ X: curve[3][0], Y: curve[3][1] }); + + + } + this._points = controlPoints; + this.dispatchGesture(GestureUtils.Gestures.Stroke); } this._points = []; @@ -649,6 +666,10 @@ export default class GestureOverlay extends Touchable { } makePolygon = (shape: string, gesture: boolean) => { + //take off gesture recognition for now + if (gesture) { + return false; + } const xs = this._points.map(p => p.X); const ys = this._points.map(p => p.Y); var right = Math.max(...xs); @@ -684,18 +705,53 @@ export default class GestureOverlay extends Touchable { //must be (points[0].X,points[0]-1) case "rectangle": this._points.push({ X: left, Y: top }); + this._points.push({ X: left, Y: top }); + + this._points.push({ X: right, Y: top }); + this._points.push({ X: right, Y: top }); this._points.push({ X: right, Y: top }); + this._points.push({ X: right, Y: top }); + + this._points.push({ X: right, Y: bottom }); this._points.push({ X: right, Y: bottom }); + this._points.push({ X: right, Y: bottom }); + this._points.push({ X: right, Y: bottom }); + this._points.push({ X: left, Y: bottom }); + this._points.push({ X: left, Y: bottom }); + this._points.push({ X: left, Y: bottom }); + this._points.push({ X: left, Y: bottom }); + + this._points.push({ X: left, Y: top }); this._points.push({ X: left, Y: top }); - this._points.push({ X: left, Y: top - 1 }); + // this._points.push({ X: left, Y: top }); + // this._points.push({ X: left, Y: top }); + + // this._points.push({ X: left, Y: top - 1 }); break; case "triangle": + // this._points.push({ X: left, Y: bottom }); + // this._points.push({ X: right, Y: bottom }); + // this._points.push({ X: (right + left) / 2, Y: top }); + // this._points.push({ X: left, Y: bottom }); + // this._points.push({ X: left, Y: bottom - 1 }); this._points.push({ X: left, Y: bottom }); + this._points.push({ X: left, Y: bottom }); + + this._points.push({ X: right, Y: bottom }); this._points.push({ X: right, Y: bottom }); + this._points.push({ X: right, Y: bottom }); + this._points.push({ X: right, Y: bottom }); + this._points.push({ X: (right + left) / 2, Y: top }); + this._points.push({ X: (right + left) / 2, Y: top }); + this._points.push({ X: (right + left) / 2, Y: top }); + this._points.push({ X: (right + left) / 2, Y: top }); + + this._points.push({ X: left, Y: bottom }); this._points.push({ X: left, Y: bottom }); - this._points.push({ X: left, Y: bottom - 1 }); + + break; case "circle": const centerX = (right + left) / 2; @@ -712,11 +768,37 @@ export default class GestureOverlay extends Touchable { } this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top }); this._points.push({ X: Math.sqrt(Math.pow(radius, 2) - (Math.pow((top - centerY), 2))) + centerX, Y: top - 1 }); + // this._points.push({ X: centerX, Y: top }); + // this._points.push({ X: centerX + radius / 2, Y: top }); + + // this._points.push({ X: right, Y: top + radius / 2 }); + // this._points.push({ X: right, Y: top + radius }); + // this._points.push({ X: right, Y: top + radius }); + // this._points.push({ X: right, Y: bottom - radius / 2 }); + + // this._points.push({ X: right - radius / 2, Y: bottom }); + // this._points.push({ X: right - radius, Y: bottom }); + // this._points.push({ X: right - radius, Y: bottom }); + // this._points.push({ X: left + radius / 2, Y: bottom }); + + // this._points.push({ X: left, Y: bottom - radius / 2 }); + // this._points.push({ X: left, Y: bottom - radius }); + // this._points.push({ X: left, Y: bottom - radius }); + // this._points.push({ X: left, Y: top + radius / 2 }); + + // this._points.push({ X: left + radius / 2, Y: top }); + // this._points.push({ X: left + radius, Y: top }); + + + + + + + break; case "line": this._points.push({ X: left, Y: top }); this._points.push({ X: right, Y: bottom }); - // this._points.push({ X: right, Y: bottom - 1 }); break; case "arrow": const x1 = left; diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index e26ad47f9..5892e8346 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -17,6 +17,7 @@ import { Scripting } from "../util/Scripting"; import { Doc } from "../../fields/Doc"; import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane"; import { action } from "mobx"; +import { setupMoveUpEvents } from "../../Utils"; library.add(faPaintBrush); @@ -45,6 +46,38 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume FormatShapePane.Instance.Pinned = true; } + private _prevX = 0; + private _prevY = 0; + private _controlNum = 0; + @action + onControlDown = (e: React.PointerEvent, i: number): void => { + setupMoveUpEvents(this, e, this.onControlMove, this.onControlup, (e) => { }); + this._prevX = e.clientX; + this._prevY = e.clientY; + this._controlNum = i; + } + + @action + changeCurrPoint = (i: number) => { + FormatShapePane.Instance._currPoint = i; + } + + @action + onControlMove = (e: PointerEvent, down: number[]): boolean => { + const xDiff = this._prevX - e.clientX; + const yDiff = this._prevY - e.clientY; + FormatShapePane.Instance.control(xDiff, yDiff, this._controlNum); + this._prevX = e.clientX; + this._prevY = e.clientY; + return false; + } + + onControlup = (e: PointerEvent) => { + this._prevX = 0; + this._prevY = 0; + this._controlNum = 0; + } + public static MaskDim = 50000; render() { TraceMobx(); @@ -62,14 +95,74 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume const scaleX = (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth); const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth); const strokeColor = StrCast(this.layoutDoc.color, ""); + const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"), StrCast(this.layoutDoc.strokeStartMarker), StrCast(this.layoutDoc.strokeEndMarker), StrCast(this.layoutDoc.strokeDash), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false); + const hpoints = InteractionUtils.CreatePolyline(data, left, top, this.props.isSelected() && strokeWidth > 5 ? strokeColor : "transparent", strokeWidth, (strokeWidth + 15), StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"), "none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true); + + var controlPoints: { X: number, Y: number, I: number }[] = []; + var handlePoints: { X: number, Y: number, I: number, dot1: number, dot2: number }[] = []; + var handleLine: { X1: number, Y1: number, X2: number, Y2: number, X3: number, Y3: number, dot1: number, dot2: number }[] = []; + if (data.length >= 4) { + for (var i = 0; i <= data.length - 4; i += 4) { + controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i }); + controlPoints.push({ X: data[i + 3].X, Y: data[i + 3].Y, I: i + 3 }); + handlePoints.push({ X: data[i + 1].X, Y: data[i + 1].Y, I: i + 1, dot1: i, dot2: i === 0 ? i : i - 1 }); + handlePoints.push({ X: data[i + 2].X, Y: data[i + 2].Y, I: i + 2, dot1: i + 3, dot2: i === data.length ? i + 3 : i + 4 }); + } + + handleLine.push({ X1: data[0].X, Y1: data[0].Y, X2: data[0].X, Y2: data[0].Y, X3: data[1].X, Y3: data[1].Y, dot1: 0, dot2: 0 }); + for (var i = 2; i < data.length - 4; i += 4) { + + handleLine.push({ X1: data[i].X, Y1: data[i].Y, X2: data[i + 1].X, Y2: data[i + 1].Y, X3: data[i + 3].X, Y3: data[i + 3].Y, dot1: i + 1, dot2: i + 2 }); + + } + handleLine.push({ X1: data[data.length - 2].X, Y1: data[data.length - 2].Y, X2: data[data.length - 1].X, Y2: data[data.length - 1].Y, X3: data[data.length - 1].X, Y3: data[data.length - 1].Y, dot1: data.length - 1, dot2: data.length - 1 }); + + + } + if (data.length <= 4) { + handlePoints = []; + handleLine = []; + controlPoints = []; + for (var i = 0; i < data.length; i++) { + controlPoints.push({ X: data[i].X, Y: data[i].Y, I: i }); + } + + } + const dotsize = String(Math.min(width * scaleX, height * scaleY) / 40); + + const controls = controlPoints.map((pts, i) => + + <svg height="10" width="10"> + <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" stroke-width={String(Number(dotsize) / 2)} fill="red" + onPointerDown={(e) => { this.changeCurrPoint(pts.I); this.onControlDown(e, pts.I); }} pointerEvents="all" cursor="all-scroll" /> + </svg>); + const handles = handlePoints.map((pts, i) => + + <svg height="10" width="10"> + <circle cx={(pts.X - left - strokeWidth / 2) * scaleX + strokeWidth / 2} cy={(pts.Y - top - strokeWidth / 2) * scaleY + strokeWidth / 2} r={dotsize} stroke="black" stroke-width={String(Number(dotsize) / 2)} fill="green" + onPointerDown={(e) => this.onControlDown(e, pts.I)} pointerEvents="all" cursor="all-scroll" display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} /> + </svg>); + const handleLines = handleLine.map((pts, i) => + + <svg height="100" width="100"> + <line x1={(pts.X1 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y1 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} + x2={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" stroke-width={String(Number(dotsize) / 2)} + display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} /> + <line x1={(pts.X2 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y1={(pts.Y2 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} + x2={(pts.X3 - left - strokeWidth / 2) * scaleX + strokeWidth / 2} y2={(pts.Y3 - top - strokeWidth / 2) * scaleY + strokeWidth / 2} stroke="green" stroke-width={String(Number(dotsize) / 2)} + display={(pts.dot1 === FormatShapePane.Instance._currPoint || pts.dot2 === FormatShapePane.Instance._currPoint) ? "inherit" : "none"} /> + + </svg>); + + return ( <svg className="inkingStroke" width={width} @@ -92,6 +185,10 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume </defs> {hpoints} {points} + {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? controls : ""} + {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handles : ""} + {FormatShapePane.Instance._controlBtn && this.props.isSelected() ? handleLines : ""} + </svg> ); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index af4f1978e..ec0bff8a7 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -170,7 +170,8 @@ export class MainView extends React.Component { fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, fa.faFillDrip, fa.faLink, fa.faUnlink, fa.faBold, fa.faItalic, fa.faChevronLeft, fa.faUnderline, fa.faStrikethrough, fa.faSuperscript, fa.faSubscript, fa.faIndent, fa.faEyeDropper, fa.faPaintRoller, fa.faBars, fa.faBrush, fa.faShapes, fa.faEllipsisH, fa.faHandPaper, fa.faMap, fa.faUser, faHireAHelper, - fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive); + fa.faDesktop, fa.faTrashRestore, fa.faUsers, fa.faWrench, fa.faCog, fa.faMap, fa.faBellSlash, fa.faExpandAlt, fa.faArchive, fa.faBezierCurve, fa.faCircle, + fa.faLongArrowAltRight, fa.faPenFancy, fa.faAngleDoubleRight); this.initEventListeners(); this.initAuthenticationRouters(); } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index b81f00bc4..e7c0ad6e6 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -27,6 +27,8 @@ import { ColorState } from "react-color"; import { ObjectField } from "../../../fields/ObjectField"; import RichTextMenu from "../nodes/formattedText/RichTextMenu"; import { RichTextField } from "../../../fields/RichTextField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; @observer export default class CollectionMenu extends AntimodeMenu { @@ -118,6 +120,22 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp immediate: undoBatch((source: Doc[]) => { }), initialize: emptyFunction, }; + _openLinkInCommand = { + params: ["target", "container"], title: "link follow target", + script: `{ if (self.container?.length) { + getProto(self.target).linkContainer = self.container[0]; + getProto(self.target).isLinkButton = true; + getProto(self.target).onClick = makeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])"); + }}`, + immediate: undoBatch((container: Doc[]) => { + if (container.length) { + Doc.GetProto(this.target).linkContainer = container[0]; + Doc.GetProto(this.target).isLinkButton = true; + Doc.GetProto(this.target).onClick = ScriptField.MakeScript("getProto(self.linkContainer).data = new List([self.links[0]?.anchor2])"); + } + }), + initialize: emptyFunction, + }; _viewCommand = { params: ["target"], title: "bookmark view", script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale'];", @@ -147,7 +165,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp _stacking_commands = [this._contentCommand, this._templateCommand]; _masonry_commands = [this._contentCommand, this._templateCommand]; _schema_commands = [this._templateCommand, this._narrativeCommand]; - _doc_commands = [this._onClickCommand]; + _doc_commands = [this._openLinkInCommand, this._onClickCommand]; _tree_commands = []; private get _buttonizableCommands() { switch (this.props.type) { @@ -192,7 +210,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp this.document._facetWidth = 0; } - @computed get subChrome() { switch (this.props.type) { default: @@ -203,6 +220,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" {...this.props} />); case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" {...this.props} />); case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" {...this.props} />); + case CollectionViewType.Docking: return (<CollectionDockingChrome key="collchrome" {...this.props} />); } } private dropDisposer?: DragManager.DragDropDisposer; @@ -309,9 +327,10 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp <div className="collectionMenu-cont" > <div className="collectionMenu"> <div className="collectionViewBaseChrome"> - {!(this.isText && this.props.type === CollectionViewType.Freeform) && - !(this.props.type === CollectionViewType.Invalid) ? this.viewModes : null} - {!(this.isText && this.props.type === CollectionViewType.Freeform) ? this.templateChrome : null} + {this.props.type === CollectionViewType.Invalid || + this.props.type === CollectionViewType.Docking || this.isText ? (null) : this.viewModes} + {this.props.type === CollectionViewType.Invalid || + this.props.type === CollectionViewType.Docking || this.isText ? (null) : this.templateChrome} <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}> <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} > <FontAwesomeIcon icon="filter" size="lg" /> @@ -326,6 +345,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp } @observer +export class CollectionDockingChrome extends React.Component<CollectionMenuProps> { + render() { + return (null); + } +} + +@observer export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps & { isOverlay: boolean }> { public static Instance: CollectionFreeFormViewChrome; constructor(props: any) { @@ -383,11 +409,17 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu } private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""]; private _width = ["1", "5", "10", "100"]; - private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"]; - private _head = ["", "", "arrow", "", "", "arrow", "", "", ""]; - private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""]; - private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"]; - + // private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"]; + // private _head = ["", "", "arrow", "", "", "arrow", "", "", ""]; + // private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""]; + // private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"]; + private _dotsize = [10, 20, 30, 40]; + private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"]; + private _head = ["", "", "", "arrow", "", ""]; + private _end = ["", "", "arrow", "arrow", "", ""]; + private _shape = ["", "line", "line", "line", "rectangle", "circle"]; + private _title = ["pen", "line", "line with arrow", "line with double arrows", "square", "circle",]; + private _faName = ["pen-fancy", "minus", "long-arrow-alt-right", "arrows-alt-h", "square", "circle"]; @observable _shapesNum = this._shape.length; @observable _selected = this._shapesNum; @@ -450,9 +482,11 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu }); return <div className="btn-draw" key="draw"> {this._draw.map((icon, i) => - <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)} + <button className="antimodeMenu-button" title={this._title[i]} key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)} style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}> - {this._draw[i]} + {/* {this._draw[i]} */} + <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" /> + </button>)} </div>; } @@ -471,11 +505,11 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu return !this._widthBtn ? widthPicker : <div className="btn2-group" key="width"> {widthPicker} - {this._width.map(wid => - <button className="antimodeMenu-button" key={wid} + {this._width.map((wid, i) => + <button className="antimodeMenu-button" key={wid} title="change width" onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })} - style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}> - {wid} + style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}> + • </button>)} </div>; } @@ -515,7 +549,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane" onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)} style={{ backgroundColor: this._fillBtn ? "121212" : "" }}> - <FontAwesomeIcon icon="chevron-right" size="lg" /> + <FontAwesomeIcon icon="angle-double-right" size="lg" /> </button>; } @@ -538,16 +572,22 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div> : null} {!this.props.isOverlay || this.isText ? (null) : - <button className={"antimodeMenu-button"} key="hypothesis" style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined }} title="Use Hypothesis" onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}> + <button className={"antimodeMenu-button"} key="hypothesis" + style={{ + backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, + borderRight: "1px solid gray" + }} + title="Use Hypothesis" + onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}> <FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} /> </button> } {(!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating) && !this.isText ? <> + {this.drawButtons} {this.widthPicker} {this.colorPicker} {this.fillPicker} - {this.drawButtons} {this.formatPane} </> : (null) diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 22a3877ab..2e4055256 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -39,7 +39,15 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { @computed get contents() { return <div className="collectionPileView-innards" style={{ pointerEvents: this.layoutEngine() === "starburst" ? undefined : "none" }} > - <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} /> + <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} + addDocument={(doc: Doc | Doc[]) => { + (doc instanceof Doc ? [doc] : doc).map((d) => DocUtils.iconify(d)); + return this.props.addDocument(doc); + }} + moveDocument={(doc: Doc | Doc[], targetCollection: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { + (doc instanceof Doc ? [doc] : doc).map((d) => Doc.deiconifyView(d)); + return this.props.moveDocument(doc, targetCollection, addDoc); + }} /> </div>; } toggleStarburst = action(() => { @@ -72,24 +80,13 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { } }); - @undoBatch - @action - onInternalDrop = (e: Event, de: DragManager.DropEvent) => { - if (super.onInternalDrop(e, de)) { - if (de.complete.docDragData) { - DocUtils.pileup(this.childDocs); - } - } - return true; - } - _undoBatch: UndoManager.Batch | undefined; pointerDown = (e: React.PointerEvent) => { let dist = 0; SnappingManager.SetIsDragging(true); // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { - if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) { + if (this.layoutEngine() === "pass" && this.childDocs.length && e.shiftKey) { dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); if (dist > 100) { if (!this._undoBatch) { @@ -110,11 +107,11 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { if (!this.childDocs.length) { this.props.ContainingCollectionView?.removeDocument(this.props.Document); } - }, emptyFunction, false, this.layoutEngine() === "pass" && this.props.isSelected(true)); // this sets _doubleTap + }, emptyFunction, e.shiftKey && this.layoutEngine() === "pass", this.layoutEngine() === "pass" && e.shiftKey); // this sets _doubleTap } onClick = (e: React.MouseEvent) => { - if (e.button === 0 && this._doubleTap) { + if (e.button === 0) {//} && this._doubleTap) { SelectionManager.DeselectAll(); this.toggleStarburst(); e.stopPropagation(); @@ -124,7 +121,6 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { render() { return <div className={"collectionPileView"} onClick={this.onClick} onPointerDown={this.pointerDown} - ref={this.createDashEventsTarget} style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}> {this.contents} </div>; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index fe8f1f322..500d72fdb 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -208,7 +208,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) NativeHeight={returnZero} NativeWidth={returnZero} fitToBox={false} - dontRegisterView={this.props.dontRegisterView} + dontRegisterView={BoolCast(this.layoutDoc.dontRegisterChildViews, this.props.dontRegisterView)} rootSelected={this.rootSelected} dropAction={StrCast(this.layoutDoc.childDropAction) as dropActionType} onClick={this.onChildClickHandler} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 99acfdcc2..a89fcc703 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -195,7 +195,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); const res = addedDocs.length ? this.addDocument(addedDocs) : true; - added = movedDocs.length ? docDragData.moveDocument(movedDocs, this.props.Document, Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document) || de.embedKey || !this.props.isAnnotationOverlay ? this.addDocument : returnFalse) : res; + if (movedDocs.length) { + const canAdd = this.props.Document._viewType === CollectionViewType.Pile || de.embedKey || !this.props.isAnnotationOverlay || + Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); + added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); + } else added = res; } else { added = this.addDocument(docDragData.droppedDocuments); } @@ -430,7 +434,7 @@ import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { DocumentType } from "../../documents/DocumentTypes"; import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox"; -import { CollectionView } from "./CollectionView"; +import { CollectionView, CollectionViewType } from "./CollectionView"; import { SelectionManager } from "../../util/SelectionManager"; import { OverlayView } from "../OverlayView"; import { setTimeout } from "timers"; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 5834e5542..9a7df8693 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -45,7 +45,6 @@ import { SubCollectionViewProps } from './CollectionSubView'; import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; -import CollectionMenu from './CollectionMenu'; import { ContextMenuProps } from '../ContextMenuItem'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -589,11 +588,11 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href : ""))} - {/* {this.props.hideFilter || this.props.Document.hideFilterView || !this.props.isSelected() && !this.props.Document.forceActive ? (null) : + {/* {(!this.props.isSelected() && !this.props.Document.forceActive) || this.props.Document.hideFilterView ? (null) : <div className="collectionView-filterDragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 1, top: this.props.Document._viewType === CollectionViewType.Docking ? "25%" : "55%" }} /> } - {this.facetWidth() < 10 ? (null) : this.filterView} */} + {this.facetWidth() < 10 ? (null) : this.filterView} */} </div>); } } diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss index 88876471c..010beb836 100644 --- a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss @@ -24,11 +24,13 @@ .formatShapePane-inputBtn { width: inherit; - position: absolute; + position: absolute; } .sketch-picker { background: #323232; + width: 160px !important; + height: 80% !important; .flexbox-fit { background: #323232; diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx index 4e328d838..eb6097acf 100644 --- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx @@ -12,6 +12,8 @@ import { SelectionManager } from "../../../util/SelectionManager"; import AntimodeMenu from "../../AntimodeMenu"; import "./FormatShapePane.scss"; import { undoBatch } from "../../../util/UndoManager"; +import { ColorState, SketchPicker } from 'react-color'; +import { DocumentView } from "../../../views/nodes/DocumentView" @observer export default class FormatShapePane extends AntimodeMenu { @@ -20,14 +22,16 @@ export default class FormatShapePane extends AntimodeMenu { private _lastFill = "#D0021B"; private _lastLine = "#D0021B"; private _lastDash = "2"; - private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF"]; private _mode = ["fill-drip", "ruler-combined"]; - @observable private _subOpen = [false, false, false, false]; + @observable private _subOpen = [false, false]; @observable private _currMode = "fill-drip"; - @observable private _lock = false; + @observable _lock = false; @observable private _fillBtn = false; @observable private _lineBtn = false; + @observable _controlBtn = false; + @observable private _controlPoints: { X: number, Y: number }[] = []; + @observable _currPoint = -1; getField(key: string) { return this.selectedInk?.reduce((p, i) => @@ -101,19 +105,58 @@ export default class FormatShapePane extends AntimodeMenu { upDownButtons = (dirs: string, field: string) => { switch (field) { case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break; + // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break; case "Xps": this.selectedInk?.forEach(i => i.rootDoc.x = NumCast(i.rootDoc.x) + (dirs === "up" ? 10 : -10)); break; case "Yps": this.selectedInk?.forEach(i => i.rootDoc.y = NumCast(i.rootDoc.y) + (dirs === "up" ? 10 : -10)); break; case "stk": this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = NumCast(i.rootDoc.strokeWidth) + (dirs === "up" ? .1 : -.1)); break; case "wid": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { + //redraw points const oldWidth = NumCast(i.rootDoc._width); + const oldHeight = NumCast(i.rootDoc._height); + const oldX = NumCast(i.rootDoc.x); + const oldY = NumCast(i.rootDoc.y); i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10); this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height))); + const doc = Document(i.rootDoc); + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { + console.log(doc.x, doc.y, doc._height, doc._width); + const ink = Cast(doc.data, InkField)?.inkData; + console.log(ink); + if (ink) { + const newPoints: { X: number, Y: number }[] = []; + for (var j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = (doc.x - oldX) + (ink[j].X * doc._width) / oldWidth; + const newY = (doc.y - oldY) + (ink[j].Y * doc._height) / oldHeight; + newPoints.push({ X: newX, Y: newY }); + } + doc.data = new InkField(newPoints); + } + } }); break; case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { + const oldWidth = NumCast(i.rootDoc._width); const oldHeight = NumCast(i.rootDoc._height); - i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10); + const oldX = NumCast(i.rootDoc.x); + const oldY = NumCast(i.rootDoc.y); i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10); this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width))); + const doc = Document(i.rootDoc); + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { + console.log(doc.x, doc.y, doc._height, doc._width); + const ink = Cast(doc.data, InkField)?.inkData; + console.log(ink); + if (ink) { + const newPoints: { X: number, Y: number }[] = []; + for (var j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = (doc.x - oldX) + (ink[j].X * doc._width) / oldWidth; + const newY = (doc.y - oldY) + (ink[j].Y * doc._height) / oldHeight; + newPoints.push({ X: newX, Y: newY }); + } + doc.data = new InkField(newPoints); + } + } }); break; } @@ -121,12 +164,11 @@ export default class FormatShapePane extends AntimodeMenu { @undoBatch @action - rotate = (degrees: number) => { - this.selectedInk?.forEach(action(inkView => { + rotate = (angle: number) => { + const _centerPoints: { X: number, Y: number }[] = []; + SelectionManager.SelectedDocuments().forEach(action(inkView => { const doc = Document(inkView.rootDoc); if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { - const angle = Number(degrees) - Number(doc.rotation); - doc.rotation = Number(degrees); const ink = Cast(doc.data, InkField)?.inkData; if (ink) { const xs = ink.map(p => p.X); @@ -135,143 +177,280 @@ export default class FormatShapePane extends AntimodeMenu { const top = Math.min(...ys); const right = Math.max(...xs); const bottom = Math.max(...ys); - const _centerPoints: { X: number, Y: number }[] = []; _centerPoints.push({ X: left, Y: top }); + } + } + })); + + var index = 0; + SelectionManager.SelectedDocuments().forEach(action(inkView => { + const doc = Document(inkView.rootDoc); + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { + doc.rotation = Number(doc.rotation) + Number(angle); + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { const newPoints: { X: number, Y: number }[] = []; for (var i = 0; i < ink.length; i++) { - const newX = Math.cos(angle) * (ink[i].X - _centerPoints[0].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].X; - const newY = Math.sin(angle) * (ink[i].X - _centerPoints[0].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].Y; + const newX = Math.cos(angle) * (ink[i].X - _centerPoints[index].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].X; + const newY = Math.sin(angle) * (ink[i].X - _centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].Y; newPoints.push({ X: newX, Y: newY }); } doc.data = new InkField(newPoints); - const xs2 = newPoints.map(p => p.X); - const ys2 = newPoints.map(p => p.Y); - const left2 = Math.min(...xs2); - const top2 = Math.min(...ys2); - const right2 = Math.max(...xs2); - const bottom2 = Math.max(...ys2); - doc._height = (bottom2 - top2) * inkView.props.ScreenToLocalTransform().Scale; - doc._width = (right2 - left2) * inkView.props.ScreenToLocalTransform().Scale; + const xs = newPoints.map(p => p.X); + const ys = newPoints.map(p => p.Y); + const left = Math.min(...xs); + const top = Math.min(...ys); + const right = Math.max(...xs); + const bottom = Math.max(...ys); + + doc._height = (bottom - top); + doc._width = (right - left); } + index++; } })); } + @undoBatch + @action + control = (xDiff: number, yDiff: number, controlNum: number) => { + this.selectedInk?.forEach(action(inkView => { + if (this.selectedInk?.length === 1) { + const doc = Document(inkView.rootDoc); + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { + + const newPoints: { X: number, Y: number }[] = []; + const order = controlNum % 4; + for (var i = 0; i < ink.length; i++) { + if (controlNum === i || + (order === 0 && i === controlNum + 1) || + (order === 0 && controlNum !== 0 && i === controlNum - 2) || + (order === 0 && controlNum !== 0 && i === controlNum - 1) || + (order === 3 && i === controlNum - 1) || + (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 1) || + (order === 3 && controlNum !== ink.length - 1 && i === controlNum + 2) + || ((ink[0].X === ink[ink.length - 1].X) && (ink[0].Y === ink[ink.length - 1].Y) && (i === 0 || i === ink.length - 1) && (controlNum === 0 || controlNum === ink.length - 1)) + ) { + newPoints.push({ X: ink[i].X - (xDiff * inkView.props.ScreenToLocalTransform().Scale), Y: ink[i].Y - (yDiff * inkView.props.ScreenToLocalTransform().Scale) }); + } + else { + newPoints.push({ X: ink[i].X, Y: ink[i].Y }); + } + } + const oldx = doc.x; + const oldy = doc.y; + const xs = ink.map(p => p.X); + const ys = ink.map(p => p.Y); + const left = Math.min(...xs); + const top = Math.min(...ys); + doc.data = new InkField(newPoints); + const xs2 = newPoints.map(p => p.X); + const ys2 = newPoints.map(p => p.Y); + const left2 = Math.min(...xs2); + const top2 = Math.min(...ys2); + const right2 = Math.max(...xs2); + const bottom2 = Math.max(...ys2); + doc._height = (bottom2 - top2); + doc._width = (right2 - left2); + //if points move out of bounds - colorPicker(setter: (color: string) => {}) { - return <div className="btn-group-palette" key="colorpicker" > - {this._palette.map(color => - <button className="antimodeMenu-button" key={color} onPointerDown={undoBatch(action(() => setter(color)))} style={{ zIndex: 1001, position: "relative" }}> - <div className="color-previewII" style={{ backgroundColor: color }} /> - </button>)} + doc.x = oldx - (left - left2); + doc.y = oldy - (top - top2); + + } + } + } + })); + } + + @undoBatch + @action + switchStk = (color: ColorState) => { + const val = String(color.hex); + this.colorStk = val; + return true; + } + + @undoBatch + @action + switchFil = (color: ColorState) => { + const val = String(color.hex); + this.colorFil = val; + return true; + } + + + colorPicker(setter: (color: string) => {}, type: string) { + return <div className="btn-group-palette" key="colorpicker" style={{ width: 160, margin: 10 }}> + <SketchPicker onChange={type === "stk" ? this.switchStk : this.switchFil} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} + color={type === "stk" ? this.colorStk : this.colorFil} /> </div>; } inputBox = (key: string, value: any, setter: (val: string) => {}) => { return <> - <input style={{ color: "black", width: 80, position: "absolute", right: 20 }} + <input style={{ color: "black", width: 40, position: "absolute", right: 20 }} type="text" value={value} - onChange={e => setter(e.target.value)} + onChange={undoBatch(action((e) => setter(e.target.value)))} autoFocus /> - <button className="antiMenu-Buttonup" key="up" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}> + <button className="antiMenu-Buttonup" key="up1" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}> ˄ </button> <br /> - <button className="antiMenu-Buttonup" key="down" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}> + <button className="antiMenu-Buttonup" key="down1" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}> ˅ </button> </>; } + inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => { + return <> + {title1} + <p style={{ marginTop: -20, right: 70, position: "absolute" }}>{title2}</p> + + <input style={{ color: "black", width: 40, position: "absolute", right: 130 }} + type="text" value={value} + onChange={e => setter(e.target.value)} + autoFocus /> + <button className="antiMenu-Buttonup" key="up2" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} style={{ right: 110 }}> + ˄ + </button> + <button className="antiMenu-Buttonup" key="down2" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: 12, right: 110 }}> + ˅ + </button> + {title2 === "" ? "" : <> + <input style={{ color: "black", width: 40, position: "absolute", right: 20 }} + type="text" value={value2} + onChange={e => setter2(e.target.value)} + autoFocus /> + <button className="antiMenu-Buttonup" key="up3" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key2)))}> + ˄ + </button> + <br /> + <button className="antiMenu-Buttonup" key="down3" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key2)))} style={{ marginTop: -8 }}> + ˅ + </button></>} + </>; + } + + colorButton(value: string, setter: () => {}) { return <> - <button className="antimodeMenu-button" key="fill" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "absolute", right: 80 }}> - <FontAwesomeIcon icon="fill-drip" size="lg" /> - <div className="color-previewI" style={{ backgroundColor: value ?? "121212" }} /> + <button className="antimodeMenu-button" key="color" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "relative", marginTop: -5 }}> + <div className="color-previewII" style={{ backgroundColor: value ?? "121212" }} /> + {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -23, position: "fixed" }}>☒</p> : ""} + </button> + </>; + } + + controlPointsButton() { + return <> + <button className="antimodeMenu-button" title="Edit points" key="bezier" onPointerDown={action(() => this._controlBtn = this._controlBtn ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._controlBtn ? "black" : "" }}> + <FontAwesomeIcon icon="bezier-curve" size="lg" /> + </button> + <button className="antimodeMenu-button" title="Lock ratio" key="ratio" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "relative", marginTop: 10, backgroundColor: this._lock ? "black" : "" }}> + <FontAwesomeIcon icon="lock" size="lg" /> + + </button> + <button className="antimodeMenu-button" key="rotate" title="Rotate 90˚" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "relative", marginTop: 10, fontSize: 15 }}> + ⟲ </button> <br /> <br /> </>; } - @computed get fillButton() { return this.colorButton(this.colorFil, () => this._fillBtn = !this._fillBtn); } - @computed get lineButton() { return this.colorButton(this.colorStk, () => this._lineBtn = !this._lineBtn); } + lockRatioButton() { + return <> + <button className="antimodeMenu-button" key="lock" onPointerDown={action(() => this._lock = this._lock ? false : true)} style={{ position: "absolute", right: 80, backgroundColor: this._lock ? "black" : "" }}> + {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */} + <FontAwesomeIcon icon="lock" size="lg" /> + + </button> + <br /> <br /> + </>; + } - @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color); } - @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color); } + rotate90Button() { + return <> + <button className="antimodeMenu-button" key="rot" onPointerDown={action(() => this.rotate(Math.PI / 2))} style={{ position: "absolute", right: 80, }}> + {/* <FontAwesomeIcon icon="bezier-curve" size="lg" /> */} + ⟲ + + </button> + <br /> <br /> + </>; + } + @computed get fillButton() { return this.colorButton(this.colorFil, () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } + @computed get lineButton() { return this.colorButton(this.colorStk, () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } + + @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } + @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } @computed get stkInput() { return this.inputBox("stk", this.widthStk, (val: string) => this.widthStk = val); } - @computed get hgtInput() { return this.inputBox("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val); } + @computed get dashInput() { return this.inputBox("dsh", this.widthStk, (val: string) => this.widthStk = val); } + + @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val, "H:", "wid", this.shapeWid, (val: string) => this.shapeWid = val, "W:"); } @computed get widInput() { return this.inputBox("wid", this.shapeWid, (val: string) => this.shapeWid = val); } - @computed get rotInput() { return this.inputBox("rot", this.shapeRot, (val: string) => this.shapeRot = val); } - @computed get XpsInput() { return this.inputBox("Xps", this.shapeXps, (val: string) => this.shapeXps = val); } + @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; return true; }, "∠:", "rot", this.shapeRot, (val: string) => this.shapeRot = val, ""); } + @computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); } - @computed get propertyGroupItems() { - const fillCheck = <div key="fill" style={{ display: this._subOpen[0] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> - <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={undoBatch(action(() => this.unFilled = true))} /> - No Fill - <br /> - <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={undoBatch(action(() => this.solidFil = true))} /> - Solid Fill - <br /> <br /> - {this.solidFil ? "Color" : ""} - {this.solidFil ? this.fillButton : ""} - {this._fillBtn && this.solidFil ? this.fillPicker : ""} - </div>; + @computed get controlPoints() { return this.controlPointsButton(); } + @computed get lockRatio() { return this.lockRatioButton(); } + @computed get rotate90() { return this.rotate90Button(); } + @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => this.shapeXps = val, "X:", "Yps", this.shapeYps, (val: string) => this.shapeYps = val, "Y:"); } - const markers = <> - <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} /> - Arrow Head - <br /> - <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} /> - Arrow End - <br /> - </>; - const lineCheck = <div key="lineCheck" style={{ display: this._subOpen[1] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> - <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={undoBatch(action(() => this.unStrokd = true))} /> - No Line - <br /> - <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={undoBatch(action(() => this.solidStk = true))} /> - Solid Line - <br /> - <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={undoBatch(action(() => this.dashdStk = "2"))} /> - Dash Line - <br /> - <br /> - {(this.solidStk || this.dashdStk) ? "Color" : ""} - {(this.solidStk || this.dashdStk) ? this.lineButton : ""} - {(this.solidStk || this.dashdStk) && this._lineBtn ? this.linePicker : ""} - <br /> + @computed get propertyGroupItems() { + const fillCheck = <div key="fill" style={{ display: (this._subOpen[0] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> + Fill: + {this.fillButton} + <div style={{ float: "left", width: 100 }} > + Stroke: + {this.lineButton} + </div> + + {this._fillBtn ? this.fillPicker : ""} + {this._lineBtn ? this.linePicker : ""} + {this._fillBtn || this._lineBtn ? "" : <br />} {(this.solidStk || this.dashdStk) ? "Width" : ""} {(this.solidStk || this.dashdStk) ? this.stkInput : ""} - {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={e => this.widthStk = e.target.value} /> : (null)} - <br /> <br /> - {(this.solidStk || this.dashdStk) ? markers : ""} - </div>; - const sizeCheck = <div key="sizeCheck" style={{ display: this._subOpen[2] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> - Height {this.hgtInput} - <br /> <br /> - Width {this.widInput} - <br /> <br /> - <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={undoBatch(action(() => this._lock = !this._lock))} /> - Lock Ratio - <br /> <br /> - Rotation {this.rotInput} - <br /> <br /> - </div>; - const positionCheck = <div key="posCheck" style={{ display: this._subOpen[3] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> - Horizontal {this.XpsInput} - <br /> <br /> - Vertical {this.YpsInput} - <br /> <br /> + {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={undoBatch(action((e) => this.widthStk = e.target.value))} /> : (null)} + <br /> + {(this.solidStk || this.dashdStk) ? <> + <p style={{ position: "absolute", fontSize: 12 }}>Arrow Head</p> + <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} style={{ position: "absolute", right: 110, width: 20 }} /> + <p style={{ position: "absolute", fontSize: 12, right: 30 }}>Arrow End</p> + <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} style={{ position: "absolute", right: 0, width: 20 }} /> + <br /> + </> : ""} + Dash: <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.dashdStk === "2"} onChange={undoBatch(action(() => this.dashdStk = this.dashdStk === "2" ? "0" : "2"))} style={{ position: "absolute", right: 110, width: 20 }} /> + + + </div>; - const subMenus = this._currMode === "fill-drip" ? [`fill`, `line`] : [`size`, `position`]; - const menuItems = this._currMode === "fill-drip" ? [fillCheck, lineCheck] : [sizeCheck, positionCheck]; - const indexOffset = this._currMode === "fill-drip" ? 0 : 2; + + + const sizeCheck = + + <div key="sizeCheck" style={{ display: (this._subOpen[1] && this.selectedInk && this.selectedInk.length >= 1) ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}> + {this.controlPoints} + {this.hgtInput} + {this.XpsInput} + {this.rotInput} + + </div>; + + + const subMenus = this._currMode === "fill-drip" ? [`Appearance`, 'Transform'] : []; + const menuItems = this._currMode === "fill-drip" ? [fillCheck, sizeCheck] : []; + const indexOffset = 0; + return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}> {subMenus.map((subMenu, i) => <div key={subMenu} style={{ width: "inherit" }}> @@ -302,6 +481,7 @@ export default class FormatShapePane extends AntimodeMenu { } render() { - return this.getElementVert([this.closeBtn, this.propertyGroupBtn, this.propertyGroupItems]); + return this.getElementVert([this.closeBtn, + this.propertyGroupItems]); } }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss index 4ccc0950b..83e0fc0d5 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss @@ -117,6 +117,74 @@ } } + .propertiesView-appearance { + border-bottom: 1px solid black; + //padding: 8.5px; + + .propertiesView-appearance-title { + font-weight: bold; + font-size: 12.5px; + padding: 4px; + display: flex; + width: 200px; + color: white; + padding-left: 8px; + background-color: rgb(51, 51, 51); + + .propertiesView-appearance-title-icon { + float: right; + right: 0; + position: absolute; + margin-left: 2px; + margin-right: 9px; + + &:hover { + cursor: pointer; + } + } + } + + .propertiesView-appearance-content { + font-size: 10px; + padding: 10px; + margin-left: 5px; + } + } + + .propertiesView-transform { + border-bottom: 1px solid black; + //padding: 8.5px; + + .propertiesView-transform-title { + font-weight: bold; + font-size: 12.5px; + padding: 4px; + display: flex; + width: 200px; + color: white; + padding-left: 8px; + background-color: rgb(51, 51, 51); + + .propertiesView-transform-title-icon { + float: right; + right: 0; + position: absolute; + margin-left: 2px; + margin-right: 9px; + + &:hover { + cursor: pointer; + } + } + } + + .propertiesView-transform-content { + font-size: 10px; + padding: 10px; + margin-left: 5px; + } + } + .notify-button { padding: 2px; width: 12px; diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx index b33b37f4b..c4291f2a4 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -18,6 +18,8 @@ import { SelectionManager } from "../../../util/SelectionManager"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip, Checkbox } from "@material-ui/core"; import SharingManager from "../../../util/SharingManager"; +import { DocumentType } from "../../../documents/DocumentTypes"; +import FormatShapePane from "./FormatShapePane"; import { SharingPermissions, GetEffectiveAcl } from "../../../../fields/util"; @@ -48,6 +50,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @observable openSharing: boolean = true; @observable openFields: boolean = true; @observable openLayout: boolean = true; + @observable openAppearance: boolean = true; + @observable openTransform: boolean = true; + + @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; } @action rtfWidth = () => { @@ -397,6 +403,40 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {this.sharingTable} </div> : null} </div> + + + + + {this.isInk ? <div className="propertiesView-appearance"> + <div className="propertiesView-appearance-title" style={{ backgroundColor: this.openAppearance ? "black" : "" }}> + Appearance + <div className="propertiesView-appearance-title-icon" + onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })}> + <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openAppearance ? <div className="propertiesView-appearance-content"> + <FormatShapePane /> + </div> : null} + </div> : null} + + {this.isInk ? <div className="propertiesView-transform"> + <div className="propertiesView-transform-title" style={{ backgroundColor: this.openTransform ? "black" : "" }}> + Transform + <div className="propertiesView-transform-title-icon" + onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })}> + <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" /> + </div> + </div> + {this.openTransform ? <div className="propertiesView-transform-content"> + transform + </div> : null} + </div> : null} + + + + + <div className="propertiesView-fields"> <div className="propertiesView-fields-title" style={{ backgroundColor: this.openFields ? "black" : "" }}> <div className="propertiesView-fields-title-name"> diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 57028b0ca..090cf015a 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -61,6 +61,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument <SketchPicker onChange={ColorBox.switchColor} presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} color={StrCast(ActiveInkPen()?.backgroundColor, StrCast(selDoc?._backgroundColor, StrCast(selDoc?.backgroundColor, "black")))} /> + <div style={{ display: "grid", gridTemplateColumns: "20% 80%", paddingTop: "10px" }}> <div> {ActiveInkWidth() ?? 2}</div> <input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 5f3a6e78d..198a90225 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -120,7 +120,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp } @action @undoBatch - finishLinkClick = (e: React.MouseEvent) => { + finishLinkClick = (screenX: number, screenY: number) => { if (DocumentLinksButton.StartLink === this.props.View) { DocumentLinksButton.StartLink = undefined; } else { @@ -135,13 +135,13 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp runInAction(() => { if (linkDoc) { TaskCompletionBox.textDisplayed = "Link Created"; - TaskCompletionBox.popupX = e.screenX; - TaskCompletionBox.popupY = e.screenY - 133; + TaskCompletionBox.popupX = screenX; + TaskCompletionBox.popupY = screenY - 133; TaskCompletionBox.taskCompleted = true; if (LinkDescriptionPopup.showDescriptions === "ON" || !LinkDescriptionPopup.showDescriptions) { - LinkDescriptionPopup.popupX = e.screenX; - LinkDescriptionPopup.popupY = e.screenY - 100; + LinkDescriptionPopup.popupX = screenX; + LinkDescriptionPopup.popupY = screenY - 100; LinkDescriptionPopup.descriptionPopup = true; } @@ -207,7 +207,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp </div> {DocumentLinksButton.StartLink && this.props.InMenu && !!!this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View ? <div className={"documentLinksButton-endLink"} style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} - onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e)} /> : (null)} + onPointerDown={this.completeLink} onClick={e => this.finishLinkClick(e.screenX, e.screenY)} /> : (null)} {DocumentLinksButton.StartLink === this.props.View && this.props.InMenu && this.props.StartLink ? <div className={"documentLinksButton-startLink"} style={{ width: this.props.InMenu ? "20px" : "30px", height: this.props.InMenu ? "20px" : "30px" }} /> : (null)} </div>; diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index f95f46104..bcd6f716b 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -40,7 +40,7 @@ export const marks: { [index: string]: MarkSpec } = { return node.attrs.docref && node.attrs.title ? ["div", ["span", `"`], ["span", 0], ["span", `"`], ["br"], ["a", { ...node.attrs, href: node.attrs.allLinks[0].href, class: "prosemirror-attribution" }, node.attrs.title], ["br"]] : node.attrs.allLinks.length === 1 ? - ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] : + ["a", { ...node.attrs, class: linkids, targetids, style: `text-decoration: ${linkids === " " ? "underline" : undefined}`, title: `${node.attrs.title}`, href: node.attrs.allLinks[0].href }, 0] : ["div", { class: "prosemirror-anchor" }, ["span", { class: "prosemirror-linkBtn" }, ["a", { ...node.attrs, class: linkids, targetids, title: `${node.attrs.title}` }, 0], diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index bd08b2f32..f55483a5b 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -183,6 +183,10 @@ Scripting.addGlobal(function getIndexVal(list: any[], index: number) { return list.reduce((p, x, i) => (i <= index && x !== undefined) || p === undefined ? x : p, undefined as any); }, "returns the value at a given index of a list", "(list: any[], index: number)"); +Scripting.addGlobal(function makeScript(script: string) { + return ScriptField.MakeScript(script); +}, "returns the value at a given index of a list", "(list: any[], index: number)"); + export namespace ComputedField { let useComputed = true; export function DisableComputedFields() { |