aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/collectionFreeForm
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2020-08-06 22:30:36 -0400
committerbobzel <zzzman@gmail.com>2020-08-06 22:30:36 -0400
commita1977d1ae00a4f0c3907eb243ba1c04a5acc5c62 (patch)
tree66bbd9b9b912c30f893c2204716ff8e4f9086019 /src/client/views/collections/collectionFreeForm
parent72b95c76f156a15f645703ba77c05aeb62c903ff (diff)
parent0910e7387fae485d7c11eb71b6abcce865403b13 (diff)
Merge branch 'master' into new_audio
Diffstat (limited to 'src/client/views/collections/collectionFreeForm')
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx40
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss139
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx79
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.scss14
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx379
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx17
-rw-r--r--src/client/views/collections/collectionFreeForm/PropertiesView.scss733
-rw-r--r--src/client/views/collections/collectionFreeForm/PropertiesView.tsx1037
8 files changed, 2281 insertions, 157 deletions
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
index a4fd5384f..b00074cc6 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx
@@ -142,9 +142,16 @@ export function computePivotLayout(
const fieldKey = "data";
const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>();
+ let nonNumbers = 0;
const pivotFieldKey = toLabel(pivotDoc._pivotField);
childPairs.map(pair => {
- const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+ const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) :
+ Cast(pair.layout[pivotFieldKey], listSpec("string"), null);
+
+ const num = toNumber(pair.layout[pivotFieldKey]);
+ if (num === undefined || Number.isNaN(num)) {
+ nonNumbers++;
+ }
const val = Field.toString(pair.layout[pivotFieldKey] as Field);
if (lval) {
lval.forEach((val, i) => {
@@ -168,13 +175,6 @@ export function computePivotLayout(
});
}
});
- let nonNumbers = 0;
- childPairs.map(pair => {
- const num = toNumber(pair.layout[pivotFieldKey]);
- if (num === undefined || Number.isNaN(num)) {
- nonNumbers++;
- }
- });
const pivotNumbers = nonNumbers / childPairs.length < .1;
if (pivotColumnGroups.size > 10) {
const arrayofKeys = Array.from(pivotColumnGroups.keys());
@@ -434,27 +434,3 @@ function normalizeResults(
payload: gname.payload
})));
}
-
-export function AddCustomFreeFormLayout(doc: Doc, dataKey: string): () => void {
- return () => {
- const addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
- let overlayDisposer: () => void = emptyFunction; // filled in below after we have a reference to the scriptingBox
- const scriptField = Cast(doc[key], ScriptField);
- const scriptingBox = <ScriptBox initialText={scriptField && scriptField.script.originalScript}
- // tslint:disable-next-line: no-unnecessary-callback-wrapper
- onCancel={() => overlayDisposer()} // don't get rid of the function wrapper-- we don't want to use the current value of overlayDiposer, but the one set below
- onSave={(text, onError) => {
- const script = CompileScript(text, { params, requiredType, typecheck: false });
- if (!script.compiled) {
- onError(script.errors.map(error => error.messageText).join("\n"));
- } else {
- doc[key] = new ScriptField(script);
- overlayDisposer();
- }
- }} />;
- overlayDisposer = OverlayView.Instance.addWindow(scriptingBox, options);
- };
- addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300, title: "Layout Initialization" }, { collection: "Doc", docs: "Doc[]" }, undefined);
- addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300, title: "Layout Script" }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
- };
-}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 92aee3776..2b07c4efb 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -13,16 +13,153 @@
}
.collectionfreeformview-viewdef {
- > .collectionFreeFormDocumentView-container {
+ >.collectionFreeFormDocumentView-container {
pointer-events: none;
+
.contentFittingDocumentDocumentView-previewDoc {
pointer-events: all;
}
}
+
+ svg.presPaths {
+ position: absolute;
+ z-index: 100000;
+ overflow: visible;
+ }
+
+ svg.presPaths-hidden {
+ display: none;
+ }
}
.collectionfreeformview-none {
touch-action: none;
+
+ svg.presPaths {
+ position: absolute;
+ z-index: 100000;
+ overflow: visible;
+ }
+
+ svg.presPaths-hidden {
+ display: none;
+ }
+}
+
+.pathOrder {
+ position: absolute;
+ z-index: 200000;
+
+ .pathOrder-frame {
+ position: absolute;
+ width: 40;
+ text-align: center;
+ font-size: 30;
+ background-color: #69a6db;
+ font-family: Roboto;
+ font-weight: 300;
+ }
+}
+
+.progressivizeButton {
+ position: absolute;
+ display: grid;
+ grid-template-columns: auto 20px auto;
+ transform: translate(-105%, 0);
+ align-items: center;
+ border: black solid 1px;
+ border-radius: 3px;
+ justify-content: center;
+ width: 40;
+ z-index: 30000;
+ height: 20;
+ overflow: hidden;
+ background-color: #d5dce2;
+ transition: all 1s;
+
+ .progressivizeButton-prev:hover {
+ color: #5a9edd;
+ }
+
+ .progressivizeButton-frame {
+ justify-self: center;
+ text-align: center;
+ width: 15px;
+ }
+
+ .progressivizeButton-next:hover {
+ color: #5a9edd;
+ }
+}
+
+.resizable {
+ background: rgba(0, 0, 0, 0.2);
+ width: 100px;
+ height: 100px;
+ position: absolute;
+ top: 100px;
+ left: 100px;
+
+ .resizers {
+ width: 100%;
+ height: 100%;
+ border: 3px solid #69a6db;
+ box-sizing: border-box;
+
+ .resizer {
+ position: absolute;
+ width: 10px;
+ height: 10px;
+ border-radius: 50%;
+ /*magic to turn square into circle*/
+ background: white;
+ border: 3px solid #69a6db;
+ }
+
+ .resizer.top-left {
+ left: -3px;
+ top: -3px;
+ cursor: nwse-resize;
+ /*resizer cursor*/
+ }
+
+ .resizer.top-right {
+ right: -3px;
+ top: -3px;
+ cursor: nesw-resize;
+ }
+
+ .resizer.bottom-left {
+ left: -3px;
+ bottom: -3px;
+ cursor: nesw-resize;
+ }
+
+ .resizer.bottom-right {
+ right: -3px;
+ bottom: -3px;
+ cursor: nwse-resize;
+ }
+ }
+}
+
+.progressivizeMove-frame {
+ width: 20px;
+ border-radius: 2px;
+ z-index: 100000;
+ color: white;
+ text-align: center;
+ background-color: #5a9edd;
+ transform: translate(-110%, 110%);
+}
+
+.progressivizeButton:hover {
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.5);
+
+ .progressivizeButton-frame {
+ background-color: #5a9edd;
+ color: white;
+ }
}
.collectionFreeform-customText {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index daa6e7440..5b1f3c01c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,7 +1,7 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx";
+import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import { computedFn } from "mobx-utils";
import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc";
@@ -46,6 +46,7 @@ import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { PresBox } from "../../nodes/PresBox";
import { SearchUtil } from "../../../util/SearchUtil";
import { LinkManager } from "../../../util/LinkManager";
@@ -57,7 +58,6 @@ export const panZoomSchema = createSchema({
currentTimecode: "number",
displayTimecode: "number",
currentFrame: "number",
- arrangeScript: ScriptField,
arrangeInit: ScriptField,
useClusters: "boolean",
fitToBox: "boolean",
@@ -77,7 +77,7 @@ export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: boolean;
- scaleField?: string; // used by formattedTextBox when displaying a sidebar freeform view which needs its own scale field
+ scaleField?: string;
noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
};
@@ -214,7 +214,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const layoutDoc = Doc.Layout(d);
if (this.Document.currentFrame !== undefined) {
const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
- CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.opacity);
+ CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.opacity);
} else {
d.x = x + NumCast(d.x) - dropPos[0];
d.y = y + NumCast(d.y) - dropPos[1];
@@ -892,7 +892,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.focus(doc);
} else {
const contextHgt = Doc.AreProtosEqual(annotOn, this.props.Document) && this.props.VisibleHeight ? this.props.VisibleHeight() : NumCast(annotOn._height);
- const offset = annotOn && (contextHgt / 2 * 96 / 72);
+ const offset = annotOn && (contextHgt / 2);
this.props.Document._scrollY = NumCast(doc.y) - offset;
}
@@ -912,7 +912,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
// !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1);
// } else {
if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) {
- if (!doc.z) this.setPan(newPanX, newPanY, "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
+ // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active...
+ if (!doc.z) this.setPan(newPanX, newPanY, doc.presTransition || doc.presTransition === 0 ? `transform ${doc.presTransition}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow
}
Doc.BrushDoc(this.props.Document);
this.props.focus(this.props.Document);
@@ -1005,10 +1006,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return this.props.addDocTab(doc, where);
});
getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData {
- const result = this.Document.arrangeScript?.script.run(params, console.log);
- if (result?.success) {
- return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" };
- }
const layoutDoc = Doc.Layout(params.pair.layout);
const { x, y, opacity } = this.Document.currentFrame === undefined ? params.pair.layout :
CollectionFreeFormDocumentView.getValues(params.pair.layout, this.Document.currentFrame || 0);
@@ -1149,7 +1146,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@action
componentDidMount() {
super.componentDidMount?.();
- this._layoutComputeReaction = reaction(() => { TraceMobx(); return this.doLayoutComputation },
+ this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
@@ -1244,13 +1241,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const appearanceItems = appearance && "subitems" in appearance ? appearance.subitems : [];
appearanceItems.push({ description: "Reset View", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document[this.scaleFieldKey] = 1; }, icon: "compress-arrows-alt" });
appearanceItems.push({ description: `${this.fitToContent ? "Make Zoomable" : "Scale to Window"}`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
- appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+ !Doc.UserDoc().noviceMode ? appearanceItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }) : null;
!appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
const viewctrls = ContextMenu.Instance.findByDescription("UI Controls...");
const viewCtrlItems = viewctrls && "subitems" in viewctrls ? viewctrls.subitems : [];
- viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
- viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
+
+
+ !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " Snap Lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" }) : null;
+ !Doc.UserDoc().noviceMode ? viewCtrlItems.push({ description: (this.Document.useClusters ? "Hide" : "Show") + " Clusters", event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" }) : null;
!viewctrls && ContextMenu.Instance.addItem({ description: "UI Controls...", subitems: viewCtrlItems, icon: "eye" });
const options = ContextMenu.Instance.findByDescription("Options...");
@@ -1260,7 +1259,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this.props.ContainingCollectionView &&
optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
- appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+ optionItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
if (!Doc.UserDoc().noviceMode) {
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
@@ -1295,7 +1294,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
setTimeout(() => {
SearchUtil.Search(`{!join from=id to=proto_i}id:link*`, true, {}).then(docs => {
docs.docs.forEach(d => LinkManager.Instance.addLink(d));
- })
+ });
}, 2000); // need to give solr some time to update so that this query will find any link docs we've added.
}
}
@@ -1402,6 +1401,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
<CollectionFreeFormViewPannableContents
centeringShiftX={this.centeringShiftX}
centeringShiftY={this.centeringShiftY}
+ presPaths={BoolCast(this.Document.presPathView)}
+ progressivize={BoolCast(this.Document.editProgressivize)}
+ zoomProgressivize={BoolCast(this.Document.editZoomProgressivize)}
transition={Cast(this.layoutDoc._viewTransition, "string", null)}
viewDefDivClick={this.props.viewDefDivClick}
zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
@@ -1485,11 +1487,53 @@ interface CollectionFreeFormViewPannableContentsProps {
viewDefDivClick?: ScriptField;
children: () => JSX.Element[];
transition?: string;
+ presPaths?: boolean;
+ progressivize?: boolean;
+ zoomProgressivize?: boolean;
}
@observer
class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{
+ @computed get zoomProgressivize() {
+ return PresBox.Instance && this.props.zoomProgressivize ? PresBox.Instance.zoomProgressivizeContainer : (null);
+ }
+
+ @computed get progressivize() {
+ return PresBox.Instance && this.props.progressivize ? PresBox.Instance.progressivizeChildDocs : (null);
+ }
+
+ @computed get presPaths() {
+ const presPaths = "presPaths" + (this.props.presPaths ? "" : "-hidden");
+ if (PresBox.Instance) return (
+ <>
+ {!this.props.presPaths ? (null) : <><div>{PresBox.Instance.order}</div>
+ <svg className={presPaths}>
+ <defs>
+ <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
+ <path d="M0,0 L0,6 L9,3 z" fill="#69a6db" />
+ </marker>
+ <marker id="square" markerWidth="3" markerHeight="3" overflow="visible"
+ refX="5" refY="5" orient="auto" markerUnits="strokeWidth">
+ <path d="M 5,1 L 9,5 5,9 1,5 z" fill="#69a6db" />
+ </marker>
+ <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4"
+ orient="auto" overflow="visible">
+ <rect x="1" y="1" width="5" height="5" fill="#69a6db" />
+ </marker>
+
+ <marker id="markerArrow" markerWidth="5" markerHeight="5" refX="2" refY="7"
+ orient="auto" overflow="visible">
+ <path d="M2,2 L2,13 L8,7 L2,2" fill="#69a6db" />
+ </marker>
+ </defs>;
+ {PresBox.Instance.paths}
+ </svg></>}
+ </>
+ );
+ }
+
render() {
+ // trace();
const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : "-none");
const cenx = this.props.centeringShiftX();
const ceny = this.props.centeringShiftY();
@@ -1502,6 +1546,9 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
transition: this.props.transition
}}>
{this.props.children()}
+ {this.presPaths}
+ {this.progressivize}
+ {this.zoomProgressivize}
</div>;
}
}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
index 88876471c..d49ab27fb 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
@@ -24,14 +24,18 @@
.formatShapePane-inputBtn {
width: inherit;
- position: absolute;
+ position: absolute;
}
-.sketch-picker {
- background: #323232;
-
- .flexbox-fit {
+.btn-group-palette {
+ .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..6263be261 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -12,6 +12,7 @@ import { SelectionManager } from "../../../util/SelectionManager";
import AntimodeMenu from "../../AntimodeMenu";
import "./FormatShapePane.scss";
import { undoBatch } from "../../../util/UndoManager";
+import { ColorState, SketchPicker } from 'react-color';
@observer
export default class FormatShapePane extends AntimodeMenu {
@@ -20,14 +21,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 +104,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 }[] = [];
+ ink.forEach(i => {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
+ const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / 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 }[] = [];
+ ink.forEach(i => {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = ((doc.x || 0) - oldX) + (i.X * (doc._width || 0)) / oldWidth;
+ const newY = ((doc.y || 0) - oldY) + (i.Y * (doc._height || 0)) / oldHeight;
+ newPoints.push({ X: newX, Y: newY });
+ });
+ doc.data = new InkField(newPoints);
+ }
+ }
});
break;
}
@@ -121,12 +163,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 +176,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;
+ ink.forEach(i => {
+ const newX = Math.cos(angle) * (i.X - _centerPoints[index].X) - Math.sin(angle) * (i.Y - _centerPoints[index].Y) + _centerPoints[index].X;
+ const newY = Math.sin(angle) * (i.X - _centerPoints[index].X) + Math.cos(angle) * (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) {
- 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>)}
+ 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
+
+ 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 />
+ </>;
+ }
+
+ 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 fillButton() { return this.colorButton(this.colorFil, () => this._fillBtn = !this._fillBtn); }
- @computed get lineButton() { return this.colorButton(this.colorStk, () => this._lineBtn = !this._lineBtn); }
+ 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" /> */}
+ ⟲
- @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color); }
- @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color); }
+ </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 +480,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/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 764758eee..88fe03efd 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,7 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly, AclAdmin } from "../../../../fields/Doc";
-import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util";
+import { GetEffectiveAcl } from "../../../../fields/util";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -281,7 +281,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._downX = x;
this._downY = y;
const effectiveAcl = GetEffectiveAcl(this.props.Document);
- if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl) || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+ if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
this.clearSelection();
}
});
@@ -339,10 +339,21 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
this._visible = false;
}
+ @undoBatch
@action
delete = () => {
- this.props.removeDocument(this.marqueeSelect(false));
+ const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc;
+ const selected = this.marqueeSelect(false);
SelectionManager.DeselectAll();
+
+ selected.map(doc => {
+ const effectiveAcl = GetEffectiveAcl(doc);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete
+ recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true);
+ this.props.removeDocument(doc);
+ }
+ });
+
this.cleanupInteractions(false);
MarqueeOptionsMenu.Instance.fadeOut(true);
this.hideMarquee();
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
new file mode 100644
index 000000000..5e0c9fcbb
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
@@ -0,0 +1,733 @@
+.propertiesView {
+
+ background-color: rgb(205, 205, 205);
+ height: 100%;
+ font-family: "Noto Sans";
+ cursor: auto;
+
+ overflow-x: hidden;
+ overflow-y: scroll;
+
+ .propertiesView-title {
+ background-color: rgb(159, 159, 159);
+ text-align: center;
+ padding-top: 12px;
+ padding-bottom: 12px;
+ display: flex;
+ font-size: 18px;
+ font-weight: bold;
+ justify-content: center;
+
+ .propertiesView-title-icon {
+ width: 20px;
+ height: 20px;
+ padding-left: 38px;
+ margin-top: -5px;
+ right: 19;
+ position: absolute;
+
+ &:hover {
+ color: grey;
+ cursor: pointer;
+ }
+
+ }
+
+ }
+
+ .propertiesView-name {
+ border-bottom: 1px solid black;
+ padding: 8.5px;
+ font-size: 12.5px;
+
+ &:hover {
+ cursor: text;
+ }
+ }
+
+ .propertiesView-settings {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+ font-size: 12.5px;
+ font-weight: bold;
+
+ .propertiesView-settings-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .propertiesView-settings-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-settings-content {
+ margin-left: 12px;
+ padding-bottom: 10px;
+ padding-top: 8px;
+ }
+
+ }
+
+ .propertiesView-sharing {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-sharing-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .propertiesView-sharing-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-sharing-content {
+ font-size: 10px;
+ padding: 10px;
+ margin-left: 5px;
+
+ .change-buttons {
+ display: flex;
+
+ button {
+ width: 5;
+ height: 5;
+ }
+
+ input {
+ width: 100%;
+ }
+ }
+ }
+ }
+
+ .propertiesView-appearance {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-appearance-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .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;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .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;
+ height: 12px;
+ background-color: black;
+ border-radius: 10px;
+ padding-left: 2px;
+ padding-right: 2px;
+ margin-top: 2px;
+ margin-left: 3px;
+
+ .notify-button-icon {
+ width: 6px;
+ height: 6.5px;
+ margin-left: .5px;
+ }
+
+ &:hover {
+ background-color: rgb(158, 158, 158);
+ cursor: pointer;
+ }
+ }
+
+ .expansion-button-icon {
+ width: 11px;
+ height: 11px;
+ color: black;
+ margin-left: 27px;
+
+ &:hover {
+ color: rgb(131, 131, 131);
+ cursor: pointer;
+ }
+ }
+
+ .propertiesView-sharingTable {
+
+ // whatever's commented out - add it back in when adding the buttons
+
+ // border: 1.5px solid black;
+ border: 1px solid black;
+ padding: 5px; // remove when adding buttons
+ border-radius: 6px; // remove when adding buttons
+ margin-right: 10px; // remove when adding buttons
+ // width: 100%;
+ // display: inline-table;
+ background-color: #ececec;
+ max-height: 130px;
+ overflow-y: scroll;
+
+ .propertiesView-sharingTable-item {
+
+ display: flex;
+ // padding: 5px;
+ padding: 3px;
+ align-items: center;
+ border-bottom: 0.5px solid grey;
+ cursor: pointer;
+
+ &:hover .propertiesView-sharingTable-item-name {
+ overflow-x: unset;
+ white-space: unset;
+ overflow-wrap: break-word;
+ }
+
+ .propertiesView-sharingTable-item-name {
+ font-weight: bold;
+ width: 95px;
+ overflow-x: hidden;
+ display: inline-block;
+ text-overflow: ellipsis;
+ white-space: nowrap;
+ }
+
+ .propertiesView-sharingTable-item-permission {
+ display: flex;
+ right: 34;
+ float: right;
+ position: absolute;
+
+ .permissions-select {
+ z-index: 1;
+ border: none;
+ background-color: inherit;
+ width: 75px;
+ //text-align: justify; // for Edge
+ //text-align-last: end;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ &:last-child {
+ border-bottom: none;
+ }
+ }
+ }
+
+ .propertiesView-fields {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-fields-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .propertiesView-fields-title-name {
+ font-size: 12.5px;
+ font-weight: bold;
+ white-space: nowrap;
+ width: 35px;
+ display: flex;
+ }
+
+ .propertiesView-fields-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-fields-checkbox {
+ float: right;
+ height: 20px;
+ margin-top: -9px;
+
+ .propertiesView-fields-checkbox-text {
+ font-size: 7px;
+ margin-top: -10px;
+ margin-left: 6px;
+ }
+ }
+
+ .propertiesView-fields-content {
+ font-size: 10px;
+ margin-left: 2px;
+ padding: 10px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .field {
+ display: flex;
+ font-size: 7px;
+ background-color: #e8e8e8;
+ padding-right: 3px;
+ border: 0.5px solid grey;
+ border-radius: 5px;
+ padding-left: 3px;
+ }
+
+ .uneditable-field {
+ display: flex;
+ overflow-y: visible;
+ margin-bottom: 2px;
+
+ &:hover {
+ cursor: auto;
+ }
+ }
+
+ .propertiesView-layout {
+
+ .propertiesView-layout-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .propertiesView-layout-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-layout-content {
+ overflow: hidden;
+ padding: 10px;
+ }
+
+ }
+
+ .propertiesView-presTrails {
+ border-bottom: 1px solid black;
+ //padding: 8.5px;
+
+ .propertiesView-presTrails-title {
+ font-weight: bold;
+ font-size: 12.5px;
+ padding: 4px;
+ display: flex;
+ color: white;
+ padding-left: 8px;
+ background-color: rgb(51, 51, 51);
+
+ &:hover {
+ cursor: pointer;
+ }
+
+ .propertiesView-presTrails-title-icon {
+ float: right;
+ right: 0;
+ position: absolute;
+ margin-left: 2px;
+ margin-right: 9px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+ }
+
+ .propertiesView-presTrails-content {
+ font-size: 10px;
+ padding: 10px;
+ margin-left: 5px;
+ }
+ }
+}
+
+.inking-button {
+
+ display: flex;
+
+ .inking-button-points {
+ background-color: #333333;
+ padding: 7px;
+ border-radius: 7px;
+ margin-right: 32px;
+ width: 32;
+ height: 32;
+ padding-top: 9px;
+ margin-left: 18px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ .inking-button-lock {
+ background-color: #333333;
+ padding: 7px;
+ border-radius: 7px;
+ margin-right: 32px;
+ width: 32;
+ height: 32;
+ padding-top: 9px;
+ padding-left: 10px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ .inking-button-rotate {
+ background-color: #333333;
+ padding: 7px;
+ border-radius: 7px;
+ width: 32;
+ height: 32;
+ padding-top: 9px;
+ padding-left: 10px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+}
+
+.inputBox-duo {
+ display: flex;
+}
+
+.inputBox {
+
+ margin-top: 10px;
+ display: flex;
+ height: 19px;
+ margin-right: 15px;
+
+ .inputBox-title {
+ font-size: 12px;
+ padding-right: 5px;
+ }
+
+ .inputBox-input {
+ font-size: 10px;
+ width: 50px;
+ margin-right: 1px;
+ border-radius: 3px;
+
+ &:hover {
+ cursor: pointer;
+ }
+ }
+
+ .inputBox-button {
+
+ .inputBox-button-up {
+ background-color: #333333;
+ height: 9px;
+ padding-left: 3px;
+ padding-right: 3px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ border-radius: 1.5px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ .inputBox-button-down {
+ background-color: #333333;
+ height: 9px;
+ padding-left: 3px;
+ padding-right: 3px;
+ padding-top: 1px;
+ padding-bottom: 1px;
+ border-radius: 1.5px;
+
+ &:hover {
+ background: rgb(131, 131, 131);
+ transform: scale(1.05);
+ cursor: pointer;
+ }
+ }
+
+ }
+}
+
+.color-palette {
+ width: 160px;
+ height: 360;
+}
+
+.strokeAndFill {
+ display: flex;
+ margin-top: 10px;
+
+ .fill {
+ margin-right: 40px;
+ display: flex;
+ padding-bottom: 7px;
+ margin-left: 35px;
+
+ .fill-title {
+ font-size: 12px;
+ margin-right: 2px;
+ }
+
+ .fill-button {
+ padding-top: 2px;
+ margin-top: -1px;
+ }
+ }
+
+ .stroke {
+ display: flex;
+
+ .stroke-title {
+ font-size: 12px;
+ }
+
+ .stroke-button {
+ padding-top: 2px;
+ margin-left: 2px;
+ margin-top: -1px;
+ }
+ }
+}
+
+.propertiesView-presSelected {
+ border-top: solid 1px darkgrey;
+ width: 100%;
+ padding-top: 5px;
+ font-family: Roboto;
+ font-weight: 500;
+ display: inline-flex;
+
+ .propertiesView-selectedList {
+ border-left: solid 1px darkgrey;
+ margin-left: 10px;
+ padding-left: 5px;
+
+ .selectedList-items {
+ font-size: 12;
+ font-weight: 300;
+ margin-top: 1;
+ }
+ }
+}
+
+.widthAndDash {
+
+ .width {
+ .width-top {
+ display: flex;
+
+ .width-title {
+ font-size: 12;
+ margin-right: 20px;
+ margin-left: 35px;
+ text-align: center;
+ }
+
+ .width-input {
+ margin-right: 30px;
+ margin-top: -10px;
+ }
+ }
+
+ .width-range {
+ margin-right: 1px;
+ margin-bottom: 6;
+ }
+ }
+
+ .arrows {
+
+ display: flex;
+ margin-bottom: 3px;
+ margin-left: 4px;
+
+ .arrows-head {
+
+ display: flex;
+ margin-right: 35px;
+
+ .arrows-head-title {
+ font-size: 10;
+ }
+
+ .arrows-head-input {
+ margin-left: 6px;
+ margin-top: 2px;
+ }
+ }
+
+ .arrows-tail {
+ display: flex;
+
+ .arrows-tail-title {
+ font-size: 10;
+ }
+
+ .arrows-tail-input {
+ margin-left: 6px;
+ margin-top: 2px;
+ }
+ }
+ }
+
+ .dashed {
+
+ display: flex;
+ margin-left: 64px;
+ margin-bottom: 6px;
+
+ .dashed-title {
+ font-size: 10;
+ }
+
+ .dashed-input {
+ margin-left: 6px;
+ margin-top: 2px;
+ }
+ }
+}
+
+.editable-title {
+ border: none;
+ padding: 6px;
+ padding-bottom: 2px;
+
+
+ &:hover {
+ border: 0.75px solid rgb(122, 28, 28);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
new file mode 100644
index 000000000..1a8ee3ea1
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -0,0 +1,1037 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import "./PropertiesView.scss";
+import { observable, action, computed, runInAction } from "mobx";
+import { Doc, Field, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt, DocCastAsync } from "../../../../fields/Doc";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { EditableView } from "../../EditableView";
+import { KeyValueBox } from "../../nodes/KeyValueBox";
+import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { ContentFittingDocumentView } from "../../nodes/ContentFittingDocumentView";
+import { returnFalse, returnOne, emptyFunction, emptyPath, returnTrue, returnZero, returnEmptyFilter, Utils } from "../../../../Utils";
+import { Id } from "../../../../fields/FieldSymbols";
+import { Transform } from "../../../util/Transform";
+import { PropertiesButtons } from "../../PropertiesButtons";
+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 { SharingPermissions, GetEffectiveAcl } from "../../../../fields/util";
+import { InkField } from "../../../../fields/InkField";
+import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { ColorState, SketchPicker } from "react-color";
+import "./FormatShapePane.scss";
+import { discovery_v1 } from "googleapis";
+import { PresBox } from "../../nodes/PresBox";
+import { DocumentManager } from "../../../util/DocumentManager";
+import FormatShapePane from "./FormatShapePane";
+const higflyout = require("@hig/flyout");
+export const { anchorPoints } = higflyout;
+export const Flyout = higflyout.default;
+
+// import * as fa from '@fortawesome/free-solid-svg-icons';
+// import { library } from "@fortawesome/fontawesome-svg-core";
+
+// library.add(fa.faPlus, fa.faMinus, fa.faCog);
+
+interface PropertiesViewProps {
+ width: number;
+ height: number;
+ renderDepth: number;
+ ScreenToLocalTransform: () => Transform;
+ onDown: (event: any) => void;
+}
+
+@observer
+export class PropertiesView extends React.Component<PropertiesViewProps> {
+ private _widthUndo?: UndoManager.Batch;
+
+ @computed get MAX_EMBED_HEIGHT() { return 200; }
+
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else if (PresBox.Instance._selectedArray.length) {
+ return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc);
+ } else { return undefined; }
+ }
+ @computed get isPres(): boolean {
+ if (this.selectedDoc?.type === DocumentType.PRES) return true;
+ return false;
+ }
+ @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get dataDoc() { return this.selectedDocumentView?.dataDoc; }
+
+ @observable layoutFields: boolean = false;
+
+ @observable openActions: boolean = true;
+ @observable openSharing: boolean = true;
+ @observable openFields: boolean = true;
+ @observable openLayout: boolean = true;
+ @observable openAppearance: boolean = true;
+ @observable openTransform: boolean = true;
+ // @observable selectedUser: string = "";
+ // @observable addButtonPressed: boolean = false;
+
+ //Pres Trails booleans:
+ @observable openPresTransitions: boolean = false;
+ @observable openPresProgressivize: boolean = false;
+ @observable openAddSlide: boolean = false;
+ @observable openSlideOptions: boolean = false;
+
+ @observable inActions: boolean = false;
+ @observable _controlBtn: boolean = false;
+ @observable _lock: boolean = false;
+
+ @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; }
+
+ @action
+ rtfWidth = () => {
+ if (this.selectedDoc) {
+ return Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20);
+ } else {
+ return 0;
+ }
+ }
+ @action
+ rtfHeight = () => {
+ if (this.selectedDoc) {
+ return this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT;
+ } else {
+ return 0;
+ }
+ }
+
+ @action
+ docWidth = () => {
+ if (this.selectedDoc) {
+ const layoutDoc = this.selectedDoc;
+ const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
+ if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.width - 20));
+ return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20;
+ } else {
+ return 0;
+ }
+ }
+
+ @action
+ docHeight = () => {
+ if (this.selectedDoc && this.dataDoc) {
+ const layoutDoc = this.selectedDoc;
+ return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => {
+ const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]());
+ if (aspect) return this.docWidth() * aspect;
+ return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) :
+ Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth,
+ NumCast(this.props.height)))) :
+ NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50;
+ })()));
+ } else {
+ return 0;
+ }
+ }
+
+ @computed get expandedField() {
+ if (this.dataDoc && this.selectedDoc) {
+ const ids: { [key: string]: string } = {};
+ const doc = this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc;
+ doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
+ const rows: JSX.Element[] = [];
+ for (const key of Object.keys(ids).slice().sort()) {
+ const contents = doc[key];
+ if (key[0] === "#") {
+ rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "2px" }} key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
+ &nbsp;
+ </div>);
+ } else {
+ let contentElement: (JSX.Element | null)[] | JSX.Element = [];
+ contentElement = <EditableView key="editableView"
+ contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
+ height={13}
+ fontSize={10}
+ GetValue={() => Field.toKeyValueString(doc, key)}
+ SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)}
+ />;
+ rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
+ &nbsp;
+ {contentElement}
+ </div>);
+ }
+ }
+ rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
+ <EditableView
+ key="editableView"
+ contents={"add key:value or #tags"}
+ height={13}
+ fontSize={10}
+ GetValue={() => ""}
+ SetValue={this.setKeyValue} />
+ </div>);
+ return rows;
+ }
+ }
+
+ @computed get noviceFields() {
+ if (this.dataDoc && this.selectedDoc) {
+ const ids: { [key: string]: string } = {};
+ const doc = this.dataDoc;
+ doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key));
+ const rows: JSX.Element[] = [];
+ for (const key of Object.keys(ids).slice().sort()) {
+ if ((key[0] === key[0].toUpperCase() && key.substring(0, 3) !== "ACL" && key !== "UseCors")
+ || key[0] === "#" || key === "author" ||
+ key === "creationDate" || key.indexOf("lastModified") !== -1) {
+
+ const contents = doc[key];
+ if (key[0] === "#") {
+ rows.push(<div className="uneditable-field" key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key}</span>
+ &nbsp;
+ </div>);
+ } else {
+ const value = Field.toString(contents as Field);
+ if (key === "author" || key === "creationDate" || key.indexOf("lastModified") !== -1) {
+ rows.push(<div className="uneditable-field" key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ": "}</span>
+ <div style={{ whiteSpace: "nowrap", overflowX: "hidden" }}>{value}</div>
+ </div>);
+ } else {
+ let contentElement: (JSX.Element | null)[] | JSX.Element = [];
+ contentElement = <EditableView key="editableView"
+ contents={contents !== undefined ? Field.toString(contents as Field) : "null"}
+ height={13}
+ fontSize={10}
+ GetValue={() => Field.toKeyValueString(doc, key)}
+ SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)}
+ />;
+
+ rows.push(<div style={{ display: "flex", overflowY: "visible", marginBottom: "-1px" }} key={key}>
+ <span style={{ fontWeight: "bold", whiteSpace: "nowrap" }}>{key + ":"}</span>
+ &nbsp;
+ {contentElement}
+ </div>);
+ }
+ }
+ }
+ }
+ rows.push(<div className="field" key={"newKeyValue"} style={{ marginTop: "3px" }}>
+ <EditableView
+ key="editableView"
+ contents={"add key:value or #tags"}
+ height={13}
+ fontSize={10}
+ GetValue={() => ""}
+ SetValue={this.setKeyValue} />
+ </div>);
+ return rows;
+ }
+ }
+
+ @undoBatch
+ setKeyValue = (value: string) => {
+ if (this.selectedDoc && this.dataDoc) {
+ const doc = this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc;
+ if (value.indexOf(":") !== -1) {
+ const newVal = value[0].toUpperCase() + value.substring(1, value.length);
+ KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
+ return true;
+ } else if (value[0] === "#") {
+ const newVal = value + `:'${value}'`;
+ KeyValueBox.SetField(doc, newVal.substring(0, newVal.indexOf(":")), newVal.substring(newVal.indexOf(":") + 1, newVal.length), true);
+ return true;
+ }
+ }
+ return false;
+ }
+
+ @computed get layoutPreview() {
+ if (this.selectedDoc) {
+ const layoutDoc = Doc.Layout(this.selectedDoc);
+ const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight;
+ const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth;
+ return <div style={{ display: "inline-block", height: panelHeight() }} key={this.selectedDoc[Id]}>
+ <ContentFittingDocumentView
+ Document={layoutDoc}
+ DataDoc={this.dataDoc}
+ LibraryPath={emptyPath}
+ renderDepth={this.props.renderDepth + 1}
+ rootSelected={returnFalse}
+ treeViewDoc={undefined}
+ backgroundColor={() => "lightgrey"}
+ fitToBox={true}
+ FreezeDimensions={true}
+ NativeWidth={layoutDoc.type ===
+ StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : returnZero}
+ NativeHeight={layoutDoc.type ===
+ StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : returnZero}
+ PanelWidth={panelWidth}
+ PanelHeight={panelHeight}
+ focus={returnFalse}
+ ScreenToLocalTransform={this.props.ScreenToLocalTransform}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionDoc={undefined}
+ ContainingCollectionView={undefined}
+ addDocument={returnFalse}
+ moveDocument={undefined}
+ removeDocument={returnFalse}
+ parentActive={() => false}
+ whenActiveChanged={emptyFunction}
+ addDocTab={returnFalse}
+ pinToPres={emptyFunction}
+ bringToFront={returnFalse}
+ ContentScaling={returnOne}
+ dontRegisterView={true}
+ dropAction={undefined}
+ />
+ </div>;
+ } else {
+ return null;
+ }
+ }
+
+ /**
+ * Handles the changing of a user's permissions from the permissions panel.
+ */
+ @undoBatch
+ changePermissions = (e: any, user: string) => {
+ SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!);
+ }
+
+ /**
+ * @returns the options for the permissions dropdown.
+ */
+ getPermissionsSelect(user: string, permission: string) {
+ return <select className="permissions-select"
+ defaultValue={permission}
+ onChange={e => this.changePermissions(e, user)}>
+ {Object.values(SharingPermissions).map(permission => {
+ return (
+ <option key={permission} value={permission} selected={this.selectedDoc![`ACL-${user.replace(".", "_")}`] === permission}>
+ {permission}
+ </option>);
+ })}
+ </select>;
+ }
+
+ /**
+ * @returns the notification icon. On clicking, it should notify someone of a document been shared with them.
+ */
+ @computed get notifyIcon() {
+ return <Tooltip title={<><div className="dash-tooltip">Notify with message</div></>}>
+ <div className="notify-button">
+ <FontAwesomeIcon className="notify-button-icon" icon="bell" color="white" size="sm" />
+ </div>
+ </Tooltip>;
+ }
+
+ /**
+ * ... next to the owner that opens the main SharingManager interface on click.
+ */
+ @computed get expansionIcon() {
+ return <Tooltip title={<><div className="dash-tooltip">{"Show more permissions"}</div></>}>
+ <div className="expansion-button" onPointerDown={() => {
+ if (this.selectedDocumentView) {
+ SharingManager.Instance.open(this.selectedDocumentView);
+ }
+ }}>
+ <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" />
+ </div>
+ </Tooltip>;
+ }
+
+ /**
+ * @returns a row of the permissions panel
+ */
+ sharingItem(name: string, effectiveAcl: symbol, permission: string) {
+ return <div className="propertiesView-sharingTable-item"
+ // style={{ backgroundColor: this.selectedUser === name ? "#bcecfc" : "" }}
+ // onPointerDown={action(() => this.selectedUser = this.selectedUser === name ? "" : name)}
+ >
+ <div className="propertiesView-sharingTable-item-name" style={{ width: name !== "Me" ? "85px" : "80px" }}> {name} </div>
+ {/* {name !== "Me" ? this.notifyIcon : null} */}
+ <div className="propertiesView-sharingTable-item-permission">
+ {effectiveAcl === AclAdmin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission}
+ {permission === "Owner" ? this.expansionIcon : null}
+ </div>
+ </div>;
+ }
+
+ /**
+ * @returns the sharing and permissiosn panel.
+ */
+ @computed get sharingTable() {
+ const AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit],
+ [AclAdmin, SharingPermissions.Admin]
+ ]);
+
+ const effectiveAcl = GetEffectiveAcl(this.selectedDoc!);
+ const tableEntries = [];
+
+ // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
+ if (this.selectedDoc![AclSym]) {
+ for (const [key, value] of Object.entries(this.selectedDoc![AclSym])) {
+ const name = key.substring(4).replace("_", ".");
+ if (name !== Doc.CurrentUserEmail && name !== this.selectedDoc!.author/* && sidebarUsersDisplayed![name] !== false*/) tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value)!));
+ }
+ }
+
+ // if (Doc.UserDoc().sidebarUsersDisplayed) {
+ // for (const [name, value] of Object.entries(sidebarUsersDisplayed!)) {
+ // if (value === true && !this.selectedDoc![`ACL-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None));
+ // }
+ // }
+ // })
+
+ // shifts the current user and the owner to the top of the doc.
+ tableEntries.unshift(this.sharingItem("Me", effectiveAcl, Doc.CurrentUserEmail === this.selectedDoc!.author ? "Owner" : StrCast(this.selectedDoc![`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`])));
+ if (Doc.CurrentUserEmail !== this.selectedDoc!.author) tableEntries.unshift(this.sharingItem(StrCast(this.selectedDoc!.author), effectiveAcl, "Owner"));
+
+ return <div className="propertiesView-sharingTable">
+ {tableEntries}
+ </div>;
+ }
+
+ @computed get fieldsCheckbox() {
+ return <Checkbox
+ color="primary"
+ onChange={this.toggleCheckbox}
+ checked={this.layoutFields}
+ />;
+ }
+
+ @action
+ toggleCheckbox = () => {
+ this.layoutFields = !this.layoutFields;
+ }
+
+ @computed get editableTitle() {
+ return <div className="editable-title"><EditableView
+ key="editableView"
+ contents={StrCast(this.selectedDoc?.title)}
+ height={25}
+ fontSize={14}
+ GetValue={() => StrCast(this.selectedDoc?.title)}
+ SetValue={this.setTitle} /> </div>;
+ }
+
+ @undoBatch
+ @action
+ setTitle = (value: string) => {
+ if (this.dataDoc) {
+ this.selectedDoc && (this.selectedDoc.title = value);
+ KeyValueBox.SetField(this.dataDoc, "title", value, true);
+ return true;
+ }
+ return false;
+ }
+
+
+ @undoBatch
+ @action
+ rotate = (angle: number) => {
+ const _centerPoints: { X: number, Y: number }[] = [];
+ if (this.selectedDoc) {
+ const doc = this.selectedDoc;
+ 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 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);
+ _centerPoints.push({ X: left, Y: top });
+ }
+ }
+
+ var index = 0;
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ doc.rotation = Number(doc.rotation) + Number(angle);
+ const inks = Cast(doc.data, InkField)?.inkData;
+ if (inks) {
+ const newPoints: { X: number, Y: number }[] = [];
+ inks.forEach(ink => {
+ const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X;
+ const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y;
+ newPoints.push({ X: newX, Y: newY });
+ });
+ doc.data = new InkField(newPoints);
+ 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++;
+ }
+ }
+ }
+
+
+
+ @computed
+ get controlPointsButton() {
+ return <div className="inking-button">
+ <Tooltip title={<><div className="dash-tooltip">{"Edit points"}</div></>}>
+ <div className="inking-button-points" onPointerDown={action(() => FormatShapePane.Instance._controlBtn = !FormatShapePane.Instance._controlBtn)} style={{ backgroundColor: FormatShapePane.Instance._controlBtn ? "black" : "" }}>
+ <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{FormatShapePane.Instance._lock ? "Unlock ratio" : "Lock ratio"}</div></>}>
+ <div className="inking-button-lock" onPointerDown={action(() => FormatShapePane.Instance._lock = !FormatShapePane.Instance._lock)} >
+ <FontAwesomeIcon icon={FormatShapePane.Instance._lock ? "lock" : "unlock"} color="white" size="lg" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{"Rotate 90˚"}</div></>}>
+ <div className="inking-button-rotate" onPointerDown={action(() => this.rotate(Math.PI / 2))}>
+ <FontAwesomeIcon icon="undo" color="white" size="lg" />
+ </div>
+ </Tooltip>
+ </div>;
+ }
+
+ inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => {
+ return <div className="inputBox"
+ style={{
+ marginRight: title === "X:" ? "19px" : "",
+ marginLeft: title === "∠:" ? "39px" : ""
+ }}>
+ <div className="inputBox-title"> {title} </div>
+ <input className="inputBox-input"
+ type="text" value={value}
+ onChange={e => setter(e.target.value)} />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
+ <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
+ <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ </div>
+ </div>
+ </div>;
+ }
+
+ inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => {
+ return <div className="inputBox-duo">
+ {this.inputBox(key, value, setter, title1)}
+ {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)}
+ </div>;
+ }
+
+ @action
+ 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.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break;
+ case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break;
+ case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
+ case "wid":
+ const oldWidth = NumCast(this.selectedDoc?._width);
+ const oldHeight = NumCast(this.selectedDoc?._height);
+ const oldX = NumCast(this.selectedDoc?.x);
+ const oldY = NumCast(this.selectedDoc?.y);
+ this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10));
+ FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height)));
+ const doc = this.selectedDoc;
+ 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 j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth;
+ const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ doc.data = new InkField(newPoints);
+ }
+ }
+ break;
+ case "hgt":
+ const oWidth = NumCast(this.selectedDoc?._width);
+ const oHeight = NumCast(this.selectedDoc?._height);
+ const oX = NumCast(this.selectedDoc?.x);
+ const oY = NumCast(this.selectedDoc?.y);
+ this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10));
+ FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width)));
+ const docu = this.selectedDoc;
+ if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) {
+ const ink = Cast(docu.data, InkField)?.inkData;
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth;
+ const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ docu.data = new InkField(newPoints);
+ }
+ }
+ break;
+ }
+ }
+
+ getField(key: string) {
+ //if (this.selectedDoc) {
+ return Field.toString(this.selectedDoc?.[key] as Field);
+ // } else {
+ // return undefined as Opt<string>;
+ // }
+ }
+
+ @computed get shapeXps() { return this.getField("x"); }
+ @computed get shapeYps() { return this.getField("y"); }
+ @computed get shapeRot() { return this.getField("rotation"); }
+ @computed get shapeHgt() { return this.getField("_height"); }
+ @computed get shapeWid() { return this.getField("_width"); }
+ set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); }
+ set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); }
+ set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); }
+ set shapeWid(value) {
+ const oldWidth = NumCast(this.selectedDoc?._width);
+ this.selectedDoc && (this.selectedDoc._width = Number(value));
+ FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth);
+ }
+ set shapeHgt(value) {
+ const oldHeight = NumCast(this.selectedDoc?._height);
+ this.selectedDoc && (this.selectedDoc._height = Number(value));
+ FormatShapePane.Instance._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight);
+ }
+
+ @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); }
+ @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); }
+ @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); }
+
+
+ @observable private _fillBtn = false;
+ @observable private _lineBtn = false;
+
+ private _lastFill = "#D0021B";
+ private _lastLine = "#D0021B";
+ private _lastDash: any = "2";
+
+ @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; }
+ @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; }
+ set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); }
+ set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); }
+
+ colorButton(value: string, type: string, setter: () => {}) {
+ return <Flyout anchorPoint={anchorPoints.LEFT_TOP}
+ content={type === "fill" ? this.fillPicker : this.linePicker}>
+ <div className="color-button" key="color" onPointerDown={undoBatch(action(e => setter()))}>
+ <div className="color-button-preview" style={{
+ backgroundColor: value ?? "121212", width: 15, height: 15,
+ display: value === "" || value === "transparent" ? "none" : ""
+ }} />
+ {value === "" || value === "transparent" ? <p style={{ fontSize: 25, color: "red", marginTop: -14, position: "fixed" }}>☒</p> : ""}
+ </div>
+ </Flyout>;
+
+ }
+
+ @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 <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} />;
+ }
+
+ @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); }
+ @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { 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 strokeAndFill() {
+ return <div>
+ <div key="fill" className="strokeAndFill">
+ <div className="fill">
+ <div className="fill-title">Fill:</div>
+ <div className="fill-button">{this.fillButton}</div>
+ </div>
+ <div className="stroke">
+ <div className="stroke-title"> Stroke: </div>
+ <div className="stroke-button">{this.lineButton}</div>
+ </div>
+ </div>
+ {/* {this._fillBtn ? this.fillPicker : ""}
+ {this._lineBtn ? this.linePicker : ""} */}
+ </div>;
+ }
+
+ @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; }
+ @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; }
+ @computed get unStrokd() { return this.selectedDoc?.color ? true : false; }
+ @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+ @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+ @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+ set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+ set dashdStk(value) {
+ value && (this._lastDash = value) && (this.unStrokd = false);
+ this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined);
+ }
+ set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); }
+ set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+ set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); }
+ set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); }
+
+
+ @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); }
+
+
+ regInput = (key: string, value: any, setter: (val: string) => {}) => {
+ return <div className="inputBox">
+ <input className="inputBox-input"
+ type="text" value={value}
+ onChange={e => setter(e.target.value)} />
+ <div className="inputBox-button">
+ <div className="inputBox-button-up" key="up2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))} >
+ <FontAwesomeIcon icon="caret-up" color="white" size="sm" />
+ </div>
+ <div className="inputbox-Button-down" key="down2"
+ onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} >
+ <FontAwesomeIcon icon="caret-down" color="white" size="sm" />
+ </div>
+ </div>
+ </div>;
+ }
+
+ @computed get widthAndDash() {
+ return <div className="widthAndDash">
+ <div className="width">
+ <div className="width-top">
+ <div className="width-title">Width:</div>
+ <div className="width-input">{this.stkInput}</div>
+ </div>
+ <input className="width-range" type="range"
+ defaultValue={Number(this.widthStk)} min={1} max={100}
+ onChange={(action((e) => this.widthStk = e.target.value))}
+ onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }}
+ onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }}
+ />
+ </div>
+
+ <div className="arrows">
+ <div className="arrows-head">
+ <div className="arrows-head-title" >Arrow Head: </div>
+ <input key="markHead" className="arrows-head-input" type="checkbox"
+ checked={this.markHead !== ""}
+ onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
+ </div>
+ <div className="arrows-tail">
+ <div className="arrows-tail-title" >Arrow End: </div>
+ <input key="markTail" className="arrows-tail-input" type="checkbox"
+ checked={this.markTail !== ""}
+ onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
+ </div>
+ </div>
+ <div className="dashed">
+ <div className="dashed-title">Dashed Line:</div>
+ <input key="markHead" className="dashed-input"
+ type="checkbox" checked={this.dashdStk === "2"}
+ onChange={this.changeDash} />
+ </div>
+ </div>;
+ }
+
+ @undoBatch @action
+ changeDash = () => {
+ this.dashdStk = this.dashdStk === "2" ? "0" : "2";
+ }
+
+ @computed get appearanceEditor() {
+ return <div className="appearance-editor">
+ {this.widthAndDash}
+ {this.strokeAndFill}
+ </div>;
+ }
+
+ @computed get transformEditor() {
+ return <div className="transform-editor">
+ {this.controlPointsButton}
+ {this.hgtInput}
+ {this.XpsInput}
+ {this.rotInput}
+ </div>;
+ }
+
+ /**
+ * Handles adding and removing members from the sharing panel
+ */
+ // handleUserChange = (selectedUser: string, add: boolean) => {
+ // if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc;
+ // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => {
+ // sidebarUsersDisplayed![`display-${selectedUser}`] = add;
+ // !add && runInAction(() => this.selectedUser = "");
+ // });
+ // }
+
+ render() {
+ if (!this.selectedDoc && !this.isPres) {
+ return <div className="propertiesView" style={{ width: this.props.width }}>
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ No Document Selected
+ </div>
+ </div>;
+
+ } else {
+ const novice = Doc.UserDoc().noviceMode;
+
+ if (this.selectedDoc && !this.isPres) {
+ return <div className="propertiesView" style={{
+ width: this.props.width,
+ // overflowY: this.inActions ? "visible" : "scroll"
+ }} >
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ Properties
+ {/* <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}>
+ <FontAwesomeIcon icon="times" color="black" size="sm" />
+ </div> */}
+ </div>
+ <div className="propertiesView-name">
+ {this.editableTitle}
+ </div>
+ <div className="propertiesView-settings" onPointerEnter={() => runInAction(() => { this.inActions = true; })}
+ onPointerLeave={action(() => this.inActions = false)}>
+ <div className="propertiesView-settings-title"
+ onPointerDown={() => runInAction(() => { this.openActions = !this.openActions; })}
+ style={{ backgroundColor: this.openActions ? "black" : "" }}>
+ Actions
+ <div className="propertiesView-settings-title-icon">
+ <FontAwesomeIcon icon={this.openActions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openActions ? (null) :
+ <div className="propertiesView-settings-content">
+ <PropertiesButtons />
+ </div>}
+ </div>
+ <div className="propertiesView-sharing">
+ <div className="propertiesView-sharing-title"
+ onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })}
+ style={{ backgroundColor: this.openSharing ? "black" : "" }}>
+ Sharing {"&"} Permissions
+ <div className="propertiesView-sharing-title-icon">
+ <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openSharing ? (null) :
+ <div className="propertiesView-sharing-content">
+ {this.sharingTable}
+ {/* <div className="change-buttons">
+ <button
+ onPointerDown={action(() => this.addButtonPressed = !this.addButtonPressed)}
+ >
+ <FontAwesomeIcon icon={fa.faPlus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} />
+ </button>
+ <button
+ id="sharingProperties-removeUser"
+ onPointerDown={() => this.handleUserChange(this.selectedUser, false)}
+ style={{ backgroundColor: this.selectedUser ? "#121721" : "#777777" }}
+ ><FontAwesomeIcon icon={fa.faMinus} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
+ <button onClick={() => SharingManager.Instance.open(this.selectedDocumentView!)}><FontAwesomeIcon icon={fa.faCog} size={"sm"} style={{ marginTop: -3, marginLeft: -3 }} /></button>
+ {this.addButtonPressed ?
+ // <input type="text" onKeyDown={this.handleKeyPress} /> :
+ <select onChange={e => this.handleUserChange(e.target.value, true)}>
+ <option selected disabled hidden>
+ Add users
+ </option>
+ {SharingManager.Instance.users.map(user =>
+ (<option value={user.user.email}>
+ {user.user.email}
+ </option>)
+ )}
+ {GroupManager.Instance.getAllGroups().map(group =>
+ (<option value={StrCast(group.groupName)}>
+ {StrCast(group.groupName)}
+ </option>))}
+ </select> :
+ null}
+ </div> */}
+ </div>}
+ </div>
+
+ {!this.isInk ? (null) :
+ <div className="propertiesView-appearance">
+ <div className="propertiesView-appearance-title"
+ onPointerDown={() => runInAction(() => { this.openAppearance = !this.openAppearance; })}
+ style={{ backgroundColor: this.openAppearance ? "black" : "" }}>
+ Appearance
+ <div className="propertiesView-appearance-title-icon">
+ <FontAwesomeIcon icon={this.openAppearance ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {!this.openAppearance ? (null) :
+ <div className="propertiesView-appearance-content">
+ {this.appearanceEditor}
+ </div>}
+ </div>}
+
+ {this.isInk ? <div className="propertiesView-transform">
+ <div className="propertiesView-transform-title"
+ onPointerDown={() => runInAction(() => { this.openTransform = !this.openTransform; })}
+ style={{ backgroundColor: this.openTransform ? "black" : "" }}>
+ Transform
+ <div className="propertiesView-transform-title-icon">
+ <FontAwesomeIcon icon={this.openTransform ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openTransform ? <div className="propertiesView-transform-content">
+ {this.transformEditor}
+ </div> : null}
+ </div> : null}
+
+ <div className="propertiesView-fields">
+ <div className="propertiesView-fields-title"
+ onPointerDown={() => runInAction(() => { this.openFields = !this.openFields; })}
+ style={{ backgroundColor: this.openFields ? "black" : "" }}>
+ <div className="propertiesView-fields-title-name">
+ Fields {"&"} Tags
+ <div className="propertiesView-fields-title-icon">
+ <FontAwesomeIcon icon={this.openFields ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ </div>
+ {!novice && this.openFields ? <div className="propertiesView-fields-checkbox">
+ {this.fieldsCheckbox}
+ <div className="propertiesView-fields-checkbox-text">Layout</div>
+ </div> : null}
+ {!this.openFields ? (null) :
+ <div className="propertiesView-fields-content">
+ {novice ? this.noviceFields : this.expandedField}
+ </div>}
+ </div>
+ <div className="propertiesView-layout">
+ <div className="propertiesView-layout-title"
+ onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}
+ style={{ backgroundColor: this.openLayout ? "black" : "" }}>
+ Layout
+ <div className="propertiesView-layout-title-icon" onPointerDown={() => runInAction(() => { this.openLayout = !this.openLayout; })}>
+ <FontAwesomeIcon icon={this.openLayout ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openLayout ? <div className="propertiesView-layout-content">{this.layoutPreview}</div> : null}
+ </div>
+ </div>;
+ }
+ if (this.isPres) {
+ const selectedItem: boolean = PresBox.Instance._selectedArray.length > 0;
+ return <div className="propertiesView" style={{ width: this.props.width }} >
+ <div className="propertiesView-title" style={{ width: this.props.width }}>
+ Presentation
+ <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}>
+ <FontAwesomeIcon icon="times" color="black" size="sm" />
+ </div>
+ </div>
+ <div className="propertiesView-name">
+ {this.editableTitle}
+ <div className="propertiesView-presSelected">
+ {PresBox.Instance?._selectedArray.length} selected
+ <div className="propertiesView-selectedList">
+ {PresBox.Instance?.listOfSelected}
+ </div>
+ </div>
+ </div>
+ {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })}
+ style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"rocket"} /> &nbsp; Transitions
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openPresTransitions ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.transitionDropdown}
+ </div> : null}
+ </div>}
+ {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openPresProgressivize = !this.openPresProgressivize; })}
+ style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"tasks"} /> &nbsp; Progressivize
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openPresProgressivize ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openPresProgressivize ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.progressivizeDropdown}
+ </div> : null}
+ </div>}
+ {!selectedItem ? (null) : <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openSlideOptions = !this.openSlideOptions; })}
+ style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"cog"} /> &nbsp; {PresBox.Instance.stringType} options
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openSlideOptions ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.optionsDropdown}
+ </div> : null}
+ </div>}
+ <div className="propertiesView-presTrails">
+ <div className="propertiesView-presTrails-title"
+ onPointerDown={() => runInAction(() => { this.openAddSlide = !this.openAddSlide; })}
+ style={{ backgroundColor: this.openAddSlide ? "black" : "" }}>
+ &nbsp; <FontAwesomeIcon icon={"plus"} /> &nbsp; Add new slide
+ <div className="propertiesView-presTrails-title-icon">
+ <FontAwesomeIcon icon={this.openAddSlide ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openAddSlide ? <div className="propertiesView-presTrails-content">
+ {PresBox.Instance.newDocumentDropdown}
+ </div> : null}
+ </div>
+ <div className="propertiesView-sharing">
+ <div className="propertiesView-sharing-title"
+ onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })}
+ style={{ backgroundColor: this.openSharing ? "black" : "" }}>
+ Sharing {"&"} Permissions
+ <div className="propertiesView-sharing-title-icon">
+ <FontAwesomeIcon icon={this.openSharing ? "caret-down" : "caret-right"} size="lg" color="white" />
+ </div>
+ </div>
+ {this.openSharing ? <div className="propertiesView-sharing-content">
+ {this.sharingTable}
+ </div> : null}
+ </div>
+ </div>;
+ }
+ }
+ }
+} \ No newline at end of file