diff options
author | bobzel <zzzman@gmail.com> | 2020-07-11 20:49:00 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-07-11 20:49:00 -0400 |
commit | ff642c9f9846a182e7ca8893b2ca414bb8ce480f (patch) | |
tree | 534cb1d9b12e95d351595e8f7f11b4573123275c /src | |
parent | bf8c338e662327b39cdef3f90c436447e48d2807 (diff) | |
parent | 49b6ab8536f570ef244199ac39194d3b176c9e77 (diff) |
Merge pull request #438 from browngraphicslab/ink_menu
format shape pane
Diffstat (limited to 'src')
-rw-r--r-- | src/client/apis/HypothesisAuthenticationManager.tsx | 2 | ||||
-rw-r--r-- | src/client/documents/Documents.ts | 3 | ||||
-rw-r--r-- | src/client/util/CurrentUserUtils.ts | 6 | ||||
-rw-r--r-- | src/client/util/InteractionUtils.tsx | 15 | ||||
-rw-r--r-- | src/client/util/SelectionManager.ts | 1 | ||||
-rw-r--r-- | src/client/views/AntimodeMenu.tsx | 24 | ||||
-rw-r--r-- | src/client/views/DocumentDecorations.tsx | 5 | ||||
-rw-r--r-- | src/client/views/GestureOverlay.tsx | 23 | ||||
-rw-r--r-- | src/client/views/InkingStroke.tsx | 34 | ||||
-rw-r--r-- | src/client/views/MainView.tsx | 21 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/FormatShapePane.scss | 59 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/FormatShapePane.tsx | 361 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss | 7 | ||||
-rw-r--r-- | src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx | 320 | ||||
-rw-r--r-- | src/client/views/nodes/ColorBox.tsx | 12 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 |
16 files changed, 746 insertions, 149 deletions
diff --git a/src/client/apis/HypothesisAuthenticationManager.tsx b/src/client/apis/HypothesisAuthenticationManager.tsx index cffb87227..a7fcf86a4 100644 --- a/src/client/apis/HypothesisAuthenticationManager.tsx +++ b/src/client/apis/HypothesisAuthenticationManager.tsx @@ -34,7 +34,7 @@ export default class HypothesisAuthenticationManager extends React.Component<{}> } public fetchAccessToken = async (displayIfFound = false) => { - let response: any = await Networking.FetchFromServer("/readHypothesisAccessToken"); + const response: any = await Networking.FetchFromServer("/readHypothesisAccessToken"); // if this is an authentication url, activate the UI to register the new access token if (!response) { // new RegExp(AuthenticationUrl).test(response)) { this.isOpen = true; diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fa85d58f0..26abd4c3c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -671,7 +671,7 @@ export namespace Docs { I.type = DocumentType.INK; I.layout = InkingStroke.LayoutString("data"); I.color = color; - I.strokeWidth = strokeWidth; + I.strokeWidth = Number(strokeWidth); I.strokeBezier = strokeBezier; I.fillColor = fillColor; I.arrowStart = arrowStart; @@ -685,6 +685,7 @@ export namespace Docs { I._width = options._width; I._height = options._height; I.author = Doc.CurrentUserEmail; + I.rotation = 0; I.data = new InkField(points); return I; // return I; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 8099228c6..ce910c062 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -820,9 +820,9 @@ export class CurrentUserUtils { doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); doc.activeInkWidth = StrCast(doc.activeInkWidth, "1"); doc.activeInkBezier = StrCast(doc.activeInkBezier, "0"); - doc.activeFillColor = StrCast(doc.activeFillColor, "none"); - doc.activeArrowStart = StrCast(doc.activeArrowStart, "none"); - doc.activeArrowEnd = StrCast(doc.activeArrowEnd, "none"); + doc.activeFillColor = StrCast(doc.activeFillColor, ""); + doc.activeArrowStart = StrCast(doc.activeArrowStart, ""); + doc.activeArrowEnd = StrCast(doc.activeArrowEnd, ""); doc.activeDash = StrCast(doc.activeDash, "0"); doc.fontSize = NumCast(doc.fontSize, 12); doc["constants-snapThreshold"] = NumCast(doc["constants-snapThreshold"], 10); // diff --git a/src/client/util/InteractionUtils.tsx b/src/client/util/InteractionUtils.tsx index 02b444cd3..07adbb8b1 100644 --- a/src/client/util/InteractionUtils.tsx +++ b/src/client/util/InteractionUtils.tsx @@ -94,6 +94,7 @@ export namespace InteractionUtils { export function CreatePolyline(points: { X: number, Y: number }[], left: number, top: number, color: string, width: number, strokeWidth: number, bezier: string, fill: string, arrowStart: string, arrowEnd: string, dash: string, scalex: number, scaley: number, shape: string, pevents: string, drawHalo: boolean, nodefs: boolean) { + let pts: { X: number; Y: number; }[] = []; if (shape) { //if any of the shape are true pts = makePolygon(shape, points); @@ -119,15 +120,15 @@ export namespace InteractionUtils { const dashArray = String(Number(width) * Number(dash)); const defGuid = Utils.GenerateGuid(); const arrowDim = Math.max(0.5, 8 / Math.log(Math.max(2, strokeWidth))); - return (<svg fill={fill === "none" ? color : fill}> {/* setting the svg fill sets the arrowhead fill */} + return (<svg fill={color}> {/* setting the svg fill sets the arrowStart fill */} {nodefs ? (null) : <defs> {arrowStart !== "dot" && arrowEnd !== "dot" ? (null) : <marker id={`dot${defGuid}`} orient="auto" overflow="visible"> <circle r={1} fill="context-stroke" /> </marker>} - {arrowStart !== "arrowHead" && arrowEnd !== "arrowHead" ? (null) : <marker id={`arrowHead${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7"> + {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowStart${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7"> <polygon points={`${arrowDim} ${-Math.max(1, arrowDim / 2)}, ${arrowDim} ${Math.max(1, arrowDim / 2)}, -1 0`} /> </marker>} - {arrowStart !== "arrowEnd" && arrowEnd !== "arrowEnd" ? (null) : <marker id={`arrowEnd${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7"> + {arrowStart !== "arrow" && arrowEnd !== "arrow" ? (null) : <marker id={`arrowEnd${defGuid}`} orient="auto" overflow="visible" refX="1.6" refY="0" markerWidth="10" markerHeight="7"> <polygon points={`${2 - arrowDim} ${-Math.max(1, arrowDim / 2)}, ${2 - arrowDim} ${Math.max(1, arrowDim / 2)}, 3 0`} /> </marker>} </defs>} @@ -136,7 +137,7 @@ export namespace InteractionUtils { points={strpts} style={{ filter: drawHalo ? "url(#inkSelectionHalo)" : undefined, - fill, + fill: fill ? fill : "transparent", opacity: strokeWidth !== width ? 0.5 : undefined, pointerEvents: pevents as any, stroke: color ?? "rgb(0, 0, 0)", @@ -145,8 +146,8 @@ export namespace InteractionUtils { strokeLinecap: "round", strokeDasharray: dashArray }} - markerStart={`url(#${arrowStart + defGuid})`} - markerEnd={`url(#${arrowEnd + defGuid})`} + markerStart={`url(#${arrowStart + "Start" + defGuid})`} + markerEnd={`url(#${arrowEnd + "End" + defGuid})`} /> </svg>); @@ -155,7 +156,7 @@ export namespace InteractionUtils { // export function makeArrow() { // return ( // InkOptionsMenu.Instance.getColors().map(color => { - // const id1 = "arrowHeadTest" + color; + // const id1 = "arrowStartTest" + color; // console.log(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} /> diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 024532f90..9a968aeda 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -14,6 +14,7 @@ export namespace SelectionManager { SelectedDocuments: ObservableMap<DocumentView, boolean> = new ObservableMap(); @action SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { + // if doc is not in SelectedDocuments, add it if (!manager.SelectedDocuments.get(docView)) { if (!ctrlPressed) { diff --git a/src/client/views/AntimodeMenu.tsx b/src/client/views/AntimodeMenu.tsx index cb293dee4..68ccefcb5 100644 --- a/src/client/views/AntimodeMenu.tsx +++ b/src/client/views/AntimodeMenu.tsx @@ -134,13 +134,35 @@ export default abstract class AntimodeMenu extends React.Component { 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, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay }}> + style={{ + left: this._left, top: this._top, opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, + position: this.Pinned ? "unset" : undefined + }}> <div className="antimodeMenu-dragger" onPointerDown={this.dragStart} style={{ width: "20px" }} /> {buttons} </div> ); } + protected getElementVert(buttons: JSX.Element[]) { + return ( + <div className="antimodeMenu-cont" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu} + style={{ + left: this.Pinned ? undefined : this._left, + top: this.Pinned ? 0 : this._top, + right: this.Pinned ? 0 : undefined, + height: "inherit", + width: 200, + opacity: this._opacity, transitionProperty: this._transitionProperty, transitionDuration: this._transitionDuration, transitionDelay: this._transitionDelay, + position: this.Pinned ? "absolute" : undefined + }}> + {buttons} + </div> + ); + } + + + protected getElementWithRows(rows: JSX.Element[], numRows: number, hasDragger: boolean = true) { return ( <div className="antimodeMenu-cont with-rows" onPointerLeave={this.pointerLeave} onPointerEnter={this.pointerEntered} ref={this._mainCont} onContextMenu={this.handleContextMenu} diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index a45ef8862..0bf4814e7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -152,8 +152,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (e.button === 0 && !e.altKey && !e.ctrlKey) { let child = SelectionManager.SelectedDocuments()[0].ContentDiv!.children[0]; while (child.children.length) { - const next = Array.from(child.children).find(c => !c.className.includes("collectionViewChrome")); - if (next?.className.includes("documentView-node")) break; + const next = Array.from(child.children).find(c => typeof (c.className) !== "string" || !c.className.includes("collectionViewChrome")); + if (typeof (next?.className) === "string" && next?.className.includes("documentView-node")) break; if (next) child = next; else break; } @@ -293,6 +293,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => { const doc = Document(element.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) { diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index cdc468066..fc8530811 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -24,6 +24,7 @@ import HorizontalPalette from "./Palette"; import { Touchable } from "./Touchable"; import TouchScrollableMenu, { TouchScrollableMenuItem } from "./TouchScrollableMenu"; import HeightLabel from "./collections/collectionMulticolumn/MultirowHeightLabel"; +import InkOptionsMenu from "./collections/collectionFreeForm/InkOptionsMenu"; @observer export default class GestureOverlay extends Touchable { @@ -623,7 +624,8 @@ export default class GestureOverlay extends Touchable { this.makePolygon(this.InkShape, false); this.dispatchGesture(GestureUtils.Gestures.Stroke); this._points = []; - if (this.InkShape !== "noRec") { + if (InkOptionsMenu.Instance._double === "") { + this.InkShape = ""; } } @@ -675,12 +677,19 @@ export default class GestureOverlay extends Touchable { } else { this._points = []; } - SetActiveArrowStart("none"); - GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart(); - SetActiveArrowEnd("none"); - GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd(); + //get out of ink mode after each stroke= + console.log("now"); + if (InkOptionsMenu.Instance._double === "") { + Doc.SetSelectedTool(InkTool.None); + InkOptionsMenu.Instance._selected = InkOptionsMenu.Instance._shapesNum; + SetActiveArrowStart("none"); + GestureOverlay.Instance.SavedArrowStart = ActiveArrowStart(); + SetActiveArrowEnd("none"); + GestureOverlay.Instance.SavedArrowEnd = ActiveArrowEnd(); + } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); + } makePolygon = (shape: string, gesture: boolean) => { @@ -876,7 +885,9 @@ export default class GestureOverlay extends Touchable { render() { return ( - <div className="gestureOverlay-cont" onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}> + + <div className="gestureOverlay-cont" style={{ position: "relative" }} + onPointerDown={this.onPointerDown} onTouchStart={this.onReactTouchStart}> {this.showMobileInkOverlay ? <MobileInkOverlay /> : <></>} {this.elements} diff --git a/src/client/views/InkingStroke.tsx b/src/client/views/InkingStroke.tsx index 7d6e7e5dd..d7f956e4f 100644 --- a/src/client/views/InkingStroke.tsx +++ b/src/client/views/InkingStroke.tsx @@ -15,6 +15,8 @@ import { FieldView, FieldViewProps } from "./nodes/FieldView"; import React = require("react"); import { Scripting } from "../util/Scripting"; import { Doc } from "../../fields/Doc"; +import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane"; +import { action } from "mobx"; library.add(faPaintBrush); @@ -38,10 +40,16 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume this.props.Document.isInkMask = true; } + @action + private formatShape = () => { + FormatShapePane.Instance.Pinned = true; + } + render() { TraceMobx(); const data: InkData = Cast(this.dataDoc[this.fieldKey], InkField)?.inkData ?? []; - const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth())); + // const strokeWidth = Number(StrCast(this.layoutDoc.strokeWidth, ActiveInkWidth())); + const strokeWidth = Number(this.layoutDoc.strokeWidth); const xs = data.map(p => p.X); const ys = data.map(p => p.Y); const left = Math.min(...xs) - strokeWidth / 2; @@ -52,14 +60,14 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume const height = bottom - top; const scaleX = (this.props.PanelWidth() - strokeWidth) / (width - strokeWidth); const scaleY = (this.props.PanelHeight() - strokeWidth) / (height - strokeWidth); - const strokeColor = StrCast(this.layoutDoc.color, ActiveInkColor()); + const strokeColor = StrCast(this.layoutDoc.color, ""); const points = InteractionUtils.CreatePolyline(data, left, top, strokeColor, strokeWidth, strokeWidth, - StrCast(this.layoutDoc.strokeBezier, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()), - StrCast(this.layoutDoc.arrowStart, ActiveArrowStart()), StrCast(this.layoutDoc.arrowEnd, ActiveArrowEnd()), - StrCast(this.layoutDoc.dash, ActiveDash()), scaleX, scaleY, "", "none", this.props.isSelected() && strokeWidth <= 5, false); + StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"), + StrCast(this.layoutDoc.strokeArrowStart), StrCast(this.layoutDoc.strokeArrowEnd), + 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, ActiveInkBezierApprox()), StrCast(this.layoutDoc.fillColor, ActiveFillColor()), + StrCast(this.layoutDoc.strokeBezier), StrCast(this.layoutDoc.fillColor, "transparent"), "none", "none", "0", scaleX, scaleY, "", this.props.active() ? "visiblepainted" : "none", false, true); return ( <svg className="inkingStroke" @@ -72,8 +80,12 @@ export class InkingStroke extends ViewBoxBaseComponent<FieldViewProps, InkDocume overflow: "visible", }} onContextMenu={() => { - ContextMenu.Instance?.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" }); - ContextMenu.Instance?.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" }); + const cm = ContextMenu.Instance; + if (cm) { + cm.addItem({ description: "Analyze Stroke", event: this.analyzeStrokes, icon: "paint-brush" }); + cm.addItem({ description: "Make Mask", event: this.makeMask, icon: "paint-brush" }); + cm.addItem({ description: "Format Shape", event: this.formatShape, icon: "paint-brush" }); + } }} ><defs> </defs> @@ -94,9 +106,9 @@ export function SetActiveArrowEnd(value: string) { ActiveInkPen() && (ActiveInkP export function SetActiveDash(dash: string): void { !isNaN(parseInt(dash)) && ActiveInkPen() && (ActiveInkPen().activeDash = dash); } export function ActiveInkPen(): Doc { return Cast(Doc.UserDoc().activeInkPen, Doc, null); } export function ActiveInkColor(): string { return StrCast(ActiveInkPen()?.activeInkColor, "black"); } -export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, "none"); } -export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, "none"); } -export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, "none"); } +export function ActiveFillColor(): string { return StrCast(ActiveInkPen()?.activeFillColor, ""); } +export function ActiveArrowStart(): string { return StrCast(ActiveInkPen()?.activeArrowStart, ""); } +export function ActiveArrowEnd(): string { return StrCast(ActiveInkPen()?.activeArrowEnd, ""); } export function ActiveDash(): string { return StrCast(ActiveInkPen()?.activeDash, "0"); } export function ActiveInkWidth(): string { return StrCast(ActiveInkPen()?.activeInkWidth, "1"); } export function ActiveInkBezierApprox(): string { return StrCast(ActiveInkPen()?.activeInkBezier); } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 81195b550..ab23a6ee9 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -6,7 +6,7 @@ import { faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTimesCircle, faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight, - faHeading + faHeading, faRulerCombined, faFillDrip } from '@fortawesome/free-solid-svg-icons'; import { ANTIMODEMENU_HEIGHT } from './globalCssVariables.scss'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -60,9 +60,9 @@ import { DocumentManager } from '../util/DocumentManager'; import { DocumentLinksButton } from './nodes/DocumentLinksButton'; import { LinkMenu } from './linking/LinkMenu'; import { LinkDocPreview } from './nodes/LinkDocPreview'; -import { Fade } from '@material-ui/core'; import { LinkCreatedBox } from './nodes/LinkCreatedBox'; import { LinkDescriptionPopup } from './nodes/LinkDescriptionPopup'; +import FormatShapePane from "./collections/collectionFreeForm/FormatShapePane"; import HypothesisAuthenticationManager from '../apis/HypothesisAuthenticationManager'; @observer @@ -147,7 +147,7 @@ export class MainView extends React.Component { faCommentAlt, faCompressArrowsAlt, faCut, faEllipsisV, faEraser, faExclamation, faFileAlt, faFileAudio, faFilePdf, faFilm, faFilter, faFont, faGlobeAsia, faHighlighter, faLongArrowAltRight, faMicrophone, faMousePointer, faMusic, faObjectGroup, faPause, faPen, faPenNib, faPhone, faPlay, faPortrait, faRedoAlt, faStamp, faStickyNote, faTrashAlt, faAngleRight, faBell, faThumbtack, faTree, faTv, faUndoAlt, faVideo, faAsterisk, faBrain, faImage, faPaintBrush, faTimes, faEye, faArrowsAlt, faQuoteLeft, faSortAmountDown, faAlignLeft, faAlignCenter, faAlignRight, - faHeading); + faHeading, faRulerCombined, faFillDrip); this.initEventListeners(); this.initAuthenticationRouters(); } @@ -468,7 +468,9 @@ export class MainView extends React.Component { return !this.userDoc || !(sidebar instanceof Doc) ? (null) : ( <div className="mainView-mainContent" style={{ color: this.darkScheme ? "rgb(205,205,205)" : "black", - height: RichTextMenu.Instance?.Pinned ? `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%" + //change to times 2 for both pinned + height: (RichTextMenu.Instance?.Pinned || InkOptionsMenu.Instance?.Pinned) ? (RichTextMenu.Instance?.Pinned && InkOptionsMenu.Instance?.Pinned) ? `calc(100% - 2*${ANTIMODEMENU_HEIGHT})` : `calc(100% - ${ANTIMODEMENU_HEIGHT})` : "100%", + width: (FormatShapePane.Instance?.Pinned) ? `calc(100% - 200px)` : "100%" }} > <div style={{ display: "contents", flexDirection: "row", position: "relative" }}> <div className="mainView-flyoutContainer" onPointerLeave={this.pointerLeaveDragger} style={{ width: this.flyoutWidth }}> @@ -607,7 +609,13 @@ export class MainView extends React.Component { <GoogleAuthenticationManager /> <HypothesisAuthenticationManager /> <DocumentDecorations /> - <GestureOverlay> + <InkOptionsMenu /> + + + <GestureOverlay > + <FormatShapePane /> + + <RichTextMenu key="rich" /> {this.mainContent} </GestureOverlay> @@ -619,10 +627,11 @@ export class MainView extends React.Component { linkDoc={LinkDocPreview.LinkInfo.linkDoc} linkSrc={LinkDocPreview.LinkInfo.linkSrc} href={LinkDocPreview.LinkInfo.href} addDocTab={LinkDocPreview.LinkInfo.addDocTab} /> : (null)} <ContextMenu /> + <FormatShapePane /> <RadialMenu /> <PDFMenu /> <MarqueeOptionsMenu /> - <InkOptionsMenu /> + <OverlayView /> <TimelineMenu /> {this.snapLines} diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss new file mode 100644 index 000000000..7720140b9 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss @@ -0,0 +1,59 @@ +.antimodeMenu-button { + width: 200px; + position: relative; + text-align: left; + + .color-previewI { + width: 100%; + height: 40%; + } + + .color-previewII { + width: 100%; + height: 100%; + } +} + +.antimenu-Buttonup { + position: absolute; + width: 20; + height: 10; + right: 0; + padding: 0; +} + +.sketch-picker { + background: #323232; + + .flexbox-fit { + background: #323232; + } +} + +.btn-group { + display: grid; + grid-template-columns: auto auto auto auto; + /* Make the buttons appear below each other */ +} + +.btn-group-palette { + display: block; + /* Make the buttons appear below each other */ +} + +.btn-draw { + display: inline; + /* Make the buttons appear below each other */ +} + +.btn2-group { + display: block; + background: #323232; + grid-template-columns: auto; + + /* Make the buttons appear below each other */ + .antimodeMenu-button { + background: #323232; + display: block; + } +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx new file mode 100644 index 000000000..77d1b646d --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx @@ -0,0 +1,361 @@ +import React = require("react"); +import AntimodeMenu from "../../AntimodeMenu"; +import { observer } from "mobx-react"; +import { observable, action, computed } from "mobx"; +import "./FormatShapePane.scss"; +import { Scripting } from "../../../util/Scripting"; +import { InkField } from "../../../../fields/InkField"; +import { Doc, Opt, Field } from "../../../../fields/Doc"; +import { SelectionManager } from "../../../util/SelectionManager"; +import { DocumentView } from "../../../views/nodes/DocumentView"; +import { Document } from "../../../../fields/documentSchemas"; +import { DocumentType } from "../../../documents/DocumentTypes"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { IconProp } from '@fortawesome/fontawesome-svg-core'; +import { Cast, StrCast, BoolCast, NumCast } from "../../../../fields/Types"; + +@observer +export default class FormatShapePane extends AntimodeMenu { + static Instance: FormatShapePane; + + 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"]; + private _subMenu = ["fill", "line", "size", "position"]; + + @observable private _subOpen = [false, false, false, false]; + @observable private _currMode: string = "fill-drip"; + @observable private _lock = false; + @observable private _fillBtn = false; + @observable private _lineBtn = false; + + getField(key: string) { + return this.inks?.reduce((p, i) => + (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>) + } + + @computed get inks() { + const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK); + return inks.length ? inks : undefined; + } + @computed get _noFill() { return this.inks?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; } + @computed get _solidFill() { return this.inks?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; } + @computed get _noLine() { return this.inks?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; } + @computed get _solidLine() { return this.inks?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; } + @computed get _arrowStart() { return this.getField("strokeArrowStart") || ""; } + @computed get _arrowEnd() { return this.getField("strokeArrowEnd") || ""; } + @computed get _dashLine() { return !this._noLine && this.getField("strokeDash") || ""; } + @computed get _currSizeHeight() { return this.getField("_height"); } + @computed get _currSizeWidth() { return this.getField("_width"); } + @computed get _currRotation() { return this.getField("rotation"); } + @computed get _currXpos() { return this.getField("x"); } + @computed get _currYpos() { return this.getField("y"); } + @computed get _currStrokeWidth() { return this.getField("strokeWidth"); } + @computed get _currFill() { const cfill = this.getField("fillColor") || ""; cfill && (this._lastFill = cfill); return cfill; } + @computed get _currColor() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } + set _noFill(value) { this._currFill = value ? "" : this._lastFill; } + set _solidFill(value) { this._noFill = !value; } + set _currFill(value) { value && (this._lastFill = value); this.inks?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); } + set _currColor(value) { value && (this._lastLine = value); this.inks?.forEach(i => i.rootDoc.color = value ? value : undefined); } + set _arrowStart(value) { this.inks?.forEach(i => i.rootDoc.strokeArrowStart = value); } + set _arrowEnd(value) { this.inks?.forEach(i => i.rootDoc.strokeArrowEnd = value); } + set _noLine(value) { this._currColor = value ? "" : this._lastLine; } + set _solidLine(value) { this._dashLine = ""; this._noLine = !value; } + set _dashLine(value) { + value && (this._lastDash = value) && (this._noLine = false); + this.inks?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined); + } + set _currXpos(value) { this.inks?.forEach(i => i.rootDoc.x = Number(value)); } + set _currYpos(value) { this.inks?.forEach(i => i.rootDoc.y = Number(value)); } + set _currRotation(value) { this.inks?.forEach(i => i.rootDoc.rotation = Number(value)); } + set _currStrokeWidth(value) { this.inks?.forEach(i => i.rootDoc.strokeWidth = Number(value)); } + set _currSizeWidth(value) { + this.inks?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { + const oldWidth = NumCast(i.rootDoc._width); + i.rootDoc._width = Number(value); + this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth); + }); + } + set _currSizeHeight(value) { + this.inks?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => { + const oldHeight = NumCast(i.rootDoc._height); + i.rootDoc._height = Number(value); + this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight); + }); + } + + constructor(props: Readonly<{}>) { + super(props); + FormatShapePane.Instance = this; + this._canFade = false; + this.Pinned = BoolCast(Doc.UserDoc()["formatShapePane-pinned"]); + } + + @action + closePane = () => { + this.jumpTo(-300, -300); + this.Pinned = false; + } + + @action + upDownButtons = (dirs: string, field: string) => { + switch (field) { + case "horizontal": this.inks?.forEach(i => i.rootDoc.x = NumCast(i.rootDoc.x) + (dirs === "up" ? 10 : -10)); break; + case "vertical": this.inks?.forEach(i => i.rootDoc.y = NumCast(i.rootDoc.y) + (dirs === "up" ? 10 : -10)); break; + case "rotation": this.rotate((dirs === "up" ? .1 : -.1)); break; + case "width": this.inks?.forEach(i => i.rootDoc.strokeWidth = NumCast(i.rootDoc.strokeWidth) + (dirs === "up" ? .1 : -.1)); break; + case "sizeWidth": + this.inks?.forEach(i => { + const doc = i.rootDoc; + if (doc._width && doc._height) { + const oldWidth = NumCast(doc._width); + const oldHeight = NumCast(doc._height); + doc._width = NumCast(doc._width) + (dirs === "up" ? 10 : - 10); + if (this._lock) { + doc._height = (NumCast(doc._width) * oldHeight) / oldWidth; + } + } + }); + break; + case "sizeHeight": + this.inks?.forEach(i => { + const doc = i.rootDoc; + if (doc._width && doc._height) { + const oldWidth = NumCast(doc._width); + const oldHeight = NumCast(doc._height); + doc._height = NumCast(doc._height) + (dirs === "up" ? 10 : - 10); + if (this._lock) { + doc._width = (NumCast(doc._height) * oldWidth) / oldHeight; + } + } + }); + break; + } + } + + @computed get close() { + return <button className="antimodeMenu-button" key="close" onPointerDown={action(() => this.closePane())} style={{ position: "absolute", right: 0 }}> + X + </button>; + } + + //select either coor&fill or size&position + @computed get modes() { + return <div className="antimodeMenu-button-tab" key="modes"> + {this._mode.map(mode => + <button className="antimodeMenu-button" key={mode} onPointerDown={action(() => { this._currMode = mode; })} + style={{ backgroundColor: this._currMode === mode ? "121212" : "", position: "relative", top: 30 }}> + <FontAwesomeIcon icon={mode as IconProp} size="lg" /> + </button>)} + </div>; + } + + @action + rotate = (degrees: number) => { + SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => { + const doc = Document(element.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); + const ys = ink.map(p => p.Y); + const left = Math.min(...xs); + 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 }); + + 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; + 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) * element.props.ScreenToLocalTransform().Scale; + doc._width = (right2 - left2) * element.props.ScreenToLocalTransform().Scale; + } + } + })); + } + + @computed get subMenu() { + const fillCheck = <div key="fill" style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}> + <input id="nofill" style={{ width: "inherit", position: "absolute" }} type="radio" checked={this._noFill} onChange={action(() => this._noFill = true)} /> + No Fill + <br /> + <input id="solidfill" style={{ width: "inherit", position: "absolute" }} type="radio" checked={this._solidFill} onChange={action(() => this._solidFill = true)} /> + Solid Fill + <br /> + <br /> + {this._solidFill ? "Color" : ""} + {this._solidFill ? this.fillButton : ""} + {this._fillBtn && this._solidFill ? this.fillPicker : ""} + </div>; + + const arrows = <> + <input id="arrowStart" key="arrowstart" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._arrowStart !== ""} onChange={action(() => this._arrowStart = this._arrowStart ? "" : "arrow")} /> + Arrow Head + <br /> + <input id="arrowEnd" key="arrowend" style={{ width: "inherit", position: "absolute" }} type="checkbox" checked={this._arrowEnd !== ""} onChange={action(() => this._arrowEnd = this._arrowEnd ? "" : "arrow")} /> + Arrow End + <br /> + </>; + + const lineCheck = <div key="lineCheck" style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}> + <input id="noLine" style={{ width: "inherit", position: "absolute" }} type="radio" checked={this._noLine} onChange={action(() => this._noLine = true)} /> + No Line + <br /> + <input id="solidLine" style={{ width: "inherit", position: "absolute" }} type="radio" checked={this._solidLine} onChange={action(() => this._solidLine = true)} /> + Solid Line + <br /> + <input id="dashLine" style={{ width: "inherit", position: "absolute" }} type="radio" checked={this._dashLine ? true : false} onChange={action(() => this._dashLine = "2")} /> + Dash Line + <br /> + <br /> + {(this._solidLine || this._dashLine) ? "Color" : ""} + {(this._solidLine || this._dashLine) ? this.lineButton : ""} + {this._lineBtn && (this._solidLine || this._dashLine) ? this.linePicker : ""} + <br /> + {(this._solidLine || this._dashLine) ? "Width" : ""} + {(this._solidLine || this._dashLine) ? this.widthInput : ""} + {(this._solidLine || this._dashLine) ? <input type="range" defaultValue={Number(this._currStrokeWidth || "1")} min={1} max={100} onChange={e => this._currStrokeWidth = e.target.value} /> : (null)} + <br /> + <br /> + {(this._solidLine || this._dashLine) ? arrows : ""} + </div>; + + const sizeCheck = <div key="sizeCheck" style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}> + Height {this.sizeHeightInput} + <br /> + <br /> + + Width {this.sizeWidthInput} + <br /> + <br /> + + <input id="lock" style={{ width: "inherit", position: "absolute", right: 0 }} type="checkbox" checked={this._lock} onChange={action(() => this._lock = !this._lock)} /> + Lock Ratio + <br /> + <br /> + + Rotation {this.rotationInput} + <br /> + <br /> + </div>; + + const positionCheck = <div key="posCheck" style={{ width: "inherit", backgroundColor: "#323232", color: "white", }}> + Horizontal {this.positionHorizontalInput} + <br /> + <br /> + + Vertical {this.positionVerticalInput} + <br /> + <br /> + </div>; + + return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}> + {this._subMenu.map((subMenu, i) => { + if (subMenu === "fill" || subMenu === "line") { + return <div key={subMenu} style={{ width: "inherit" }}> + <button className="antimodeMenu-button" + onPointerDown={action(() => { this._subOpen[i] = this._subOpen[i] ? false : true; })} + style={{ backgroundColor: "121212", position: "relative", display: this._currMode === "fill-drip" ? "" : "none", width: "inherit" }}> + {this._subOpen[i] ? "▼" : "▶︎"} + {subMenu} + </button> + {this._currMode === "fill-drip" && subMenu === "fill" && this._subOpen[0] ? fillCheck : ""} + {this._currMode === "fill-drip" && subMenu === "line" && this._subOpen[1] ? lineCheck : ""} + </div>; + } + else if (subMenu === "size" || subMenu === "position") { + return <div key={subMenu} style={{ width: "inherit" }}> + <button className="antimodeMenu-button" + onPointerDown={action(() => { this._subOpen[i] = this._subOpen[i] ? false : true; })} + style={{ backgroundColor: "121212", position: "relative", display: this._currMode === "ruler-combined" ? "" : "none", width: "inherit" }}> + {this._subOpen[i] ? "▼" : "▶︎"} + {subMenu} + </button> + {this._currMode === "ruler-combined" && subMenu === "size" && this._subOpen[2] ? sizeCheck : ""} + {this._currMode === "ruler-combined" && subMenu === "position" && this._subOpen[3] ? positionCheck : ""} + </div>; + } + })} + </div>; + } + + colorPicker(setter: (color: string) => {}) { + return <div className="btn-group-palette" key="colorpicker" > + {this._palette.map(color => + <button className="antimodeMenu-button" key={color} onPointerDown={action(() => setter(color))} style={{ zIndex: 1001, position: "relative" }}> + <div className="color-previewII" style={{ backgroundColor: color }} /> + </button>)} + </div>; + } + inputBox = (key: string, value: any, setter: (val: string) => {}) => { + return <> + <input style={{ color: "black", width: 80, position: "absolute", right: 20 }} + type="text" value={value} + onChange={e => setter(e.target.value)} + autoFocus /> + <button className="antiMenu-Buttonup" key="up" onPointerDown={action(() => this.upDownButtons("up", key))}> + ˄ + </button> + <br /> + <button className="antiMenu-Buttonup" key="down" onPointerDown={action(() => this.upDownButtons("down", key))} style={{ marginTop: -8 }}> + ˅ + </button> + </>; + } + + colorButton(value: string, setter: () => {}) { + return <> + <button className="antimodeMenu-button" key="fill" onPointerDown={action(e => setter())} style={{ position: "absolute", right: 80 }}> + <FontAwesomeIcon icon="fill-drip" size="lg" /> + <div className="color-previewI" style={{ backgroundColor: value ?? "121212" }} /> + </button> + <br></br> + <br></br> + </>; + } + + @computed get fillButton() { return this.colorButton(this._currFill, () => this._fillBtn = !this._fillBtn); } + @computed get lineButton() { return this.colorButton(this._currColor, () => this._lineBtn = !this._lineBtn); } + + @computed get fillPicker() { return this.colorPicker((color: string) => this._currFill = color); } + @computed get linePicker() { return this.colorPicker((color: string) => this._currColor = color); } + + @computed get widthInput() { return this.inputBox("width", this._currStrokeWidth, (val: string) => this._currStrokeWidth = val); } + @computed get sizeHeightInput() { return this.inputBox("height", this._currSizeHeight, (val: string) => this._currSizeHeight = val); } + @computed get sizeWidthInput() { return this.inputBox("height", this._currSizeWidth, (val: string) => this._currSizeWidth = val); } + @computed get rotationInput() { return this.inputBox("rotation", this._currRotation, (val: string) => this._currRotation = val); } + @computed get positionHorizontalInput() { return this.inputBox("horizontal", this._currXpos, (val: string) => this._currXpos = val); } + @computed get positionVerticalInput() { return this.inputBox("vertical", this._currYpos, (val: string) => this._currYpos = val); } + + render() { + return this.getElementVert([this.close, this.modes, this.subMenu]); + } +} +Scripting.addGlobal(function activatePen2(penBtn: any) { + if (penBtn) { + //no longer changes to inkmode + // Doc.SetSelectedTool(InkTool.Pen); + FormatShapePane.Instance.jumpTo(300, 300); + FormatShapePane.Instance.Pinned = true; + } else { + // Doc.SetSelectedTool(InkTool.None); + FormatShapePane.Instance.Pinned = false; + FormatShapePane.Instance.fadeOut(true); + } +});
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss index 753de6bef..03c6c97ab 100644 --- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss +++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss @@ -25,8 +25,13 @@ /* Make the buttons appear below each other */ } +.btn-draw { + display: inline; + /* Make the buttons appear below each other */ +} + .btn2-group { - display: block; + display: grid; background: #323232; grid-template-columns: auto; diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx index f47fca6ac..7f0bb5364 100644 --- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx @@ -17,6 +17,8 @@ import { DocumentType } from "../../../documents/DocumentTypes"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons"; +import { Cast, StrCast, BoolCast } from "../../../../fields/Types"; +import FormatShapePane from "./FormatShapePane"; library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve); @@ -26,23 +28,33 @@ library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSup export default class InkOptionsMenu extends AntimodeMenu { static Instance: InkOptionsMenu; - private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", "none"]; + private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""]; private _width = ["1", "5", "10", "100"]; // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"]; // private _icons = ["O", "∆", "ロ", "➜", "-"]; - private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",]; - private _icons = ["O", "∆", "ロ", "⎯", "✖︎", " "]; + // private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",]; + // private _icons = ["O", "∆", "ロ", "⎯⎯⎯", "✖︎", " "]; //arrowStart and arrowEnd must match and defs must exist in Inking Stroke - private _arrowStart = ["arrowHead", "arrowHead", "dot", "dot", "none"]; - private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"]; - private _arrowIcons = ["→", "↔︎", "•", "••", " "]; + // private _arrowStart = ["arrowStart", "arrowStart", "dot", "dot", "none"]; + // private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"]; + // private _arrowIcons = ["→", "↔︎", "•", "••", " "]; + private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"]; + private _head = ["", "", "arrow", "", "", "arrow", "", "", ""]; + private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""]; + private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"]; + + @observable _shapesNum = this._shape.length; + @observable _selected = this._shapesNum; + + @observable private collapsed: boolean = false; + @observable _double = ""; @observable _colorBtn = false; @observable _widthBtn = false; @observable _fillBtn = false; - @observable _arrowBtn = false; - @observable _dashBtn = false; - @observable _shapeBtn = false; + // @observable _arrowBtn = false; + // @observable _dashBtn = false; + // @observable _shapeBtn = false; @@ -50,18 +62,40 @@ export default class InkOptionsMenu extends AntimodeMenu { super(props); InkOptionsMenu.Instance = this; this._canFade = false; // don't let the inking menu fade away + this.Pinned = BoolCast(Doc.UserDoc()["inkOptionsMenu-pinned"]); + } - getColors = () => { - return this._palette; + @action + toggleMenuPin = (e: React.MouseEvent) => { + Doc.UserDoc()["inkOptionsMenu-pinned"] = this.Pinned = !this.Pinned; + if (!this.Pinned) { + // this.fadeOut(true); + } } @action - changeArrow = (arrowStart: string, arrowEnd: string) => { - SetActiveArrowStart(arrowStart); - SetActiveArrowEnd(arrowEnd); + protected toggleCollapse = (e: React.MouseEvent) => { + this.collapsed = !this.collapsed; + setTimeout(() => { + const x = Math.min(this._left, window.innerWidth - InkOptionsMenu.Instance.width); + InkOptionsMenu.Instance.jumpTo(x, this._top, true); + }, 0); + } + + + + + getColors = () => { + return this._palette; } + // @action + // changeArrow = (arrowStart: string, arrowEnd: string) => { + // SetActiveArrowStart(arrowStart); + // SetActiveArrowEnd(arrowEnd); + // } + @action changeColor = (color: string, type: string) => { const col: ColorState = { @@ -93,14 +127,8 @@ export default class InkOptionsMenu extends AntimodeMenu { case "bezier": // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300; break; - case "arrowStart": - doc.arrowStart = String(value); - break; - case "arrowEnd": - doc.arrowEnd = String(value); - break; case "dash": - doc.dash = Number(value); + doc.strokeDash = Number(value); default: break; } @@ -117,43 +145,103 @@ export default class InkOptionsMenu extends AntimodeMenu { @action changeDash = (e: React.PointerEvent): void => { SetActiveDash(ActiveDash() === "0" ? "2" : "0"); - this.editProperties(ActiveDash(), "dash"); + this.editProperties(ActiveDash(), "strokeDash"); } - @computed get arrowPicker() { - var currIcon; - for (var i = 0; i < this._arrowStart.length; i++) { - if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) { - currIcon = this._arrowIcons[i]; - if (this._arrowIcons[i] === " ") { - currIcon = "➤"; - } - } - } - var arrowPicker = <button - className="antimodeMenu-button" - key="arrow" - onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)} - style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}> - {currIcon} - </button>; - if (this._arrowBtn) { - arrowPicker = <div className="btn2-group" key="arrows"> - {arrowPicker} - {this._arrowStart.map((arrowStart, i) => { - return <button - className="antimodeMenu-button" - key={arrowStart} - onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })} - style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}> - {this._arrowIcons[i]} - </button>; - })} - </div>; - } - return arrowPicker; + @computed get drawButtons() { + const drawButtons = <div className="btn-draw" key="draw"> + {this._draw.map((icon, i) => { + return <button + className="antimodeMenu-button" + key={icon} + onPointerDown={action(() => { + this._double = ""; + + if (this._selected !== i) { + this._selected = i; + Doc.SetSelectedTool(InkTool.Pen); + SetActiveArrowStart(this._head[i]); + SetActiveArrowEnd(this._end[i]); + SetActiveBezierApprox("300"); + + GestureOverlay.Instance.InkShape = this._shape[i]; + } else { + this._selected = this._shapesNum; + Doc.SetSelectedTool(InkTool.None); + SetActiveArrowStart(""); + SetActiveArrowEnd(""); + GestureOverlay.Instance.InkShape = ""; + SetActiveBezierApprox("0"); + + } + console.log(this._selected); + + })} + onDoubleClick={action(() => { + console.log("double"); + this._double = this._draw[i]; + if (this._selected !== i) { + this._selected = i; + Doc.SetSelectedTool(InkTool.Pen); + SetActiveArrowStart(this._head[i]); + SetActiveArrowEnd(this._end[i]); + SetActiveBezierApprox("300"); + + GestureOverlay.Instance.InkShape = this._shape[i]; + } else { + this._selected = this._shapesNum; + Doc.SetSelectedTool(InkTool.None); + SetActiveArrowStart(""); + SetActiveArrowEnd(""); + GestureOverlay.Instance.InkShape = ""; + SetActiveBezierApprox("0"); + + } + + + + })} + style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}> + {this._draw[i]} + </button>; + })}</div>; + return drawButtons; } + // @computed get arrowPicker() { + // var currIcon; + // for (var i = 0; i < this._arrowStart.length; i++) { + // if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) { + // currIcon = this._arrowIcons[i]; + // if (this._arrowIcons[i] === " ") { + // currIcon = "➤"; + // } + // } + // } + // var arrowPicker = <button + // className="antimodeMenu-button" + // key="arrow" + // onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)} + // style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}> + // {currIcon} + // </button>; + // if (this._arrowBtn) { + // arrowPicker = <div className="btn2-group" key="arrows"> + // {arrowPicker} + // {this._arrowStart.map((arrowStart, i) => { + // return <button + // className="antimodeMenu-button" + // key={arrowStart} + // onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })} + // style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}> + // {this._arrowIcons[i]} + // </button>; + // })} + // </div>; + // } + // return arrowPicker; + // } + @computed get widthPicker() { var widthPicker = <button className="antimodeMenu-button" @@ -170,7 +258,7 @@ export default class InkOptionsMenu extends AntimodeMenu { className="antimodeMenu-button" key={wid} onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })} - style={{ backgroundColor: this._widthBtn ? "121212" : "" }}> + style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}> {wid} </button>; })} @@ -200,7 +288,7 @@ export default class InkOptionsMenu extends AntimodeMenu { className="antimodeMenu-button" key={color} onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })} - style={{ backgroundColor: this._colorBtn ? "121212" : "" }}> + style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}> {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */} <div className="color-previewII" style={{ backgroundColor: color }}></div> </button>; @@ -209,6 +297,14 @@ export default class InkOptionsMenu extends AntimodeMenu { } return colorPicker; } + @computed get formatPane() { + 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" /> + </button>; + } @computed get fillPicker() { var fillPicker = <button @@ -221,14 +317,14 @@ export default class InkOptionsMenu extends AntimodeMenu { <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div> </button>; if (this._fillBtn) { - fillPicker = <div className="btn-group" key="fill"> + fillPicker = <div className="btn-group" key="fill" > {fillPicker} {this._palette.map(color => { return <button className="antimodeMenu-button" key={color} onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })} - style={{ backgroundColor: this._fillBtn ? "121212" : "" }}> + style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}> <div className="color-previewII" style={{ backgroundColor: color }}></div> </button>; })} @@ -238,48 +334,48 @@ export default class InkOptionsMenu extends AntimodeMenu { return fillPicker; } - @computed get shapePicker() { - var currIcon; - if (GestureOverlay.Instance.InkShape === "") { - currIcon = <FontAwesomeIcon icon="shapes" size="lg" />; - } else { - for (var i = 0; i < this._icons.length; i++) { - if (GestureOverlay.Instance.InkShape === this._buttons[i]) { - currIcon = this._icons[i]; - } - } - } - var shapePicker = <button - className="antimodeMenu-button" - key="shape" - onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)} - style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}> - {currIcon} - </button>; - if (this._shapeBtn) { - shapePicker = <div className="btn2-group" key="shape"> - {shapePicker} - {this._buttons.map((btn, i) => { - var ttl = btn; - if (btn === "") { - ttl = "no shape"; - } - if (btn === "noRec") { - ttl = "disable shape recognition"; - } - return <button - className="antimodeMenu-button" - title={`Draw ${btn}`} - key={ttl} - onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })} - style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}> - {this._icons[i]} - </button>; - })} - </div>; - } - return shapePicker; - } + // @computed get shapePicker() { + // var currIcon; + // if (GestureOverlay.Instance.InkShape === "") { + // currIcon = <FontAwesomeIcon icon="shapes" size="lg" />; + // } else { + // for (var i = 0; i < this._icons.length; i++) { + // if (GestureOverlay.Instance.InkShape === this._buttons[i]) { + // currIcon = this._icons[i]; + // } + // } + // } + // var shapePicker = <button + // className="antimodeMenu-button" + // key="shape" + // onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)} + // style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}> + // {currIcon} + // </button>; + // if (this._shapeBtn) { + // shapePicker = <div className="btn2-group" key="shape"> + // {shapePicker} + // {this._buttons.map((btn, i) => { + // var ttl = btn; + // if (btn === "") { + // ttl = "no shape"; + // } + // if (btn === "noRec") { + // ttl = "disable shape recognition"; + // } + // return <button + // className="antimodeMenu-button" + // title={`Draw ${btn}`} + // key={ttl} + // onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })} + // style={{ backgroundColor: this._shapeBtn ? "121212" : "", fontSize: "20" }}> + // {this._icons[i]} + // </button>; + // })} + // </div>; + // } + // return shapePicker; + // } @computed get bezierButton() { return <button @@ -310,23 +406,35 @@ export default class InkOptionsMenu extends AntimodeMenu { // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}> // <FontAwesomeIcon icon="arrows-alt" size="lg" /> // </button>, - this.shapePicker, - this.bezierButton, + // this.shapePicker, + // this.bezierButton, this.widthPicker, this.colorPicker, this.fillPicker, - this.arrowPicker, - this.dashButton, + this.drawButtons, + this.formatPane, + // this.arrowPicker, + // this.dashButton, + <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: this.Pinned ? "#121212" : "", display: this.collapsed ? "none" : undefined }}> + <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} /> + </button> ]; + + // return this.getElement(buttons); return this.getElement(buttons); } } Scripting.addGlobal(function activatePen(penBtn: any) { if (penBtn) { - Doc.SetSelectedTool(InkTool.Pen); + //no longer changes to inkmode + // Doc.SetSelectedTool(InkTool.Pen); InkOptionsMenu.Instance.jumpTo(300, 300); + InkOptionsMenu.Instance.Pinned = true; + } else { - Doc.SetSelectedTool(InkTool.None); + // Doc.SetSelectedTool(InkTool.None); + InkOptionsMenu.Instance.Pinned = false; InkOptionsMenu.Instance.fadeOut(true); + } }); diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index b3a9b6fee..57028b0ca 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -14,7 +14,7 @@ import { ViewBoxBaseComponent } from "../DocComponent"; import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox } from "../InkingStroke"; import "./ColorBox.scss"; import { FieldView, FieldViewProps } from './FieldView'; -import { FormattedTextBox } from "./formattedText/FormattedTextBox"; +import { DocumentType } from "../../documents/DocumentTypes"; type ColorDocument = makeInterface<[typeof documentSchema]>; const ColorDocument = makeInterface(documentSchema); @@ -63,9 +63,15 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument 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>) => SetActiveInkWidth(e.target.value)} /> + <input type="range" defaultValue={ActiveInkWidth() ?? 2} min={1} max={100} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + SetActiveInkWidth(e.target.value); + SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeWidth = Number(e.target.value)); + }} /> <div> {ActiveInkBezierApprox() ?? 2}</div> - <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => SetActiveBezierApprox(e.target.value)} /> + <input type="range" defaultValue={ActiveInkBezierApprox() ?? 2} min={0} max={300} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + SetActiveBezierApprox(e.target.value); + SelectionManager.SelectedDocuments().filter(i => StrCast(i.rootDoc.type) === DocumentType.INK).map(i => i.rootDoc.strokeBezier = e.target.value); + }} /> <br /> <br /> </div> diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 962d0012f..3c5c48c7c 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1168,7 +1168,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } else if ([this._editorView!.state.schema.nodes.ordered_list, this._editorView!.state.schema.nodes.listItem].includes(node?.type) && node !== (this._editorView!.state.selection as NodeSelection)?.node && pcords) { - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos!))); + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(NodeSelection.create(this._editorView!.state.doc, pcords.pos))); } } if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } |