diff options
| author | Andy Rickert <andrew_rickert@brown.edu> | 2020-08-03 16:27:09 -0400 |
|---|---|---|
| committer | Andy Rickert <andrew_rickert@brown.edu> | 2020-08-03 16:27:09 -0400 |
| commit | f21febd2fff86c9f4a537cd81adb6de98eaec929 (patch) | |
| tree | 2685d95e3a14ce5ee2db7fb37e9c3abef9d1017c /src/client/views/collections | |
| parent | 76b8ce516103a469d36e876ddf3dd5b53f09c734 (diff) | |
| parent | fb8150f01232a3f8e9333f9ed7012b6982b00c24 (diff) | |
merge with new ui
Diffstat (limited to 'src/client/views/collections')
22 files changed, 1848 insertions, 222 deletions
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 1895c06a1..6ebd5103b 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,39 @@ .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; + margin-top: -1px; + border-bottom: 1px black; + .collectionDockingView-gear { + display: none; + } +} + +.lm_header .lm_tab.lm_active { + padding-right: 20px; + margin-top: 1px; + border-bottom: unset; + .collectionDockingView-gear { + display: inline-block; + } } .lm_popout { - display:none; + display: none; } .messageCounter { @@ -73,14 +92,15 @@ 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; height: 15px; width: 18px; - display: inline-block; margin: auto; } + .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..533c8bffe 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"; @@ -31,6 +30,8 @@ import { SnappingManager } from '../../util/SnappingManager'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import { listSpec } from '../../../fields/Schema'; import { clamp } from 'lodash'; +import { InteractionUtils } from '../../util/InteractionUtils'; +import { InkTool } from '../../../fields/InkField'; const _global = (window /* browser */ || global /* node */) as any; @observer @@ -464,6 +465,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") { this._flush = true; } + if (e.nativeEvent.cancelBubble || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE) || InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || (Doc.GetSelectedTool() === InkTool.Highlighter || Doc.GetSelectedTool() === InkTool.Pen)) { + return; + } else { + e.stopPropagation(); + } } updateDataField = async (json: string) => { @@ -505,7 +511,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 +525,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 +683,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 +752,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); } @@ -749,6 +770,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { private onActiveContentItemChanged() { if (this.props.glContainer.tab) { this._isActive = this.props.glContainer.tab.isActive; + setTimeout(() => { + const dv = this._document && DocumentManager.Instance.getFirstDocumentView(this._document); + dv && SelectionManager.SelectDoc(dv, false); + }); !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } } diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 3b31947f7..a9e812ad3 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -161,7 +161,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { PanelHeight={nested ? pair.layout[HeightSym] : () => this.dimension()} renderDepth={this.props.renderDepth + 1} focus={emptyFunction} - backgroundColor={returnEmptyString} + backgroundColor={this.props.backgroundColor} parentActive={returnTrue} whenActiveChanged={emptyFunction} bringToFront={emptyFunction} diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 9a7ea2c93..c772dcfe7 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -238,7 +238,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr contents: "+ NEW", HeadingObject: this.props.headingObject, toggle: this.toggleVisibility, - color: this.color }; const showChrome = (chromeStatus !== 'view-mode' && chromeStatus !== 'disabled'); const stackPad = showChrome ? `0px ${this.props.parent.xMargin}px` : `${this.props.parent.yMargin}px ${this.props.parent.xMargin}px 0px ${this.props.parent.xMargin}px `; @@ -278,7 +277,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr oneLine: true, HeadingObject: this.props.headingObject, toggle: this.toggleVisibility, - color: this.color }; return this.props.parent.props.Document.miniHeaders ? <div className="collectionStackingView-miniHeader"> diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index 9da204787..0a316317f 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -2,8 +2,8 @@ .collectionMenu-cont { - position:relative; - display:inline-flex; + position: relative; + display: inline-flex; width: 100%; opacity: 0.9; z-index: 9001; @@ -12,14 +12,15 @@ color: white; transform-origin: top left; top: 0; - width:100%; + width: 100%; .antimodeMenu-button { padding: 0; width: 30px; display: flex; + svg { - margin:auto; + margin: auto; } } @@ -108,6 +109,7 @@ margin-top: auto; margin-bottom: auto; } + .collectionViewBaseChrome-viewSpecs { margin-left: 5px; display: grid; @@ -318,12 +320,14 @@ text-align: center; display: block; } + .color-previewI { width: 80%; height: 20%; bottom: 0; position: absolute; } + .color-previewII { width: 80%; height: 80%; @@ -336,7 +340,7 @@ margin: auto; /* Make the buttons appear below each other */ } - + .btn-draw { display: inline-flex; margin: auto; @@ -374,6 +378,7 @@ display: block; margin: auto; } + border-right: solid gray 1px; } } @@ -398,14 +403,14 @@ .collectionSchemaViewChrome-toggler { width: 100px; - height: 41px; + height: 35px; background-color: black; position: relative; } .collectionSchemaViewChrome-togglerButton { width: 47px; - height: 35px; + height: 30px; background-color: $light-color-secondary; // position: absolute; transition: all 0.5s ease; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 0ca86172f..a7d2c07fa 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -25,9 +25,13 @@ 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"; +import { Tooltip } from "@material-ui/core"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @observer export default class CollectionMenu extends AntimodeMenu { @@ -47,7 +51,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 @@ -63,16 +67,37 @@ export default class CollectionMenu extends AntimodeMenu { } } + @action + toggleProperties = () => { + if (CurrentUserUtils.propertiesWidth > 0) { + CurrentUserUtils.propertiesWidth = 0; + } else { + CurrentUserUtils.propertiesWidth = 250; + } + } + render() { - const button = <button className="antimodeMenu-button" key="pin menu" title="Pin menu" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}> - <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} /> - </button>; + const button = <Tooltip title={<div className="dash-tooltip">Pin Menu</div>} key="pin menu" placement="bottom"> + <button className="antimodeMenu-button" onClick={this.toggleMenuPin} style={{ backgroundColor: "#121721" }}> + <FontAwesomeIcon icon="thumbtack" size="lg" style={{ transitionProperty: "transform", transitionDuration: "0.1s", transform: `rotate(${this.Pinned ? 45 : 0}deg)` }} /> + </button> + </Tooltip>; + + const propIcon = CurrentUserUtils.propertiesWidth > 0 ? "angle-double-right" : "angle-double-left"; + const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel"; + + const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom"> + <button className="antimodeMenu-button" key="properties" onPointerDown={this.toggleProperties}> + <FontAwesomeIcon icon={propIcon} size="lg" /> + </button> + </Tooltip>; return this.getElement(!this.SelectedCollection ? [button] : [<CollectionViewBaseChrome key="chrome" docView={this.SelectedCollection} fieldKey={Doc.LayoutFieldKey(this.SelectedCollection?.props.Document)} type={StrCast(this.SelectedCollection?.props.Document._viewType, CollectionViewType.Invalid) as CollectionViewType} />, + prop, button]); } } @@ -160,12 +185,12 @@ 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]; - _stacking_commands = [this._contentCommand, this._templateCommand]; - _masonry_commands = [this._contentCommand, this._templateCommand]; - _schema_commands = [this._templateCommand, this._narrativeCommand]; - _doc_commands = [this._openLinkInCommand, this._onClickCommand]; - _tree_commands = []; + @computed get _freeform_commands() { return Doc.UserDoc().noviceMode ? [this._viewCommand, this._saveFilterCommand] : [this._viewCommand, this._saveFilterCommand, this._contentCommand, this._templateCommand, this._narrativeCommand]; } + @computed get _stacking_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } + @computed get _masonry_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._contentCommand, this._templateCommand]; } + @computed get _schema_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._templateCommand, this._narrativeCommand]; } + @computed get _doc_commands() { return Doc.UserDoc().noviceMode ? undefined : [this._openLinkInCommand, this._onClickCommand]; } + @computed get _tree_commands() { return undefined; } private get _buttonizableCommands() { switch (this.props.type) { default: return this._doc_commands; @@ -185,7 +210,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp @observable private _currentKey: string = ""; componentDidMount = action(() => { - this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : ""); + this._currentKey = this._currentKey || (this._buttonizableCommands?.length ? this._buttonizableCommands[0]?.title : ""); }); @undoBatch @@ -235,7 +260,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp protected drop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; if (docDragData?.draggedDocuments.length) { - this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || [])); + this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate(docDragData.draggedDocuments || [])); e.stopPropagation(); } return true; @@ -257,76 +282,100 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp } dragCommandDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, (e, down, delta) => { - this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => + this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => DragManager.StartButtonDrag([this._commandRef.current!], c.script, c.title, { target: this.document }, c.params, c.initialize, e.clientX, e.clientY)); return true; }, emptyFunction, () => { - this._buttonizableCommands.filter(c => c.title === this._currentKey).map(c => c.immediate([])); + this._buttonizableCommands?.filter(c => c.title === this._currentKey).map(c => c.immediate([])); }); } @computed get templateChrome() { return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} > - <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._commandRef} onPointerDown={this.dragCommandDown}> - <button className={"antimodeMenu-button"} > - <FontAwesomeIcon icon="bullseye" size="lg" /> - </button> - <select - className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}> - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} /> - {this._buttonizableCommands.map(cmd => - <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option> - )} - </select> - </div> + <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom"> + <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}> + <button className={"antimodeMenu-button"} > + <FontAwesomeIcon icon="bullseye" size="lg" /> + </button> + <select + className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}> + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={"empty"} value={""} /> + {this._buttonizableCommands?.map(cmd => + <option className="collectionViewBaseChrome-viewOption" onPointerDown={stopPropagation} key={cmd.title} value={cmd.title}>{cmd.title}</option> + )} + </select> + </div> + </Tooltip> </div>; } @computed get viewModes() { return <div className="collectionViewBaseChrome-viewModes" > - <div className="commandEntry-outerDiv" title="drop document to apply or drag to create button" ref={this._viewRef} onPointerDown={this.dragViewDown}> - <button className={"antimodeMenu-button"}> - <FontAwesomeIcon icon="bullseye" size="lg" /> - </button> - <select - className="collectionViewBaseChrome-viewPicker" - onPointerDown={stopPropagation} - onChange={this.viewChanged} - value={StrCast(this.props.type)}> - {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : ( - <option - key={Utils.GenerateGuid()} - className="collectionViewBaseChrome-viewOption" - onPointerDown={stopPropagation} - value={type}> - {type[0].toUpperCase() + type.substring(1)} - </option> - ))} - </select> - </div> + <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom"> + <div className="commandEntry-outerDiv" ref={this._viewRef} onPointerDown={this.dragViewDown}> + <button className={"antimodeMenu-button"}> + <FontAwesomeIcon icon="bullseye" size="lg" /> + </button> + <select + className="collectionViewBaseChrome-viewPicker" + onPointerDown={stopPropagation} + onChange={this.viewChanged} + value={StrCast(this.props.type)}> + {Object.values(CollectionViewType).map(type => [CollectionViewType.Invalid, CollectionViewType.Docking].includes(type) ? (null) : ( + <option + key={Utils.GenerateGuid()} + className="collectionViewBaseChrome-viewOption" + onPointerDown={stopPropagation} + value={type}> + {type[0].toUpperCase() + type.substring(1)} + </option> + ))} + </select> + </div> + </Tooltip> </div>; } + @computed get selectedDocumentView() { + if (SelectionManager.SelectedDocuments().length) { + return SelectionManager.SelectedDocuments()[0]; + } else { return undefined; } + } + @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } + @computed get notACollection() { + if (this.selectedDoc) { + const layoutField = Doc.LayoutField(this.selectedDoc); + return this.props.type === CollectionViewType.Docking || + typeof (layoutField) === "string" && !layoutField?.includes("CollectionView"); + } + 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" }}> - <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} > - <FontAwesomeIcon icon="filter" size="lg" /> - </button> - </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" }} - title="Toggle Overlay Layer" - onClick={() => DocumentView.FloatDoc(this.props.docView)}> - <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} /> - </button>} + {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} + {!this._buttonizableCommands ? (null) : this.templateChrome} + {Doc.UserDoc().noviceMode ? (null) : + <Tooltip title={<div className="dash-tooltip">filter documents to show</div>} placement="bottom"> + <div className="collectionViewBaseChrome-viewSpecs" style={{ display: "grid" }}> + <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} > + <FontAwesomeIcon icon="filter" size="lg" /> + </button> + </div> + </Tooltip>} + + {this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? (null) : + <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom"> + <button className={"antimodeMenu-button"} key="float" + style={{ backgroundColor: this.props.docView.layoutDoc.z ? "121212" : undefined, borderRight: "1px solid gray" }} + onClick={() => DocumentView.FloatDoc(this.props.docView)}> + <FontAwesomeIcon icon={["fab", "buffer"]} size={"lg"} /> + </button> + </Tooltip>} </div> {this.subChrome} </div> @@ -356,6 +405,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,12 +447,9 @@ 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", "∆"]; - // private _head = ["", "", "arrow", "", "", "arrow", "", "", ""]; - // private _end = ["", "arrow", "arrow", "", "arrow", "arrow", "", "", ""]; - // private _shape = ["line", "line", "line", "", "", "", "rectangle", "circle", "triangle"]; private _dotsize = [10, 20, 30, 40]; private _draw = ["∿", "⎯", "→", "↔︎", "ロ", "O"]; private _head = ["", "", "", "arrow", "", ""]; @@ -459,22 +519,24 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu }); return <div className="btn-draw" key="draw"> {this._draw.map((icon, i) => - <button className="antimodeMenu-button" title={this._title[i]} key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)} - style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}> - {/* {this._draw[i]} */} - <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" /> - - </button>)} + <Tooltip key={icon} title={<div className="dash-tooltip">{this._title[i]}</div>} placement="bottom"> + <button className="antimodeMenu-button" onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)} + style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}> + <FontAwesomeIcon icon={this._faName[i] as IconProp} size="sm" /> + </button> + </Tooltip>)} </div>; } toggleButton = (key: string, value: boolean, setter: () => {}, icon: FontAwesomeIconProps["icon"], ele: JSX.Element | null) => { - return <button className="antimodeMenu-button" key={key} title={key} - onPointerDown={action(e => setter())} - style={{ backgroundColor: value ? "121212" : "" }}> - <FontAwesomeIcon icon={icon} size="lg" /> - {ele} - </button>; + return <Tooltip title={<div className="dash-tooltip">{key}</div>} placement="bottom"> + <button className="antimodeMenu-button" key={key} + onPointerDown={action(e => setter())} + style={{ backgroundColor: value ? "121212" : "" }}> + <FontAwesomeIcon icon={icon} size="lg" /> + {ele} + </button> + </Tooltip>; } @computed get widthPicker() { @@ -483,11 +545,13 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu <div className="btn2-group" key="width"> {widthPicker} {this._width.map((wid, i) => - <button className="antimodeMenu-button" key={wid} title="change width" - onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })} - style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}> - • - </button>)} + <Tooltip title={<div className="dash-tooltip">change width</div>} placement="bottom"> + <button className="antimodeMenu-button" key={wid} + onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })} + style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001, fontSize: this._dotsize[i], padding: 0, textAlign: "center" }}> + • + </button> + </Tooltip>)} </div>; } @@ -522,51 +586,56 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div>; } - @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>; - } - render() { - return !this.props.docView.layoutDoc ? (null) : <div className="collectionFreeFormMenu-cont"> - {this.props.docView.props.renderDepth !== 0 ? (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}> - <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> - </div> - <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}> - <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> - </div> - - {!this.props.isOverlay || this.document.type !== DocumentType.WEB ? (null) : - <button className={"antimodeMenu-button"} key="hypothesis" - style={{ backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, borderRight: "1px solid gray" }} - title="Use Hypothesis" - onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}> - <FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} /> - </button> - } - {!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating ? - <> - {this.drawButtons} - {this.widthPicker} - {this.colorPicker} - {this.fillPicker} - {this.formatPane} - </> : - (null) - } - </div>; + return !this.props.docView.layoutDoc ? (null) : + <div className="collectionFreeFormMenu-cont"> + {this.props.docView.props.renderDepth !== 0 || this.isText ? (null) : + <Tooltip key="map" title={<div className="dash-tooltip">Toggle Mini Map</div>} placement="bottom"> + <div className="backKeyframe" onClick={this.miniMap}> + <FontAwesomeIcon icon={"map"} size={"lg"} /> + </div> + </Tooltip> + } + {!this.isText ? <Tooltip key="back" title={<div className="dash-tooltip">Back Frame</div>} placement="bottom"> + <div className="backKeyframe" onClick={this.prevKeyframe}> + <FontAwesomeIcon icon={"caret-left"} size={"lg"} /> + </div> + </Tooltip> : null} + {!this.isText ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> + <div className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }} + onClick={action(() => this.document.editing = !this.document.editing)} > + {NumCast(this.document.currentFrame)} + </div> + </Tooltip> : null} + {!this.isText ? <Tooltip key="fwd" title={<div className="dash-tooltip">Forward Frame</div>} placement="bottom"> + <div className="fwdKeyframe" onClick={this.nextKeyframe}> + <FontAwesomeIcon icon={"caret-right"} size={"lg"} /> + </div> + </Tooltip> : null} + + {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText ? (null) : + <Tooltip key="hypothesis" title={<div className="dash-tooltip">Use Hypothesis</div>} placement="bottom"> + <button className={"antimodeMenu-button"} key="hypothesis" + style={{ + backgroundColor: !this.props.docView.layoutDoc.isAnnotating ? "121212" : undefined, + borderRight: "1px solid gray" + }} + onClick={() => this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating}> + <FontAwesomeIcon icon={["fab", "hire-a-helper"]} size={"lg"} /> + </button> + </Tooltip> + } + {(!this.props.isOverlay || this.props.docView.layoutDoc.isAnnotating) && !this.isText ? + <> + {this.drawButtons} + {this.widthPicker} + {this.colorPicker} + {this.fillPicker} + </> : + (null) + } + {this.isText ? <RichTextMenu key="rich" /> : null} + </div>; } } @observer diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 11382b722..b93a7f7b8 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -257,7 +257,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 1c5cf4290..4998aaaae 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -230,6 +230,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) addDocTab={this.addDocTab} bringToFront={returnFalse} ContentScaling={returnOne} + scriptContext={this.props.scriptContext} pinToPres={this.props.pinToPres} />; } @@ -484,7 +485,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) })} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} - onWheel={e => this.props.active() && e.stopPropagation()} > + onWheel={e => this.props.active(true) && e.stopPropagation()} > {this.renderedSections} {!this.showAddAGroup ? (null) : <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 76af70cd1..4042a070d 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -298,7 +298,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC oneLine: true, HeadingObject: this.props.headingObject, toggle: this.toggleVisibility, - color: this._color }; const newEditableViewProps = { GetValue: () => "", @@ -306,7 +305,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC contents: "+ NEW", HeadingObject: this.props.headingObject, toggle: this.toggleVisibility, - color: this._color }; const headingView = this.props.headingObject ? <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef} @@ -332,11 +330,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC {this._paletteOn ? this.renderColorPicker() : (null)} </div> } - {evContents === `NO ${key.toUpperCase()} VALUE` ? - (null) : - <button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}> - <FontAwesomeIcon icon="trash" size="lg" /> - </button>} + {<button className="collectionStackingView-sectionDelete" onClick={this.deleteColumn}> + <FontAwesomeIcon icon="trash" size="lg" /> + </button>} {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : <div className="collectionStackingView-sectionOptions"> <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}> @@ -355,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 4463eef3e..ab28e1904 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -194,13 +194,10 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: @action protected onInternalDrop(e: Event, de: DragManager.DropEvent): boolean { const docDragData = de.complete.docDragData; - ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); if (docDragData) { let added = false; - const dropaction = docDragData.dropAction || docDragData.userDropAction; - if (dropaction && dropaction !== "move") { - added = this.addDocument(docDragData.droppedDocuments); - } else if (docDragData.moveDocument) { + const dropAction = docDragData.dropAction || docDragData.userDropAction; + if ((!dropAction || dropAction === "move") && docDragData.moveDocument) { const movedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] === d); const addedDocs = docDragData.droppedDocuments.filter((d, i) => docDragData.draggedDocuments[i] !== d); const res = addedDocs.length ? this.addDocument(addedDocs) : true; @@ -210,6 +207,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); } else added = res; } else { + ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); added = this.addDocument(docDragData.droppedDocuments); } added && e.stopPropagation(); @@ -305,7 +303,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 705871a6f..3c7471d7c 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"); } @@ -328,7 +331,7 @@ class TreeView extends React.Component<TreeViewProps> { [...this.props.renderedIds, this.doc[Id]], this.props.libraryPath, this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields)} </ul >; } else if (this.treeViewExpandedView === "fields") { - return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.doc[Id] + this.doc.title}> + return <ul key={this.doc[Id] + this.doc.title}><div ref={this._dref} style={{ display: "inline-block" }} > {this.expandedField} </div></ul>; } else { @@ -782,7 +785,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll onClicks.push({ description: "Edit onChecked Script", event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.doc, undefined, "onCheckedClick"), "edit onCheckedClick"), icon: "edit" }); - !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); } outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {}); 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 d41248a77..123519b08 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -17,7 +17,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls, SharingPermissions } from '../../../fields/util'; +import { TraceMobx, GetEffectiveAcl, SharingPermissions } from '../../../fields/util'; import { emptyFunction, emptyPath, returnEmptyFilter, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -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 { @@ -143,20 +144,20 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus const effectiveAcl = GetEffectiveAcl(this.props.Document); if (added.length) { - if (effectiveAcl === AclPrivate || (effectiveAcl === AclReadonly && !getPlaygroundMode())) { + if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { return false; } else { - if (this.props.Document[AclSym]) { - // change so it only adds if more restrictive - added.forEach(d => { - // const dataDoc = d[DataSym]; - for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); - } - // dataDoc[AclSym] = d[AclSym] = this.props.Document[AclSym]; - }); - } + // if (this.props.Document[AclSym]) { + // // change so it only adds if more restrictive + // added.forEach(d => { + // // const dataDoc = d[DataSym]; + // for (const [key, value] of Object.entries(this.props.Document[AclSym])) { + // // key.substring(4).replace("_", ".") !== Doc.CurrentUserEmail && distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); + // distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); + // } + // }); + // } if (effectiveAcl === AclAddonly) { added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc)); @@ -180,7 +181,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus doc.context = this.props.Document; }); added.map(add => Doc.AddDocToList(Cast(Doc.UserDoc().myCatalog, Doc, null), "data", add)); - targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]); + // targetDataDoc[this.props.fieldKey] = new List<Doc>([...docList, ...added]); + (targetDataDoc[this.props.fieldKey] as List<Doc>).push(...added); targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); targetDataDoc["lastModified"] = new DateField(new Date(Date.now())); @@ -192,14 +194,16 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus @action.bound removeDocument = (doc: any): boolean => { - const effectiveAcl = GetEffectiveAcl(this.props.Document); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin || getPlaygroundMode()) { + const collectionEffectiveAcl = GetEffectiveAcl(this.props.Document); + const docEffectiveAcl = GetEffectiveAcl(doc); + // you can remove the document if you either have Admin/Edit access to the collection or to the specific document + if (collectionEffectiveAcl === AclEdit || collectionEffectiveAcl === AclAdmin || docEffectiveAcl === AclAdmin || docEffectiveAcl === AclEdit) { const docs = doc instanceof Doc ? [doc] : doc as Doc[]; const targetDataDoc = this.props.Document[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); - const result = value.filter(v => !docs.includes(v)); - if (result.length !== value.length) { - targetDataDoc[this.props.fieldKey] = new List<Doc>(result); + const toRemove = value.filter(v => docs.includes(v)); + if (toRemove.length !== 0) { + toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc)); return true; } } @@ -309,7 +313,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" }); } @@ -338,7 +342,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus icon: "edit", event: () => Doc.GetProto(this.props.Document)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), })); - !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" }); + !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); if (!Doc.UserDoc().noviceMode) { const more = cm.findByDescription("More..."); @@ -368,7 +372,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 +494,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() { @@ -560,6 +565,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); @@ -590,11 +596,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) : + {(Doc.UserDoc()?.noviceMode || !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} + {Doc.UserDoc()?.noviceMode || this.facetWidth() < 10 ? (null) : this.filterView} </div>); } } diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss index 4e704b58f..bc9cf4848 100644 --- a/src/client/views/collections/ParentDocumentSelector.scss +++ b/src/client/views/collections/ParentDocumentSelector.scss @@ -2,11 +2,13 @@ div { overflow: visible !important; } + .metadataEntry-outerDiv { overflow: hidden !important; pointer-events: all; } } + .parentDocumentSelector-flyout { position: relative; z-index: 9999; @@ -31,26 +33,31 @@ border-left: 0px; } } + .parentDocumentSelector-button { - pointer-events: all; + pointer-events: all; position: relative; display: inline-block; + svg { - width:20px !important; - height:20px; + // width:20px !important; + //height:20px; } } + .parentDocumentSelector-metadata { pointer-events: auto; padding-right: 5px; width: 25px; display: inline-block; } + .buttonSelector { div { overflow: visible !important; } - display: inline-block; + + display: inline-block; width:100%; height:100%; }
\ No newline at end of file diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 8c0b8de9d..4c8cac3ed 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(() => { @@ -129,7 +129,7 @@ export class DockingViewButtonSelector extends React.Component<{ views: () => Do this.props.views()[0]?.select(false); }} className="buttonSelector"> <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}> - <FontAwesomeIcon icon={"cog"} size={"sm"} /> + <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} /> </Flyout> </span>; } diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index 829db9c6a..5c199096e 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>[] { 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.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss index 010beb836..d49ab27fb 100644 --- a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss @@ -27,13 +27,15 @@ position: absolute; } -.sketch-picker { - background: #323232; - width: 160px !important; - height: 80% !important; - - .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 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/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 764758eee..a32c8b363 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(); } }); diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss new file mode 100644 index 000000000..7df56115f --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss @@ -0,0 +1,644 @@ +.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); + + &: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; + } + } + + .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 { + + 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: 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; + } + + } +} + +.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..f5e0cd077 --- /dev/null +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -0,0 +1,850 @@ +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> + + </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> + + {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[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> + + </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> + + {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={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; + } + } + + @undoBatch + changePermissions = (e: any, user: string) => { + SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, this.selectedDoc!); + } + + getPermissionsSelect(user: string) { + return <select className="permissions-select" + 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>; + } + + @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" ? "85px" : "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} + />; + } + + @undoBatch + @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} />; + } + + @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 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 = () => { + 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>; + } + + render() { + + if (!this.selectedDoc) { + return <div className="propertiesView" style={{ width: this.props.width }}> + <div className="propertiesView-title" style={{ width: this.props.width }}> + No Document Selected + <div className="propertiesView-title-icon" onPointerDown={this.props.onDown}> + <FontAwesomeIcon icon="times" color="black" size="xs" /> + </div> + </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 ? (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>} + </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>; + } +}
\ No newline at end of file |
