aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionDockingView.scss17
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx20
-rw-r--r--src/client/views/collections/CollectionMenu.tsx79
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx2
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx1
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx2
-rw-r--r--src/client/views/collections/CollectionSubView.tsx2
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx7
-rw-r--r--src/client/views/collections/CollectionView.scss1
-rw-r--r--src/client/views/collections/CollectionView.tsx11
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx6
-rw-r--r--src/client/views/collections/SchemaTable.tsx8
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx27
-rw-r--r--src/client/views/collections/collectionFreeForm/PropertiesView.scss620
-rw-r--r--src/client/views/collections/collectionFreeForm/PropertiesView.tsx852
16 files changed, 1604 insertions, 63 deletions
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 1895c06a1..27d66f796 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -12,12 +12,14 @@
width: 100%;
height: 100%;
position: absolute;
+
.miniThumb {
background: #25252525;
position: absolute;
}
}
}
+
.lm_title {
margin-top: 3px;
border-radius: 5px;
@@ -27,6 +29,7 @@
transform: translate(0px, -3px);
cursor: grab;
}
+
.lm_title.focus-visible {
cursor: text;
}
@@ -34,23 +37,25 @@
.lm_title_wrap {
overflow: hidden;
height: 19px;
- margin-top: -3px;
- display:inline-block;
+ margin-top: -2px;
+ display: inline-block;
}
+
.lm_active .lm_title {
border: solid 1px lightgray;
}
+
.lm_header .lm_tab .lm_close_tab {
position: absolute;
text-align: center;
}
.lm_header .lm_tab {
- padding-right : 20px;
+ padding-right: 20px;
}
.lm_popout {
- display:none;
+ display: none;
}
.messageCounter {
@@ -73,6 +78,7 @@
position: absolute;
top: 0;
left: 0;
+
// overflow: hidden; // bcz: menus don't show up when this is on (e.g., the parentSelectorMenu)
.collectionDockingView-gear {
padding-left: 5px;
@@ -80,7 +86,10 @@
width: 18px;
display: inline-block;
margin: auto;
+
+ display: none;
}
+
.collectionDockingView-dragAsDocument {
touch-action: none;
position: absolute;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 4952dedc9..7a0a06069 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,9 +1,8 @@
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, computed, Lambda, observable, reaction, runInAction, trace } from "mobx";
+import { action, computed, Lambda, observable, reaction, runInAction, trace, IReactionDisposer } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
-import Measure from "react-measure";
import * as GoldenLayout from "../../../client/goldenLayout";
import { DateField } from '../../../fields/DateField';
import { Doc, DocListCast, Field, Opt, DataSym } from "../../../fields/Doc";
@@ -505,7 +504,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
if (doc instanceof Doc) {
- //tab.titleElement[0].outerHTML = `<input class='lm_title' style="background:black" value='${doc.title}' />`;
tab.titleElement[0].onclick = (e: any) => tab.titleElement[0].focus();
tab.titleElement[0].onchange = (e: any) => {
tab.titleElement[0].size = e.currentTarget.value.length + 1;
@@ -520,6 +518,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
gearSpan.style.paddingLeft = "0px";
gearSpan.style.paddingRight = "12px";
const stack = tab.contentItem.parent;
+ tab.element[0].onpointerdown = (e: any) => {
+ const view = DocumentManager.Instance.getDocumentView(doc);
+ view && SelectionManager.SelectDoc(view, false);
+ };
// shifts the focus to this tab when another tab is dragged over it
tab.element[0].onmouseenter = (e: any) => {
if (!this._isPointerDown || !SnappingManager.GetIsDragging()) return;
@@ -674,10 +676,15 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
@observable private _panelHeight = 0;
@observable private _document: Opt<Doc>;
@observable private _isActive: boolean = false;
+ _tabReaction: IReactionDisposer | undefined;
get _stack(): any {
return (this.props as any).glContainer.parent.parent;
}
+ get _tab(): any {
+ const tab = (this.props as any).glContainer.tab.element[0] as HTMLElement;
+ return tab.getElementsByClassName("lm_title")?.[0];
+ }
constructor(props: any) {
super(props);
DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
@@ -738,9 +745,16 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged);
this.props.glContainer.on("tab", this.onActiveContentItemChanged);
this.onActiveContentItemChanged();
+ this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, "white") }),
+ (data) => {
+ const selected = data.views.some(v => Doc.AreProtosEqual(v.props.Document, this._document));
+ this._tab.style.backgroundColor = selected ? data.color : "";
+ }
+ );
}
componentWillUnmount() {
+ this._tabReaction?.();
this.props.glContainer.layoutManager.off("activeContentItemChanged", this.onActiveContentItemChanged);
this.props.glContainer.off("tab", this.onActiveContentItemChanged);
}
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 0ca86172f..fdd1b4e81 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -25,6 +25,8 @@ import { SelectionManager } from "../../util/SelectionManager";
import { DocumentView } from "../nodes/DocumentView";
import { ColorState } from "react-color";
import { ObjectField } from "../../../fields/ObjectField";
+import RichTextMenu from "../nodes/formattedText/RichTextMenu";
+import { RichTextField } from "../../../fields/RichTextField";
import { ScriptField } from "../../../fields/ScriptField";
import { IconProp } from '@fortawesome/fontawesome-svg-core';
import { DocUtils } from "../../documents/Documents";
@@ -47,7 +49,7 @@ export default class CollectionMenu extends AntimodeMenu {
componentDidMount() {
reaction(() => SelectionManager.SelectedDocuments().length && SelectionManager.SelectedDocuments()[0],
- (doc) => doc && this.SetSelection(doc))
+ (doc) => doc && this.SetSelection(doc));
}
@action
@@ -160,7 +162,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
initialize: (button: Doc) => { button['target-docFilters'] = this.target._docFilters instanceof ObjectField ? ObjectField.MakeCopy(this.target._docFilters as any as ObjectField) : ""; },
};
- _freeform_commands = [this._viewCommand, this._saveFilterCommand, this._fitContentCommand, this._clusterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand];
+ @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; }
+
_stacking_commands = [this._contentCommand, this._templateCommand];
_masonry_commands = [this._contentCommand, this._templateCommand];
_schema_commands = [this._templateCommand, this._narrativeCommand];
@@ -308,18 +311,32 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp
</div>;
}
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else { return undefined; }
+ }
+ @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get isText() {
+ if (this.selectedDoc) {
+ return this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField;
+ }
+ else return false;
+ }
+
render() {
return (
<div className="collectionMenu-cont" >
<div className="collectionMenu">
<div className="collectionViewBaseChrome">
- {this.props.type === CollectionViewType.Invalid || this.props.type === CollectionViewType.Docking ? (null) : this.viewModes}
- {this.props.type === CollectionViewType.Docking ? (null) : this.templateChrome}
- <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
+ {this.props.type === CollectionViewType.Invalid ||
+ this.props.type === CollectionViewType.Docking || this.isText ? (null) : this.viewModes}
+ {this.props.type === CollectionViewType.Docking || this.isText ? (null) : this.templateChrome}
+ {/* <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
<button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} >
<FontAwesomeIcon icon="filter" size="lg" />
</button>
- </div>
+ </div> */}
{this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? (null) : <button className={"antimodeMenu-button"} key="float"
style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, borderRight: "1px solid gray" }}
@@ -356,6 +373,20 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
@computed get childDocs() {
return DocListCast(this.dataField);
}
+
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else { return undefined; }
+ }
+ @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; }
+ @computed get isText() {
+ if (this.selectedDoc) {
+ return this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField;
+ }
+ else return false;
+ }
+
@undoBatch
@action
nextKeyframe = (): void => {
@@ -384,6 +415,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
miniMap = (): void => {
this.document.hideMinimap = !this.document.hideMinimap;
}
+
private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", ""];
private _width = ["1", "5", "10", "100"];
// private _draw = ["⎯", "→", "↔︎", "∿", "↝", "↭", "ロ", "O", "∆"];
@@ -523,40 +555,44 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
}
@computed get formatPane() {
- return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
- onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="angle-double-right" size="lg" />
- </button>;
+ // return <button className="antimodeMenu-button" key="format" title="toggle foramatting pane"
+ // onPointerDown={action(e => FormatShapePane.Instance.Pinned = !FormatShapePane.Instance.Pinned)}
+ // style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
+ // <FontAwesomeIcon icon="angle-double-right" size="lg" />
+ // </button>;
+ return null;
}
render() {
return !this.props.docView.layoutDoc ? (null) : <div className="collectionFreeFormMenu-cont">
- {this.props.docView.props.renderDepth !== 0 ? (null) :
+ {this.props.docView.props.renderDepth !== 0 || this.isText ? (null) :
<div key="map" title="mini map" className="backKeyframe" onClick={this.miniMap}>
<FontAwesomeIcon icon={"map"} size={"lg"} />
</div>
}
- <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
+ {!!!this.isText ? <div key="back" title="back frame" className="backKeyframe" onClick={this.prevKeyframe}>
<FontAwesomeIcon icon={"caret-left"} size={"lg"} />
- </div>
- <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }}
+ </div> : null}
+ {!!!this.isText ? <div key="num" title="toggle view all" className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }}
onClick={action(() => this.document.editing = !this.document.editing)} >
{NumCast(this.document.currentFrame)}
- </div>
- <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
+ </div> : null}
+ {!!!this.isText ? <div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
<FontAwesomeIcon icon={"caret-right"} size={"lg"} />
- </div>
+ </div> : null}
- {!this.props.isOverlay || this.document.type !== DocumentType.WEB ? (null) :
+ {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText ? (null) :
<button className={"antimodeMenu-button"} key="hypothesis"
- style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, borderRight: "1px solid gray" }}
+ style={{
+ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined,
+ borderRight: "1px solid gray"
+ }}
title="Use Hypothesis"
onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}>
<FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} />
</button>
}
- {!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating ?
+ {(!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating) && !this.isText ?
<>
{this.drawButtons}
{this.widthPicker}
@@ -566,6 +602,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu
</> :
(null)
}
+ {this.isText ? <RichTextMenu key="rich" /> : null}
</div>;
}
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 5553bbbb7..f67e049fd 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -253,7 +253,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<div className="collectionSchema-headerMenu-group">
<div onClick={() => this.typesDropdownChange(!this._openTypes)}>
<label>Column type:</label>
- <FontAwesomeIcon icon={"caret-down"} size="sm" style={{ float: "right" }} />
+ <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right" }} />
</div>
{this._openTypes ? allColumnTypes : justColType}
</div >
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 312dd92f0..54ce39cea 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -227,6 +227,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
addDocTab={this.addDocTab}
bringToFront={returnFalse}
ContentScaling={returnOne}
+ scriptContext={this.props.scriptContext}
pinToPres={this.props.pinToPres}
/>;
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index a6983de05..4042a070d 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -351,7 +351,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
{this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
{
this.collapsed ? (null) :
- <div style={{ marginTop: 5 }}>
+ <div>
<div key={`${heading}-stack`} className={`collectionStackingView-masonry${singleColumn ? "Single" : "Grid"}`}
style={{
padding: singleColumn ? `${columnYMargin}px ${0}px ${style.yMargin}px ${0}px` : `${columnYMargin}px ${0}px`,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 9f78c15eb..c90e85271 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -296,7 +296,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const reg = new RegExp(Utils.prepend(""), "g");
const modHtml = srcUrl ? html.replace(reg, srcUrl) : html;
const htmlDoc = Docs.Create.HtmlDocument(modHtml, { ...options, title: "-web page-", _width: 300, _height: 300 });
- Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc)["text"] = text;
+ Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text;
this.props.addDocument(htmlDoc);
if (srcWeb) {
const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 21292b900..7787a8b38 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -90,7 +90,10 @@ class TreeView extends React.Component<TreeViewProps> {
get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive
get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); }
@observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
- set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; }
+ set treeViewOpen(c: boolean) {
+ if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c;
+ else this.doc.treeViewOpen = this._overrideTreeViewOpen = c;
+ }
@computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; }
@computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.defaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); }
@@ -101,7 +104,7 @@ class TreeView extends React.Component<TreeViewProps> {
const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined;
return ((this.props.dataDoc ? DocListCast(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field
(layout ? DocListCast(layout[field]) : undefined) || // else if there's a layout doc, display it's fields
- DocListCast(this.doc[field])) as Doc[]; // otherwise use the document's data field
+ DocListCast(this.doc[field])); // otherwise use the document's data field
}
@computed get childDocs() { return this.childDocList(this.fieldKey); }
@computed get childLinks() { return this.childDocList("links"); }
diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss
index b630f9cf8..a5aef86de 100644
--- a/src/client/views/collections/CollectionView.scss
+++ b/src/client/views/collections/CollectionView.scss
@@ -24,6 +24,7 @@
border-right: unset;
z-index: 2;
}
+
.collectionTimeView-treeView {
display: flex;
flex-direction: column;
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 7e7ea6786..62e8dc26a 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -78,6 +78,7 @@ export interface CollectionViewCustomProps {
childLayoutTemplate?: () => Opt<Doc>; // specify a layout Doc template to use for children of the collection
childLayoutString?: string; // specify a layout string to use for children of the collection
childOpacity?: () => number;
+ hideFilter?: true;
}
export interface CollectionRenderProps {
@@ -309,7 +310,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
const options = cm.findByDescription("Options...");
const optionItems = options && "subitems" in options ? options.subitems : [];
- optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
+ !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }) : null;
if (this.props.Document.childLayout instanceof Doc) {
optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
}
@@ -368,7 +369,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
get _facetWidth() { return NumCast(this.props.Document._facetWidth); }
set _facetWidth(value) { this.props.Document._facetWidth = value; }
- bodyPanelWidth = () => this.props.PanelWidth() - this.facetWidth();
+ bodyPanelWidth = () => this.props.PanelWidth();
facetWidth = () => Math.max(0, Math.min(this.props.PanelWidth() - 25, this._facetWidth));
@computed get dataDoc() {
@@ -490,6 +491,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return false;
}), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0), false);
}
+
filterBackground = () => "rgba(105, 105, 105, 0.432)";
get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
@computed get scriptField() {
@@ -559,6 +561,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
</div>
</div>;
}
+
childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null);
childLayoutString = this.props.childLayoutString || StrCast(this.props.Document.childLayoutString);
@@ -588,11 +591,11 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href
:
""))}
- {(!this.props.isSelected() && !this.props.Document.forceActive) || this.props.Document.hideFilterView ? (null) :
+ {/* {(!this.props.isSelected() && !this.props.Document.forceActive) || this.props.Document.hideFilterView ? (null) :
<div className="collectionView-filterDragger" title="library View Dragger" onPointerDown={this.onPointerDown}
style={{ right: this.facetWidth() - 1, top: this.props.Document._viewType === CollectionViewType.Docking ? "25%" : "55%" }} />
}
- {this.facetWidth() < 10 ? (null) : this.filterView}
+ {this.facetWidth() < 10 ? (null) : this.filterView} */}
</div>);
}
}
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 8c0b8de9d..532dd6abc 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -42,14 +42,14 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
async fetchDocuments() {
const aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document));
const containerProtoSets = await Promise.all(aliases.map(async alias =>
- await Promise.all((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
+ ((await SearchUtil.Search("", true, { fq: `data_l:"${alias[Id]}"` })).docs)));
const containerProtos = containerProtoSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
const containerSets = await Promise.all(Array.from(containerProtos.keys()).map(async container => {
- return (await SearchUtil.GetAliasesOfDocument(container));
+ return (SearchUtil.GetAliasesOfDocument(container));
}));
const containers = containerSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>());
const doclayoutSets = await Promise.all(Array.from(containers.keys()).map(async (dp) => {
- return (await SearchUtil.GetAliasesOfDocument(dp));
+ return (SearchUtil.GetAliasesOfDocument(dp));
}));
const doclayouts = Array.from(doclayoutSets.reduce((p, set) => { set.map(s => p.add(s)); return p; }, new Set<Doc>()).keys());
runInAction(() => {
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index cde795098..7e2840c2c 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -148,7 +148,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@action
- changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown;
+ changeTitleMode = () => this._showTitleDropdown = !this._showTitleDropdown
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
@computed get tableColumns(): Column<Doc>[] {
@@ -208,7 +208,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}}>
{col.heading}</div>;
- const sortIcon = col.desc === undefined ? "circle" : col.desc === true ? "caret-down" : "caret-up";
+ const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up";
const header =
<div //className="collectionSchemaView-header"
@@ -224,12 +224,12 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
{keysDropdown}
</div>
<div onClick={e => this.changeSorting(col)}
- style={{ paddingRight: "6px", display: "inline" }}>
+ style={{ paddingRight: "6px", marginLeft: "4px", display: "inline" }}>
<FontAwesomeIcon icon={sortIcon} size="sm" />
</div>
<div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)}
style={{ float: "right", paddingRight: "6px" }}>
- <FontAwesomeIcon icon={"compass"} size="sm" />
+ <FontAwesomeIcon icon={"cog"} size="sm" />
</div>
</div>;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 57336131a..badbc48a1 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1144,7 +1144,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" });
@@ -1239,13 +1239,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...");
@@ -1290,7 +1292,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.
}
}
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
index ddc282e57..6263be261 100644
--- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -13,7 +13,6 @@ import AntimodeMenu from "../../AntimodeMenu";
import "./FormatShapePane.scss";
import { undoBatch } from "../../../util/UndoManager";
import { ColorState, SketchPicker } from 'react-color';
-import { DocumentView } from "../../../views/nodes/DocumentView"
@observer
export default class FormatShapePane extends AntimodeMenu {
@@ -124,12 +123,12 @@ export default class FormatShapePane extends AntimodeMenu {
console.log(ink);
if (ink) {
const newPoints: { X: number, Y: number }[] = [];
- for (var j = 0; j < ink.length; j++) {
+ ink.forEach(i => {
// (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = (doc.x - oldX) + (ink[j].X * doc._width) / oldWidth;
- const newY = (doc.y - oldY) + (ink[j].Y * doc._height) / oldHeight;
+ 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);
}
}
@@ -148,12 +147,12 @@ export default class FormatShapePane extends AntimodeMenu {
console.log(ink);
if (ink) {
const newPoints: { X: number, Y: number }[] = [];
- for (var j = 0; j < ink.length; j++) {
+ ink.forEach(i => {
// (new x — oldx) + (oldxpoint * newWidt)/oldWidth
- const newX = (doc.x - oldX) + (ink[j].X * doc._width) / oldWidth;
- const newY = (doc.y - oldY) + (ink[j].Y * doc._height) / oldHeight;
+ 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);
}
}
@@ -191,11 +190,11 @@ export default class FormatShapePane extends AntimodeMenu {
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[index].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].X;
- const newY = Math.sin(angle) * (ink[i].X - _centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].Y;
+ 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 xs = newPoints.map(p => p.X);
const ys = newPoints.map(p => p.Y);
@@ -395,12 +394,12 @@ export default class FormatShapePane extends AntimodeMenu {
@computed get widInput() { return this.inputBox("wid", this.shapeWid, (val: string) => this.shapeWid = 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 XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => this.shapeXps = val, "X:", "Yps", this.shapeYps, (val: string) => this.shapeYps = val, "Y:"); }
@computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); }
@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:"); }
@computed get propertyGroupItems() {
diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
new file mode 100644
index 000000000..74f32275a
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss
@@ -0,0 +1,620 @@
+.propertiesView {
+
+ background-color: rgb(205, 205, 205);
+ height: 100%;
+ font-family: "Noto Sans";
+ cursor: auto;
+
+ overflow-x: visible;
+ overflow-y: visible;
+
+ .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: pointer;
+ }
+ }
+
+ .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);
+
+ .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);
+
+ .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;
+ }
+ }
+
+ .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);
+
+ .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);
+
+ .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 {
+
+ border: 1px solid black;
+ padding: 5px;
+ border-radius: 6px;
+ /* width: 170px; */
+ margin-right: 10px;
+ background-color: #ececec;
+ max-height: 130px;
+ overflow-y: scroll;
+
+ .propertiesView-sharingTable-item {
+
+ display: flex;
+ padding: 3px;
+ align-items: center;
+ border-bottom: 0.5px solid grey;
+
+ &:hover .propertiesView-sharingTable-item-name {
+ overflow-x: unset;
+ white-space: unset;
+ overflow-wrap: break-word;
+ }
+
+ .propertiesView-sharingTable-item-name {
+ font-weight: bold;
+ width: 80px;
+ 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);
+
+ .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);
+
+ .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;
+ }
+
+ }
+}
+
+.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;
+ }
+ }
+}
+
+.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;
+
+ .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: 74px;
+ margin-bottom: 6px;
+
+ .dashed-title {
+ font-size: 10;
+ }
+
+ .dashed-input {
+ margin-left: 6px;
+ margin-top: 2px;
+ }
+ }
+} \ 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..3eb3ef8ab
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx
@@ -0,0 +1,852 @@
+import React = require("react");
+import { observer } from "mobx-react";
+import "./PropertiesView.scss";
+import { observable, action, computed, runInAction } from "mobx";
+import { Doc, Field, DocListCast, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt } from "../../../../fields/Doc";
+import { DocumentView } from "../../nodes/DocumentView";
+import { ComputedField } from "../../../../fields/ScriptField";
+import { EditableView } from "../../EditableView";
+import { KeyValueBox } from "../../nodes/KeyValueBox";
+import { Cast, NumCast, StrCast } from "../../../../fields/Types";
+import { listSpec } from "../../../../fields/Schema";
+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, Divider } from "@material-ui/core";
+import SharingManager from "../../../util/SharingManager";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import FormatShapePane from "./FormatShapePane";
+import { SharingPermissions, GetEffectiveAcl } from "../../../../fields/util";
+import { InkField } from "../../../../fields/InkField";
+import { undoBatch } from "../../../util/UndoManager";
+import { ColorState, SketchPicker } from "react-color";
+import AntimodeMenu from "../../AntimodeMenu";
+import "./FormatShapePane.scss";
+import { discovery_v1 } from "googleapis";
+
+
+interface PropertiesViewProps {
+ width: number;
+ height: number;
+ renderDepth: number;
+ ScreenToLocalTransform: () => Transform;
+ onDown: (event: any) => void;
+}
+
+@observer
+export class PropertiesView extends React.Component<PropertiesViewProps> {
+
+ @computed get MAX_EMBED_HEIGHT() { return 200; }
+
+ @computed get selectedDocumentView() {
+ if (SelectionManager.SelectedDocuments().length) {
+ return SelectionManager.SelectedDocuments()[0];
+ } else { return undefined; }
+ }
+ @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;
+
+ @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[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;
+ }
+ }
+
+
+ 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={false}
+ 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}
+ />
+ </div>;
+ } else {
+ return null;
+ }
+ }
+
+ getPermissionsSelect(user: string) {
+ return <select className="permissions-select"
+ onChange={e => SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!)}>
+ {Object.values(SharingPermissions).map(permission => {
+ return (
+ <option key={permission} value={permission} selected={this.selectedDoc![`ACL-${user.replace(".", "_")}`] === permission}>
+ {permission}
+ </option>);
+ })}
+ </select>;
+ }
+
+ @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>;
+ }
+
+ @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>;
+ }
+
+ sharingItem(name: string, effectiveAcl: symbol, permission?: string) {
+ return <div className="propertiesView-sharingTable-item">
+ <div className="propertiesView-sharingTable-item-name" style={{ width: name !== "Me" ? "70px" : "80px" }}> {name} </div>
+ {name !== "Me" ? this.notifyIcon : null}
+ <div className="propertiesView-sharingTable-item-permission">
+ {effectiveAcl === AclAdmin && permission !== "Owner" ? this.getPermissionsSelect(name) : permission}
+ {permission === "Owner" ? this.expansionIcon : null}
+ </div>
+ </div>;
+ }
+
+ @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 = [];
+
+ 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) tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value)!));
+ }
+ }
+
+ 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 <EditableView
+ key="editableView"
+ contents={StrCast(this.selectedDoc?.title)}
+ height={25}
+ fontSize={14}
+ GetValue={() => StrCast(this.selectedDoc?.title)}
+ SetValue={this.setTitle} />;
+ }
+
+ @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 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[index].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].X;
+ const newY = Math.sin(angle) * (ink[i].X - _centerPoints[index].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[index].Y) + _centerPoints[index].Y;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ doc.data = new InkField(newPoints);
+ const 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++;
+ }
+ }
+ }
+
+ @observable _controlBtn: boolean = false;
+ @observable _lock: boolean = false;
+
+ @computed
+ get controlPointsButton() {
+ return <div className="inking-button">
+ <Tooltip title={<><div className="dash-tooltip">{"Edit points"}</div></>}>
+ <div className="inking-button-points" onPointerDown={action(() => this._controlBtn = !this._controlBtn)} style={{ backgroundColor: this._controlBtn ? "black" : "" }}>
+ <FontAwesomeIcon icon="bezier-curve" color="white" size="lg" />
+ </div>
+ </Tooltip>
+ <Tooltip title={<><div className="dash-tooltip">{this._lock ? "Unlock points" : "Lock points"}</div></>}>
+ <div className="inking-button-lock" onPointerDown={action(() => this._lock = !this._lock)} >
+ <FontAwesomeIcon icon={this._lock ? "unlock" : "lock"} 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));
+ this._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) {
+ console.log(doc.x, doc.y, doc._height, doc._width);
+ const ink = Cast(doc.data, InkField)?.inkData;
+ console.log(ink);
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (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));
+ this._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) {
+ console.log(docu.x, docu.y, docu._height, docu._width);
+ const ink = Cast(docu.data, InkField)?.inkData;
+ console.log(ink);
+ if (ink) {
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var j = 0; j < ink.length; j++) {
+ // (new x — oldx) + (oldxpoint * newWidt)/oldWidth
+ const newX = (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));
+ this._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));
+ this._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) => this.shapeHgt = val, "H:", "wid", this.shapeWid, (val: string) => this.shapeWid = val, "W:"); }
+ @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => this.shapeXps = val, "X:", "Yps", this.shapeYps, (val: string) => this.shapeYps = val, "Y:"); }
+ @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, ""); }
+
+ @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, setter: () => {}) {
+ return <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>;
+ }
+
+ @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, () => { 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 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={undoBatch(action((e) => this.widthStk = e.target.value))} />
+ </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">Dash:</div>
+ <input key="markHead" className="dashed-input"
+ type="checkbox" checked={this.dashdStk === "2"}
+ onChange={this.changeDash} />
+ </div>
+ </div>;
+ }
+
+ @undoBatch @action
+ changeDash = () => {
+ const dash = this.dashdStk;
+ if (dash === "2") {
+ this.dashdStk = "0";
+ } else {
+ this.dashdStk = "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>;
+ }
+
+ render() {
+
+ if (!this.selectedDoc) {
+ return <div className="propertiesView" style={{ width: this.props.width }}>
+ <div className="propertiesView-title" style={{ width: this.props.width, paddingLeft: 6 }}>
+ No Document Selected
+ </div> </div>;
+ }
+
+ const novice = Doc.UserDoc().noviceMode;
+
+ return <div className="propertiesView" style={{ width: this.props.width }} >
+ <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">
+ <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 ? <div className="propertiesView-settings-content">
+ <PropertiesButtons />
+ </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>
+
+
+
+
+ {this.isInk ? <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 ? <div className="propertiesView-appearance-content">
+ {this.appearanceEditor}
+ </div> : null}
+ </div> : null}
+
+ {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 ?
+ <div className="propertiesView-fields-content">
+ {novice ? this.noviceFields : this.expandedField}
+ </div> : null}
+ </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>;
+ }
+} \ No newline at end of file