aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorMelissa Zhang <mzhang19096@gmail.com>2020-07-23 13:33:29 -0700
committerMelissa Zhang <mzhang19096@gmail.com>2020-07-23 13:33:29 -0700
commit38b264599af2dca710b6c54d76cf30aade5e2f49 (patch)
tree4ec3b7fb03fcc4c81160dedea651f41cdb84c331 /src/client/views/collections
parent86a8751ca5a66fd42d7b2b3dee56210f2ef2b62d (diff)
parent3bcc0e3a8ce4ab67dff4b3d62191c346764aa351 (diff)
merge with master
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionCarousel3DView.tsx26
-rw-r--r--src/client/views/collections/CollectionCarouselView.tsx23
-rw-r--r--src/client/views/collections/CollectionDockingView.scss18
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx150
-rw-r--r--src/client/views/collections/CollectionLinearView.tsx43
-rw-r--r--src/client/views/collections/CollectionMasonryViewFieldRow.tsx1
-rw-r--r--src/client/views/collections/CollectionMenu.scss (renamed from src/client/views/collections/CollectionViewChromes.scss)111
-rw-r--r--src/client/views/collections/CollectionMenu.tsx (renamed from src/client/views/collections/CollectionViewChromes.tsx)437
-rw-r--r--src/client/views/collections/CollectionSchemaCells.tsx12
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx1
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx8
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx68
-rw-r--r--src/client/views/collections/CollectionSubView.tsx47
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx33
-rw-r--r--src/client/views/collections/CollectionView.tsx136
-rw-r--r--src/client/views/collections/SchemaTable.tsx3
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx80
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx160
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.scss64
-rw-r--r--src/client/views/collections/collectionFreeForm/FormatShapePane.tsx307
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss78
-rw-r--r--src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx332
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx37
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.scss3
-rw-r--r--src/client/views/collections/collectionGrid/CollectionGridView.tsx60
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx5
-rw-r--r--src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx4
28 files changed, 1296 insertions, 952 deletions
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx
index 1344b70f4..0f3b6f212 100644
--- a/src/client/views/collections/CollectionCarousel3DView.tsx
+++ b/src/client/views/collections/CollectionCarousel3DView.tsx
@@ -38,15 +38,15 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
panelWidth = () => this.props.PanelWidth() / 3;
panelHeight = () => this.props.PanelHeight() * 0.6;
+ onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
@computed get content() {
const currentIndex = NumCast(this.layoutDoc._itemIndex);
const displayDoc = (childPair: { layout: Doc, data: Doc }) => {
+ const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout });
+ const onChildClick = script && (() => script);
return <ContentFittingDocumentView {...this.props}
- onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
- onClick={ScriptField.MakeScript(
- "child._showCaption = 'caption'",
- { child: Doc.name },
- { child: childPair.layout })}
+ onDoubleClick={this.onChildDoubleClick}
+ onClick={onChildClick}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.ChildLayoutTemplate}
LayoutTemplateString={this.props.ChildLayoutString}
@@ -86,7 +86,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
interval?: number;
startAutoScroll = (direction: number) => {
this.interval = window.setInterval(() => {
- console.log(this.interval, this.scrollSpeed);
this.changeSlide(direction);
}, this.scrollSpeed);
}
@@ -108,29 +107,16 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
}, 1500);
}
- onContextMenu = (e: React.MouseEvent): void => {
- // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped()) {
- ContextMenu.Instance.addItem({
- description: "Make Hero Image", event: () => {
- const index = NumCast(this.layoutDoc._itemIndex);
- (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
- }, icon: "plus"
- });
- }
- }
_downX = 0;
_downY = 0;
onPointerDown = (e: React.PointerEvent) => {
this._downX = e.clientX;
this._downY = e.clientY;
- console.log("CAROUSEL down");
document.addEventListener("pointerup", this.onpointerup);
}
private _lastTap: number = 0;
private _doubleTap = false;
onpointerup = (e: PointerEvent) => {
- console.log("CAROUSEL up");
this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
this._lastTap = Date.now();
}
@@ -184,7 +170,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume
const index = NumCast(this.layoutDoc._itemIndex);
const translateX = (1 - index) / this.childLayoutPairs.length * 100;
- return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+ return <div className="collectionCarousel3DView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
<div className="carousel-wrapper" style={{ transform: `translateX(calc(${translateX}%` }}>
{this.content}
</div>
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx
index bd0e4fc9a..27aea4b99 100644
--- a/src/client/views/collections/CollectionCarouselView.tsx
+++ b/src/client/views/collections/CollectionCarouselView.tsx
@@ -14,6 +14,7 @@ import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox';
import { ContextMenu } from '../ContextMenu';
import { ObjectField } from '../../../fields/ObjectField';
import { returnFalse } from '../../../Utils';
+import { ScriptField } from '../../../fields/ScriptField';
type CarouselDocument = makeInterface<[typeof documentSchema, typeof collectionSchema]>;
const CarouselDocument = makeInterface(documentSchema, collectionSchema);
@@ -40,14 +41,16 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length;
}
panelHeight = () => this.props.PanelHeight() - 50;
+ onContentDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick);
+ onContentClick = () => ScriptCast(this.layoutDoc.onChildClick);
@computed get content() {
const index = NumCast(this.layoutDoc._itemIndex);
return !(this.childLayoutPairs?.[index]?.layout instanceof Doc) ? (null) :
<>
<div className="collectionCarouselView-image" key="image">
<ContentFittingDocumentView {...this.props}
- onDoubleClick={ScriptCast(this.layoutDoc.onChildDoubleClick)}
- onClick={ScriptCast(this.layoutDoc.onChildClick)}
+ onDoubleClick={this.onContentDoubleClick}
+ onClick={this.onContentClick}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={this.props.ChildLayoutTemplate}
LayoutTemplateString={this.props.ChildLayoutString}
@@ -83,30 +86,16 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
</>;
}
-
- onContextMenu = (e: React.MouseEvent): void => {
- // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
- if (!e.isPropagationStopped()) {
- ContextMenu.Instance?.addItem({
- description: "Make Hero Image", event: () => {
- const index = NumCast(this.layoutDoc._itemIndex);
- (this.dataDoc || Doc.GetProto(this.props.Document)).hero = ObjectField.MakeCopy(this.childLayoutPairs[index].layout.data as ObjectField);
- }, icon: "plus"
- });
- }
- }
_downX = 0;
_downY = 0;
onPointerDown = (e: React.PointerEvent) => {
this._downX = e.clientX;
this._downY = e.clientY;
- console.log("CAROUSEL down");
document.addEventListener("pointerup", this.onpointerup);
}
private _lastTap: number = 0;
private _doubleTap = false;
onpointerup = (e: PointerEvent) => {
- console.log("CAROUSEL up");
this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2);
this._lastTap = Date.now();
}
@@ -119,7 +108,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument)
}
render() {
- return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget} onContextMenu={this.onContextMenu}>
+ return <div className="collectionCarouselView-outer" onClick={this.onClick} onPointerDown={this.onPointerDown} ref={this.createDashEventsTarget}>
{this.content}
{this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)}
</div>;
diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss
index 18d642510..1895c06a1 100644
--- a/src/client/views/collections/CollectionDockingView.scss
+++ b/src/client/views/collections/CollectionDockingView.scss
@@ -1,5 +1,23 @@
@import "../../views/globalCssVariables.scss";
+.miniMap {
+ position: absolute;
+ overflow: hidden;
+ right: 10;
+ bottom: 10;
+ border: solid 1px;
+ box-shadow: black 0.4vw 0.4vw 0.8vw;
+
+ .miniOverlay {
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ .miniThumb {
+ background: #25252525;
+ position: absolute;
+ }
+ }
+}
.lm_title {
margin-top: 3px;
border-radius: 5px;
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 79c577b6d..53b2d5254 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -11,7 +11,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { FieldId } from "../../../fields/RefField";
import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { TraceMobx } from '../../../fields/util';
-import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse } from "../../../Utils";
+import { emptyFunction, returnOne, returnTrue, Utils, returnZero, returnEmptyFilter, setupMoveUpEvents, returnFalse, emptyPath, aggregateBounds } from "../../../Utils";
import { DocServer } from "../../DocServer";
import { Docs } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
@@ -28,6 +28,9 @@ import { DockingViewButtonSelector } from './ParentDocumentSelector';
import React = require("react");
import { CollectionViewType } from './CollectionView';
import { SnappingManager } from '../../util/SnappingManager';
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { listSpec } from '../../../fields/Schema';
+import { clamp } from 'lodash';
const _global = (window /* browser */ || global /* node */) as any;
@observer
@@ -390,6 +393,12 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
reactionDisposer?: Lambda;
componentDidMount: () => void = () => {
if (this._containerRef.current) {
+ const observer = new _global.ResizeObserver(action((entries: any) => {
+ for (const entry of entries) {
+ this.onResize(null as any);
+ }
+ }));
+ observer.observe(this._containerRef.current);
this.reactionDisposer = reaction(
() => this.props.Document.dockingConfig,
() => {
@@ -430,7 +439,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
const cur = this._containerRef.current;
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
- this._goldenLayout && this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
+ this._goldenLayout?.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
}
@action
@@ -646,16 +655,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (this.props.renderDepth > 0) {
return <div style={{ width: "100%", height: "100%" }}>Nested workspaces can't be rendered</div>;
}
- return (
- <Measure offset onResize={this.onResize}>
- {({ measureRef }) =>
- <div ref={measureRef}>
- <div className="collectiondockingview-container" id="menuContainer"
- onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
- </div>
- }
- </Measure>
- );
+ return <div className="collectiondockingview-container" id="menuContainer"
+ onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />;
}
}
@@ -664,7 +665,6 @@ interface DockedFrameProps {
documentId: FieldId;
glContainer: any;
libraryPath: (FieldId[]);
- backgroundColor?: (doc: Doc) => string | undefined;
//collectionDockingView: CollectionDockingView
}
@observer
@@ -818,35 +818,109 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
}
}
+ @computed get renderContentBounds() {
+ const bounds = this._document ? Cast(this._document._renderContentBounds, listSpec("number"), [0, 0, this.returnMiniSize(), this.returnMiniSize()]) : [0, 0, 0, 0];
+ const xbounds = bounds[2] - bounds[0];
+ const ybounds = bounds[3] - bounds[1];
+ const dim = Math.max(xbounds, ybounds);
+ return { l: bounds[0] + xbounds / 2 - dim / 2, t: bounds[1] + ybounds / 2 - dim / 2, cx: bounds[0] + xbounds / 2, cy: bounds[1] + ybounds / 2, dim };
+ }
+ @computed get miniLeft() { return 50 + (NumCast(this._document?._panX) - this.renderContentBounds.cx) / this.renderContentBounds.dim * 100 - this.miniWidth / 2; }
+ @computed get miniTop() { return 50 + (NumCast(this._document?._panY) - this.renderContentBounds.cy) / this.renderContentBounds.dim * 100 - this.miniHeight / 2; }
+ @computed get miniWidth() { return this.panelWidth() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ @computed get miniHeight() { return this.panelHeight() / NumCast(this._document?._viewScale, 1) / this.renderContentBounds.dim * 100; }
+ childLayoutTemplate = () => Cast(this._document?.childLayoutTemplate, Doc, null);
+ returnMiniSize = () => NumCast(this._document?._miniMapSize, 150);
+ miniDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => {
+ this._document!._panX = clamp(NumCast(this._document!._panX) + delta[0] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.l, this.renderContentBounds.l + this.renderContentBounds.dim);
+ this._document!._panY = clamp(NumCast(this._document!._panY) + delta[1] / this.returnMiniSize() * this.renderContentBounds.dim, this.renderContentBounds.t, this.renderContentBounds.t + this.renderContentBounds.dim);
+ return false;
+ }), emptyFunction, emptyFunction);
+ }
+ renderMiniMap() {
+ return <div className="miniMap" style={{
+ width: this.returnMiniSize(), height: this.returnMiniSize(), background: StrCast(this._document!._backgroundColor,
+ StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!))),
+ }}>
+ <CollectionFreeFormView
+ Document={this._document!}
+ LibraryPath={emptyPath}
+ CollectionView={undefined}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined}
+ ChildLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid havin to set stuff like this.
+ noOverlay={true} // don't render overlay Docs since they won't scale
+ active={returnTrue}
+ select={emptyFunction}
+ dropAction={undefined}
+ isSelected={returnFalse}
+ dontRegisterView={true}
+ annotationsKey={""}
+ fieldKey={Doc.LayoutFieldKey(this._document!)}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={returnFalse}
+ moveDocument={returnFalse}
+ removeDocument={returnFalse}
+ ContentScaling={returnOne}
+ PanelWidth={this.returnMiniSize}
+ PanelHeight={this.returnMiniSize}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
+ fitToBox={true}
+ />
+ <div className="miniOverlay" onPointerDown={this.miniDown} >
+ <div className="miniThumb" style={{
+ width: `${this.miniWidth}%`,
+ height: `${this.miniHeight}%`,
+ left: `${this.miniLeft}%`,
+ top: `${this.miniTop}%`,
+ }}
+ />
+ </div>
+ </div>;
+ }
@computed get docView() {
TraceMobx();
if (!this._document) return (null);
const document = this._document;
- const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;// document.layout instanceof Doc ? document : this._dataDoc;
- return <DocumentView key={document[Id]}
- LibraryPath={this._libraryPath}
- Document={document}
- DataDoc={resolvedDataDoc}
- bringToFront={emptyFunction}
- rootSelected={returnTrue}
- addDocument={undefined}
- removeDocument={undefined}
- ContentScaling={this.contentScaling}
- PanelWidth={this.panelWidth}
- PanelHeight={this.panelHeight}
- NativeHeight={this.nativeHeight}
- NativeWidth={this.nativeWidth}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- renderDepth={0}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- focus={emptyFunction}
- backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
- addDocTab={this.addDocTab}
- pinToPres={DockedFrameRenderer.PinDoc}
- docFilters={returnEmptyFilter}
- ContainingCollectionView={undefined}
- ContainingCollectionDoc={undefined} />;
+ const resolvedDataDoc = !Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined;
+ return <>
+ <DocumentView key={document[Id]}
+ LibraryPath={this._libraryPath}
+ Document={document}
+ DataDoc={resolvedDataDoc}
+ bringToFront={emptyFunction}
+ rootSelected={returnTrue}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ NativeHeight={this.nativeHeight}
+ NativeWidth={this.nativeWidth}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ backgroundColor={CollectionDockingView.Instance.props.backgroundColor}
+ addDocTab={this.addDocTab}
+ pinToPres={DockedFrameRenderer.PinDoc}
+ docFilters={returnEmptyFilter}
+ ContainingCollectionView={undefined}
+ ContainingCollectionDoc={undefined} />
+ {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)}
+ </>;
}
render() {
diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx
index 0bbe97ec9..cd6e60de6 100644
--- a/src/client/views/collections/CollectionLinearView.tsx
+++ b/src/client/views/collections/CollectionLinearView.tsx
@@ -16,6 +16,7 @@ import { Id } from '../../../fields/FieldSymbols';
import { DocumentLinksButton } from '../nodes/DocumentLinksButton';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup';
+import { Tooltip } from '@material-ui/core';
type LinearDocument = makeInterface<[typeof documentSchema,]>;
@@ -110,17 +111,22 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
const flexDir: any = StrCast(this.Document.flexDirection);
const backgroundColor = StrCast(this.props.Document.backgroundColor, "black");
const color = StrCast(this.props.Document.color, "white");
+
+ const menuOpener = <label htmlFor={`${guid}`} style={{
+ background: backgroundColor === color ? "black" : backgroundColor,
+ // width: "18px", height: "18px", fontSize: "12.5px",
+ // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
+ // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
+ }}
+ onPointerDown={e => e.stopPropagation()} >
+ <p>+</p>
+ </label>;
+
return <div className="collectionLinearView-outer">
<div className="collectionLinearView" ref={this.createDashEventsTarget} >
- <label htmlFor={`${guid}`} title="Close Menu" style={{
- background: backgroundColor === color ? "black" : backgroundColor,
- // width: "18px", height: "18px", fontSize: "12.5px",
- // transition: this.props.Document.linearViewIsExpanded ? "transform 0.2s" : "transform 0.5s",
- // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)"
- }}
- onPointerDown={e => e.stopPropagation()} >
- <p>+</p>
- </label>
+ <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top">
+ {menuOpener}
+ </Tooltip>
<input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle}
onChange={action((e: any) => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} />
@@ -170,12 +176,21 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) {
}}
onPointerDown={e => e.stopPropagation()} >
<span className="bottomPopup-text" >
- Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in" : ""} {DocumentLinksButton.StartLink.title} </span>
- <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}
- > Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ Creating link from: {DocumentLinksButton.AnnotationId ? "Annotation in" : ""} {DocumentLinksButton.StartLink.title}
</span>
- <span className="bottomPopup-exit" onClick={this.exitLongLinks}
- >Exit</span>
+
+ <Tooltip title={<><div className="dash-tooltip">{LinkDescriptionPopup.showDescriptions ? "Turn off description pop-up" :
+ "Turn on description pop-up"} </div></>} placement="top">
+ <span className="bottomPopup-descriptions" onClick={this.changeDescriptionSetting}>
+ Labels: {LinkDescriptionPopup.showDescriptions ? LinkDescriptionPopup.showDescriptions : "ON"}
+ </span>
+ </Tooltip>
+
+ <Tooltip title={<><div className="dash-tooltip">Exit link clicking mode </div></>} placement="top">
+ <span className="bottomPopup-exit" onClick={this.exitLongLinks}>
+ Clear
+ </span>
+ </Tooltip>
{/* <FontAwesomeIcon icon="times-circle" size="lg" style={{ color: "red" }}
onClick={this.exitLongLinks} /> */}
diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
index 627b22417..9a7ea2c93 100644
--- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
+++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx
@@ -84,7 +84,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr
@undoBatch
rowDrop = action((e: Event, de: DragManager.DropEvent) => {
- console.log("masronry row drop");
this._createAliasSelected = false;
if (de.complete.docDragData) {
(this.props.parent.Document.dropConverter instanceof ScriptField) &&
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionMenu.scss
index b1e8d20ad..9da204787 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionMenu.scss
@@ -1,35 +1,47 @@
@import "../globalCssVariables";
-@import '~js-datepicker/dist/datepicker.min.css';
-.collectionViewChrome-cont {
- position: absolute;
+
+.collectionMenu-cont {
+ position:relative;
+ display:inline-flex;
width: 100%;
opacity: 0.9;
z-index: 9001;
transition: top .5s;
- background: lightgrey;
+ background: #323232;
+ color: white;
transform-origin: top left;
+ top: 0;
+ width:100%;
+
+ .antimodeMenu-button {
+ padding: 0;
+ width: 30px;
+ display: flex;
+ svg {
+ margin:auto;
+ }
+ }
- .collectionViewChrome {
+ .collectionMenu {
display: flex;
padding-bottom: 1px;
height: 32px;
border-bottom: .5px solid rgb(180, 180, 180);
overflow: visible;
z-index: 9001;
+ border: unset;
.collectionViewBaseChrome {
display: flex;
.collectionViewBaseChrome-viewPicker {
font-size: 75%;
- //text-transform: uppercase;
- //letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #323232;
outline-color: black;
+ color: white;
border: none;
- //padding: 12px 10px 11px 10px;
+ border-right: solid gray 1px;
}
.collectionViewBaseChrome-viewPicker:active {
@@ -52,21 +64,22 @@
margin-left: 3px;
margin-right: 0px;
font-size: 75%;
- background: rgb(238, 238, 238);
+ background: #323232;
+ color: white;
border: none;
- color: grey;
+ border-right: solid gray 1px;
}
.commandEntry-outerDiv {
pointer-events: all;
- background-color: gray;
+ background-color: #323232;
display: flex;
flex-direction: row;
height: 30px;
.commandEntry-drop {
color: white;
- width: 25px;
+ width: 30px;
margin-top: auto;
margin-bottom: auto;
}
@@ -75,7 +88,7 @@
.collectionViewBaseChrome-collapse {
transition: all .5s, opacity 0.3s;
position: absolute;
- width: 40px;
+ width: 30px;
transform-origin: top left;
pointer-events: all;
// margin-top: 10px;
@@ -94,27 +107,25 @@
color: grey;
margin-top: auto;
margin-bottom: auto;
- margin-left: 5px;
}
-
- .collectionViewBaseChrome-viewModes {
- margin-left: 25px;
- }
-
.collectionViewBaseChrome-viewSpecs {
margin-left: 5px;
display: grid;
+ border: none;
+ border-right: solid gray 1px;
.collectionViewBaseChrome-filterIcon {
position: relative;
display: flex;
margin: auto;
- background: gray;
+ background: #323232;
color: white;
width: 30px;
height: 30px;
align-items: center;
justify-content: center;
+ border: none;
+ border-right: solid gray 1px;
}
.collectionViewBaseChrome-viewSpecsInput {
@@ -190,11 +201,12 @@
font-size: 75%;
//text-transform: uppercase;
//letter-spacing: 2px;
- background: rgb(238, 238, 238);
- color: grey;
+ background: #121721;
+ color: white;
outline-color: black;
+ color: white;
border: none;
- //padding: 12px 10px 11px 10px;
+ border-right: solid gray 1px;
}
.collectionGridViewChrome-viewPicker:active {
@@ -297,29 +309,55 @@
}
}
-.collectionFreeFormViewChrome-cont {
- width: 60px;
- display: flex;
+.collectionFreeFormMenu-cont {
+ display: inline-flex;
position: relative;
align-items: center;
+ .antimodeMenu-button {
+ text-align: center;
+ display: block;
+ }
+ .color-previewI {
+ width: 80%;
+ height: 20%;
+ bottom: 0;
+ position: absolute;
+ }
+ .color-previewII {
+ width: 80%;
+ height: 80%;
+ margin-left: 10%;
+ }
+
+ .btn-group {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ margin: auto;
+ /* Make the buttons appear below each other */
+ }
+
+ .btn-draw {
+ display: inline-flex;
+ margin: auto;
+ /* Make the buttons appear below each other */
+ }
+
.fwdKeyframe,
.numKeyframe,
.backKeyframe {
cursor: pointer;
- position: absolute;
+ position: relative;
width: 20;
height: 30;
bottom: 0;
- background: gray;
- display: flex;
+ background: #323232;
+ display: inline-flex;
align-items: center;
color: white;
}
.backKeyframe {
- left: 0;
-
svg {
display: block;
margin: auto;
@@ -327,19 +365,16 @@
}
.numKeyframe {
- left: 20;
- display: flex;
flex-direction: column;
- padding: 5px;
+ padding-top: 5px;
}
.fwdKeyframe {
- left: 40;
-
svg {
display: block;
margin: auto;
}
+ border-right: solid gray 1px;
}
}
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionMenu.tsx
index 7f1fe7649..992c1f600 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -1,74 +1,107 @@
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, observable, runInAction, Lambda } from "mobx";
+import React = require("react");
+import { FontAwesomeIcon, FontAwesomeIconProps } from "@fortawesome/react-fontawesome";
+import { action, computed, observable, reaction, runInAction, Lambda } from "mobx";
import { observer } from "mobx-react";
-import * as React from "react";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
-import { Id } from "../../../fields/FieldSymbols";
-import { List } from "../../../fields/List";
-import { listSpec } from "../../../fields/Schema";
-import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types";
-import { Utils, emptyFunction, setupMoveUpEvents } from "../../../Utils";
-import { DragManager } from "../../util/DragManager";
+import { BoolCast, Cast, StrCast, NumCast } from "../../../fields/Types";
+import AntimodeMenu from "../AntimodeMenu";
+import "./CollectionMenu.scss";
import { undoBatch } from "../../util/UndoManager";
-import { EditableView } from "../EditableView";
-import { COLLECTION_BORDER_WIDTH } from "../globalCssVariables.scss";
-import { CollectionViewType } from "./CollectionView";
-import { CollectionView } from "./CollectionView";
-import "./CollectionViewChromes.scss";
+import { CollectionViewType, CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils";
+import { DragManager } from "../../util/DragManager";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
+import { List } from "../../../fields/List";
+import { EditableView } from "../EditableView";
+import { Id } from "../../../fields/FieldSymbols";
+import { listSpec } from "../../../fields/Schema";
+import FormatShapePane from "./collectionFreeForm/FormatShapePane";
+import { ActiveFillColor, SetActiveInkWidth, ActiveInkColor, SetActiveBezierApprox, SetActiveArrowEnd, SetActiveArrowStart, SetActiveFillColor, SetActiveInkColor } from "../InkingStroke";
+import GestureOverlay from "../GestureOverlay";
+import { InkTool } from "../../../fields/InkField";
+import { DocumentType } from "../../documents/DocumentTypes";
+import { Document } from "../../../fields/documentSchemas";
+import { SelectionManager } from "../../util/SelectionManager";
+import { DocumentView } from "../nodes/DocumentView";
+import { ColorState } from "react-color";
-interface CollectionViewChromeProps {
- CollectionView: CollectionView;
- type: CollectionViewType;
- collapse?: (value: boolean) => any;
- PanelWidth: () => number;
+@observer
+export default class CollectionMenu extends AntimodeMenu {
+ static Instance: CollectionMenu;
+
+ @observable SelectedCollection: CollectionView | undefined;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ CollectionMenu.Instance = this;
+ this._canFade = false; // don't let the inking menu fade away
+ this.Pinned = Cast(Doc.UserDoc()["menuCollections-pinned"], "boolean", true);
+ this.jumpTo(300, 300);
+ }
+
+ @action
+ toggleMenuPin = (e: React.MouseEvent) => {
+ Doc.UserDoc()["menuCollections-pinned"] = this.Pinned = !this.Pinned;
+ if (!this.Pinned && this._left < 0) {
+ this.jumpTo(300, 300);
+ }
+ }
+
+ 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>;
+
+ return this.getElement(!this.SelectedCollection ? [button] :
+ [<CollectionViewBaseChrome key="chrome" CollectionView={this.SelectedCollection} type={StrCast(this.SelectedCollection.props.Document._viewType) as CollectionViewType} />,
+ button]);
+ }
}
-interface Filter {
- key: string;
- value: string;
- contains: boolean;
+interface CollectionMenuProps {
+ CollectionView: CollectionView;
+ type: CollectionViewType;
}
const stopPropagation = (e: React.SyntheticEvent) => e.stopPropagation();
@observer
-export class CollectionViewBaseChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionViewBaseChrome extends React.Component<CollectionMenuProps> {
//(!)?\(\(\(doc.(\w+) && \(doc.\w+ as \w+\).includes\(\"(\w+)\"\)
get target() { return this.props.CollectionView.props.Document; }
_templateCommand = {
- params: ["target", "source"], title: "=> item view",
- script: "this.target.childLayout = getDocTemplate(this.source?.[0])",
- immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayout = Doc.getDocTemplate(source?.[0]))),
+ params: ["target", "source"], title: "item view",
+ script: "this.target.childLayoutTemplate = getDocTemplate(this.source?.[0])",
+ immediate: undoBatch((source: Doc[]) => source.length && (this.target.childLayoutTemplate = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_narrativeCommand = {
- params: ["target", "source"], title: "=> child click view",
+ params: ["target", "source"], title: "child click view",
script: "this.target.childClickedOpenTemplateView = getDocTemplate(this.source?.[0])",
immediate: undoBatch((source: Doc[]) => source.length && (this.target.childClickedOpenTemplateView = Doc.getDocTemplate(source?.[0]))),
initialize: emptyFunction,
};
_contentCommand = {
- params: ["target", "source"], title: "=> clear content",
+ params: ["target", "source"], title: "clear content",
script: "getProto(this.target).data = copyField(this.source);",
immediate: undoBatch((source: Doc[]) => Doc.GetProto(this.target).data = new List<Doc>(source)), // Doc.aliasDocs(source),
initialize: emptyFunction,
};
_viewCommand = {
- params: ["target"], title: "=> reset view",
- script: "this.target._panX = this.restoredPanX; this.target._panY = this.restoredPanY; this.target.scale = this.restoredScale;",
- immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target.scale = 1; }),
- initialize: (button: Doc) => { button.restoredPanX = this.target._panX; button.restoredPanY = this.target._panY; button.restoredScale = this.target.scale; },
+ params: ["target"], title: "bookmark view",
+ script: "this.target._panX = this['target-panX']; this.target._panY = this['target-panY']; this.target._viewScale = this['target-viewScale'];",
+ immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; }),
+ initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; },
};
_clusterCommand = {
- params: ["target"], title: "=> fit content",
+ params: ["target"], title: "fit content",
script: "this.target._fitToBox = !this.target._fitToBox;",
immediate: undoBatch((source: Doc[]) => this.target._fitToBox = !this.target._fitToBox),
initialize: emptyFunction
};
_fitContentCommand = {
- params: ["target"], title: "=> toggle clusters",
+ params: ["target"], title: "toggle clusters",
script: "this.target.useClusters = !this.target.useClusters;",
immediate: undoBatch((source: Doc[]) => this.target.useClusters = !this.target.useClusters),
initialize: emptyFunction
@@ -99,14 +132,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
componentDidMount = action(() => {
this._currentKey = this._currentKey || (this._buttonizableCommands.length ? this._buttonizableCommands[0]?.title : "");
- // chrome status is one of disabled, collapsed, or visible. this determines initial state from document
- switch (this.props.CollectionView.props.Document._chromeStatus) {
- case "disabled":
- throw new Error("how did you get here, if chrome status is 'disabled' on a collection, a chrome shouldn't even be instantiated!");
- case "collapsed":
- this.props.collapse?.(true);
- break;
- }
});
@undoBatch
@@ -130,93 +155,16 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
this.document._facetWidth = 0;
}
- // @action
- // openDatePicker = (e: React.PointerEvent) => {
- // if (this._picker) {
- // this._picker.alwaysShow = true;
- // this._picker.show();
- // // TODO: calendar is offset when zoomed in/out
- // // this._picker.calendar.style.position = "absolute";
- // // let transform = this.props.CollectionView.props.ScreenToLocalTransform();
- // // let x = parseInt(this._picker.calendar.style.left) / transform.Scale;
- // // let y = parseInt(this._picker.calendar.style.top) / transform.Scale;
- // // this._picker.calendar.style.left = x;
- // // this._picker.calendar.style.top = y;
-
- // e.stopPropagation();
- // }
- // }
-
- // <input className="collectionViewBaseChrome-viewSpecsMenu-rowRight"
- // id={Utils.GenerateGuid()}
- // ref={this.datePickerRef}
- // value={this._dateValue instanceof Date ? this._dateValue.toLocaleDateString() : this._dateValue}
- // onChange={(e) => runInAction(() => this._dateValue = e.target.value)}
- // onPointerDown={this.openDatePicker}
- // placeholder="Value" />
- // @action.bound
- // applyFilter = (e: React.MouseEvent) => {
- // const keyRestrictionScript = "(" + this._keyRestrictions.map(i => i[1]).filter(i => i.length > 0).join(" && ") + ")";
- // const yearOffset = this._dateWithinValue[1] === 'y' ? 1 : 0;
- // const monthOffset = this._dateWithinValue[1] === 'm' ? parseInt(this._dateWithinValue[0]) : 0;
- // const weekOffset = this._dateWithinValue[1] === 'w' ? parseInt(this._dateWithinValue[0]) : 0;
- // const dayOffset = (this._dateWithinValue[1] === 'd' ? parseInt(this._dateWithinValue[0]) : 0) + weekOffset * 7;
- // let dateRestrictionScript = "";
- // if (this._dateValue instanceof Date) {
- // const lowerBound = new Date(this._dateValue.getFullYear() - yearOffset, this._dateValue.getMonth() - monthOffset, this._dateValue.getDate() - dayOffset);
- // const upperBound = new Date(this._dateValue.getFullYear() + yearOffset, this._dateValue.getMonth() + monthOffset, this._dateValue.getDate() + dayOffset + 1);
- // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- // }
- // else {
- // const createdDate = new Date(this._dateValue);
- // if (!isNaN(createdDate.getTime())) {
- // const lowerBound = new Date(createdDate.getFullYear() - yearOffset, createdDate.getMonth() - monthOffset, createdDate.getDate() - dayOffset);
- // const upperBound = new Date(createdDate.getFullYear() + yearOffset, createdDate.getMonth() + monthOffset, createdDate.getDate() + dayOffset + 1);
- // dateRestrictionScript = `((doc.creationDate as any).date >= ${lowerBound.valueOf()} && (doc.creationDate as any).date <= ${upperBound.valueOf()})`;
- // }
- // }
- // const fullScript = dateRestrictionScript.length || keyRestrictionScript.length ? dateRestrictionScript.length ?
- // `${dateRestrictionScript} ${keyRestrictionScript.length ? "&&" : ""} (${keyRestrictionScript})` :
- // `(${keyRestrictionScript}) ${dateRestrictionScript.length ? "&&" : ""} ${dateRestrictionScript}` :
- // "true";
-
- // this.props.CollectionView.props.Document.viewSpecScript = ScriptField.MakeFunction(fullScript, { doc: Doc.name });
- // }
-
- // datePickerRef = (node: HTMLInputElement) => {
- // if (node) {
- // try {
- // this._picker = datepicker("#" + node.id, {
- // disabler: (date: Date) => date > new Date(),
- // onSelect: (instance: any, date: Date) => runInAction(() => {}), // this._dateValue = date),
- // dateSelected: new Date()
- // });
- // } catch (e) {
- // console.log("date picker exception:" + e);
- // }
- // }
- // }
-
-
- @action
- toggleCollapse = () => {
- this.document._chromeStatus = this.document._chromeStatus === "enabled" ? "collapsed" : "enabled";
- if (this.props.collapse) {
- this.props.collapse(this.props.CollectionView.props.Document._chromeStatus !== "enabled");
- }
- }
@computed get subChrome() {
- const collapsed = this.document._chromeStatus !== "enabled";
- if (collapsed) return null;
switch (this.props.type) {
- case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
- case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Freeform: return (<CollectionFreeFormViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Stacking: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Carousel3D: return (<Collection3DCarouselViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
+ case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" CollectionView={this.props.CollectionView} type={this.props.type} />);
default: return null;
}
}
@@ -270,15 +218,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@computed get templateChrome() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- return <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{ display: collapsed ? "none" : undefined }}>
+ 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}>
- <div className="commandEntry-drop">
- <FontAwesomeIcon icon="bullseye" size="2x" />
- </div>
+ <button className={"antimodeMenu-button"} >
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
<select
- className="collectionViewBaseChrome-cmdPicker" onPointerDown={stopPropagation} onChange={this.commandChanged} value={this._currentKey}
- style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}>
+ 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>
@@ -289,14 +235,13 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@computed get viewModes() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- return <div className="collectionViewBaseChrome-viewModes" style={{ display: collapsed ? "none" : undefined }}>
+ 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}>
- <div className="commandEntry-drop">
- <FontAwesomeIcon icon="bullseye" size="2x" />
- </div>
+ <button className={"antimodeMenu-button"}>
+ <FontAwesomeIcon icon="bullseye" size="lg" />
+ </button>
<select
- className="collectionViewBaseChrome-viewPicker" style={{ width: this.props.PanelWidth() < 300 ? 15 : undefined }}
+ className="collectionViewBaseChrome-viewPicker"
onPointerDown={stopPropagation}
onChange={this.viewChanged}
value={StrCast(this.props.CollectionView.props.Document._viewType)}>
@@ -315,34 +260,17 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
render() {
- const collapsed = this.props.CollectionView.props.Document._chromeStatus !== "enabled";
- const scale = Math.min(1, this.props.CollectionView.props.ScreenToLocalTransform()?.Scale);
return (
- <div className="collectionViewChrome-cont" style={{
- top: collapsed ? -70 : 0, height: collapsed ? 0 : undefined,
- transform: collapsed ? "" : `scale(${scale})`,
- width: `${this.props.PanelWidth() / scale}px`
- }}>
- <div className="collectionViewChrome" style={{ border: "unset", pointerEvents: collapsed ? "none" : undefined }}>
+ <div className="collectionMenu-cont" >
+ <div className="collectionMenu">
<div className="collectionViewBaseChrome">
- <button className="collectionViewBaseChrome-collapse"
- style={{
- top: collapsed ? 70 : 10,
- transform: `rotate(${collapsed ? 180 : 0}deg) scale(0.5) translate(${collapsed ? "-100%, -100%" : "0, 0"})`,
- opacity: 0.9,
- display: (collapsed && !this.props.CollectionView.props.isSelected()) ? "none" : undefined,
- left: (collapsed ? 0 : "unset"),
- }}
- title="Collapse collection chrome" onClick={this.toggleCollapse}>
- <FontAwesomeIcon icon="caret-up" size="2x" />
- </button>
{this.viewModes}
- <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: collapsed ? "none" : "grid" }}>
- <div className="collectionViewBaseChrome-filterIcon" onPointerDown={this.toggleViewSpecs} >
- <FontAwesomeIcon icon="filter" size="2x" />
- </div>
- </div>
{this.templateChrome}
+ <div className="collectionViewBaseChrome-viewSpecs" title="filter documents to show" style={{ display: "grid" }}>
+ <button className={"antimodeMenu-button"} onClick={this.toggleViewSpecs} >
+ <FontAwesomeIcon icon="filter" size="lg" />
+ </button>
+ </div>
</div>
{this.subChrome}
</div>
@@ -352,8 +280,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
}
@observer
-export class CollectionFreeFormViewChrome extends React.Component<CollectionViewChromeProps> {
-
+export class CollectionFreeFormViewChrome extends React.Component<CollectionMenuProps> {
+ public static Instance: CollectionFreeFormViewChrome;
+ constructor(props: any) {
+ super(props);
+ CollectionFreeFormViewChrome.Instance = this;
+ }
get Document() { return this.props.CollectionView.props.Document; }
@computed get dataField() {
return this.props.CollectionView.props.Document[Doc.LayoutFieldKey(this.props.CollectionView.props.Document)];
@@ -364,7 +296,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
@undoBatch
@action
nextKeyframe = (): void => {
- const currentFrame = NumCast(this.Document.currentFrame);
+ const currentFrame = Cast(this.Document.currentFrame, "number", null);
if (currentFrame === undefined) {
this.Document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -376,7 +308,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
@undoBatch
@action
prevKeyframe = (): void => {
- const currentFrame = NumCast(this.Document.currentFrame);
+ const currentFrame = Cast(this.Document.currentFrame, "number", null);
if (currentFrame === undefined) {
this.Document.currentFrame = 0;
CollectionFreeFormDocumentView.setupKeyframes(this.childDocs, 0);
@@ -384,9 +316,155 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
CollectionFreeFormDocumentView.gotoKeyframe(this.childDocs.slice());
this.Document.currentFrame = Math.max(0, (currentFrame || 0) - 1);
}
+ @undoBatch
+ @action
+ 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"];
+
+ @observable _shapesNum = this._shape.length;
+ @observable _selected = this._shapesNum;
+
+ @observable _keepMode = false;
+
+ @observable _colorBtn = false;
+ @observable _widthBtn = false;
+ @observable _fillBtn = false;
+
+ @action
+ clearKeep() { this._selected = this._shapesNum; }
+
+ @action
+ changeColor = (color: string, type: string) => {
+ const col: ColorState = {
+ hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
+ rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
+ };
+ if (type === "color") {
+ SetActiveInkColor(Utils.colorString(col));
+ } else if (type === "fill") {
+ SetActiveFillColor(Utils.colorString(col));
+ }
+ }
+
+ @action
+ editProperties = (value: any, field: string) => {
+ SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
+ const doc = Document(element.rootDoc);
+ if (doc.type === DocumentType.INK) {
+ switch (field) {
+ case "width": doc.strokeWidth = Number(value); break;
+ case "color": doc.color = String(value); break;
+ case "fill": doc.fillColor = String(value); break;
+ case "dash": doc.strokeDash = value;
+ }
+ }
+ }));
+ }
+
+ @computed get drawButtons() {
+ const func = action((i: number, keep: boolean) => {
+ this._keepMode = keep;
+ if (this._selected !== i) {
+ this._selected = i;
+ Doc.SetSelectedTool(InkTool.Pen);
+ SetActiveArrowStart(this._head[i]);
+ SetActiveArrowEnd(this._end[i]);
+ SetActiveBezierApprox("300");
+
+ GestureOverlay.Instance.InkShape = this._shape[i];
+ } else {
+ this._selected = this._shapesNum;
+ Doc.SetSelectedTool(InkTool.None);
+ SetActiveArrowStart("");
+ SetActiveArrowEnd("");
+ GestureOverlay.Instance.InkShape = "";
+ SetActiveBezierApprox("0");
+ }
+ });
+ return <div className="btn-draw" key="draw">
+ {this._draw.map((icon, i) =>
+ <button className="antimodeMenu-button" key={icon} onPointerDown={() => func(i, false)} onDoubleClick={() => func(i, true)}
+ style={{ backgroundColor: i === this._selected ? "121212" : "", fontSize: "20" }}>
+ {this._draw[i]}
+ </button>)}
+ </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>;
+ }
+
+ @computed get widthPicker() {
+ const widthPicker = this.toggleButton("stroke width", this._widthBtn, () => this._widthBtn = !this._widthBtn, "bars", null);
+ return !this._widthBtn ? widthPicker :
+ <div className="btn2-group" key="width">
+ {widthPicker}
+ {this._width.map(wid =>
+ <button className="antimodeMenu-button" key={wid}
+ onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
+ style={{ backgroundColor: this._widthBtn ? "121212" : "", zIndex: 1001 }}>
+ {wid}
+ </button>)}
+ </div>;
+ }
+
+ @computed get colorPicker() {
+ const colorPicker = this.toggleButton("stroke color", this._colorBtn, () => this._colorBtn = !this._colorBtn, "pen-nib",
+ <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }} />);
+ return !this._colorBtn ? colorPicker :
+ <div className="btn-group" key="color">
+ {colorPicker}
+ {this._palette.map(color =>
+ <button className="antimodeMenu-button" key={color}
+ onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
+ style={{ backgroundColor: this._colorBtn ? "121212" : "", zIndex: 1001 }}>
+ {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
+ <div className="color-previewII" style={{ backgroundColor: color }} />
+ </button>)}
+ </div>;
+ }
+ @computed get fillPicker() {
+ const fillPicker = this.toggleButton("shape fill color", this._fillBtn, () => this._fillBtn = !this._fillBtn, "fill-drip",
+ <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }} />);
+ return !this._fillBtn ? fillPicker :
+ <div className="btn-group" key="fill" >
+ {fillPicker}
+ {this._palette.map(color =>
+ <button className="antimodeMenu-button" key={color}
+ onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
+ style={{ backgroundColor: this._fillBtn ? "121212" : "", zIndex: 1001 }}>
+ <div className="color-previewII" style={{ backgroundColor: color }}></div>
+ </button>)}
+
+ </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="chevron-right" size="lg" />
+ </button>;
+ }
+
render() {
return this.Document.isAnnotationOverlay ? (null) :
- <div className="collectionFreeFormViewChrome-cont">
+ <div className="collectionFreeFormMenu-cont">
+ <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>
@@ -397,12 +475,17 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionView
<div key="fwd" title="forward frame" className="fwdKeyframe" onClick={this.nextKeyframe}>
<FontAwesomeIcon icon={"caret-right"} size={"lg"} />
</div>
+
+ {this.widthPicker}
+ {this.colorPicker}
+ {this.fillPicker}
+ {this.drawButtons}
+ {this.formatPane}
</div>;
}
}
-
@observer
-export class CollectionStackingViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionStackingViewChrome extends React.Component<CollectionMenuProps> {
@observable private _currentKey: string = "";
@observable private suggestions: string[] = [];
@@ -502,7 +585,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionView
@observer
-export class CollectionSchemaViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionSchemaViewChrome extends React.Component<CollectionMenuProps> {
// private _textwrapAllRows: boolean = Cast(this.props.CollectionView.props.Document.textwrappedSchemaRows, listSpec("string"), []).length > 0;
@undoBatch
@@ -549,7 +632,7 @@ export class CollectionSchemaViewChrome extends React.Component<CollectionViewCh
}
@observer
-export class CollectionTreeViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionTreeViewChrome extends React.Component<CollectionMenuProps> {
get sortAscending() {
return this.props.CollectionView.props.Document[this.props.CollectionView.props.fieldKey + "-sortAscending"];
@@ -585,7 +668,7 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro
// Enter scroll speed for 3D Carousel
@observer
-export class Collection3DCarouselViewChrome extends React.Component<CollectionViewChromeProps> {
+export class Collection3DCarouselViewChrome extends React.Component<CollectionMenuProps> {
@computed get scrollSpeed() {
return this.props.CollectionView.props.Document._autoScrollSpeed;
}
@@ -624,7 +707,7 @@ export class Collection3DCarouselViewChrome extends React.Component<CollectionVi
* Chrome for grid view.
*/
@observer
-export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> {
+export class CollectionGridViewChrome extends React.Component<CollectionMenuProps> {
private clicked: boolean = false;
private entered: boolean = false;
@@ -770,7 +853,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
</span>
<select className="collectionGridViewChrome-viewPicker"
- style={{ marginRight: 5, width: this.props.PanelWidth() < 300 ? 25 : undefined }}
+ style={{ marginRight: 5 }}
onPointerDown={stopPropagation}
onChange={this.changeCompactType}
value={StrCast(this.props.CollectionView.props.Document.gridStartCompaction, StrCast(this.props.CollectionView.props.Document.gridCompaction))}>
@@ -797,4 +880,4 @@ export class CollectionGridViewChrome extends React.Component<CollectionViewChro
</div>
);
}
-}
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx
index d76b6d204..eecaf7672 100644
--- a/src/client/views/collections/CollectionSchemaCells.tsx
+++ b/src/client/views/collections/CollectionSchemaCells.tsx
@@ -151,7 +151,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
// let field = this.props.rowProps.original[this.props.rowProps.column.id as string];
// let doc = FieldValue(Cast(field, Doc));
- // console.log("Expanding doc", StrCast(doc!.title));
// this.props.setPreviewDoc(doc!);
// // this.props.changeFocusedCellByIndex(this.props.row, this.props.col);
@@ -265,7 +264,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
return "0";
} else {
const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey]));
- console.log(cfield);
if (type === "number") {
return StrCast(cfield);
}
@@ -286,7 +284,6 @@ export class CollectionSchemaCell extends React.Component<CellProps> {
const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } });
if (script.compiled) {
retVal = this.applyToDoc(props.Document, this.props.row, this.props.col, script.run);
- console.log("compiled");
}
}
@@ -350,17 +347,13 @@ export class CollectionSchemaDateCell extends CollectionSchemaCell {
@action
handleChange = (date: any) => {
- console.log(date);
this._date = date;
// const script = CompileScript(date.toString(), { requiredType: "Date", addReturn: true, params: { this: Doc.name } });
// if (script.compiled) {
- // console.log("scripting");
// this.applyToDoc(this._document, this.props.row, this.props.col, script.run);
// } else {
- console.log(DateCast(date));
// ^ DateCast is always undefined for some reason, but that is what the field should be set to
this._document[this.props.rowProps.column.id as string] = date as Date;
- console.log(this._document[this.props.rowProps.column.id as string]);
//}
}
@@ -425,8 +418,6 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
const results = script.compiled && script.run();
if (results && results.success) {
-
- console.log(results.result);
this._doc = results.result;
this._document[this.prop.fieldKey] = results.result;
this._docTitle = this._doc?.title;
@@ -457,10 +448,8 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell {
this._preview = false;
} else {
if (bool) {
- console.log("show doc");
this.props.showDoc(this._doc, this.prop.DataDoc, e.clientX, e.clientY);
} else {
- console.log("no doc");
this.props.showDoc(undefined);
}
}
@@ -675,7 +664,6 @@ export class CollectionSchemaListCell extends CollectionSchemaCell {
@action
toggleOpened(open: boolean) {
- console.log("open: " + open);
this._opened = open;
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 3c42a2f1c..5553bbbb7 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -332,7 +332,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@action
openHeader = (col: any, screenx: number, screeny: number) => {
- console.log("header opening");
this._col = col;
this._headerOpen = !this._headerOpen;
this._pointerX = screenx;
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index 9d8790dda..dd4c34885 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -123,7 +123,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
changed = true;
});
}
- changed && setTimeout(action(() => { if (this.columnHeaders) { this.columnHeaders.length = 0; this.columnHeaders.push(...columnHeaders); } }), 0);
+ changed && setTimeout(action(() => this.columnHeaders?.splice(0, this.columnHeaders.length, ...columnHeaders)), 0);
return fields;
}
@@ -163,8 +163,8 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
this.createDashEventsTarget(ele!); //so the whole grid is the drop target?
}
- @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
+ @computed get onChildClickHandler() { return () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
+ @computed get onChildDoubleClickHandler() { return () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
@@ -249,7 +249,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument)
return wid * aspect;
}
return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin :
- Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym]();
+ Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : Math.max(20, layoutDoc[HeightSym]());
}
columnDividerDown = (e: React.PointerEvent) => {
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index 2f4a25bfe..76af70cd1 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faPalette } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, observable, runInAction } from "mobx";
+import { action, observable, runInAction, computed } from "mobx";
import { observer } from "mobx-react";
import { Doc, DocListCast } from "../../../fields/Doc";
import { RichTextField } from "../../../fields/RichTextField";
@@ -279,8 +279,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
const pt = this.props.screenToLocalTransform().inverse().transformPoint(x, y);
ContextMenu.Instance.displayMenu(x, y);
}
-
- render() {
+ @computed get innards() {
TraceMobx();
const cols = this.props.cols();
const key = StrCast(this.props.parent.props.Document._pivotField);
@@ -351,6 +350,43 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
</div> : (null);
for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `;
const chromeStatus = this.props.parent.props.Document._chromeStatus;
+
+ return <>
+ {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
+ {
+ this.collapsed ? (null) :
+ <div style={{ marginTop: 5 }}>
+ <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`,
+ margin: "auto",
+ width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
+ height: 'max-content',
+ position: "relative",
+ gridGap: style.gridGap,
+ gridTemplateColumns: singleColumn ? undefined : templatecols,
+ gridAutoRows: singleColumn ? undefined : "0px"
+ }}>
+ {this.props.parent.children(this.props.docList, uniqueHeadings.length)}
+ {singleColumn ? (null) : this.props.parent.columnDragger}
+ </div>
+ {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
+ <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
+ style={{ width: style.columnWidth / style.numGroupColumns }}>
+ <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
+ </div> : null}
+ </div>
+ }
+ </>;
+ }
+
+
+ render() {
+ TraceMobx();
+ const headings = this.props.headings();
+ const heading = this._heading;
+ const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx);
+ const chromeStatus = this.props.parent.props.Document._chromeStatus;
return (
<div className={"collectionStackingViewFieldColumn" + (SnappingManager.GetIsDragging() ? "Dragging" : "")} key={heading}
style={{
@@ -359,31 +395,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
background: this._background
}}
ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}>
- {this.props.parent.Document._columnsHideIfEmpty ? (null) : headingView}
- {
- this.collapsed ? (null) :
- <div style={{ marginTop: 5 }}>
- <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`,
- margin: "auto",
- width: "max-content", //singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`,
- height: 'max-content',
- position: "relative",
- gridGap: style.gridGap,
- gridTemplateColumns: singleColumn ? undefined : templatecols,
- gridAutoRows: singleColumn ? undefined : "0px"
- }}>
- {this.props.parent.children(this.props.docList, uniqueHeadings.length)}
- {singleColumn ? (null) : this.props.parent.columnDragger}
- </div>
- {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ?
- <div key={`${heading}-add-document`} className="collectionStackingView-addDocumentButton"
- style={{ width: style.columnWidth / style.numGroupColumns }}>
- <EditableView {...newEditableViewProps} menuCallback={this.menuCallback} />
- </div> : null}
- </div>
- }
+ {this.innards}
</div >
);
}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ce6872695..8480a56cc 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx";
import { basename } from 'path';
import CursorField from "../../../fields/CursorField";
import { Doc, Opt, Field } from "../../../fields/Doc";
@@ -19,6 +19,7 @@ import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
import React = require("react");
import * as rp from 'request-promise';
+import ReactLoading from 'react-loading';
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc | Doc[]) => boolean;
@@ -90,6 +91,11 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// to its children which may be templates.
// If 'annotationField' is specified, then all children exist on that field of the extension document, otherwise, they exist directly on the data document under 'fieldKey'
@computed get dataField() {
+ // sets the dataDoc's data field to an empty list if the data field is undefined - prevents issues with addonly
+ // setTimeout changes it outside of the @computed section
+ setTimeout(() => {
+ if (!this.dataDoc[this.props.annotationsKey || this.props.fieldKey]) this.dataDoc[this.props.annotationsKey || this.props.fieldKey] = new List<Doc>();
+ }, 1000);
return this.dataDoc[this.props.annotationsKey || this.props.fieldKey];
}
@@ -214,6 +220,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
const { dataTransfer } = e;
const html = dataTransfer.getData("text/html");
const text = dataTransfer.getData("text/plain");
+ const uriList = dataTransfer.getData("text/uri-list");
if (text && text.startsWith("<div")) {
return;
@@ -306,9 +313,9 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
}
}
- if (text) {
- if (text.includes("www.youtube.com/watch") || text.includes("www.youtube.com/embed")) {
- const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
+ if (uriList || text) {
+ if ((uriList || text).includes("www.youtube.com/watch") || text.includes("www.youtube.com/embed")) {
+ const url = (uriList || text).replace("youtube.com/watch?v=", "youtube.com/embed/").split("&")[0];
this.addDocument(Docs.Create.VideoDocument(url, {
...options,
title: url,
@@ -333,10 +340,20 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
// if ((matches = /(https:\/\/)?photos\.google\.com\/(u\/3\/)?album\/([^\\]+)/g.exec(text)) !== null) {
// const albumId = matches[3];
// const mediaItems = await GooglePhotos.Query.AlbumSearch(albumId);
- // console.log(mediaItems);
// return;
// }
}
+ if (uriList) {
+ this.addDocument(Docs.Create.WebDocument(uriList, {
+ ...options,
+ title: uriList,
+ _width: 400,
+ _height: 315,
+ _nativeWidth: 600,
+ _nativeHeight: 472.5
+ }));
+ return;
+ }
const { items } = e.dataTransfer;
const { length } = items;
@@ -363,7 +380,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
file?.type && files.push(file);
file?.type === "application/json" && Utils.readUploadedFileAsText(file).then(result => {
- console.log(result);
const json = JSON.parse(result as string);
this.addDocument(Docs.Create.TreeDocument(
json["rectangular-puzzle"].crossword.clues[0].clue.map((c: any) => {
@@ -377,13 +393,20 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
});
}
}
+ this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY);
+ batch.end();
+ }
+ slowLoadDocuments = async (files: File[], options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: (() => void) | undefined, clientX: number, clientY: number) => {
+ runInAction(() => CollectionSubViewLoader.Waiting = "block");
+ const disposer = OverlayView.Instance.addElement(
+ <ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 });
generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options));
if (generatedDocuments.length) {
const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d));
if (set) {
- this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!);
+ UndoManager.RunInBatch(() => this.addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!), "drop");
} else {
- generatedDocuments.forEach(this.addDocument);
+ UndoManager.RunInBatch(() => generatedDocuments.forEach(this.addDocument), "drop");
}
completed?.();
} else {
@@ -391,13 +414,17 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?:
this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 }));
}
}
- batch.end();
+ disposer();
}
}
return CollectionSubView;
}
+export class CollectionSubViewLoader {
+ @observable public static Waiting = "none";
+}
+
import { DragManager, dropActionType } from "../../util/DragManager";
import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -405,4 +432,6 @@ import { DocumentType } from "../../documents/DocumentTypes";
import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox";
import { CollectionView } from "./CollectionView";
import { SelectionManager } from "../../util/SelectionManager";
+import { OverlayView } from "../OverlayView";
+import { setTimeout } from "timers";
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 26c41f524..651357e5d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -61,8 +61,8 @@ export interface TreeViewProps {
treeViewHideHeaderFields: () => boolean;
treeViewPreventOpen: boolean;
renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle
- onCheckedClick?: ScriptField;
- onChildClick?: ScriptField;
+ onCheckedClick?: () => ScriptField;
+ onChildClick?: () => ScriptField;
ignoreFields?: string[];
}
@@ -76,7 +76,7 @@ export interface TreeViewProps {
* treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
class TreeView extends React.Component<TreeViewProps> {
- private _editTitleScript: ScriptField | undefined;
+ private _editTitleScript: (() => ScriptField) | undefined;
private _header?: React.RefObject<HTMLDivElement> = React.createRef();
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
@@ -124,7 +124,8 @@ class TreeView extends React.Component<TreeViewProps> {
constructor(props: any) {
super(props);
- this._editTitleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `);
+ const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `);
+ this._editTitleScript = script && (() => script);
if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false);
}
@@ -368,13 +369,13 @@ class TreeView extends React.Component<TreeViewProps> {
}
}
- get onCheckedClick() { return this.props.onCheckedClick || ScriptCast(this.doc.onCheckedClick); }
+ get onCheckedClick() { return this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); }
@action
bulletClick = (e: React.MouseEvent) => {
if (this.onCheckedClick && this.doc.type !== DocumentType.COL) {
// this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check";
- this.onCheckedClick.script.run({
+ this.onCheckedClick?.script.run({
this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc,
heading: this.props.containingCollection.title,
checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check",
@@ -404,6 +405,7 @@ class TreeView extends React.Component<TreeViewProps> {
contextMenuItems = () => [{ script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }];
truncateTitleWidth = () => NumCast(this.props.treeViewDoc.treeViewTruncateTitleWidth, 0);
showTitleEdit = () => ["*", this._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || "");
+ onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.editTitleScript));
/**
* Renders the EditableView title element for placement into the tree.
*/
@@ -437,7 +439,7 @@ class TreeView extends React.Component<TreeViewProps> {
addDocTab={this.props.addDocTab}
rootSelected={returnTrue}
pinToPres={emptyFunction}
- onClick={this.props.onChildClick || this._editTitleScript}
+ onClick={this.onChildClick}
dropAction={this.props.dropAction}
moveDocument={this.move}
removeDocument={this.removeDoc}
@@ -539,8 +541,8 @@ class TreeView extends React.Component<TreeViewProps> {
treeViewPreventOpen: boolean,
renderedIds: string[],
libraryPath: Doc[] | undefined,
- onCheckedClick: ScriptField | undefined,
- onChildClick: ScriptField | undefined,
+ onCheckedClick: undefined | (() => ScriptField),
+ onChildClick: undefined | (() => ScriptField),
ignoreFields: string[] | undefined
) {
const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
@@ -658,8 +660,8 @@ class TreeView extends React.Component<TreeViewProps> {
export type collectionTreeViewProps = {
treeViewHideTitle?: boolean;
treeViewHideHeaderFields?: boolean;
- onCheckedClick?: ScriptField;
- onChildClick?: ScriptField;
+ onCheckedClick?: () => ScriptField;
+ onChildClick?: () => ScriptField;
};
@observer
@@ -780,7 +782,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...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {});
@@ -794,8 +796,8 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
</div >;
}
- onKeyPress = (e: React.KeyboardEvent) => {
- console.log(e);
+ onChildClick = () => {
+ return this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick);
}
render() {
TraceMobx();
@@ -814,7 +816,6 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
paddingTop: `${NumCast(this.doc._yPadding, 20)}px`,
pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined
}}
- onKeyPress={this.onKeyPress}
onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()}
onDrop={this.onTreeDrop}
ref={this.createTreeDropTarget}>
@@ -839,7 +840,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll
moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.backgroundColor, this.props.ScreenToLocalTransform,
this.outerXf, this.props.active, this.props.PanelWidth, this.props.ChromeHeight, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields),
BoolCast(this.doc.treeViewPreventOpen), [], this.props.LibraryPath, this.props.onCheckedClick,
- this.props.onChildClick || ScriptCast(this.doc.onChildClick), this.props.ignoreFields)
+ this.onChildClick, this.props.ignoreFields)
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index df21d6a28..e2f78d6f9 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -8,7 +8,7 @@ import * as React from 'react';
import Lightbox from 'react-image-lightbox-with-rotate';
import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app
import { DateField } from '../../../fields/DateField';
-import { AclAddonly, AclReadonly, AclSym, DataSym, Doc, DocListCast, Field, Opt } from '../../../fields/Doc';
+import { AclAddonly, AclReadonly, DataSym, Doc, DocListCast, Field, Opt, AclEdit, AclSym, AclPrivate } from '../../../fields/Doc';
import { Id } from '../../../fields/FieldSymbols';
import { List } from '../../../fields/List';
import { ObjectField } from '../../../fields/ObjectField';
@@ -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 } from '../../../fields/util';
+import { TraceMobx, GetEffectiveAcl, getPlaygroundMode, distributeAcls } 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';
@@ -47,7 +47,8 @@ import { SubCollectionViewProps } from './CollectionSubView';
import { CollectionTimeView } from './CollectionTimeView';
import { CollectionTreeView } from "./CollectionTreeView";
import './CollectionView.scss';
-import { CollectionViewBaseChrome } from './CollectionViewChromes';
+import CollectionMenu from './CollectionMenu';
+import { SharingPermissions } from '../../util/SharingManager';
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -106,6 +107,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
protected multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer;
+ private AclMap = new Map<symbol, string>([
+ [AclPrivate, SharingPermissions.None],
+ [AclReadonly, SharingPermissions.View],
+ [AclAddonly, SharingPermissions.Add],
+ [AclEdit, SharingPermissions.Edit]
+ ]);
+
get collectionViewType(): CollectionViewType | undefined {
const viewField = StrCast(this.props.Document._viewType);
if (CollectionView._safeMode) {
@@ -128,36 +136,54 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
if (this.props.filterAddDocument?.(doc) === false) {
return false;
}
+
const docs = doc instanceof Doc ? [doc] : doc;
const targetDataDoc = this.props.Document[DataSym];
const docList = DocListCast(targetDataDoc[this.props.fieldKey]);
const added = docs.filter(d => !docList.includes(d));
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+
if (added.length) {
- if (this.dataDoc[AclSym] === AclReadonly) {
+ if (effectiveAcl === AclReadonly && !getPlaygroundMode()) {
return false;
- } else if (this.dataDoc[AclSym] === AclAddonly) {
- added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
- } else {
- added.map(doc => {
- const context = Cast(doc.context, Doc, null);
- if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG) &&
- !DocListCast(doc.links).some(d => d.isPushpin)) {
- const pushpin = Docs.Create.FontIconDocument({
- icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
- _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
- });
- Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
- const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
- const first = DocListCast(pushpin.links).find(d => d instanceof Doc);
- first && (first.hidden = true);
- pushpinLink && (Doc.GetProto(pushpinLink).isPushpin = true);
- doc.displayTimecode = undefined;
- }
- 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 + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
+ 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 (effectiveAcl === AclAddonly) {
+ added.map(doc => Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc));
+ }
+ else {
+ added.map(doc => {
+ const context = Cast(doc.context, Doc, null);
+ if (context && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) {
+ const pushpin = Docs.Create.FontIconDocument({
+ title: "pushpin", label: "",
+ icon: "map-pin", x: Cast(doc.x, "number", null), y: Cast(doc.y, "number", null), _backgroundColor: "#0000003d", color: "#ACCEF7",
+ _width: 15, _height: 15, _xPadding: 0, isLinkButton: true, displayTimecode: Cast(doc.displayTimecode, "number", null)
+ });
+ pushpin.isPushpin = true;
+ Doc.GetProto(pushpin).annotationOn = doc.annotationOn;
+ Doc.SetInPlace(doc, "annotationOn", undefined, true);
+ Doc.AddDocToList(context, Doc.LayoutFieldKey(context) + "-annotations", pushpin);
+ const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", "");
+ doc.displayTimecode = undefined;
+ }
+ 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 + "-lastModified"] = new DateField(new Date(Date.now()));
+ }
}
}
return true;
@@ -165,13 +191,15 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
@action.bound
removeDocument = (doc: any): boolean => {
- 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);
- return true;
+ if (GetEffectiveAcl(this.props.Document) === AclEdit || getPlaygroundMode()) {
+ 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);
+ return true;
+ }
}
return false;
}
@@ -234,16 +262,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
}
}
- @action
- private collapse = (value: boolean) => {
- this.props.Document._chromeStatus = value ? "collapsed" : "enabled";
- }
-
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
- // currently cant think of a reason for collection docking view to have a chrome. mind may change if we ever have nested docking views -syip
- const chrome = this.props.Document._chromeStatus === "disabled" || this.props.Document._chromeStatus === "replaced" || type === CollectionViewType.Docking ? (null) :
- <CollectionViewBaseChrome key="chrome" CollectionView={this} PanelWidth={this.bodyPanelWidth} type={type} collapse={this.collapse} />;
- return <>{chrome} {this.SubViewHelper(type, renderProps)}</>;
+ return this.SubViewHelper(type, renderProps);
}
@@ -272,7 +292,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) });
}
addExtras && subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" });
- !existingVm && ContextMenu.Instance.addItem({ description: category, subitems: subItems, icon: "eye" });
+ !existingVm && ContextMenu.Instance.addItem({ description: category, noexpand: true, subitems: subItems, icon: "eye" });
}
onContextMenu = (e: React.MouseEvent): void => {
@@ -285,18 +305,18 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
return newRendition;
}, false);
- const existing = cm.findByDescription("Options...");
- const layoutItems = existing && "subitems" in existing ? existing.subitems : [];
- layoutItems.push({ description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" });
+ 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" });
if (this.props.Document.childLayout instanceof Doc) {
- layoutItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
+ optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "onRight"), icon: "project-diagram" });
}
if (this.props.Document.childClickedOpenTemplateView instanceof Doc) {
- layoutItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
+ optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "onRight"), icon: "project-diagram" });
}
- layoutItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
+ !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" });
- !existing && cm.addItem({ description: "Options...", subitems: layoutItems, icon: "hand-point-right" });
+ !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" });
const existingOnClick = cm.findByDescription("OnClick...");
const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : [];
@@ -305,16 +325,18 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
{ key: "onChildDoubleClick", name: "On Child Double Clicked" }];
funcs.map(func => onClicks.push({
description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => {
- ScriptBox.EditButtonScript(func.name + "...", this.props.Document, func.key, obj.x, obj.y, { thisContainer: Doc.name });
+ const alias = Doc.MakeAlias(this.props.Document);
+ DocUtils.makeCustomViewClicked(alias, undefined, func.key);
+ this.props.addDocTab(alias, "onRight");
}
}));
DocListCast(Cast(Doc.UserDoc()["clickFuncs-child"], Doc, null).data).forEach(childClick =>
onClicks.push({
description: `Set child ${childClick.title}`,
icon: "edit",
- event: () => this.props.Document[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
+ event: () => Doc.GetProto(this.props.Document)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)),
}));
- !existingOnClick && cm.addItem({ description: "OnClick...", subitems: onClicks, icon: "hand-point-right" });
+ !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "hand-point-right" });
if (!Doc.UserDoc().noviceMode) {
const more = cm.findByDescription("More...");
@@ -470,7 +492,8 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves)
@computed get scriptField() {
const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)";
- return ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name });
+ return script ? () => script : undefined;
}
@computed get filterView() {
TraceMobx();
@@ -523,7 +546,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ContentScaling={returnOne}
focus={returnFalse}
treeViewHideHeaderFields={true}
- onCheckedClick={this.scriptField!}
+ onCheckedClick={this.scriptField}
ignoreFields={this.ignoreFields}
annotationsKey={""}
dontRegisterView={true}
@@ -549,6 +572,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus
ChildLayoutTemplate: this.childLayoutTemplate,
ChildLayoutString: this.childLayoutString,
};
+ setTimeout(action(() => this.props.isSelected(true) && (CollectionMenu.Instance.SelectedCollection = this)), 0);
const boxShadow = Doc.UserDoc().renderStyle === "comic" || this.props.Document.isBackground || this.collectionViewType === CollectionViewType.Linear ? undefined :
`${Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "rgb(30, 32, 31) " : "#9c9396 "} ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}`;
return (<div className={"collectionView"} onContextMenu={this.onContextMenu}
diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx
index 9e3b4d961..cde795098 100644
--- a/src/client/views/collections/SchemaTable.tsx
+++ b/src/client/views/collections/SchemaTable.tsx
@@ -135,7 +135,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
@action
changeSorting = (col: any) => {
- console.log(col.heading);
if (col.desc === undefined) {
// no sorting
this.props.changeColumnSort(col, true);
@@ -149,7 +148,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> {
}
@action
- changeTitleMode = () => { console.log("header clicked"); 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/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 05111adb4..8cbda310a 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -16,4 +16,5 @@
stroke: rgb(0,0,0);
opacity: 0.5;
pointer-events: all;
+ cursor: move;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index a24693c30..bfe569853 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,13 +1,12 @@
import { observer } from "mobx-react";
import { Doc } from "../../../../fields/Doc";
-import { Utils } from '../../../../Utils';
+import { Utils, setupMoveUpEvents, emptyFunction, returnFalse } from '../../../../Utils';
import { DocumentView } from "../../nodes/DocumentView";
import "./CollectionFreeFormLinkView.scss";
import React = require("react");
-import v5 = require("uuid/v5");
import { DocumentType } from "../../../documents/DocumentTypes";
-import { observable, action, reaction, IReactionDisposer } from "mobx";
-import { StrCast, Cast } from "../../../../fields/Types";
+import { observable, action, reaction, IReactionDisposer, trace, computed } from "mobx";
+import { StrCast, Cast, NumCast } from "../../../../fields/Types";
import { Id } from "../../../../fields/FieldSymbols";
import { SnappingManager } from "../../../util/SnappingManager";
@@ -20,18 +19,27 @@ export interface CollectionFreeFormLinkViewProps {
@observer
export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFormLinkViewProps> {
@observable _opacity: number = 0;
+ @observable _start = 0;
_anchorDisposer: IReactionDisposer | undefined;
+ _timeout: NodeJS.Timeout | undefined;
+ componentWillUnmount() {
+ this._anchorDisposer?.();
+ }
@action
+ timeout = () => (Date.now() < this._start++ + 1000) && setTimeout(this.timeout, 25)
componentDidMount() {
this._anchorDisposer = reaction(() => [this.props.A.props.ScreenToLocalTransform(), this.props.B.props.ScreenToLocalTransform(), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document), this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document)],
action(() => {
- if (SnappingManager.GetIsDragging()) return;
+ this._start = Date.now();
+ this._timeout && clearTimeout(this._timeout);
+ this._timeout = setTimeout(this.timeout, 25);
+ if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv) return;
setTimeout(action(() => this._opacity = 1), 0); // since the render code depends on querying the Dom through getBoudndingClientRect, we need to delay triggering render()
setTimeout(action(() => (!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && (this._opacity = 0.05)), 750); // this will unhighlight the link line.
- const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
- const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont") : [];
- const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv!);
- const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv!);
+ const acont = this.props.A.props.Document.type === DocumentType.LINK ? this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const bcont = this.props.B.props.Document.type === DocumentType.LINK ? this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont") : [];
+ const adiv = (acont.length ? acont[0] : this.props.A.ContentDiv);
+ const bdiv = (bcont.length ? bcont[0] : this.props.B.ContentDiv);
const a = adiv.getBoundingClientRect();
const b = bdiv.getBoundingClientRect();
const abounds = adiv.parentElement!.getBoundingClientRect();
@@ -82,17 +90,31 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
})
, { fireImmediately: true });
}
- @action
- componentWillUnmount() {
- this._anchorDisposer?.();
+
+
+ pointerDown = (e: React.PointerEvent) => {
+ setupMoveUpEvents(this, e, (e, down, delta) => {
+ this.props.LinkDocs[0].linkOffsetX = NumCast(this.props.LinkDocs[0].linkOffsetX) + delta[0];
+ this.props.LinkDocs[0].linkOffsetY = NumCast(this.props.LinkDocs[0].linkOffsetY) + delta[1];
+ return false;
+ }, emptyFunction, () => {
+ // OverlayView.Instance.addElement(
+ // <LinkEditor sourceDoc={this.props.A.props.Document} linkDoc={this.props.LinkDocs[0]}
+ // showLinks={action(() => { })}
+ // />, { x: 300, y: 300 });
+ });
}
- render() {
- if (SnappingManager.GetIsDragging()) return null;
+
+ @computed get renderData() {
+ this._start;
+ if (SnappingManager.GetIsDragging() || !this.props.A.ContentDiv || !this.props.B.ContentDiv || !this.props.LinkDocs.length) {
+ return undefined;
+ }
this.props.A.props.ScreenToLocalTransform().transform(this.props.B.props.ScreenToLocalTransform());
- const acont = this.props.A.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
- const bcont = this.props.B.ContentDiv!.getElementsByClassName("linkAnchorBox-cont");
- const a = (acont.length ? acont[0] : this.props.A.ContentDiv!).getBoundingClientRect();
- const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv!).getBoundingClientRect();
+ const acont = this.props.A.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const bcont = this.props.B.ContentDiv.getElementsByClassName("linkAnchorBox-cont");
+ const a = (acont.length ? acont[0] : this.props.A.ContentDiv).getBoundingClientRect();
+ const b = (bcont.length ? bcont[0] : this.props.B.ContentDiv).getBoundingClientRect();
const apt = Utils.closestPtBetweenRectangles(a.left, a.top, a.width, a.height,
b.left, b.top, b.width, b.height,
a.left + a.width / 2, a.top + a.height / 2);
@@ -105,18 +127,26 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
const pt2vec = [pt2[0] - (b.left + b.width / 2), pt2[1] - (b.top + b.height / 2)];
const pt1len = Math.sqrt((pt1vec[0] * pt1vec[0]) + (pt1vec[1] * pt1vec[1]));
const pt2len = Math.sqrt((pt2vec[0] * pt2vec[0]) + (pt2vec[1] * pt2vec[1]));
- const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 3;
+ const ptlen = Math.sqrt((pt1[0] - pt2[0]) * (pt1[0] - pt2[0]) + (pt1[1] - pt2[1]) * (pt1[1] - pt2[1])) / 2;
const pt1norm = [pt1vec[0] / pt1len * ptlen, pt1vec[1] / pt1len * ptlen];
const pt2norm = [pt2vec[0] / pt2len * ptlen, pt2vec[1] / pt2len * ptlen];
const aActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- const bActive = this.props.A.isSelected() || Doc.IsBrushed(this.props.A.props.Document);
- const text = StrCast(this.props.A.props.Document.linkRelationship);
- return !a.width || !b.width || ((!this.props.LinkDocs.length || !this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
- <text x={(Math.min(pt1[0], pt2[0]) * 2 + Math.max(pt1[0], pt2[0])) / 3} y={(pt1[1] + pt2[1]) / 2}>
- {text !== "-ungrouped-" ? text : ""}
- </text>
+ const bActive = this.props.B.isSelected() || Doc.IsBrushed(this.props.B.props.Document);
+
+ const textX = (Math.min(pt1[0], pt2[0]) + Math.max(pt1[0], pt2[0])) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetX);
+ const textY = (pt1[1] + pt2[1]) / 2 + NumCast(this.props.LinkDocs[0].linkOffsetY);
+ return { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 };
+ }
+
+ render() {
+ if (!this.renderData) return (null);
+ const { a, b, pt1norm, pt2norm, aActive, bActive, textX, textY, pt1, pt2 } = this.renderData;
+ return !a.width || !b.width || ((!this.props.LinkDocs[0].linkDisplay) && !aActive && !bActive) ? (null) : (<>
<path className="collectionfreeformlinkview-linkLine" style={{ opacity: this._opacity, strokeDasharray: "2 2" }}
d={`M ${pt1[0]} ${pt1[1]} C ${pt1[0] + pt1norm[0]} ${pt1[1] + pt1norm[1]}, ${pt2[0] + pt2norm[0]} ${pt2[1] + pt2norm[1]}, ${pt2[0]} ${pt2[1]}`} />
+ <text className="collectionfreeformlinkview-linkText" x={textX} y={textY} onPointerDown={this.pointerDown} >
+ {StrCast(this.props.LinkDocs[0].description)}
+ </text>
</>);
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 9bf425db2..979b21321 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -46,6 +46,8 @@ import "./CollectionFreeFormView.scss";
import MarqueeOptionsMenu from "./MarqueeOptionsMenu";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
+import { SearchUtil } from "../../../util/SearchUtil";
+import { LinkManager } from "../../../util/LinkManager";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload);
@@ -75,7 +77,8 @@ export type collectionFreeformViewProps = {
forceScaling?: boolean; // whether to force scaling of content (needed by ImageBox)
viewDefDivClick?: ScriptField;
childPointerEvents?: boolean;
- scaleField?: string;
+ scaleField?: string; // used by formattedTextBox when displaying a sidebar freeform view which needs its own scale field
+ noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale)
};
@observer
@@ -101,6 +104,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
@observable _clusterSets: (Doc[])[] = [];
@observable _timelineRef = React.createRef<Timeline>();
+ @observable _marqueeRef = React.createRef<HTMLDivElement>();
+ @observable canPanX: boolean = true;
+ @observable canPanY: boolean = true;
+
@computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; }
@computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; }
@computed get parentScaling() { return this.props.ContentScaling && this.fitToContent && !this.isAnnotationOverlay ? this.props.ContentScaling() : 1; }
@@ -180,7 +187,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll();
- docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
+ docs.map(doc => DocumentManager.Instance.getDocumentView(doc, this.props.CollectionView)).map(dv => dv && SelectionManager.SelectDoc(dv, true));
}
public isCurrent(doc: Doc) { return (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); }
@@ -205,7 +212,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
const d = docDragData.droppedDocuments[i];
const layoutDoc = Doc.Layout(d);
- if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) {
+ if (this.Document.currentFrame !== undefined) {
const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000));
CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.opacity);
} else {
@@ -490,10 +497,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const start = this.getTransform().transformPoint(Math.min(...ge.points.map(p => p.X)), Math.min(...ge.points.map(p => p.Y)));
this._inkToTextStartX = start[0];
this._inkToTextStartY = start[1];
- console.log("start");
break;
case GestureUtils.Gestures.EndBracket:
- console.log("end");
if (this._inkToTextStartX && this._inkToTextStartY) {
const end = this.getTransform().transformPoint(Math.max(...ge.points.map(p => p.X)), Math.max(...ge.points.map(p => p.Y)));
const setDocs = this.getActiveDocuments().filter(s => s.proto?.type === "rtf" && s.color);
@@ -932,9 +937,9 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}
@computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; }
- @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); }
@computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); }
+ onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick);
backgroundHalo = () => BoolCast(this.Document.useClusters);
parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.backgroundActive ? true : false;
getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps {
@@ -946,8 +951,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
DataDoc: childData,
Document: childLayout,
LibraryPath: this.libraryPath,
- LayoutTemplate: this.props.ChildLayoutTemplate,
- LayoutTemplateString: this.props.ChildLayoutString,
+ LayoutTemplate: childLayout.z ? undefined : this.props.ChildLayoutTemplate,
+ LayoutTemplateString: childLayout.z ? undefined : this.props.ChildLayoutString,
FreezeDimensions: this.props.freezeChildDimensions,
layoutKey: undefined,
setupDragLines: this.setupDragLines,
@@ -1027,7 +1032,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const transform = `translate(${x}px, ${y}px)`;
if (viewDef.type === "text") {
const text = Cast(viewDef.text, "string"); // don't use NumCast, StrCast, etc since we want to test for undefined below
- const fontSize = Cast(viewDef.fontSize, "number");
+ const fontSize = Cast(viewDef.fontSize, "string");
return [text, x, y].some(val => val === undefined) ? undefined :
{
ele: <div className="collectionFreeform-customText" key={(text || "") + x + y + z + color} style={{ width, height, color, fontSize, transform }}>
@@ -1065,7 +1070,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
doFreeformLayout(poolData: Map<string, PoolData>) {
const layoutDocs = this.childLayoutPairs.map(pair => pair.layout);
- const initResult = this.Document.arrangeInit && this.Document.arrangeInit.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
+ const initResult = this.Document.arrangeInit?.script.run({ docs: layoutDocs, collection: this.Document }, console.log);
const state = initResult?.success ? initResult.result.scriptState : undefined;
const elements = initResult?.success ? this.viewDefsToJSX(initResult.result.views) : [];
@@ -1143,10 +1148,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
this._layoutComputeReaction = reaction(() => this.doLayoutComputation,
(elements) => this._layoutElements = elements || [],
{ fireImmediately: true, name: "doLayout" });
+
+ this._marqueeRef.current?.addEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
+
componentWillUnmount() {
this._layoutComputeReaction?.();
+ this._marqueeRef.current?.removeEventListener("dashDragAutoScroll", this.onDragAutoScroll as any);
}
+
@computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); }
elementFunc = () => this._layoutElements;
@@ -1155,6 +1165,29 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+
+ // <div ref={this._marqueeRef}>
+
+ @action
+ onDragAutoScroll = (e: CustomEvent<React.DragEvent>) => {
+ if ((e as any).handlePan || this.props.isAnnotationOverlay) return;
+ (e as any).handlePan = true;
+
+ if (this._marqueeRef?.current) {
+ const dragX = e.detail.clientX;
+ const dragY = e.detail.clientY;
+ const bounds = this._marqueeRef.current?.getBoundingClientRect();
+
+ const deltaX = dragX - bounds.left < 25 ? -(25 + (bounds.left - dragX)) : bounds.right - dragX < 25 ? 25 - (bounds.right - dragX) : 0;
+ const deltaY = dragY - bounds.top < 25 ? -(25 + (bounds.top - dragY)) : bounds.bottom - dragY < 25 ? 25 - (bounds.bottom - dragY) : 0;
+ if (deltaX !== 0 || deltaY !== 0) {
+ this.Document._panY = NumCast(this.Document._panY) + deltaY / 2;
+ this.Document._panX = NumCast(this.Document._panX) + deltaX / 2;
+ }
+ }
+ e.stopPropagation();
+ }
+
promoteCollection = undoBatch(action(() => {
const childDocs = this.childDocs.slice();
childDocs.forEach(doc => {
@@ -1205,55 +1238,69 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
const appearance = ContextMenu.Instance.findByDescription("Appearance...");
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 ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" });
- appearanceItems.push({ description: `${this.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: () => this.updateClusters(!this.Document.useClusters), icon: "braille" });
- appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
+ 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" });
!appearance && ContextMenu.Instance.addItem({ description: "Appearance...", subitems: appearanceItems, icon: "eye" });
+ const viewctrls = ContextMenu.Instance.findByDescription("View 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" });
+ !viewctrls && ContextMenu.Instance.addItem({ description: "View Controls...", subitems: viewCtrlItems, icon: "eye" });
+
const options = ContextMenu.Instance.findByDescription("Options...");
const optionItems = options && "subitems" in options ? options.subitems : [];
- !this.props.isAnnotationOverlay &&
+ !this.props.isAnnotationOverlay && !Doc.UserDoc().noviceMode &&
optionItems.push({ description: (this.showTimeline ? "Close" : "Open") + " Animation Timeline", event: action(() => this.showTimeline = !this.showTimeline), icon: faEye });
this.props.ContainingCollectionView &&
optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" });
- optionItems.push({ description: (Doc.UserDoc().showSnapLines ? "Hide" : "Show") + " snap lines", event: () => Doc.UserDoc().showSnapLines = !Doc.UserDoc().showSnapLines, icon: "compress-arrows-alt" });
optionItems.push({ description: this.layoutDoc._lockedTransform ? "Unlock Transform" : "Lock Transform", event: this.toggleLockTransform, icon: this.layoutDoc._lockedTransform ? "unlock" : "lock" });
- optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" });
+ appearanceItems.push({ description: "Use Background Color as Default", event: () => Cast(Doc.UserDoc().emptyCollection, Doc, null)._backgroundColor = StrCast(this.layoutDoc._backgroundColor), icon: "palette" });
if (!Doc.UserDoc().noviceMode) {
optionItems.push({ description: (!this.layoutDoc._nativeWidth || !this.layoutDoc._nativeHeight ? "Freeze" : "Unfreeze") + " Aspect", event: this.toggleNativeDimensions, icon: "snowflake" });
optionItems.push({ description: `${this.Document._freeformLOD ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._freeformLOD = !this.Document._freeformLOD, icon: "table" });
- optionItems.push({
- description: "Import document", icon: "upload", event: ({ x, y }) => {
- const input = document.createElement("input");
- input.type = "file";
- input.accept = ".zip";
- input.onchange = async _e => {
- const upload = Utils.prepend("/uploadDoc");
- const formData = new FormData();
- const file = input.files && input.files[0];
- if (file) {
- formData.append('file', file);
- formData.append('remap', "true");
- const response = await fetch(upload, { method: "POST", body: formData });
- const json = await response.json();
- if (json !== "error") {
- const doc = await DocServer.GetRefField(json);
- if (doc instanceof Doc) {
- const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
- doc.x = xx, doc.y = yy;
- this.props.addDocument?.(doc);
- }
- }
- }
- };
- input.click();
- }
- });
+
}
!options && ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" });
+ const mores = ContextMenu.Instance.findByDescription("More...");
+ const moreItems = mores && "subitems" in mores ? mores.subitems : [];
+ moreItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => this.importDocument(x, y) });
+ !mores && ContextMenu.Instance.addItem({ description: "More...", subitems: moreItems, icon: "eye" });
+ }
+ importDocument = (x: number, y: number) => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".zip";
+ input.onchange = async _e => {
+ const upload = Utils.prepend("/uploadDoc");
+ const formData = new FormData();
+ const file = input.files && input.files[0];
+ if (file) {
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json !== "error") {
+ const doc = await DocServer.GetRefField(json);
+ if (doc instanceof Doc) {
+ const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y);
+ doc.x = xx, doc.y = yy;
+ this.props.addDocument?.(doc);
+ 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.
+ }
+ }
+ }
+ };
+ input.click();
}
+
+
@observable showTimeline = false;
intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -1336,7 +1383,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
return false;
});
@computed get marqueeView() {
- return <MarqueeView {...this.props}
+ return <MarqueeView
+ {...this.props}
nudge={this.nudge}
addDocTab={this.addDocTab}
activeDocuments={this.getActiveDocuments}
@@ -1346,14 +1394,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
getContainerTransform={this.getContainerTransform}
getTransform={this.getTransform}
isAnnotationOverlay={this.isAnnotationOverlay}>
- <CollectionFreeFormViewPannableContents
- centeringShiftX={this.centeringShiftX}
- centeringShiftY={this.centeringShiftY}
- transition={Cast(this.layoutDoc._viewTransition, "string", null)}
- viewDefDivClick={this.props.viewDefDivClick}
- zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
- {this.children}
- </CollectionFreeFormViewPannableContents>
+ <div ref={this._marqueeRef}>
+ <CollectionFreeFormViewPannableContents
+ centeringShiftX={this.centeringShiftX}
+ centeringShiftY={this.centeringShiftY}
+ transition={Cast(this.layoutDoc._viewTransition, "string", null)}
+ viewDefDivClick={this.props.viewDefDivClick}
+ zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
+ {this.children}
+ </CollectionFreeFormViewPannableContents></div>
{this.showTimeline ? <Timeline ref={this._timelineRef} {...this.props} /> : (null)}
</MarqueeView>;
}
@@ -1370,6 +1419,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
render() {
TraceMobx();
const clientRect = this._mainCont?.getBoundingClientRect();
+ !this.fitToContent && this._layoutElements?.length && setTimeout(() => this.Document._renderContentBounds = new List<number>([this.contentBounds.x, this.contentBounds.y, this.contentBounds.r, this.contentBounds.b]), 0);
return <div className={"collectionfreeformview-container"} ref={this.createDashEventsTarget}
onPointerOver={this.onPointerOver}
onWheel={this.onPointerWheel}
@@ -1388,7 +1438,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P
}}>
{this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && !this.props.annotationsKey && this.props.renderDepth > 0 ?
this.placeholder : this.marqueeView}
- <CollectionFreeFormOverlayView elements={this.elementFunc} />
+ {!this.props.noOverlay ? <CollectionFreeFormOverlayView elements={this.elementFunc} /> : (null)}
<div className={"pullpane-indicator"}
style={{
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.scss b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
new file mode 100644
index 000000000..88876471c
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.scss
@@ -0,0 +1,64 @@
+.antimodeMenu-button {
+ width: 200px;
+ position: relative;
+ text-align: left;
+
+ .color-previewI {
+ width: 100%;
+ height: 40%;
+ }
+
+ .color-previewII {
+ width: 100%;
+ height: 100%;
+ }
+}
+
+.antimenu-Buttonup {
+ position: absolute;
+ width: 20;
+ height: 10;
+ right: 0;
+ padding: 0;
+}
+
+.formatShapePane-inputBtn {
+ width: inherit;
+ position: absolute;
+}
+
+.sketch-picker {
+ background: #323232;
+
+ .flexbox-fit {
+ background: #323232;
+ }
+}
+
+.btn-group {
+ display: grid;
+ grid-template-columns: auto auto auto auto;
+ /* Make the buttons appear below each other */
+}
+
+.btn-group-palette {
+ display: block;
+ /* Make the buttons appear below each other */
+}
+
+.btn-draw {
+ display: inline;
+ /* Make the buttons appear below each other */
+}
+
+.btn2-group {
+ display: block;
+ background: #323232;
+ grid-template-columns: auto;
+
+ /* Make the buttons appear below each other */
+ .antimodeMenu-button {
+ background: #323232;
+ display: block;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
new file mode 100644
index 000000000..4e328d838
--- /dev/null
+++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx
@@ -0,0 +1,307 @@
+import React = require("react");
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, observable } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, Field, Opt } from "../../../../fields/Doc";
+import { Document } from "../../../../fields/documentSchemas";
+import { InkField } from "../../../../fields/InkField";
+import { BoolCast, Cast, NumCast } from "../../../../fields/Types";
+import { DocumentType } from "../../../documents/DocumentTypes";
+import { SelectionManager } from "../../../util/SelectionManager";
+import AntimodeMenu from "../../AntimodeMenu";
+import "./FormatShapePane.scss";
+import { undoBatch } from "../../../util/UndoManager";
+
+@observer
+export default class FormatShapePane extends AntimodeMenu {
+ static Instance: FormatShapePane;
+
+ private _lastFill = "#D0021B";
+ private _lastLine = "#D0021B";
+ private _lastDash = "2";
+ private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF"];
+ private _mode = ["fill-drip", "ruler-combined"];
+
+ @observable private _subOpen = [false, false, false, false];
+ @observable private _currMode = "fill-drip";
+ @observable private _lock = false;
+ @observable private _fillBtn = false;
+ @observable private _lineBtn = false;
+
+ getField(key: string) {
+ return this.selectedInk?.reduce((p, i) =>
+ (p === undefined || (p && p === i.rootDoc[key])) && i.rootDoc[key] !== "0" ? Field.toString(i.rootDoc[key] as Field) : "", undefined as Opt<string>);
+ }
+
+ @computed get selectedInk() {
+ const inks = SelectionManager.SelectedDocuments().filter(i => Document(i.rootDoc).type === DocumentType.INK);
+ return inks.length ? inks : undefined;
+ }
+ @computed get unFilled() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get unStrokd() { return this.selectedInk?.reduce((p, i) => p && !i.rootDoc.color ? true : false, true) || false; }
+ @computed get solidFil() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.fillColor ? true : false, true) || false; }
+ @computed get solidStk() { return this.selectedInk?.reduce((p, i) => p && i.rootDoc.color && (!i.rootDoc.strokeDash || i.rootDoc.strokeDash === "0") ? true : false, true) || false; }
+ @computed get dashdStk() { return !this.unStrokd && this.getField("strokeDash") || ""; }
+ @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; }
+ @computed get widthStk() { return this.getField("strokeWidth") || "1"; }
+ @computed get markHead() { return this.getField("strokeStartMarker") || ""; }
+ @computed get markTail() { return this.getField("strokeEndMarker") || ""; }
+ @computed get shapeHgt() { return this.getField("_height"); }
+ @computed get shapeWid() { return this.getField("_width"); }
+ @computed get shapeXps() { return this.getField("x"); }
+ @computed get shapeYps() { return this.getField("y"); }
+ @computed get shapeRot() { return this.getField("rotation"); }
+ set unFilled(value) { this.colorFil = value ? "" : this._lastFill; }
+ set solidFil(value) { this.unFilled = !value; }
+ set colorFil(value) { value && (this._lastFill = value); this.selectedInk?.forEach(i => i.rootDoc.fillColor = value ? value : undefined); }
+ set colorStk(value) { value && (this._lastLine = value); this.selectedInk?.forEach(i => i.rootDoc.color = value ? value : undefined); }
+ set markHead(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeStartMarker = value); }
+ set markTail(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeEndMarker = value); }
+ set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; }
+ set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; }
+ set dashdStk(value) {
+ value && (this._lastDash = value) && (this.unStrokd = false);
+ this.selectedInk?.forEach(i => i.rootDoc.strokeDash = value ? this._lastDash : undefined);
+ }
+ set shapeXps(value) { this.selectedInk?.forEach(i => i.rootDoc.x = Number(value)); }
+ set shapeYps(value) { this.selectedInk?.forEach(i => i.rootDoc.y = Number(value)); }
+ set shapeRot(value) { this.selectedInk?.forEach(i => i.rootDoc.rotation = Number(value)); }
+ set widthStk(value) { this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = Number(value)); }
+ set shapeWid(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldWidth = NumCast(i.rootDoc._width);
+ i.rootDoc._width = Number(value);
+ this._lock && (i.rootDoc._height = (i.rootDoc._width * NumCast(i.rootDoc._height)) / oldWidth);
+ });
+ }
+ set shapeHgt(value) {
+ this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldHeight = NumCast(i.rootDoc._height);
+ i.rootDoc._height = Number(value);
+ this._lock && (i.rootDoc._width = (i.rootDoc._height * NumCast(i.rootDoc._width)) / oldHeight);
+ });
+ }
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ FormatShapePane.Instance = this;
+ this._canFade = false;
+ this.Pinned = BoolCast(Doc.UserDoc()["menuFormatShape-pinned"]);
+ }
+
+ @action
+ closePane = () => {
+ this.fadeOut(false);
+ this.Pinned = false;
+ }
+
+ @action
+ upDownButtons = (dirs: string, field: string) => {
+ switch (field) {
+ case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break;
+ case "Xps": this.selectedInk?.forEach(i => i.rootDoc.x = NumCast(i.rootDoc.x) + (dirs === "up" ? 10 : -10)); break;
+ case "Yps": this.selectedInk?.forEach(i => i.rootDoc.y = NumCast(i.rootDoc.y) + (dirs === "up" ? 10 : -10)); break;
+ case "stk": this.selectedInk?.forEach(i => i.rootDoc.strokeWidth = NumCast(i.rootDoc.strokeWidth) + (dirs === "up" ? .1 : -.1)); break;
+ case "wid": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldWidth = NumCast(i.rootDoc._width);
+ i.rootDoc._width = oldWidth + (dirs === "up" ? 10 : - 10);
+ this._lock && (i.rootDoc._height = (i.rootDoc._width / oldWidth * NumCast(i.rootDoc._height)));
+ });
+ break;
+ case "hgt": this.selectedInk?.filter(i => i.rootDoc._width && i.rootDoc._height).forEach(i => {
+ const oldHeight = NumCast(i.rootDoc._height);
+ i.rootDoc._height = oldHeight + (dirs === "up" ? 10 : - 10);
+ this._lock && (i.rootDoc._width = (i.rootDoc._height / oldHeight * NumCast(i.rootDoc._width)));
+ });
+ break;
+ }
+ }
+
+ @undoBatch
+ @action
+ rotate = (degrees: number) => {
+ this.selectedInk?.forEach(action(inkView => {
+ const doc = Document(inkView.rootDoc);
+ if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) {
+ const angle = Number(degrees) - Number(doc.rotation);
+ doc.rotation = Number(degrees);
+ const ink = Cast(doc.data, InkField)?.inkData;
+ if (ink) {
+ const xs = ink.map(p => p.X);
+ const ys = ink.map(p => p.Y);
+ const left = Math.min(...xs);
+ const top = Math.min(...ys);
+ const right = Math.max(...xs);
+ const bottom = Math.max(...ys);
+ const _centerPoints: { X: number, Y: number }[] = [];
+ _centerPoints.push({ X: left, Y: top });
+
+ const newPoints: { X: number, Y: number }[] = [];
+ for (var i = 0; i < ink.length; i++) {
+ const newX = Math.cos(angle) * (ink[i].X - _centerPoints[0].X) - Math.sin(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].X;
+ const newY = Math.sin(angle) * (ink[i].X - _centerPoints[0].X) + Math.cos(angle) * (ink[i].Y - _centerPoints[0].Y) + _centerPoints[0].Y;
+ newPoints.push({ X: newX, Y: newY });
+ }
+ doc.data = new InkField(newPoints);
+ const xs2 = newPoints.map(p => p.X);
+ const ys2 = newPoints.map(p => p.Y);
+ const left2 = Math.min(...xs2);
+ const top2 = Math.min(...ys2);
+ const right2 = Math.max(...xs2);
+ const bottom2 = Math.max(...ys2);
+ doc._height = (bottom2 - top2) * inkView.props.ScreenToLocalTransform().Scale;
+ doc._width = (right2 - left2) * inkView.props.ScreenToLocalTransform().Scale;
+ }
+ }
+ }));
+ }
+
+
+ colorPicker(setter: (color: string) => {}) {
+ return <div className="btn-group-palette" key="colorpicker" >
+ {this._palette.map(color =>
+ <button className="antimodeMenu-button" key={color} onPointerDown={undoBatch(action(() => setter(color)))} style={{ zIndex: 1001, position: "relative" }}>
+ <div className="color-previewII" style={{ backgroundColor: color }} />
+ </button>)}
+ </div>;
+ }
+ inputBox = (key: string, value: any, setter: (val: string) => {}) => {
+ return <>
+ <input style={{ color: "black", width: 80, position: "absolute", right: 20 }}
+ type="text" value={value}
+ onChange={e => setter(e.target.value)}
+ autoFocus />
+ <button className="antiMenu-Buttonup" key="up" onPointerDown={undoBatch(action(() => this.upDownButtons("up", key)))}>
+ ˄
+ </button>
+ <br />
+ <button className="antiMenu-Buttonup" key="down" onPointerDown={undoBatch(action(() => this.upDownButtons("down", key)))} style={{ marginTop: -8 }}>
+ ˅
+ </button>
+ </>;
+ }
+
+ colorButton(value: string, setter: () => {}) {
+ return <>
+ <button className="antimodeMenu-button" key="fill" onPointerDown={undoBatch(action(e => setter()))} style={{ position: "absolute", right: 80 }}>
+ <FontAwesomeIcon icon="fill-drip" size="lg" />
+ <div className="color-previewI" style={{ backgroundColor: value ?? "121212" }} />
+ </button>
+ <br /> <br />
+ </>;
+ }
+
+ @computed get fillButton() { return this.colorButton(this.colorFil, () => this._fillBtn = !this._fillBtn); }
+ @computed get lineButton() { return this.colorButton(this.colorStk, () => this._lineBtn = !this._lineBtn); }
+
+ @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color); }
+ @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color); }
+
+ @computed get stkInput() { return this.inputBox("stk", this.widthStk, (val: string) => this.widthStk = val); }
+ @computed get hgtInput() { return this.inputBox("hgt", this.shapeHgt, (val: string) => this.shapeHgt = val); }
+ @computed get widInput() { return this.inputBox("wid", this.shapeWid, (val: string) => this.shapeWid = val); }
+ @computed get rotInput() { return this.inputBox("rot", this.shapeRot, (val: string) => this.shapeRot = val); }
+ @computed get XpsInput() { return this.inputBox("Xps", this.shapeXps, (val: string) => this.shapeXps = val); }
+ @computed get YpsInput() { return this.inputBox("Yps", this.shapeYps, (val: string) => this.shapeYps = val); }
+
+ @computed get propertyGroupItems() {
+ const fillCheck = <div key="fill" style={{ display: this._subOpen[0] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+ <input className="formatShapePane-inputBtn" type="radio" checked={this.unFilled} onChange={undoBatch(action(() => this.unFilled = true))} />
+ No Fill
+ <br />
+ <input className="formatShapePane-inputBtn" type="radio" checked={this.solidFil} onChange={undoBatch(action(() => this.solidFil = true))} />
+ Solid Fill
+ <br /> <br />
+ {this.solidFil ? "Color" : ""}
+ {this.solidFil ? this.fillButton : ""}
+ {this._fillBtn && this.solidFil ? this.fillPicker : ""}
+ </div>;
+
+ const markers = <>
+ <input key="markHead" className="formatShapePane-inputBtn" type="checkbox" checked={this.markHead !== ""} onChange={undoBatch(action(() => this.markHead = this.markHead ? "" : "arrow"))} />
+ Arrow Head
+ <br />
+ <input key="markTail" className="formatShapePane-inputBtn" type="checkbox" checked={this.markTail !== ""} onChange={undoBatch(action(() => this.markTail = this.markTail ? "" : "arrow"))} />
+ Arrow End
+ <br />
+ </>;
+
+ const lineCheck = <div key="lineCheck" style={{ display: this._subOpen[1] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+ <input className="formatShapePane-inputBtn" type="radio" checked={this.unStrokd} onChange={undoBatch(action(() => this.unStrokd = true))} />
+ No Line
+ <br />
+ <input className="formatShapePane-inputBtn" type="radio" checked={this.solidStk} onChange={undoBatch(action(() => this.solidStk = true))} />
+ Solid Line
+ <br />
+ <input className="formatShapePane-inputBtn" type="radio" checked={this.dashdStk ? true : false} onChange={undoBatch(action(() => this.dashdStk = "2"))} />
+ Dash Line
+ <br />
+ <br />
+ {(this.solidStk || this.dashdStk) ? "Color" : ""}
+ {(this.solidStk || this.dashdStk) ? this.lineButton : ""}
+ {(this.solidStk || this.dashdStk) && this._lineBtn ? this.linePicker : ""}
+ <br />
+ {(this.solidStk || this.dashdStk) ? "Width" : ""}
+ {(this.solidStk || this.dashdStk) ? this.stkInput : ""}
+ {(this.solidStk || this.dashdStk) ? <input type="range" defaultValue={Number(this.widthStk)} min={1} max={100} onChange={e => this.widthStk = e.target.value} /> : (null)}
+ <br /> <br />
+ {(this.solidStk || this.dashdStk) ? markers : ""}
+ </div>;
+
+ const sizeCheck = <div key="sizeCheck" style={{ display: this._subOpen[2] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+ Height {this.hgtInput}
+ <br /> <br />
+ Width {this.widInput}
+ <br /> <br />
+ <input className="formatShapePane-inputBtn" style={{ right: 0 }} type="checkbox" checked={this._lock} onChange={undoBatch(action(() => this._lock = !this._lock))} />
+ Lock Ratio
+ <br /> <br />
+ Rotation {this.rotInput}
+ <br /> <br />
+ </div>;
+
+ const positionCheck = <div key="posCheck" style={{ display: this._subOpen[3] ? "" : "none", width: "inherit", backgroundColor: "#323232", color: "white", }}>
+ Horizontal {this.XpsInput}
+ <br /> <br />
+ Vertical {this.YpsInput}
+ <br /> <br />
+ </div>;
+
+ const subMenus = this._currMode === "fill-drip" ? [`fill`, `line`] : [`size`, `position`];
+ const menuItems = this._currMode === "fill-drip" ? [fillCheck, lineCheck] : [sizeCheck, positionCheck];
+ const indexOffset = this._currMode === "fill-drip" ? 0 : 2;
+ return <div className="antimodeMenu-sub" key="submenu" style={{ position: "absolute", width: "inherit", top: 60 }}>
+ {subMenus.map((subMenu, i) =>
+ <div key={subMenu} style={{ width: "inherit" }}>
+ <button className="antimodeMenu-button" onPointerDown={action(() => this._subOpen[i + indexOffset] = !this._subOpen[i + indexOffset])}
+ style={{ backgroundColor: "121212", position: "relative", width: "inherit" }}>
+ {this._subOpen[i + indexOffset] ? "▼" : "▶︎"}
+ {subMenu}
+ </button>
+ {menuItems[i]}
+ </div>)}
+ </div>;
+ }
+
+ @computed get closeBtn() {
+ return <button className="antimodeMenu-button" key="close" onPointerDown={action(() => this.closePane())} style={{ position: "absolute", right: 0 }}>
+ X
+ </button>;
+ }
+
+ @computed get propertyGroupBtn() {
+ return <div className="antimodeMenu-button-tab" key="modes">
+ {this._mode.map(mode =>
+ <button className="antimodeMenu-button" key={mode} onPointerDown={action(() => this._currMode = mode)}
+ style={{ backgroundColor: this._currMode === mode ? "121212" : "", position: "relative", top: 30 }}>
+ <FontAwesomeIcon icon={mode as IconProp} size="lg" />
+ </button>)}
+ </div>;
+ }
+
+ render() {
+ return this.getElementVert([this.closeBtn, this.propertyGroupBtn, this.propertyGroupItems]);
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
deleted file mode 100644
index 753de6bef..000000000
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.scss
+++ /dev/null
@@ -1,78 +0,0 @@
-.antimodeMenu-button {
- .color-previewI {
- width: 100%;
- height: 40%;
- }
-
- .color-previewII {
- width: 100%;
- height: 100%;
- }
-
-}
-
-.sketch-picker {
- background: #323232;
-
- .flexbox-fit {
- background: #323232;
- }
-}
-
-.btn-group {
- display: grid;
- grid-template-columns: auto auto auto auto;
- /* Make the buttons appear below each other */
-}
-
-.btn2-group {
- display: block;
- background: #323232;
- grid-template-columns: auto;
-
- /* Make the buttons appear below each other */
- .antimodeMenu-button {
- background: #323232;
- display: block;
-
- }
-}
-
-@media only screen and (max-device-width: 480px) {
- .antimodeMenu-button {
- font-size: 50%;
-
- .color-preview {
- width: 100%;
- height: 100%;
- }
-
- }
-
- .sketch-picker {
- background: #323232;
-
- .flexbox-fit {
- background: #323232;
- }
- }
-
- .btn-group {
- display: grid;
- grid-template-columns: auto auto;
- /* Make the buttons appear below each other */
- }
-
- .btn2-group {
- display: block;
- background: #323232;
- grid-template-columns: auto;
-
- /* Make the buttons appear below each other */
- .antimodeMenu-button {
- background: #323232;
- display: block;
- font-size: 50%;
- }
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
deleted file mode 100644
index f47fca6ac..000000000
--- a/src/client/views/collections/collectionFreeForm/InkOptionsMenu.tsx
+++ /dev/null
@@ -1,332 +0,0 @@
-import React = require("react");
-import AntimodeMenu from "../../AntimodeMenu";
-import { observer } from "mobx-react";
-import { observable, action, computed } from "mobx";
-import "./InkOptionsMenu.scss";
-import { ActiveInkColor, ActiveInkBezierApprox, ActiveFillColor, ActiveArrowStart, ActiveArrowEnd, SetActiveInkWidth, SetActiveInkColor, SetActiveBezierApprox, SetActiveFillColor, SetActiveArrowStart, SetActiveArrowEnd, ActiveDash, SetActiveDash } from "../../InkingStroke";
-import { Scripting } from "../../../util/Scripting";
-import { InkTool } from "../../../../fields/InkField";
-import { ColorState } from "react-color";
-import { Utils } from "../../../../Utils";
-import GestureOverlay from "../../GestureOverlay";
-import { Doc } from "../../../../fields/Doc";
-import { SelectionManager } from "../../../util/SelectionManager";
-import { DocumentView } from "../../../views/nodes/DocumentView";
-import { Document } from "../../../../fields/documentSchemas";
-import { DocumentType } from "../../../documents/DocumentTypes";
-import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSubscript, faSuperscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faSleigh, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve, } from "@fortawesome/free-solid-svg-icons";
-
-library.add(faBold, faItalic, faChevronLeft, faUnderline, faStrikethrough, faSuperscript, faSubscript, faIndent, faEyeDropper, faCaretDown, faPalette, faArrowsAlt, faHighlighter, faLink, faPaintRoller, faBars, faFillDrip, faBrush, faPenNib, faShapes, faArrowLeft, faEllipsisH, faBezierCurve);
-
-
-
-@observer
-export default class InkOptionsMenu extends AntimodeMenu {
- static Instance: InkOptionsMenu;
-
- private _palette = ["#D0021B", "#F5A623", "#F8E71C", "#8B572A", "#7ED321", "#417505", "#9013FE", "#4A90E2", "#50E3C2", "#B8E986", "#000000", "#4A4A4A", "#9B9B9B", "#FFFFFF", "none"];
- private _width = ["1", "5", "10", "100"];
- // private _buttons = ["circle", "triangle", "rectangle", "arrow", "line"];
- // private _icons = ["O", "∆", "ロ", "➜", "-"];
- private _buttons = ["circle", "triangle", "rectangle", "line", "noRec", "",];
- private _icons = ["O", "∆", "ロ", "⎯", "✖︎", " "];
- //arrowStart and arrowEnd must match and defs must exist in Inking Stroke
- private _arrowStart = ["arrowHead", "arrowHead", "dot", "dot", "none"];
- private _arrowEnd = ["none", "arrowEnd", "none", "dot", "none"];
- private _arrowIcons = ["→", "↔︎", "•", "••", " "];
-
- @observable _colorBtn = false;
- @observable _widthBtn = false;
- @observable _fillBtn = false;
- @observable _arrowBtn = false;
- @observable _dashBtn = false;
- @observable _shapeBtn = false;
-
-
-
- constructor(props: Readonly<{}>) {
- super(props);
- InkOptionsMenu.Instance = this;
- this._canFade = false; // don't let the inking menu fade away
- }
-
- getColors = () => {
- return this._palette;
- }
-
- @action
- changeArrow = (arrowStart: string, arrowEnd: string) => {
- SetActiveArrowStart(arrowStart);
- SetActiveArrowEnd(arrowEnd);
- }
-
- @action
- changeColor = (color: string, type: string) => {
- const col: ColorState = {
- hex: color, hsl: { a: 0, h: 0, s: 0, l: 0, source: "" }, hsv: { a: 0, h: 0, s: 0, v: 0, source: "" },
- rgb: { a: 0, r: 0, b: 0, g: 0, source: "" }, oldHue: 0, source: "",
- };
- if (type === "color") {
- SetActiveInkColor(Utils.colorString(col));
- } else if (type === "fill") {
- SetActiveFillColor(Utils.colorString(col));
- }
- }
-
- @action
- editProperties = (value: any, field: string) => {
- SelectionManager.SelectedDocuments().forEach(action((element: DocumentView) => {
- const doc = Document(element.rootDoc);
- if (doc.type === DocumentType.INK) {
- switch (field) {
- case "width":
- doc.strokeWidth = Number(value);
- break;
- case "color":
- doc.color = String(value);
- break;
- case "fill":
- doc.fillColor = String(value);
- break;
- case "bezier":
- // doc.strokeBezier === 300 ? doc.strokeBezier = 0 : doc.strokeBezier = 300;
- break;
- case "arrowStart":
- doc.arrowStart = String(value);
- break;
- case "arrowEnd":
- doc.arrowEnd = String(value);
- break;
- case "dash":
- doc.dash = Number(value);
- default:
- break;
- }
- }
- }));
- }
-
-
- @action
- changeBezier = (e: React.PointerEvent): void => {
- SetActiveBezierApprox(!ActiveInkBezierApprox() ? "300" : "");
- this.editProperties(0, "bezier");
- }
- @action
- changeDash = (e: React.PointerEvent): void => {
- SetActiveDash(ActiveDash() === "0" ? "2" : "0");
- this.editProperties(ActiveDash(), "dash");
- }
-
- @computed get arrowPicker() {
- var currIcon;
- for (var i = 0; i < this._arrowStart.length; i++) {
- if (this._arrowStart[i] === ActiveArrowStart() && this._arrowEnd[i] === ActiveArrowEnd()) {
- currIcon = this._arrowIcons[i];
- if (this._arrowIcons[i] === " ") {
- currIcon = "➤";
- }
- }
- }
- var arrowPicker = <button
- className="antimodeMenu-button"
- key="arrow"
- onPointerDown={action(e => this._arrowBtn = !this._arrowBtn)}
- style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
- {currIcon}
- </button>;
- if (this._arrowBtn) {
- arrowPicker = <div className="btn2-group" key="arrows">
- {arrowPicker}
- {this._arrowStart.map((arrowStart, i) => {
- return <button
- className="antimodeMenu-button"
- key={arrowStart}
- onPointerDown={action(() => { SetActiveArrowStart(arrowStart); SetActiveArrowEnd(this._arrowEnd[i]); this.editProperties(arrowStart, "arrowStart"), this.editProperties(this._arrowEnd[i], "arrowEnd"); this._arrowBtn = false; })}
- style={{ backgroundColor: this._arrowBtn ? "121212" : "" }}>
- {this._arrowIcons[i]}
- </button>;
- })}
- </div>;
- }
- return arrowPicker;
- }
-
- @computed get widthPicker() {
- var widthPicker = <button
- className="antimodeMenu-button"
- key="width"
- onPointerDown={action(e => this._widthBtn = !this._widthBtn)}
- style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="bars" size="lg" />
- </button>;
- if (this._widthBtn) {
- widthPicker = <div className="btn2-group" key="width">
- {widthPicker}
- {this._width.map(wid => {
- return <button
- className="antimodeMenu-button"
- key={wid}
- onPointerDown={action(() => { SetActiveInkWidth(wid); this._widthBtn = false; this.editProperties(wid, "width"); })}
- style={{ backgroundColor: this._widthBtn ? "121212" : "" }}>
- {wid}
- </button>;
- })}
- </div>;
- }
- return widthPicker;
- }
-
-
-
- @computed get colorPicker() {
- var colorPicker = <button
- className="antimodeMenu-button"
- key="color"
- title="colorChanger"
- onPointerDown={action(e => this._colorBtn = !this._colorBtn)}
- style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="pen-nib" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: ActiveInkColor() ?? "121212" }}></div>
-
- </button>;
- if (this._colorBtn) {
- colorPicker = <div className="btn-group" key="color">
- {colorPicker}
- {this._palette.map(color => {
- return <button
- className="antimodeMenu-button"
- key={color}
- onPointerDown={action(() => { this.changeColor(color, "color"); this._colorBtn = false; this.editProperties(color, "color"); })}
- style={{ backgroundColor: this._colorBtn ? "121212" : "" }}>
- {/* <FontAwesomeIcon icon="pen-nib" size="lg" /> */}
- <div className="color-previewII" style={{ backgroundColor: color }}></div>
- </button>;
- })}
- </div>;
- }
- return colorPicker;
- }
-
- @computed get fillPicker() {
- var fillPicker = <button
- className="antimodeMenu-button"
- key="fill"
- title="fillChanger"
- onPointerDown={action(e => this._fillBtn = !this._fillBtn)}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
- <FontAwesomeIcon icon="fill-drip" size="lg" />
- <div className="color-previewI" style={{ backgroundColor: ActiveFillColor() ?? "121212" }}></div>
- </button>;
- if (this._fillBtn) {
- fillPicker = <div className="btn-group" key="fill">
- {fillPicker}
- {this._palette.map(color => {
- return <button
- className="antimodeMenu-button"
- key={color}
- onPointerDown={action(() => { this.changeColor(color, "fill"); this._fillBtn = false; this.editProperties(color, "fill"); })}
- style={{ backgroundColor: this._fillBtn ? "121212" : "" }}>
- <div className="color-previewII" style={{ backgroundColor: color }}></div>
- </button>;
- })}
-
- </div>;
- }
- return fillPicker;
- }
-
- @computed get shapePicker() {
- var currIcon;
- if (GestureOverlay.Instance.InkShape === "") {
- currIcon = <FontAwesomeIcon icon="shapes" size="lg" />;
- } else {
- for (var i = 0; i < this._icons.length; i++) {
- if (GestureOverlay.Instance.InkShape === this._buttons[i]) {
- currIcon = this._icons[i];
- }
- }
- }
- var shapePicker = <button
- className="antimodeMenu-button"
- key="shape"
- onPointerDown={action(e => this._shapeBtn = !this._shapeBtn)}
- style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
- {currIcon}
- </button>;
- if (this._shapeBtn) {
- shapePicker = <div className="btn2-group" key="shape">
- {shapePicker}
- {this._buttons.map((btn, i) => {
- var ttl = btn;
- if (btn === "") {
- ttl = "no shape";
- }
- if (btn === "noRec") {
- ttl = "disable shape recognition";
- }
- return <button
- className="antimodeMenu-button"
- title={`Draw ${btn}`}
- key={ttl}
- onPointerDown={action((e) => { GestureOverlay.Instance.InkShape = btn; this._shapeBtn = false; })}
- style={{ backgroundColor: this._shapeBtn ? "121212" : "" }}>
- {this._icons[i]}
- </button>;
- })}
- </div>;
- }
- return shapePicker;
- }
-
- @computed get bezierButton() {
- return <button
- className="antimodeMenu-button"
- title="Bezier changer"
- key="bezier"
- onPointerDown={e => this.changeBezier(e)}
- style={{ backgroundColor: ActiveInkBezierApprox() ? "121212" : "" }}>
- <FontAwesomeIcon icon="bezier-curve" size="lg" />
-
- </button>;
- }
-
- @computed get dashButton() {
- return <button
- className="antimodeMenu-button"
- title="dash changer"
- key="dash"
- onPointerDown={e => this.changeDash(e)}
- style={{ backgroundColor: ActiveDash() !== "0" ? "121212" : "" }}>
- <FontAwesomeIcon icon="ellipsis-h" size="lg" />
-
- </button>;
- }
-
- render() {
- const buttons = [
- // <button className="antimodeMenu-button" title="Drag" key="drag" onPointerDown={e => this.dragStart(e)}>
- // <FontAwesomeIcon icon="arrows-alt" size="lg" />
- // </button>,
- this.shapePicker,
- this.bezierButton,
- this.widthPicker,
- this.colorPicker,
- this.fillPicker,
- this.arrowPicker,
- this.dashButton,
- ];
- return this.getElement(buttons);
- }
-}
-Scripting.addGlobal(function activatePen(penBtn: any) {
- if (penBtn) {
- Doc.SetSelectedTool(InkTool.Pen);
- InkOptionsMenu.Instance.jumpTo(300, 300);
- } else {
- Doc.SetSelectedTool(InkTool.None);
- InkOptionsMenu.Instance.fadeOut(true);
- }
-});
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index b47236bea..aa9a3b4ae 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,6 +1,7 @@
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast, DataSym } from "../../../../fields/Doc";
+import { Doc, Opt, DocListCast, DataSym, AclEdit, AclAddonly } from "../../../../fields/Doc";
+import { GetEffectiveAcl, getPlaygroundMode } from "../../../../fields/util";
import { InkData, InkField, InkTool } from "../../../../fields/InkField";
import { List } from "../../../../fields/List";
import { RichTextField } from "../../../../fields/RichTextField";
@@ -129,7 +130,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else if (!e.ctrlKey && !e.metaKey) {
FormattedTextBox.SelectOnLoadChar = FormattedTextBox.DefaultLayout ? e.key : "";
const tbox = Docs.Create.TextDocument("", {
- _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: NumCast(Doc.UserDoc().fontSize),
+ _width: 200, _height: 100, x: x, y: y, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize),
_fontFamily: StrCast(Doc.UserDoc().fontFamily),
title: "-typed text-"
});
@@ -188,15 +189,18 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.clientX;
this._downY = this._lastY = e.clientY;
- // allow marquee if right click OR alt+left click OR space bar + left click
- if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) {
- // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
- this.setPreviewCursor(e.clientX, e.clientY, true);
- // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
- e.preventDefault();
- // }
- // bcz: do we need this? it kills the context menu on the main collection if !altKey
- // e.stopPropagation();
+ if (!(e.nativeEvent as any).marqueeHit) {
+ (e.nativeEvent as any).marqueeHit = true;
+ // allow marquee if right click OR alt+left click OR space bar + left click
+ if (e.button === 2 || (e.button === 0 && (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))))) {
+ // if (e.altKey || (MarqueeView.DragMarquee && this.props.active(true))) {
+ this.setPreviewCursor(e.clientX, e.clientY, true);
+ // (!e.altKey) && e.stopPropagation(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
+ e.preventDefault();
+ // }
+ // bcz: do we need this? it kills the context menu on the main collection if !altKey
+ // e.stopPropagation();
+ }
}
}
@@ -276,7 +280,8 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
} else {
this._downX = x;
this._downY = y;
- PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
+ const effectiveAcl = GetEffectiveAcl(this.props.Document);
+ if (effectiveAcl === AclEdit || effectiveAcl === AclAddonly || getPlaygroundMode()) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge);
this.clearSelection();
}
});
@@ -286,7 +291,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
if (Doc.GetSelectedTool() === InkTool.None) {
- !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false);
+ if (!(e.nativeEvent as any).marqueeHit) {
+ (e.nativeEvent as any).marqueeHit = true;
+ !(e.nativeEvent as any).formattedHandled && this.setPreviewCursor(e.clientX, e.clientY, false);
+ }
}
// let the DocumentView stopPropagation of this event when it selects this document
} else { // why do we get a click event when the cursor have moved a big distance?
@@ -426,8 +434,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
});
CognitiveServices.Inking.Appliers.InterpretStrokes(strokes).then((results) => {
// const wordResults = results.filter((r: any) => r.category === "inkWord");
- // console.log(wordResults);
- // console.log(results);
// for (const word of wordResults) {
// const indices: number[] = word.strokeIds;
// indices.forEach(i => {
@@ -468,7 +474,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque
// });
// }
const lines = results.filter((r: any) => r.category === "line");
- console.log(lines);
const text = lines.map((l: any) => l.recognizedText).join("\r\n");
this.props.addDocument(Docs.Create.TextDocument(text, { _width: this.Bounds.width, _height: this.Bounds.height, x: this.Bounds.left + this.Bounds.width, y: this.Bounds.top, title: text }));
});
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss
index 9c2d5cbff..4d8473be9 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.scss
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss
@@ -8,7 +8,6 @@
.collectionGridView-gridContainer {
height: 100%;
overflow-y: auto;
- background-color: white;
overflow-x: hidden;
display: flex;
@@ -22,7 +21,7 @@
}
.react-grid-layout {
- width : 100%;
+ width: 100%;
}
.react-grid-item>.react-resizable-handle {
diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
index 2015ca930..21f77e47b 100644
--- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx
+++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx
@@ -1,7 +1,7 @@
import { action, computed, Lambda, observable, reaction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from "react";
-import { Doc, Opt } from '../../../../fields/Doc';
+import { Doc, Opt, WidthSym } from '../../../../fields/Doc';
import { documentSchema } from '../../../../fields/documentSchemas';
import { Id } from '../../../../fields/FieldSymbols';
import { makeInterface } from '../../../../fields/Schema';
@@ -30,8 +30,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
private _resetListenerDisposer: Opt<Lambda>; // listens for when the reset button is clicked
@observable private _rowHeight: Opt<number>; // temporary store of row height to make change undoable
@observable private _scroll: number = 0; // required to make sure the decorations box container updates on scroll
+ private dropLocation: object = {}; // sets the drop location for external drops
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
@computed get numCols() { return NumCast(this.props.Document.gridNumCols, 10); }
@computed get rowHeight() { return this._rowHeight === undefined ? NumCast(this.props.Document.gridRowHeight, 100) : this._rowHeight; }
@@ -47,6 +48,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
@computed get flexGrid() { return BoolCast(this.props.Document.gridFlex, true); } // is grid static/flexible i.e. whether nodes be moved around and resized
@computed get compaction() { return StrCast(this.props.Document.gridStartCompaction, StrCast(this.props.Document.gridCompaction, "vertical")); } // is grid static/flexible i.e. whether nodes be moved around and resized
+ /**
+ * Sets up the listeners for the list of documents and the reset button.
+ */
componentDidMount() {
this._changeListenerDisposer = reaction(() => this.childLayoutPairs, (pairs) => {
const newLayouts: Layout[] = [];
@@ -54,7 +58,15 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
pairs.forEach((pair, i) => {
const existing = oldLayouts.find(l => l.i === pair.layout[Id]);
if (existing) newLayouts.push(existing);
- else this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ else {
+ if (Object.keys(this.dropLocation).length) { // external drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.dropLocation as { x: number, y: number }, !this.flexGrid));
+ this.dropLocation = {};
+ }
+ else { // internal drop
+ this.addLayoutItem(newLayouts, this.makeLayoutItem(pair.layout, this.unflexedPosition(i), !this.flexGrid));
+ }
+ }
});
pairs?.length && this.setLayoutList(newLayouts);
}, { fireImmediately: true });
@@ -68,11 +80,18 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
});
}
+ /**
+ * Disposes the listeners.
+ */
componentWillUnmount() {
this._changeListenerDisposer?.();
this._resetListenerDisposer?.();
}
+ /**
+ * @returns the default location of the grid node (i.e. when the grid is static)
+ * @param index
+ */
unflexedPosition(index: number): Omit<Layout, "i"> {
return {
x: (index % Math.floor(this.numCols / this.defaultW)) * this.defaultW,
@@ -83,6 +102,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
};
}
+ /**
+ * Maps the x- and y- coordinates of the event to a grid cell.
+ */
screenToCell(sx: number, sy: number) {
const pt = this.props.ScreenToLocalTransform().transformPoint(sx, sy);
const x = Math.floor(pt[0] / this.colWidthPlusGap);
@@ -90,10 +112,16 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
return { x, y };
}
+ /**
+ * Creates a layout object for a grid item
+ */
makeLayoutItem = (doc: Doc, pos: { x: number, y: number }, Static: boolean = false, w: number = this.defaultW, h: number = this.defaultH) => {
return ({ i: doc[Id], w, h, x: pos.x, y: pos.y, static: Static });
}
+ /**
+ * Adds a layout to the list of layouts.
+ */
addLayoutItem = (layouts: Layout[], layout: Layout) => {
const f = layouts.findIndex(l => l.i === layout.i);
f !== -1 && layouts.splice(f, 1);
@@ -215,6 +243,9 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
this.savedLayoutList.map((layout, index) => Object.assign(layout, this.unflexedPosition(index)));
}
+ /**
+ * Handles internal drop of Dash documents.
+ */
@action
onInternalDrop = (e: Event, de: DragManager.DropEvent) => {
const savedLayouts = this.savedLayoutList;
@@ -228,12 +259,24 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
}
/**
+ * Handles external drop of images/PDFs etc from outside Dash.
+ */
+ @action
+ onExternalDrop = async (e: React.DragEvent): Promise<void> => {
+ this.dropLocation = this.screenToCell(e.clientX, e.clientY);
+ super.onExternalDrop(e, {});
+ }
+
+ /**
* Handles the change in the value of the rowHeight slider.
*/
@action
onSliderChange = (event: React.ChangeEvent<HTMLInputElement>) => {
this._rowHeight = event.currentTarget.valueAsNumber;
}
+ /**
+ * Handles the user clicking on the slider.
+ */
@action
onSliderDown = (e: React.PointerEvent) => {
this._rowHeight = this.rowHeight; // uses _rowHeight during dragging and sets doc's rowHeight when finished so that operation is undoable
@@ -248,11 +291,13 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
*/
onContextMenu = () => {
const displayOptionsMenu: ContextMenuProps[] = [];
- displayOptionsMenu.push({ description: "Contents", event: () => this.props.Document.display = "contents", icon: "copy" });
- displayOptionsMenu.push({ description: "Undefined", event: () => this.props.Document.display = undefined, icon: "exclamation" });
+ displayOptionsMenu.push({ description: "Toggle Content Display Style", event: () => this.props.Document.display = this.props.Document.display ? undefined : "contents", icon: "copy" });
ContextMenu.Instance.addItem({ description: "Display", subitems: displayOptionsMenu, icon: "tv" });
}
+ /**
+ * Handles text document creation on double click.
+ */
onPointerDown = (e: React.PointerEvent) => {
if (this.props.active(true)) {
setupMoveUpEvents(this, e, returnFalse, returnFalse,
@@ -276,8 +321,11 @@ export class CollectionGridView extends CollectionSubView(GridSchema) {
<div className="collectionGridView-contents" ref={this.createDashEventsTarget}
style={{ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined }}
onContextMenu={this.onContextMenu}
- onPointerDown={e => this.onPointerDown(e)} >
+ onPointerDown={this.onPointerDown}
+ onDrop={this.onExternalDrop}
+ >
<div className="collectionGridView-gridContainer" ref={this._containerRef}
+ style={{ backgroundColor: StrCast(this.layoutDoc._backgroundColor, "white") }}
onWheel={e => e.stopPropagation()}
onScroll={action(e => {
if (!this.props.isSelected()) e.currentTarget.scrollTop = this._scroll;
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
index cd25c21b4..21d283547 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx
@@ -202,9 +202,8 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu
}
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
-
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {
diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
index 51dcdcfe6..d02088a6c 100644
--- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
+++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx
@@ -202,8 +202,8 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument)
}
- @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); }
- @computed get onChildDoubleClickHandler() { return ScriptCast(this.Document.onChildDoubleClick); }
+ onChildClickHandler = () => ScriptCast(this.Document.onChildClick);
+ onChildDoubleClickHandler = () => ScriptCast(this.Document.onChildDoubleClick);
addDocTab = (doc: Doc, where: string) => {
if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) {