diff options
Diffstat (limited to 'src/client/views/collections')
26 files changed, 1382 insertions, 996 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 27aea4b99..8a27f8102 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -64,7 +64,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) </div> <div className="collectionCarouselView-caption" key="caption" style={{ - background: StrCast(this.layoutDoc._captionBackgroundColor, this.props.backgroundColor?.(this.props.Document)), + background: StrCast(this.layoutDoc._captionBackgroundColor, this.props.backgroundColor?.(this.props.Document, this.props.renderDepth)), color: StrCast(this.layoutDoc._captionColor, StrCast(this.layoutDoc.color)), borderRadius: StrCast(this.layoutDoc._captionBorderRounding), }}> diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 4204ef5bb..6ebd5103b 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -20,82 +20,6 @@ } } -.miniPres:hover { - opacity: 1; -} - -.miniPres { - position: absolute; - overflow: hidden; - right: 10; - top: 10; - opacity: 0.1; - transition: all 0.4s; - /* border: solid 1px; */ - color: white; - /* box-shadow: black 0.4vw 0.4vw 0.8vw; */ - - .miniPresOverlay { - display: grid; - grid-template-columns: auto auto auto auto auto auto auto auto; - grid-template-rows: 100%; - height: 100%; - justify-items: center; - align-items: center; - - .miniPres-button-text { - display: flex; - height: 20; - font-weight: 400; - min-width: 100%; - border-radius: 5px; - align-items: center; - justify-content: center; - transition: all 0.3s; - } - - .miniPres-button-frame { - justify-self: center; - align-self: center; - align-items: center; - display: grid; - grid-template-columns: auto auto auto; - justify-content: space-around; - font-size: 11; - margin-left: 7; - width: 30; - height: 85%; - background-color: rgba(91, 157, 221, 0.4); - border-radius: 5px; - } - - .miniPres-divider { - width: 0.5px; - height: 80%; - border-right: solid 2px #5a5a5a; - } - - .miniPres-button { - display: flex; - height: 20; - min-width: 20; - border-radius: 100%; - align-items: center; - justify-content: center; - transition: all 0.3s; - } - - .miniPres-button:hover { - background-color: #5a5a5a; - } - - .miniPres-button-text:hover { - background-color: #5a5a5a; - } - } -} - - .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 3691e844f..43da0d3cf 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -191,31 +191,6 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp return retVal; } - @undoBatch - @action - public static ReplaceTab(document: Doc, stack: any): Opt<Doc> { - if (!CollectionDockingView.Instance) return undefined; - const instance = CollectionDockingView.Instance; - const replaceTab = (doc: Doc, child: any): Opt<Doc> => { - for (const contentItem of child.contentItems) { - const { config, isStack, isRow, isColumn } = contentItem; - if (isRow || isColumn || isStack) { - const val = replaceTab(doc, contentItem); - if (val) return val; - } else if (config.component === "DocumentFrameRenderer" && - config.props.documentId === doc[Id]) { - const alias = Doc.MakeAlias(doc); - config.props.documentId = alias[Id]; - config.title = alias.title; - instance.stateChanged(); - return alias; - } - } - return undefined; - }; - return replaceTab(document, instance._goldenLayout.root); - } - // // Creates a vertical split on the right side of the docking view, and then adds the Document to the right of that split // @@ -358,6 +333,32 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp return true; } + @undoBatch + @action + public ReplaceTab = (stack: any, document: Doc, libraryPath?: Doc[]) => { + Doc.GetProto(document).lastOpened = new DateField; + const docContentConfig = CollectionDockingView.makeDocumentConfig(document, undefined, libraryPath); + if (stack === undefined) { + let stack: any = this._goldenLayout.root; + while (!stack.isStack) { + if (stack.contentItems.length) { + stack = stack.contentItems[0]; + } else { + stack.addChild({ type: 'stack', content: [docContentConfig] }); + stack = undefined; + break; + } + } + if (stack) { + stack.addChild(docContentConfig); + } + } else { + stack.addChild(docContentConfig, undefined); + } + this.layoutChanged(); + return true; + } + setupGoldenLayout() { const config = StrCast(this.props.Document.dockingConfig); if (config) { @@ -727,9 +728,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { pinDoc.presZoomButton = true; pinDoc.context = curPres; Doc.AddDocToList(curPres, "data", pinDoc); + if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; if (!DocumentManager.Instance.getDocumentView(curPres)) { CollectionDockingView.AddRightSplit(curPres); } + DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null)); } } } @@ -758,7 +761,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.on("tab", this.onActiveContentItemChanged); this.onActiveContentItemChanged(); - this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, "white") }), + this._tabReaction = reaction(() => ({ views: SelectionManager.SelectedDocuments(), color: StrCast(this._document?._backgroundColor, this._document && CollectionDockingView.Instance?.props.backgroundColor?.(this._document, 0) || "white") }), (data) => { const selected = data.views.some(v => Doc.AreProtosEqual(v.props.Document, this._document)); this._tab && (this._tab.style.backgroundColor = selected ? data.color : ""); @@ -821,7 +824,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { if (this._mainCont && this._mainCont.children) { const { translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement); const scale = Utils.GetScreenTransform(this._mainCont).scale; - return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); + return CollectionDockingView.Instance?.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); } return Transform.Identity(); } @@ -837,12 +840,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { } else if (location === "close") { return CollectionDockingView.CloseRightSplit(doc); } else if (location === "replace") { - const alias = CollectionDockingView.ReplaceTab(doc, this._stack); - if (alias) { - runInAction(() => this._document = alias); - return true; - } - return false; + CollectionDockingView.UseRightSplit(doc); + return true; } else {// if (location === "inPlace") { return CollectionDockingView.Instance.AddTab(this._stack, doc, libraryPath); } @@ -873,30 +872,11 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { const currentFrame = Cast(presTargetDoc.currentFrame, "number", null); return currentFrame; } - renderMiniPres() { - return ( - <div className="miniPres" - style={{ width: 250, height: 30, background: '#323232' }} - > - {<div className="miniPresOverlay"> - <div className="miniPres-button" onClick={PresBox.Instance.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <div className="miniPres-button" onClick={() => PresBox.Instance.startAutoPres(PresBox.Instance.itemIndex)}><FontAwesomeIcon icon={PresBox.Instance.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div> - <div className="miniPres-button" onClick={PresBox.Instance.next}><FontAwesomeIcon icon={"arrow-right"} /></div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text"> - Slide {PresBox.Instance.itemIndex + 1} / {PresBox.Instance.childDocs.length} - {PresBox.Instance.playButtonFrames} - </div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text" onClick={PresBox.Instance.updateMinimize}>EXIT</div> - </div>} - </div> - ); - } + 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!))), + StrCast(this._document!.backgroundColor, CollectionDockingView.Instance.props.backgroundColor?.(this._document!, 0))), }}> <CollectionFreeFormView Document={this._document!} @@ -975,7 +955,6 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} /> {document._viewType === CollectionViewType.Freeform && !this._document?.hideMinimap ? this.renderMiniMap() : (null)} - {document._viewType === CollectionViewType.Freeform && this._document?.miniPres ? this.renderMiniPres() : (null)} </>; } diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index 3cf46dbed..e1b07077e 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -119,7 +119,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { // transform: this.props.Document.linearViewIsExpanded ? "" : "rotate(45deg)" }} onPointerDown={e => e.stopPropagation()} > - <p>+</p> + <p>{BoolCast(this.props.Document.linearViewIsExpanded) ? "–" : "+"}</p> </label>; return <div className="collectionLinearView-outer"> diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx index cfec3a6bc..1af1a05aa 100644 --- a/src/client/views/collections/CollectionMapView.tsx +++ b/src/client/views/collections/CollectionMapView.tsx @@ -69,7 +69,7 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap if (!this._initialLookupPending.get(id)) { this._initialLookupPending.set(id, true); setTimeout(() => { - titleLoc && Doc.SetInPlace(doc, "title", titleLoc, true); + titleLoc && Doc.SetInPlace(doc, `${fieldKey}-address`, titleLoc, true); this.respondToAddressChange(doc, fieldKey, address).then(() => this._initialLookupPending.delete(id)); }); } @@ -114,12 +114,12 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap } } - private renderMarker = (layout: Doc) => { - const location = this.getLocation(layout, Doc.LayoutFieldKey(layout)); + private renderMarker = (layout: Doc, fieldKey?: string) => { + const location = this.getLocation(layout, fieldKey || Doc.LayoutFieldKey(layout)); return !location ? (null) : <Marker key={layout[Id]} - label={StrCast(layout.title)} + label={StrCast(layout[`${this.props.fieldKey}-address`])} position={location} onClick={() => this.markerClick(layout, location)} icon={this.renderMarkerIcon(layout)} @@ -250,7 +250,7 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap }} > {this.reactiveContents} - {mapLoc ? this.renderMarker(this.rootDoc) : undefined} + {mapLoc && StrCast(this.rootDoc[`${fieldKey}-mapCenter-address`]) ? this.renderMarker(this.rootDoc, `${fieldKey}-mapCenter`) : undefined} </GeoMap> </div> </div>; @@ -260,9 +260,10 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap export default GoogleApiWrapper({ apiKey: process.env.GOOGLE_MAPS!, - LoadingContainer: () => ( - <div className={"loadingWrapper"}> + LoadingContainer: () => { + console.log(process.env.GOOGLE_MAPS); + return <div className={"loadingWrapper"}> <img className={"loadingGif"} src={"/assets/loading.gif"} /> - </div> - ) + </div>; + } })(CollectionMapView) as any;
\ No newline at end of file diff --git a/src/client/views/collections/CollectionMenu.scss b/src/client/views/collections/CollectionMenu.scss index b41cbe92d..d0bfd0a41 100644 --- a/src/client/views/collections/CollectionMenu.scss +++ b/src/client/views/collections/CollectionMenu.scss @@ -229,14 +229,18 @@ .flexLabel { margin-bottom: 0; } - } - .collectionGridViewChrome-entryBox { - width: 50%; + .collectionGridViewChrome-entryBox { + width: 50%; + color: black; + } + + .collectionGridViewChrome-columnButton { + color: black; + } } } - .collectionStackingViewChrome-sort, .collectionTreeViewChrome-sort { display: flex; @@ -311,10 +315,62 @@ } } +.webBox-urlEditor { + position: relative; + opacity: 0.9; + z-index: 9001; + transition: top .5s; + + .urlEditor { + display: grid; + grid-template-columns: 1fr auto; + padding-bottom: 10px; + overflow: hidden; + margin-top: 5px; + height: 35px; + + .editorBase { + display: flex; + + .editor-collapse { + transition: all .5s, opacity 0.3s; + position: absolute; + width: 40px; + transform-origin: top left; + } + + .switchToText { + color: $main-accent; + } + + .switchToText:hover { + color: $dark-color; + } + } + + button:hover { + transform: scale(1); + } + } +} + +.webpage-urlInput { + padding: 12px 10px 11px 10px; + border: 0px; + color: grey; + letter-spacing: 2px; + outline-color: black; + background: rgb(238, 238, 238); + width: 100%; + margin-right: 10px; + height: 100%; +} + .collectionFreeFormMenu-cont { display: inline-flex; position: relative; align-items: center; + margin-left: 10px; .antimodeMenu-button { text-align: center; diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 53d2a136e..388eda2b3 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -11,17 +11,18 @@ import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; -import { RichTextField } from "../../../fields/RichTextField"; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from "../../../fields/ScriptField"; import { BoolCast, Cast, NumCast, StrCast } from "../../../fields/Types"; +import { WebField } from "../../../fields/URLField"; import { emptyFunction, setupMoveUpEvents, Utils } from "../../../Utils"; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { DragManager } from "../../util/DragManager"; +import { Scripting } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch } from "../../util/UndoManager"; -import AntimodeMenu from "../AntimodeMenu"; +import AntimodeMenu, { AntimodeMenuProps } from "../AntimodeMenu"; import { EditableView } from "../EditableView"; import GestureOverlay from "../GestureOverlay"; import { ActiveFillColor, ActiveInkColor, SetActiveArrowEnd, SetActiveArrowStart, SetActiveBezierApprox, SetActiveFillColor, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; @@ -32,13 +33,13 @@ import "./CollectionMenu.scss"; import { CollectionViewType, COLLECTION_BORDER_WIDTH } from "./CollectionView"; @observer -export default class CollectionMenu extends AntimodeMenu { +export default class CollectionMenu extends AntimodeMenu<AntimodeMenuProps> { static Instance: CollectionMenu; @observable SelectedCollection: DocumentView | undefined; @observable FieldKey: string; - constructor(props: Readonly<{}>) { + constructor(props: any) { super(props); this.FieldKey = ""; CollectionMenu.Instance = this; @@ -85,7 +86,8 @@ export default class CollectionMenu extends AntimodeMenu { const propTitle = CurrentUserUtils.propertiesWidth > 0 ? "Close Properties Panel" : "Open Properties Panel"; const prop = <Tooltip title={<div className="dash-tooltip">{propTitle}</div>} key="properties" placement="bottom"> - <button className="antimodeMenu-button" key="properties" onPointerDown={this.toggleProperties}> + <button className="antimodeMenu-button" key="properties" style={{ backgroundColor: "#424242" }} + onPointerDown={this.toggleProperties}> <FontAwesomeIcon icon={propIcon} size="lg" /> </button> </Tooltip>; @@ -160,9 +162,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp }; _viewCommand = { params: ["target"], title: "bookmark view", - script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['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; }, + script: "self.target._panX = self['target-panX']; self.target._panY = self['target-panY']; self.target._viewScale = self['target-viewScale']; gotoFrame(self.target, self['target-currentFrame']);", + immediate: undoBatch((source: Doc[]) => { this.target._panX = 0; this.target._panY = 0; this.target._viewScale = 1; this.target.currentFrame = 0; }), + initialize: (button: Doc) => { button['target-panX'] = this.target._panX; button['target-panY'] = this.target._panY; button['target-viewScale'] = this.target._viewScale; button['target-currentFrame'] = this.target.currentFrame; }, }; _clusterCommand = { params: ["target"], title: "fit content", @@ -428,7 +430,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @computed get isText() { if (this.selectedDoc) { - return this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField; + const layoutField = Doc.LayoutField(this.selectedDoc); + return StrCast(layoutField).includes("FormattedText") || + (layoutField instanceof Doc && StrCast(layoutField.layout).includes("FormattedText")); } else return false; } @@ -580,9 +584,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu 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 }} /> - {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""} - + <div className="color-previewII" style={{ backgroundColor: color }}> + {color === "" ? <p style={{ fontSize: 45, color: "red", marginTop: -16, marginLeft: -5, position: "fixed" }}>☒</p> : ""} + </div> </button>)} </div>; } @@ -604,6 +608,158 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div>; } + @action + onUrlDrop = (e: React.DragEvent) => { + const { dataTransfer } = e; + const html = dataTransfer.getData("text/html"); + const uri = dataTransfer.getData("text/uri-list"); + const url = uri || html || this._url; + this._url = url.startsWith(window.location.origin) ? + url.replace(window.location.origin, this._url.match(/http[s]?:\/\/[^\/]*/)?.[0] || "") : url; + this.submitURL(); + e.stopPropagation(); + } + onUrlDragover = (e: React.DragEvent) => { + e.preventDefault(); + } + + @computed get _url() { + return this.selectedDoc ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : "hello"; + } + + set _url(value) { this.selectedDoc && (this.selectedDoc.data = value); } + + @action + submitURL = () => { + if (!this._url.startsWith("http")) this._url = "http://" + this._url; + try { + const URLy = new URL(this._url); + const future = this.selectedDoc ? Cast(this.selectedDoc["data-future"], listSpec("string"), null) : null; + const history = this.selectedDoc ? Cast(this.selectedDoc["data-history"], listSpec("string"), null) : []; + const annos = this.selectedDoc ? DocListCast(this.selectedDoc["data-annotations"]) : undefined; + const url = this.selectedDoc ? Cast(this.selectedDoc.data, WebField, null)?.url.toString() : null; + if (url) { + if (history === undefined) { + this.selectedDoc && (this.selectedDoc["data-history"] = new List<string>([url])); + + } else { + history.push(url); + } + future && (future.length = 0); + this.selectedDoc && (this.selectedDoc["data-" + this.urlHash(url)] = new List<Doc>(annos)); + } + this.selectedDoc && (this.selectedDoc.data = new WebField(URLy)); + this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>([])); + } catch (e) { + console.log("WebBox URL error:" + this._url); + } + } + + urlHash(s: string) { + return s.split('').reduce((a: any, b: any) => { a = ((a << 5) - a) + b.charCodeAt(0); return a & a; }, 0); + } + + toggleAnnotationMode = () => { + this.props.docView.layoutDoc.isAnnotating = !this.props.docView.layoutDoc.isAnnotating; + } + + @action + onURLChange = (e: React.ChangeEvent<HTMLInputElement>) => { + this._url = e.target.value; + } + + onValueKeyDown = async (e: React.KeyboardEvent) => { + if (e.key === "Enter") { + this.submitURL(); + } + e.stopPropagation(); + } + + @action + forward = () => { + const future = this.selectedDoc && (Cast(this.selectedDoc["data-future"], listSpec("string"), null)); + const history = this.selectedDoc && Cast(this.selectedDoc["data-history"], listSpec("string"), null); + if (future?.length) { + history?.push(this._url); + this.selectedDoc && (this.selectedDoc["data-annotations-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations"]))); + this.selectedDoc && (this.selectedDoc.data = new WebField(new URL(this._url = future.pop()!))); + this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)]))); + } + } + + @action + back = () => { + const future = this.selectedDoc && (Cast(this.selectedDoc["data-future"], listSpec("string"), null)); + const history = this.selectedDoc && Cast(this.selectedDoc["data-history"], listSpec("string"), null); + if (history?.length) { + if (future === undefined) this.selectedDoc && (this.selectedDoc["data-future"] = new List<string>([this._url])); + else future.push(this._url); + this.selectedDoc && (this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations"]))); + this.selectedDoc && (this.selectedDoc.data = new WebField(new URL(this._url = history.pop()!))); + this.selectedDoc && (this.selectedDoc["data-annotations"] = new List<Doc>(DocListCast(this.selectedDoc["data-annotations" + "-" + this.urlHash(this._url)]))); + } + } + + private _keyInput = React.createRef<HTMLInputElement>(); + + @computed get urlEditor() { + return ( + <div className="webBox-urlEditor" + onDrop={this.onUrlDrop} + onDragOver={this.onUrlDragover} style={{ top: 0 }}> + <div className="urlEditor"> + <div className="editorBase"> + <div className="webBox-buttons" + onDrop={this.onUrlDrop} + onDragOver={this.onUrlDragover} style={{ display: "flex" }}> + <div className="webBox-freeze" title={"Annotate"} + style={{ background: this.props.docView.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} + onClick={this.toggleAnnotationMode} > + <FontAwesomeIcon icon="pen" size={"2x"} /> + </div> + <div className="webBox-freeze" title={"Select"} + style={{ background: !this.props.docView.layoutDoc.isAnnotating ? "lightBlue" : "gray" }} + onClick={this.toggleAnnotationMode} > + <FontAwesomeIcon icon={"mouse-pointer"} size={"2x"} /> + </div> + <input className="webpage-urlInput" + placeholder="ENTER URL" + value={this._url} + onDrop={this.onUrlDrop} + onDragOver={this.onUrlDragover} + onChange={this.onURLChange} + onKeyDown={this.onValueKeyDown} + onClick={(e) => { + this._keyInput.current!.select(); + e.stopPropagation(); + }} + ref={this._keyInput} + /> + <div style={{ + display: "flex", + flexDirection: "row", + justifyContent: "space-between", + maxWidth: "120px", + }}> + <button className="submitUrl" onClick={this.submitURL} + onDragOver={this.onUrlDragover} onDrop={this.onUrlDrop}> + GO + </button> + <button className="submitUrl" onClick={this.back}> + <FontAwesomeIcon icon="caret-left" size="lg"></FontAwesomeIcon> + </button> + <button className="submitUrl" onClick={this.forward}> + <FontAwesomeIcon icon="caret-right" size="lg"></FontAwesomeIcon> + </button> + </div> + </div> + </div> + </div> + </div> + ); + } + + @observable viewType = this.selectedDoc?._viewType; render() { @@ -622,7 +778,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </div> </Tooltip> : null} {!this.isText && !this.props.isDoc ? <Tooltip key="num" title={<div className="dash-tooltip">Toggle View All</div>} placement="bottom"> - <div className="numKeyframe" style={{ backgroundColor: this.document.editing ? "#759c75" : "#c56565" }} + <div className="numKeyframe" style={{ color: this.document.editing ? "white" : "black", backgroundColor: this.document.editing ? "#5B9FDD" : "#AEDDF8" }} onClick={action(() => this.document.editing = !this.document.editing)} > {NumCast(this.document.currentFrame)} </div> @@ -645,6 +801,9 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </button> </Tooltip> } + {/* {!this.props.isOverlay || this.document.type !== DocumentType.WEB || this.isText || this.props.isDoc ? (null) : + this.urlEditor + } */} {!this.isText ? <> {this.drawButtons} @@ -654,7 +813,7 @@ export class CollectionFreeFormViewChrome extends React.Component<CollectionMenu </> : (null) } - {this.isText ? <RichTextMenu key="rich" /> : null} + {this.isText ? <RichTextMenu /> : null} </div>; } } @@ -669,7 +828,7 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu @computed get pivotField() { return StrCast(this.document._pivotField); } getKeySuggestions = async (value: string): Promise<string[]> => { - value = value.toLowerCase(); + const val = value.toLowerCase(); const docs = DocListCast(this.document[this.props.fieldKey]); if (Doc.UserDoc().noviceMode) { @@ -677,24 +836,24 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); - return keys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + return keys.filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set<string>(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || - key.indexOf("lastModified") >= 0 || (key[0].toUpperCase() === key[0] && + key.indexOf("lastModified") >= 0 || (key[0]?.toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key !== "UseCors" && key[0] !== "_")); - return noviceKeys.filter(key => key.toLowerCase().indexOf(value.toLowerCase()) > -1); + return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } } if (docs instanceof Doc) { - return Object.keys(docs).filter(key => key.toLowerCase().startsWith(value)); + return Object.keys(docs).filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set<string>(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); - return Array.from(keys).filter(key => key.toLowerCase().startsWith(value)); + return Array.from(keys).filter(key => key.toLowerCase().indexOf(val) > -1); } } @@ -735,8 +894,10 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu @action resetValue = () => { this._currentKey = this.pivotField; }; render() { + const doctype = this.props.docView.Document.type; + const isPres: boolean = (doctype === DocumentType.PRES); return ( - <div className="collectionStackingViewChrome-cont"> + isPres ? (null) : <div className="collectionStackingViewChrome-cont"> <div className="collectionStackingViewChrome-pivotField-cont"> <div className="collectionStackingViewChrome-pivotField-label"> GROUP BY: @@ -930,16 +1091,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp get numCols() { return NumCast(this.document.gridNumCols, 10); } /** - * Sets the value of `numCols` on the grid's Document to the value entered. - */ - @undoBatch - onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.key === "Enter" || e.key === "Tab") { - if (e.currentTarget.valueAsNumber > 0) { - this.document.gridNumCols = e.currentTarget.valueAsNumber; - } - - } + * Sets the value of `numCols` on the grid's Document to the value entered. + */ + onNumColsChange = (e: React.ChangeEvent<HTMLInputElement>) => { + if (e.currentTarget.valueAsNumber > 0) undoBatch(() => this.document.gridNumCols = e.currentTarget.valueAsNumber)(); } /** @@ -977,9 +1132,10 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp */ onDecrementButtonClick = () => { this.clicked = true; - if (!this.decrementLimitReached) { + if (this.numCols > 1 && !this.decrementLimitReached) { this.entered && (this.document.gridNumCols as number)++; undoBatch(() => this.document.gridNumCols = this.numCols - 1)(); + if (this.numCols === 1) this.decrementLimitReached = true; } this.entered = false; } @@ -1002,7 +1158,7 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp decrementValue = () => { this.entered = true; if (!this.clicked) { - if (this.numCols !== 1) { + if (this.numCols > 1) { this.document.gridNumCols = this.numCols - 1; } else { @@ -1035,9 +1191,9 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp <span className="grid-icon"> <FontAwesomeIcon icon="columns" size="1x" /> </span> - <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.numCols.toString()} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} /> - <input className="columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" /> - <input className="columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" /> + <input className="collectionGridViewChrome-entryBox" type="number" value={this.numCols} onChange={this.onNumColsChange} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => { e.stopPropagation(); e.preventDefault(); e.currentTarget.focus(); }} /> + <input className="collectionGridViewChrome-columnButton" onClick={this.onIncrementButtonClick} onMouseEnter={this.incrementValue} onMouseLeave={this.decrementValue} type="button" value="↑" /> + <input className="collectionGridViewChrome-columnButton" style={{ marginRight: 5 }} onClick={this.onDecrementButtonClick} onMouseEnter={this.decrementValue} onMouseLeave={this.incrementValue} type="button" value="↓" /> </span> {/* <span className="grid-control"> <span className="grid-icon"> @@ -1078,4 +1234,15 @@ export class CollectionGridViewChrome extends React.Component<CollectionMenuProp </div> ); } -}
\ No newline at end of file +} +Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) { + const dataField = doc[Doc.LayoutFieldKey(doc)]; + const childDocs = DocListCast(dataField); + const currentFrame = Cast(doc.currentFrame, "number", null); + if (currentFrame === undefined) { + doc.currentFrame = 0; + CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); + } + CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); + doc.currentFrame = Math.max(0, newFrame); +});
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 8a1dd8472..ea786800c 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { action, observable, trace, computed } from "mobx"; +import { action, observable, trace, computed, runInAction } from "mobx"; import { observer } from "mobx-react"; import { CellInfo } from "react-table"; import "react-table/react-table.css"; @@ -33,6 +33,9 @@ import DatePicker from "react-datepicker"; import "react-datepicker/dist/react-datepicker.css"; import { DateField } from "../../../fields/DateField"; import { RichTextField } from "../../../fields/RichTextField"; +import { DocumentManager } from "../../util/DocumentManager"; +import { SearchUtil } from "../../util/SearchUtil"; +import { DocumentType } from "../../documents/DocumentTypes"; const path = require('path'); library.add(faExpand); @@ -67,10 +70,12 @@ export class CollectionSchemaCell extends React.Component<CellProps> { protected _document = this.props.rowProps.original; protected _dropDisposer?: DragManager.DragDropDisposer; - componentDidMount() { + async componentDidMount() { document.addEventListener("keydown", this.onKeyDown); } + @observable contents: string = ""; + componentWillUnmount() { document.removeEventListener("keydown", this.onKeyDown); } @@ -142,10 +147,8 @@ export class CollectionSchemaCell extends React.Component<CellProps> { } protected dropRef = (ele: HTMLElement | null) => { - this._dropDisposer && this._dropDisposer(); - if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); - } + this._dropDisposer?.(); + ele && (this._dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this))); } // expandDoc = (e: React.PointerEvent) => { @@ -159,6 +162,31 @@ export class CollectionSchemaCell extends React.Component<CellProps> { // e.stopPropagation(); // } + returnHighlights(bing: (() => string), positions?: number[]) { + const results = []; + const contents = bing(); + + if (positions !== undefined) { + StrCast(this.props.Document._searchString); + const length = StrCast(this.props.Document._searchString).length; + const color = contents ? "black" : "grey"; + + results.push(<span key="-1" style={{ color }}>{contents?.slice(0, positions[0])}</span>); + positions.forEach((num, cur) => { + results.push(<span key={"start" + cur} style={{ backgroundColor: "#FFFF00", color }}>{contents?.slice(num, num + length)}</span>); + let end = 0; + cur === positions.length - 1 ? end = contents.length : end = positions[cur + 1]; + results.push(<span key={"end" + cur} style={{ color }}>{contents?.slice(num + length, end)}</span>); + } + ); + return results; + } + else { + return <span style={{ color: contents ? "black" : "grey" }}>{contents ? contents?.valueOf() : "undefined"}</span>; + } + } + type: string = ""; + renderCellWithType(type: string | undefined) { const dragRef: React.RefObject<HTMLDivElement> = React.createRef(); @@ -200,12 +228,23 @@ export class CollectionSchemaCell extends React.Component<CellProps> { const doc = FieldValue(Cast(field, Doc)); const fieldIsDoc = (type === "document" && typeof field === "object") || (typeof field === "object" && doc); - const onItemDown = (e: React.PointerEvent) => { - //fieldIsDoc && - SetupDrag(this._focusRef, - () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, - this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument, - this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); + const onItemDown = async (e: React.PointerEvent) => { + if (this.props.Document._searchDoc !== undefined) { + const doc = Doc.GetProto(this.props.rowProps.original); + const aliasdoc = await SearchUtil.GetAliasesOfDocument(doc); + let targetContext = undefined; + if (aliasdoc.length > 0) { + targetContext = Cast(aliasdoc[0].context, Doc) as Doc; + } + DocumentManager.Instance.jumpToDocument(this.props.rowProps.original, false, undefined, targetContext); + } + else { + fieldIsDoc && + SetupDrag(this._focusRef, + () => this._document[props.fieldKey] instanceof Doc ? this._document[props.fieldKey] : this._document, + this._document[props.fieldKey] instanceof Doc ? (doc: Doc | Doc[], target: Doc | undefined, addDoc: (newDoc: Doc | Doc[]) => any) => addDoc(doc) : this.props.moveDocument, + this._document[props.fieldKey] instanceof Doc ? "alias" : this.props.Document.schemaDoc ? "copy" : undefined)(e); + } }; const onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SnappingManager.GetIsDragging() && (type === "document" || type === undefined)) { @@ -217,9 +256,13 @@ export class CollectionSchemaCell extends React.Component<CellProps> { }; let contents: any = "incorrect type"; - if (type === undefined) contents = <FieldView {...props} fieldKey={fieldKey} />; + if (type === undefined) contents = field === undefined ? undefined : Field.toString(field as Field);//StrCast(field) === "" ? "--" : <FieldView {...props} fieldKey={fieldKey} />; if (type === "number") contents = typeof field === "number" ? NumCast(field) : "--" + typeof field + "--"; - if (type === "string") contents = typeof field === "string" ? (StrCast(field) === "" ? "--" : StrCast(field)) : "--" + typeof field + "--"; + if (type === "string") { + fieldKey === "text" ? + contents = Cast(field, RichTextField)?.Text : + contents = typeof field === "string" ? (StrCast(field) === "" ? "--" : StrCast(field)) : "--" + typeof field + "--"; + } if (type === "boolean") contents = typeof field === "boolean" ? (BoolCast(field) ? "true" : "false") : "--" + typeof field + "--"; if (type === "document") { const doc = FieldValue(Cast(field, Doc)); @@ -250,20 +293,10 @@ export class CollectionSchemaCell extends React.Component<CellProps> { // </div> // ); const positions = []; + let cfield = props.Document[props.fieldKey]; + this.type = props.fieldKey; if (StrCast(this.props.Document._searchString).toLowerCase() !== "") { - const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - let term = ""; - if (cfield !== undefined) { - if (cfield.Text !== undefined) { - term = cfield.Text; - } - else if (StrCast(cfield)) { - term = StrCast(cfield); - } - else { - term = String(NumCast(cfield)); - } - } + let term = (cfield instanceof Promise) ? "...promise pending..." : Field.toString(cfield as Field); term = term.toLowerCase(); const search = StrCast(this.props.Document._searchString).toLowerCase(); let start = term.indexOf(search); @@ -271,7 +304,7 @@ export class CollectionSchemaCell extends React.Component<CellProps> { if (start !== -1) { positions.push(start); } - while (start < contents.length && start !== -1) { + while (start < contents?.length && start !== -1) { term = term.slice(start + search.length + 1); tally += start + search.length + 1; start = term.indexOf(search); @@ -281,89 +314,123 @@ export class CollectionSchemaCell extends React.Component<CellProps> { positions.pop(); } } + let search = false; + if (this.props.Document._searchDoc !== undefined) { + search = true; + } + + const placeholder = type === "number" ? "0" : contents === "" ? "--" : "undefined"; return ( <div className="collectionSchemaView-cellContainer" style={{ cursor: fieldIsDoc ? "grab" : "auto" }} ref={dragRef} onPointerDown={this.onPointerDown} onPointerEnter={onPointerEnter} onPointerLeave={onPointerLeave}> <div className={className} ref={this._focusRef} onPointerDown={onItemDown} tabIndex={-1}> - <div className="collectionSchemaView-cellContents" ref={type === undefined || type === "document" ? this.dropRef : null} key={props.Document[Id]}> - <EditableView - positions={positions.length > 0 ? positions : undefined} - search={StrCast(this.props.Document._searchString) ? StrCast(this.props.Document._searchString) : undefined} - editing={this._isEditing} - isEditingCallback={this.isEditingCallback} - display={"inline"} - contents={contents ? contents : type === "number" ? "0" : "undefined"} - highlight={positions.length > 0 ? true : undefined} - //contents={StrCast(contents)} - height={"auto"} - maxHeight={Number(MAX_ROW_HEIGHT)} - placeholder={"enter value"} - bing={() => { - const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - if (cfield !== undefined) { - console.log(typeof (cfield)); - // if (typeof(cfield)===RichTextField) - const a = cfield as RichTextField; - if (a.Text !== undefined) { - return (a.Text); - } - else if (StrCast(cfield)) { - return StrCast(cfield); - } - else { - return String(NumCast(cfield)); - } - } - }} - GetValue={() => { - if (type === "number" && (contents === 0 || contents === "0")) { - return "0"; - } else { + <div className="collectionSchemaView-cellContents" + ref={type === undefined || type === "document" ? this.dropRef : null}> + {!search ? + <EditableView + positions={positions.length > 0 ? positions : undefined} + search={Cast(this.props.Document._searchString, "string", null)} + editing={this._isEditing} + isEditingCallback={this.isEditingCallback} + display={"inline"} + contents={contents} + highlight={positions.length > 0 ? true : undefined} + //contents={StrCast(contents)} + height={"auto"} + maxHeight={Number(MAX_ROW_HEIGHT)} + placeholder={placeholder} + bing={() => { const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); - if (type === "number") { - return StrCast(cfield); + if (cfield !== undefined) { + // if (typeof(cfield)===RichTextField) + const a = cfield as RichTextField; + const b = cfield as DateField; + console.log(b); + if (a.Text !== undefined) { + return (a.Text); + } + else if (b.toString() !== undefined) { + return b.toString(); + } + else if (StrCast(cfield)) { + return StrCast(cfield); + } + else { + return String(NumCast(cfield)); + } } - const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; - const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1]; - const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : - Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; - return val; + }} + GetValue={() => { + if (type === "number" && (contents === 0 || contents === "0")) { + return "0"; + } else { + const cfield = ComputedField.WithoutComputed(() => FieldValue(props.Document[props.fieldKey])); + if (type === "number") { + return StrCast(cfield); + } + const cscript = cfield instanceof ComputedField ? cfield.script.originalScript : undefined; + const cfinalScript = cscript?.split("return")[cscript.split("return").length - 1]; + const val = cscript !== undefined ? (cfinalScript?.endsWith(";") ? `:=${cfinalScript?.substring(0, cfinalScript.length - 2)}` : cfinalScript) : + Field.IsField(cfield) ? Field.toScriptString(cfield) : ""; + return val; - } + } - }} - SetValue={action((value: string) => { - let retVal = false; + }} + SetValue={action((value: string) => { + let retVal = false; - if (value.startsWith(":=")) { - retVal = this.props.setComputed(value.substring(2), props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col); - } else { + if (value.startsWith(":=") || value.startsWith("=:=")) { + const script = value.substring(value.startsWith("=:=") ? 3 : 2); + retVal = this.props.setComputed(script, value.startsWith(":=") ? Doc.GetProto(props.Document) : props.Document, this.props.rowProps.column.id!, this.props.row, this.props.col); + } else { + 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); + } + + } + if (retVal) { + this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing' + this.props.setIsEditing(false); + } + return retVal; + + //return true; + })} + OnFillDown={async (value: string) => { 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); + DocListCast(this.props.Document[this.props.fieldKey]). + forEach((doc, i) => value.startsWith(":=") ? + this.props.setComputed(value.substring(2), doc, this.props.rowProps.column.id!, i, this.props.col) : + this.applyToDoc(doc, i, this.props.col, script.run)); } - + }} + /> + : + this.returnHighlights(() => { + const dateCheck: Date | undefined = this.props.rowProps.original[this.props.rowProps.column.id as string] instanceof DateField ? DateCast(this.props.rowProps.original[this.props.rowProps.column.id as string]).date : undefined; + if (dateCheck !== undefined) { + cfield = dateCheck.toLocaleString(); } - if (retVal) { - this._isEditing = false; // need to set this here. otherwise, the assignment of the field will invalidate & cause render() to be called with the wrong value for 'editing' - this.props.setIsEditing(false); + if (props.fieldKey === "context") { + cfield = this.contents; } - return retVal; + if (props.fieldKey === "*lastModified") { + if (FieldValue(props.Document["data-lastModified"]) !== undefined) { + const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["data-lastModified"])) as DateField; + cfield = d.date.toLocaleString(); + } - //return true; - })} - OnFillDown={async (value: string) => { - const script = CompileScript(value, { requiredType: type, typecheck: false, editable: true, addReturn: true, params: { this: Doc.name, $r: "number", $c: "number", $: "any" } }); - if (script.compiled) { - DocListCast(this.props.Document[this.props.fieldKey]). - forEach((doc, i) => value.startsWith(":=") ? - this.props.setComputed(value.substring(2), doc, this.props.rowProps.column.id!, i, this.props.col) : - this.applyToDoc(doc, i, this.props.col, script.run)); + else if (FieldValue(props.Document["text-lastModified"]) !== undefined) { + const d = ComputedField.WithoutComputed(() => FieldValue(props.Document["text-lastModified"])) as DateField; + cfield = d.date.toLocaleString(); + } } - }} - /> - - + return Field.toString(cfield as Field); + }, positions) + } </div > {/* {fieldIsDoc ? docExpander : null} */} </div> @@ -371,30 +438,22 @@ export class CollectionSchemaCell extends React.Component<CellProps> { ); } - render() { - return this.renderCellWithType(undefined); - } + render() { return this.renderCellWithType(undefined); } } @observer export class CollectionSchemaNumberCell extends CollectionSchemaCell { - render() { - return this.renderCellWithType("number"); - } + render() { return this.renderCellWithType("number"); } } @observer export class CollectionSchemaBooleanCell extends CollectionSchemaCell { - render() { - return this.renderCellWithType("boolean"); - } + render() { return this.renderCellWithType("boolean"); } } @observer export class CollectionSchemaStringCell extends CollectionSchemaCell { - render() { - return this.renderCellWithType("string"); - } + render() { return this.renderCellWithType("string"); } } @observer @@ -548,7 +607,7 @@ export class CollectionSchemaDocCell extends CollectionSchemaCell { if (typeof this._field === "object" && this._doc && this._docTitle) { return ( <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} - onPointerDown={(e) => { this.onDown(e); }} + onPointerDown={this.onDown} onPointerEnter={(e) => { this.showPreview(true, e); }} onPointerLeave={(e) => { this.showPreview(false, e); }} > @@ -720,29 +779,11 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { @observable private _selectedNum = 0; @action - toggleOpened(open: boolean) { - this._opened = open; - } - - // @action - // onChange = (e: React.ChangeEvent<HTMLTextAreaElement>) => { - // this._text = e.target.value; - - // // change if its a document - // this._optionsList[this._selectedNum] = this._text; - // } - - @action onSetValue = (value: string) => { - - - this._text = value; - // change if its a document - this._optionsList[this._selectedNum] = this._text; + this._optionsList[this._selectedNum] = this._text = value; (this.prop.Document[this.prop.fieldKey] as List<any>).splice(this._selectedNum, 1, value); - } @action @@ -756,55 +797,34 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 }); } - render() { - - const dragRef: React.RefObject<HTMLDivElement> = React.createRef(); - let type = "list"; - let link = false; - let doc = false; const reference = React.createRef<HTMLDivElement>(); if (typeof this._field === "object" && this._optionsList[0]) { - - const options = this._optionsList.map((element, index) => { - - if (element instanceof Doc) { - doc = true; - type = "document"; - if (this.prop.fieldKey.toLowerCase() === "links") { - link = true; - type = "link"; + const options = !this._opened ? (null) : <div> + {this._optionsList.map((element, index) => { + let title = ""; + if (element instanceof Doc) { + type = "document"; + if (this.prop.fieldKey.toLowerCase() === "links") { + link = true; + type = "link"; + } + title = StrCast(element.title); } - const document = FieldValue(Cast(element, Doc)); - const title = element.title; - return <div - className="collectionSchemaView-dropdownOption" - onPointerDown={(e) => { this.onSelected(StrCast(element.title), index); }} - style={{ padding: "6px" }}> + return <div className="collectionSchemaView-dropdownOption" style={{ padding: "6px" }} + onPointerDown={(e) => this.onSelected(StrCast(element), index)} > + {element} {title} </div>; - - } else { - return <div - className="collectionSchemaView-dropdownOption" - onPointerDown={(e) => { this.onSelected(StrCast(element), index); }} - style={{ padding: "6px" }}>{element}</div>; - } - }); + })} + </div>; const plainText = <div style={{ padding: "5.9px" }}>{this._text}</div>; - // const textarea = <textarea onChange={this.onChange} value={this._text} - // onFocus={doc ? this.onFocus : undefined} - // onBlur={doc ? e => this._overlayDisposer?.() : undefined} - // style={{ resize: "none" }} - // placeholder={"select an item"}></textarea>; - - const textarea = <div className="collectionSchemaView-cellContents" - style={{ padding: "5.9px" }} - ref={type === undefined || type === "document" ? this.dropRef : null} key={this.prop.Document[Id]}> + const textarea = <div className="collectionSchemaView-cellContents" key={this.prop.Document[Id]} style={{ padding: "5.9px" }} + ref={type === undefined || type === "document" ? this.dropRef : null} > <EditableView editing={this._isEditing} isEditingCallback={this.isEditingCallback} @@ -812,11 +832,8 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { contents={this._text} height={"auto"} maxHeight={Number(MAX_ROW_HEIGHT)} - GetValue={() => { - return this._text; - }} + GetValue={() => this._text} SetValue={action((value: string) => { - // add special for params this.onSetValue(value); return true; @@ -825,37 +842,26 @@ export class CollectionSchemaListCell extends CollectionSchemaCell { </div >; //☰ - - const dropdown = <div> - {options} - </div>; - return ( <div className="collectionSchemaView-cellWrapper" ref={this._focusRef} tabIndex={-1} onPointerDown={this.onPointerDown}> <div className="collectionSchemaView-cellContents" key={this._document[Id]} ref={reference}> <div className="collectionSchemaView-dropDownWrapper"> - <button type="button" className="collectionSchemaView-dropdownButton" onClick={(e) => { this.toggleOpened(!this._opened); }} - style={{ right: "length", position: "relative" }}> - {this._opened ? <FontAwesomeIcon icon="caret-up" size="lg" ></FontAwesomeIcon> - : <FontAwesomeIcon icon="caret-down" size="lg" ></FontAwesomeIcon>} + <button type="button" className="collectionSchemaView-dropdownButton" style={{ right: "length", position: "relative" }} + onClick={action(e => this._opened = !this._opened)} > + <FontAwesomeIcon icon={this._opened ? "caret-up" : "caret-down"} size="lg" /> </button> <div className="collectionSchemaView-dropdownText"> {link ? plainText : textarea} </div> </div> - - {this._opened ? dropdown : null} + {options} </div > </div> ); - } else { - return this.renderCellWithType("list"); } + return this.renderCellWithType("list"); } } - - - @observer export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { @observable private _isChecked: boolean = typeof this.props.rowProps.original[this.props.rowProps.column.id as string] === "boolean" ? BoolCast(this.props.rowProps.original[this.props.rowProps.column.id as string]) : false; @@ -864,15 +870,13 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { toggleChecked = (e: React.ChangeEvent<HTMLInputElement>) => { this._isChecked = e.target.checked; const script = CompileScript(e.target.checked.toString(), { requiredType: "boolean", addReturn: true, params: { this: Doc.name } }); - if (script.compiled) { - this.applyToDoc(this._document, this.props.row, this.props.col, script.run); - } + script.compiled && this.applyToDoc(this._document, this.props.row, this.props.col, script.run); } render() { const reference = React.createRef<HTMLDivElement>(); const onItemDown = (e: React.PointerEvent) => { - (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined : + (!this.props.CollectionView?.props.isSelected() ? undefined : SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); }; return ( @@ -888,65 +892,28 @@ export class CollectionSchemaCheckboxCell extends CollectionSchemaCell { @observer export class CollectionSchemaButtons extends CollectionSchemaCell { - render() { + const doc = this.props.rowProps.original; + const searchMatch = (backward: boolean = true) => { doc.searchMatch = undefined; setTimeout(() => doc.searchMatch = backward, 0); }; // const reference = React.createRef<HTMLDivElement>(); // const onItemDown = (e: React.PointerEvent) => { // (!this.props.CollectionView || !this.props.CollectionView.props.isSelected() ? undefined : // SetupDrag(reference, () => this._document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e)); // }; - const doc = this.props.rowProps.original; - let buttons: JSX.Element | undefined = undefined; - buttons = <div style={{ - paddingTop: 8, - paddingLeft: 3, - }}><button onClick={() => { - doc.searchMatch = false; - setTimeout(() => doc.searchMatch = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); - }} style={{ padding: 2, left: 77 }}> - <FontAwesomeIcon icon="arrow-up" size="sm" /> - </button> - <button onClick={() => { - { - doc.searchMatchAlt = false; - setTimeout(() => doc.searchMatchAlt = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); - } - }} style={{ padding: 2 }}> - <FontAwesomeIcon icon="arrow-down" size="sm" /> - </button></div>; - const type = StrCast(doc.type); - if (type === "pdf") { - buttons = <div><button - style={{ - position: "relative", - height: 30, - width: 28, - left: 1, - }} - - onClick={() => { - doc.searchMatch = false; - setTimeout(() => doc.searchMatch = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); - }}> - <FontAwesomeIcon icon="arrow-down" size="sm" /> - </button></div >; - } - else if (type !== "rtf") { - buttons = undefined; - } - - if (BoolCast(this.props.Document._searchDoc) === true) { - - } - else { - buttons = undefined; - } - return ( - <div> {buttons}</div> - ); - } -} - + return !BoolCast(this.props.Document._searchDoc) ? <></> + : StrCast(doc.type) === DocumentType.PDF ? + <button style={{ position: "relative", height: 30, width: 28, left: 1, }} onClick={() => searchMatch(false)}> + <FontAwesomeIcon icon="arrow-down" size="sm" /> + </button> + : StrCast(doc.type) === DocumentType.RTF ? + <div style={{ paddingTop: 8, paddingLeft: 3, }} > + <button style={{ padding: 2, left: 77 }} onClick={() => searchMatch(true)}> + <FontAwesomeIcon icon="arrow-up" size="sm" /> + </button> + <button style={{ padding: 2 }} onClick={() => searchMatch(false)} > + <FontAwesomeIcon icon="arrow-down" size="sm" /> + </button> + </div> : + <></>; + } +}
\ No newline at end of file diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index e65adcf76..34a8bfa24 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -1,67 +1,23 @@ import React = require("react"); -import { action, observable } from "mobx"; -import { observer } from "mobx-react"; -import "./CollectionSchemaView.scss"; -import { faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar } from '@fortawesome/free-solid-svg-icons'; -import { library, IconProp } from "@fortawesome/fontawesome-svg-core"; +import { IconProp, library } from "@fortawesome/fontawesome-svg-core"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { ColumnType } from "./CollectionSchemaView"; -import { faFile } from "@fortawesome/free-regular-svg-icons"; -import { SchemaHeaderField, PastelSchemaPalette } from "../../../fields/SchemaHeaderField"; +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, DocListCast, Opt } from "../../../fields/Doc"; +import { listSpec } from "../../../fields/Schema"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; +import { ScriptField } from "../../../fields/ScriptField"; +import { Cast, StrCast } from "../../../fields/Types"; import { undoBatch } from "../../util/UndoManager"; -import { Doc } from "../../../fields/Doc"; -import { StrCast } from "../../../fields/Types"; +import { SearchBox } from "../search/SearchBox"; +import { ColumnType } from "./CollectionSchemaView"; +import "./CollectionSchemaView.scss"; +import { CollectionView } from "./CollectionView"; + const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; -library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes, faImage, faListUl, faCalendar); - -export interface HeaderProps { - keyValue: SchemaHeaderField; - possibleKeys: string[]; - existingKeys: string[]; - keyType: ColumnType; - typeConst: boolean; - onSelect: (oldKey: string, newKey: string, addnew: boolean) => void; - setIsEditing: (isEditing: boolean) => void; - deleteColumn: (column: string) => void; - setColumnType: (column: SchemaHeaderField, type: ColumnType) => void; - setColumnSort: (column: SchemaHeaderField, desc: boolean | undefined) => void; - setColumnColor: (column: SchemaHeaderField, color: string) => void; - -} - -export class CollectionSchemaHeader extends React.Component<HeaderProps> { - render() { - const icon: IconProp = this.props.keyType === ColumnType.Number ? "hashtag" : this.props.keyType === ColumnType.String ? "font" : - this.props.keyType === ColumnType.Boolean ? "check-square" : this.props.keyType === ColumnType.Doc ? "file" : - this.props.keyType === ColumnType.Image ? "image" : this.props.keyType === ColumnType.List ? "list-ul" : this.props.keyType === ColumnType.Date ? "calendar" : - "align-justify"; - return ( - <div className="collectionSchemaView-header" style={{ background: this.props.keyValue.color }}> - <CollectionSchemaColumnMenu - columnField={this.props.keyValue} - // keyValue={this.props.keyValue.heading} - possibleKeys={this.props.possibleKeys} - existingKeys={this.props.existingKeys} - // keyType={this.props.keyType} - typeConst={this.props.typeConst} - menuButtonContent={<div><FontAwesomeIcon icon={icon} size="sm" />{this.props.keyValue.heading}</div>} - addNew={false} - onSelect={this.props.onSelect} - setIsEditing={this.props.setIsEditing} - deleteColumn={this.props.deleteColumn} - onlyShowOptions={false} - setColumnType={this.props.setColumnType} - setColumnSort={this.props.setColumnSort} - setColumnColor={this.props.setColumnColor} - /> - </div> - ); - } -} - export interface AddColumnHeaderProps { createColumn: () => void; @@ -77,7 +33,6 @@ export class CollectionSchemaAddColumnHeader extends React.Component<AddColumnHe } - export interface ColumnMenuProps { columnField: SchemaHeaderField; // keyValue: string; @@ -101,37 +56,29 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> @observable private _isOpen: boolean = false; @observable private _node: HTMLDivElement | null = null; - componentDidMount() { - document.addEventListener("pointerdown", this.detectClick); - } + componentDidMount() { document.addEventListener("pointerdown", this.detectClick); } - componentWillUnmount() { - document.removeEventListener("pointerdown", this.detectClick); - } + componentWillUnmount() { document.removeEventListener("pointerdown", this.detectClick); } - detectClick = (e: PointerEvent): void => { - if (this._node && this._node.contains(e.target as Node)) { - } else { - this._isOpen = false; - this.props.setIsEditing(false); - } + @action + detectClick = (e: PointerEvent) => { + !this._node?.contains(e.target as Node) && this.props.setIsEditing(this._isOpen = false); } @action toggleIsOpen = (): void => { - this._isOpen = !this._isOpen; - this.props.setIsEditing(this._isOpen); + this.props.setIsEditing(this._isOpen = !this._isOpen); } - changeColumnType = (type: ColumnType): void => { + changeColumnType = (type: ColumnType) => { this.props.setColumnType(this.props.columnField, type); } - changeColumnSort = (desc: boolean | undefined): void => { + changeColumnSort = (desc: boolean | undefined) => { this.props.setColumnSort(this.props.columnField, desc); } - changeColumnColor = (color: string): void => { + changeColumnColor = (color: string) => { this.props.setColumnColor(this.props.columnField, color); } @@ -143,7 +90,7 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> } renderTypes = () => { - if (this.props.typeConst) return <></>; + if (this.props.typeConst) return (null); const type = this.props.columnField.type; return ( @@ -238,18 +185,6 @@ export class CollectionSchemaColumnMenu extends React.Component<ColumnMenuProps> renderContent = () => { return ( <div className="collectionSchema-header-menuOptions"> - <div className="collectionSchema-headerMenu-group"> - <label>Key:</label> - <KeysDropdown - keyValue={this.props.columnField.heading} - possibleKeys={this.props.possibleKeys} - existingKeys={this.props.existingKeys} - canAddNew={true} - addNew={this.props.addNew} - onSelect={this.props.onSelect} - setIsEditing={this.props.setIsEditing} - /> - </div> {this.props.onlyShowOptions ? <></> : <> {this.renderTypes()} @@ -286,13 +221,22 @@ export interface KeysDropdownProps { setIsEditing: (isEditing: boolean) => void; width?: string; docs?: Doc[]; + Document: Doc; + dataDoc: Doc | undefined; + fieldKey: string; + ContainingCollectionDoc: Doc | undefined; + ContainingCollectionView: Opt<CollectionView>; + active?: (outsideReaction?: boolean) => boolean; + openHeader: (column: any, screenx: number, screeny: number) => void; + col: SchemaHeaderField; + icon: IconProp; } @observer export class KeysDropdown extends React.Component<KeysDropdownProps> { @observable private _key: string = this.props.keyValue; @observable private _searchTerm: string = this.props.keyValue; @observable private _isOpen: boolean = false; - @observable private _canClose: boolean = true; + @observable private _node: HTMLDivElement | null = null; @observable private _inputRef: React.RefObject<HTMLInputElement> = React.createRef(); @action setSearchTerm = (value: string): void => { this._searchTerm = value; }; @@ -301,34 +245,73 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @action onSelect = (key: string): void => { - if (key.slice(0, this._key.length) === this._key && this._key !== key) { - const filter = key.slice(this._key.length - key.length); - this.props.onSelect(this._key, this._key, this.props.addNew, filter); + this.props.onSelect(this._key, key, this.props.addNew); + this.setKey(key); + this._isOpen = false; + this.props.setIsEditing(false); + } + + @action + setNode = (node: HTMLDivElement): void => { + if (node) { + this._node = node; } - else { - this.props.onSelect(this._key, key, this.props.addNew); - this.setKey(key); + } + + componentDidMount() { + document.addEventListener("pointerdown", this.detectClick); + } + + @action + detectClick = (e: PointerEvent): void => { + if (this._node && this._node.contains(e.target as Node)) { + } else { this._isOpen = false; this.props.setIsEditing(false); } } - @action - onSelect2 = (key: string): void => { - this._searchTerm = this._searchTerm.slice(0, this._key.length) + key; - this._isOpen = false; - + componentWillMount() { + document.removeEventListener("pointerdown", this.detectClick); + const filters = Cast(this.props.Document._docFilters, listSpec("string")); + if (filters?.includes(this._key)) { + runInAction(() => this.closeResultsVisibility = "contents"); + } } + private tempfilter: string = ""; @undoBatch onKeyDown = (e: React.KeyboardEvent): void => { if (e.key === "Enter") { - const keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); - if (keyOptions.length) { - this.onSelect(keyOptions[0]); - } else if (this._searchTerm !== "" && this.props.canAddNew) { - this.setSearchTerm(this._searchTerm || this._key); - this.onSelect(this._searchTerm); + if (this._searchTerm.includes(":")) { + const colpos = this._searchTerm.indexOf(":"); + const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); + if (temp === "") { + console.log("here we are first"); + Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined); + this.updateFilter(); + } + else { + console.log("here we are first"); + Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined); + this.tempfilter = temp; + Doc.setDocFilter(this.props.Document, this._key, temp, "check"); + this.props.col.setColor("green"); + this.closeResultsVisibility = "contents"; + } + } + else { + Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined); + this.updateFilter(); + let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + const blockedkeys = ["system", "ACL-Public", "_scrollTop", "customTitle", "limitHeight", "proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; + keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); + if (keyOptions.length) { + this.onSelect(keyOptions[0]); + } else if (this._searchTerm !== "" && this.props.canAddNew) { + this.setSearchTerm(this._searchTerm || this._key); + this.onSelect(this._searchTerm); + } } } } @@ -344,103 +327,215 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { } @action - onBlur = (e: React.FocusEvent): void => { - if (this._canClose) { - this._isOpen = false; - this.props.setIsEditing(false); - } - } - - @action - onPointerEnter = (e: React.PointerEvent): void => { - this._canClose = false; - } - - @action - onPointerOut = (e: React.PointerEvent): void => { - this._canClose = true; - } - renderOptions = (): JSX.Element[] | JSX.Element => { - if (!this._isOpen) return <></>; - + if (!this._isOpen) { + this.defaultMenuHeight = 0; + return <></>; + } const searchTerm = this._searchTerm.trim() === "New field" ? "" : this._searchTerm; - const keyOptions = searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); + let keyOptions = searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const exactFound = keyOptions.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1 || this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1; + const blockedkeys = ["proto", "x", "y", "_width", "_height", "_autoHeight", "_fontSize", "_fontFamily", "context", "zIndex", "_timeStampOnEnter", "lines", "highlighting", "searchMatch", "creationDate", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "_xMargin", "_yMargin", "layout", "layout_keyValue", "links"]; + keyOptions = keyOptions.filter(n => !blockedkeys.includes(n)); + const options = keyOptions.map(key => { return <div key={key} className="key-option" style={{ border: "1px solid lightgray", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", }} - onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect(key); this.setSearchTerm(""); }}>{key}</div>; + onPointerDown={e => { + e.stopPropagation(); + }} + onClick={() => { + this.onSelect(key); + this.setSearchTerm(""); + }}>{key}</div>; }); // if search term does not already exist as a group type, give option to create new group type + if (this._key !== this._searchTerm.slice(0, this._key.length)) { + console.log("little further"); if (!exactFound && this._searchTerm !== "" && this.props.canAddNew) { options.push(<div key={""} className="key-option" style={{ - border: "1px solid lightgray", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + border: "1px solid lightgray", width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", }} onClick={() => { this.onSelect(this._searchTerm); this.setSearchTerm(""); }}> Create "{this._searchTerm}" key</div>); } } + if (options.length === 0) { + this.defaultMenuHeight = 0; + } + else { + if (this.props.docs) { + const panesize = this.props.docs.length * 30; + options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; + } + else { + options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8; + } + } return options; } + docSafe: Doc[] = []; + + @action renderFilterOptions = (): JSX.Element[] | JSX.Element => { - if (!this._isOpen) return <></>; + if (!this._isOpen) { + this.defaultMenuHeight = 0; + return <></>; + } const keyOptions: string[] = []; - const temp = this._searchTerm.slice(this._key.length); - this.props.docs?.forEach((doc) => { + const colpos = this._searchTerm.indexOf(":"); + const temp = this._searchTerm.slice(colpos + 1, this._searchTerm.length); + if (this.docSafe.length === 0) { + this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey]); + } + const docs = this.docSafe; + docs.forEach((doc) => { const key = StrCast(doc[this._key]); - if (keyOptions.includes(key) === false && key.includes(temp)) { + if (keyOptions.includes(key) === false && key.includes(temp) && key !== "") { keyOptions.push(key); } }); - + const filters = Cast(this.props.Document._docFilters, listSpec("string")); + if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) { + this.props.col.setColor("rgb(241, 239, 235)"); + this.closeResultsVisibility = "none"; + } + for (let i = 0; i < (filters?.length ?? 0) - 1; i += 3) { + if (filters![i] === this.props.col.heading && keyOptions.includes(filters![i + 1]) === false) { + keyOptions.push(filters![i + 1]); + } + } const options = keyOptions.map(key => { + //Doc.setDocFilter(this.props.Document!, this._key, key, undefined); + let bool = false; + console.log(filters); + if (filters !== undefined) { + bool = filters.includes(key) && filters[filters.indexOf(key) + 1] === "check"; + console.log(filters.includes(key)); + } return <div key={key} className="key-option" style={{ - border: "1px solid lightgray", - width: this.props.width, maxWidth: this.props.width, overflowX: "hidden" + border: "1px solid lightgray", paddingLeft: 5, textAlign: "left", + width: this.props.width, maxWidth: this.props.width, overflowX: "hidden", background: "white", backgroundColor: "white", }} - onPointerDown={e => e.stopPropagation()} onClick={() => { this.onSelect2(key); }}>{key}</div>; + > + <input type="checkbox" + onPointerDown={e => e.stopPropagation()} + onClick={e => e.stopPropagation()} + onChange={(e) => { + e.target.checked === true ? Doc.setDocFilter(this.props.Document, this._key, key, "check") : Doc.setDocFilter(this.props.Document, this._key, key, undefined); + e.target.checked === true ? this.closeResultsVisibility = "contents" : console.log(""); + e.target.checked === true ? this.props.col.setColor("green") : this.updateFilter(); + e.target.checked === true && SearchBox.Instance.filter === true ? Doc.setDocFilter(docs[0], this._key, key, "check") : Doc.setDocFilter(docs[0], this._key, key, undefined); + }} + checked={bool} + /> + <span style={{ paddingLeft: 4 }}> + {key} + </span> + + </div>; }); + if (options.length === 0) { + this.defaultMenuHeight = 0; + } + else { + if (this.props.docs) { + const panesize = this.props.docs.length * 30; + options.length * 20 + 8 - 10 > panesize ? this.defaultMenuHeight = panesize : this.defaultMenuHeight = options.length * 20 + 8; + } + else { + options.length > 5 ? this.defaultMenuHeight = 108 : this.defaultMenuHeight = options.length * 20 + 8; + } + } return options; } + @observable defaultMenuHeight = 0; + + + updateFilter() { + const filters = Cast(this.props.Document._docFilters, listSpec("string")); + if (filters === undefined || filters.length === 0 || filters.includes(this._key) === false) { + console.log("PLEASE"); + this.props.col.setColor("rgb(241, 239, 235)"); + this.closeResultsVisibility = "none"; + } + } + + + get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } + + @computed get scriptField() { + const scriptText = "setDocFilter(containingTreeView, heading, this.title, checked)"; + const script = ScriptField.MakeScript(scriptText, { this: Doc.name, heading: "string", checked: "string", containingTreeView: Doc.name }); + return script ? () => script : undefined; + } + filterBackground = () => "rgba(105, 105, 105, 0.432)"; + @observable filterOpen: boolean | undefined = undefined; + closeResultsVisibility: string = "none"; + + removeFilters = (e: React.PointerEvent): void => { + const keyOptions: string[] = []; + if (this.docSafe.length === 0) { + this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey]); + } + const docs = this.docSafe; + docs.forEach((doc) => { + const key = StrCast(doc[this._key]); + if (keyOptions.includes(key) === false) { + keyOptions.push(key); + } + }); + keyOptions.forEach(key => { + Doc.setDocFilter(this.props.Document, this._key, key, undefined); + } + ); + Doc.setDocFilter(this.props.Document, this._key, this.tempfilter, undefined); + this.props.col.setColor("rgb(241, 239, 235)"); + this.closeResultsVisibility = "none"; + } render() { return ( - <div className="keys-dropdown" style={{ zIndex: 10, width: this.props.width, maxWidth: this.props.width }}> - {this._key === this._searchTerm.slice(0, this._key.length) ? - <div style={{ position: "absolute", marginLeft: "4px", marginTop: "3", color: "grey", pointerEvents: "none", lineHeight: 1.15 }}> - {this._key} + <div style={{ display: "flex" }} ref={this.setNode}> + <FontAwesomeIcon onClick={e => { this.props.openHeader(this.props.col, e.clientX, e.clientY); e.stopPropagation(); }} icon={this.props.icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> + + {/* <FontAwesomeIcon icon={fa.faSearchMinus} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} onClick={e => { + runInAction(() => { this._isOpen === undefined ? this._isOpen = true : this._isOpen = !this._isOpen }) + }} /> */} + + <div className="keys-dropdown" style={{ zIndex: 1, width: this.props.width, maxWidth: this.props.width }}> + <input className="keys-search" style={{ width: "100%" }} + ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} + onChange={e => { + this.onChange(e.target.value); + }} + onClick={(e) => { + //this._inputRef.current!.select(); + e.stopPropagation(); + }} onFocus={this.onFocus} ></input> + <div style={{ display: this.closeResultsVisibility }}> + <FontAwesomeIcon onPointerDown={this.removeFilters} icon={"times-circle"} size="lg" + style={{ cursor: "hand", color: "grey", padding: 2, left: -20, top: -1, height: 15, position: "relative" }} /> </div> - : undefined} - <input className="keys-search" style={{ width: "100%" }} - ref={this._inputRef} type="text" value={this._searchTerm} placeholder="Column key" onKeyDown={this.onKeyDown} - onChange={e => this.onChange(e.target.value)} - onClick={(e) => { - //this._inputRef.current!.select(); - e.stopPropagation(); - }} onFocus={this.onFocus} onBlur={this.onBlur}></input> - <div className="keys-options-wrapper" style={{ - backgroundColor: "white", - width: this.props.width, maxWidth: this.props.width, - }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerOut}> - {this._key === this._searchTerm.slice(0, this._key.length) ? - this.renderFilterOptions() : this.renderOptions()} - </div> - </div > + {!this._isOpen ? (null) : <div className="keys-options-wrapper" style={{ + width: this.props.width, maxWidth: this.props.width, height: "auto", + }}> + {this._searchTerm.includes(":") ? this.renderFilterOptions() : this.renderOptions()} + </div>} + </div > + </div> ); } } diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index dade4f2f2..4754adc90 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -1,19 +1,19 @@ import React = require("react"); -import { ReactTableDefaults, TableCellRenderer, RowInfo } from "react-table"; -import "./CollectionSchemaView.scss"; -import { Transform } from "../../util/Transform"; -import { Doc } from "../../../fields/Doc"; -import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager"; -import { Cast, FieldValue, StrCast } from "../../../fields/Types"; -import { ContextMenu } from "../ContextMenu"; -import { action } from "mobx"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faGripVertical, faTrash } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { DocumentManager } from "../../util/DocumentManager"; +import { action } from "mobx"; +import { ReactTableDefaults, RowInfo, TableCellRenderer } from "react-table"; +import { Doc } from "../../../fields/Doc"; import { SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { undoBatch } from "../../util/UndoManager"; +import { Cast, FieldValue, StrCast } from "../../../fields/Types"; +import { DocumentManager } from "../../util/DocumentManager"; +import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager"; import { SnappingManager } from "../../util/SnappingManager"; +import { Transform } from "../../util/Transform"; +import { undoBatch } from "../../util/UndoManager"; +import { ContextMenu } from "../ContextMenu"; +import "./CollectionSchemaView.scss"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; library.add(faGripVertical, faTrash); @@ -40,7 +40,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> { onPointerLeave = (e: React.PointerEvent): void => { this._header!.current!.className = "collectionSchema-col-wrapper"; document.removeEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointermove", this.onPointerMove); + !e.buttons && document.removeEventListener("pointermove", this.onPointerMove); } onDragMove = (e: PointerEvent): void => { const x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); @@ -68,6 +68,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> { const before = x[0] < bounds[0]; const colDragData = de.complete.columnDragData; if (colDragData) { + e.stopPropagation(); this.props.reorderColumns(colDragData.colKey, this.props.columnValue, before, this.props.allColumns); return true; } @@ -108,8 +109,10 @@ export class MovableColumn extends React.Component<MovableColumnProps> { onPointerDown = (e: React.PointerEvent, ref: React.RefObject<HTMLDivElement>) => { this._dragRef = ref; const [dx, dy] = this.props.ScreenToLocalTransform().transformDirection(e.clientX, e.clientY); - this._startDragPosition = { x: dx, y: dy }; - document.addEventListener("pointermove", this.onPointerMove); + if (!(e.target as any)?.tagName.includes("INPUT")) { + this._startDragPosition = { x: dx, y: dy }; + document.addEventListener("pointermove", this.onPointerMove); + } } @@ -164,6 +167,10 @@ export class MovableRow extends React.Component<MovableRowProps> { if (!before) this._header!.current!.className += " row-below"; e.stopPropagation(); } + componentWillUnmount() { + + this._rowDropDisposer?.(); + } createRowDropTarget = (ele: HTMLDivElement) => { this._rowDropDisposer?.(); @@ -219,13 +226,15 @@ export class MovableRow extends React.Component<MovableRowProps> { render() { const { children = null, rowInfo } = this.props; + if (!rowInfo) { return <ReactTableDefaults.TrComponent>{children}</ReactTableDefaults.TrComponent>; } const { original } = rowInfo; const doc = FieldValue(Cast(original, Doc)); - if (!doc) return <></>; + + if (!doc) return (null); const reference = React.createRef<HTMLDivElement>(); const onItemDown = SetupDrag(reference, () => doc, this.move, StrCast(this.props.dropAction) as dropActionType); @@ -238,11 +247,11 @@ export class MovableRow extends React.Component<MovableRowProps> { <div className={className} onKeyPress={this.onKeyDown} ref={this.createRowDropTarget} onContextMenu={this.onRowContextMenu}> <div className="collectionSchema-row-wrapper" onKeyPress={this.onKeyDown} ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> <ReactTableDefaults.TrComponent onKeyPress={this.onKeyDown} > - {/* <div className="row-dragger"> - <div className="row-option" onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div> - <div className="row-option" style={{ cursor: "grab" }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div> - <div className="row-option" onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div> - </div> */} + <div className="row-dragger"> + <div className="row-option" style={{ left: 5 }} onClick={undoBatch(() => this.props.removeDoc(this.props.rowInfo.original))}><FontAwesomeIcon icon="trash" size="sm" /></div> + <div className="row-option" style={{ cursor: "grab", left: 25 }} ref={reference} onPointerDown={onItemDown}><FontAwesomeIcon icon="grip-vertical" size="sm" /></div> + <div className="row-option" style={{ left: 40 }} onClick={() => this.props.addDocTab(this.props.rowInfo.original, "onRight")}><FontAwesomeIcon icon="external-link-alt" size="sm" /></div> + </div> {children} </ReactTableDefaults.TrComponent> </div> diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index ba0a259c5..fc5dffaec 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -43,6 +43,49 @@ // } } +.collectionSchemaView-searchContainer { + border-width: $COLLECTION_BORDER_WIDTH; + border-color: $intermediate-color; + border-style: solid; + border-radius: $border-radius; + box-sizing: border-box; + position: relative; + top: 0; + width: 100%; + height: 100%; + margin-top: 0; + transition: top 0.5s; + display: flex; + justify-content: space-between; + flex-wrap: nowrap; + touch-action: none; + + div { + touch-action: none; + } + + + .collectionSchemaView-tableContainer { + width: 100%; + height: 100%; + } + + .collectionSchemaView-dividerDragger { + position: relative; + height: 100%; + width: 20px; + z-index: 20; + right: 0; + top: 0; + background: gray; + cursor: col-resize; + } + + // .documentView-node:first-child { + // background: $light-color; + // } +} + .ReactTable { width: 100%; background: white; @@ -165,10 +208,11 @@ .collectionSchema-header-menu { - height: 100%; + height: auto; z-index: 100; position: absolute; - background:white; + background: white; + padding: 5px; .collectionSchema-header-toggler { z-index: 100; @@ -200,12 +244,13 @@ button.add-column { .collectionSchema-header-menuOptions { color: black; - width: 200px; + width: 180px; text-align: left; .collectionSchema-headerMenu-group { padding: 7px 0; border-bottom: 1px solid lightgray; + cursor: pointer; &:first-child { padding-top: 0; @@ -280,7 +325,6 @@ button.add-column { background-color: white; .key-option { - //background-color: $light-color; background-color: white; border: 1px solid lightgray; padding: 2px 3px; @@ -315,12 +359,53 @@ button.add-column { } } +.altcollectionTimeView-treeView { + display: flex; + flex-direction: column; + width: 175px; + height: auto; + position: fixed; + border-left: solid 1px; + z-index: 1; + + .collectionTimeView-addfacet { + display: inline-block; + width: 200px; + height: 30px; + background: darkGray; + text-align: left; + + .collectionTimeView-button { + align-items: center; + display: flex; + width: 100%; + height: 100%; + + .collectionTimeView-span { + margin: auto; + } + } + + >div, + >div>div { + width: 100%; + height: 100%; + } + } + + .altcollectionTimeView-tree { + display: inline-block; + width: 100%; + height: calc(100% - 30px); + } +} + .collectionSchema-row { height: 100%; background-color: white; &.row-focused .rt-td { - background-color: rgb(255, 246, 246); //$light-color-secondary; + background-color: #bfffc0; //$light-color-secondary; } &.row-wrapped { @@ -333,10 +418,11 @@ button.add-column { display: flex; justify-content: space-around; flex: 50 0 auto; - width: 50px; + width: 0; max-width: 50px; height: 100%; min-height: 30px; + align-items: center; color: lightgray; background-color: white; transition: color 0.1s ease; @@ -344,6 +430,7 @@ button.add-column { .row-option { // padding: 5px; cursor: pointer; + position: absolute; transition: color 0.1s ease; display: flex; flex-direction: column; @@ -383,6 +470,9 @@ button.add-column { .collectionSchemaView-cellWrapper { height: 100%; padding: 4px; + text-align: left; + padding-left: 19px; + position: relative; &:focus { @@ -513,19 +603,26 @@ button.add-column { height: 100%; } +.rt-td.rt-expandable { + overflow:visible; + position: relative; + height:100%; + z-index: 1; +} .reactTable-sub { - padding: 10px 30px; background-color: rgb(252, 252, 252); width: 100%; + .rt-thead { - display:none; + display: none; } - .collectionSchemaView-table{ + + .collectionSchemaView-table { border: solid 1px; overflow: hidden; } - + .row-dragger { background-color: rgb(252, 252, 252); @@ -543,13 +640,16 @@ button.add-column { .collectionSchemaView-expander { height: 100%; min-height: 30px; - position: relative; + position: absolute; color: gray; + width: 20; + height: auto; + left: 55; svg { position: absolute; top: 50%; - left: 50%; + left: 10; transform: translate(-50%, -50%); } } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index a003de0d3..257099783 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -7,11 +7,11 @@ import { observer } from "mobx-react"; import Measure from "react-measure"; import { Resize } from "react-table"; import "react-table/react-table.css"; -import { Doc } from "../../../fields/Doc"; +import { Doc, Opt } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; -import { Cast, NumCast } from "../../../fields/Types"; +import { Cast, NumCast, BoolCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; import { emptyFunction, returnFalse, returnOne, returnZero, setupMoveUpEvents } from "../../../Utils"; import { SnappingManager } from "../../util/SnappingManager"; @@ -20,10 +20,12 @@ import { undoBatch } from "../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from '../../views/globalCssVariables.scss'; import '../DocumentDecorations.scss'; import { ContentFittingDocumentView } from "../nodes/ContentFittingDocumentView"; -import { KeysDropdown } from "./CollectionSchemaHeaders"; import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; import { SchemaTable } from "./SchemaTable"; +import { SelectionManager } from "../../util/SelectionManager"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; library.add(faCog, faPlus, faSortUp, faSortDown); library.add(faTable); @@ -62,8 +64,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @observable _menuWidth = 0; @observable _headerOpen = false; - @observable _isOpen = false; - @observable _node: HTMLDivElement | null = null; @observable _headerIsEditing = false; @observable _col: any = ""; @observable _menuHeight = 0; @@ -71,12 +71,22 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @observable _pointerY = 0; @observable _openTypes: boolean = false; @computed get menuCoordinates() { - const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)); - const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)); + let searchx = 0; + let searchy = 0; + if (this.props.Document._searchDoc !== undefined) { + const el = document.getElementsByClassName("collectionSchemaView-searchContainer")[0]; + if (el !== undefined) { + const rect = el.getBoundingClientRect(); + searchx = rect.x; + searchy = rect.y; + } + } + const x = Math.max(0, Math.min(document.body.clientWidth - this._menuWidth, this._pointerX)) - searchx; + const y = Math.max(0, Math.min(document.body.clientHeight - this._menuHeight, this._pointerY)) - searchy; return this.props.ScreenToLocalTransform().transformPoint(x, y); } - @observable scale = this.props.ScreenToLocalTransform().Scale; + @computed get scale() { return this.props.ScreenToLocalTransform().Scale; } @computed get columns() { return Cast(this.props.Document._schemaHeaders, listSpec(SchemaHeaderField), []); @@ -101,31 +111,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { } @computed get possibleKeys() { return this.documentKeys.filter(key => this.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); } - - componentDidMount() { - document.addEventListener("pointerdown", this.detectClick); - } - - componentWillUnmount() { - document.removeEventListener("pointerdown", this.detectClick); - } - @action setHeaderIsEditing = (isEditing: boolean) => this._headerIsEditing = isEditing; - detectClick = (e: PointerEvent): void => { - if (this._node && this._node.contains(e.target as Node)) { - } else { - this._isOpen = false; - this.setHeaderIsEditing(false); - this.closeHeader(); - } - } - - @action - toggleIsOpen = (): void => { - this._isOpen = !this._isOpen; - this.setHeaderIsEditing(this._isOpen); - } @action changeColumnType = (type: ColumnType, col: any): void => { @@ -178,16 +165,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { this.columns = columns; } - @action - setNode = (node: HTMLDivElement): void => { - node && (this._node = node); - } - - @action - typesDropdownChange = (bool: boolean) => { - this._openTypes = bool; - } - renderTypes = (col: any) => { if (columnTypes.get(col.heading)) return (null); @@ -251,10 +228,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { type === ColumnType.Date ? dateType : imageType; return ( - <div className="collectionSchema-headerMenu-group"> - <div onClick={() => this.typesDropdownChange(!this._openTypes)}> - <label>Column type:</label> - <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right" }} /> + <div className="collectionSchema-headerMenu-group" onClick={action(() => this._openTypes = !this._openTypes)}> + <div> + <label style={{ cursor: "pointer" }}>Column type:</label> + <FontAwesomeIcon icon={"caret-down"} size="lg" style={{ float: "right", transform: `rotate(${this._openTypes ? "180deg" : 0})`, transition: "0.2s all ease" }} /> </div> {this._openTypes ? allColumnTypes : justColType} </div > @@ -336,7 +313,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { else { this.props.Document._docFilters = undefined; if (this.props.Document.selectedDoc !== undefined) { - const doc = Cast(this.props.Document.selectedDoc, Doc) as Doc; + const doc = Cast(this.props.Document.selectedDoc, Doc, null); doc._docFilters = undefined; } } @@ -348,7 +325,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action openHeader = (col: any, screenx: number, screeny: number) => { this._col = col; - this._headerOpen = !this._headerOpen; + this._headerOpen = true; this._pointerX = screenx; this._pointerY = screeny; } @@ -356,17 +333,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action closeHeader = () => { this._headerOpen = false; } - renderKeysDropDown = (col: any) => { - return <KeysDropdown - keyValue={col.heading} - possibleKeys={this.possibleKeys} - existingKeys={this.columns.map(c => c.heading)} - canAddNew={true} - addNew={false} - onSelect={this.changeColumns} - setIsEditing={this.setHeaderIsEditing} - />; - } + @undoBatch @action @@ -390,7 +357,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action onHeaderClick = (e: React.PointerEvent) => { - this.props.active(true); e.stopPropagation(); } @@ -405,12 +371,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @computed get renderMenuContent() { TraceMobx(); return <div className="collectionSchema-header-menuOptions"> - <div className="collectionSchema-headerMenu-group"> - <label>Key:</label> - {this.renderKeysDropDown(this._col)} - </div> {this.renderTypes(this._col)} - {this.renderSorting(this._col)} + {/* {this.renderSorting(this._col)} */} {this.renderColors(this._col)} <div className="collectionSchema-headerMenu-group"> <button onClick={() => { this.deleteColumn(this._col.heading); }} @@ -424,11 +386,14 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { super.CreateDropTarget(ele); } - isFocused = (doc: Doc): boolean => this.props.isSelected() && doc === this._focusedTable; + isFocused = (doc: Doc, outsideReaction: boolean): boolean => this.props.isSelected(outsideReaction) && doc === this._focusedTable; @action setFocused = (doc: Doc) => this._focusedTable = doc; - @action setPreviewDoc = (doc: Doc) => this.previewDoc = doc; + @action setPreviewDoc = (doc: Opt<Doc>) => { + SelectionManager.SelectSchemaDoc(this, doc); + this.previewDoc = doc; + } //toggles preview side-panel of schema @action @@ -533,6 +498,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { documentKeys={this.documentKeys} headerIsEditing={this._headerIsEditing} openHeader={this.openHeader} + onClick={e => { e.stopPropagation(); this.closeHeader(); }} onPointerDown={this.onTablePointerDown} onResizedChange={this.onResizedChange} setColumns={this.setColumns} @@ -554,15 +520,29 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { </div> </div>; } + onSpecificMenu = (e: React.MouseEvent) => { + if ((e.target as any)?.className?.includes?.("collectionSchemaView-cell") || (e.target instanceof HTMLSpanElement)) { + const cm = ContextMenu.Instance; + const options = cm.findByDescription("Options..."); + const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; + optionItems.push({ description: "remove", event: () => this.previewDoc && this.props.removeDocument(this.previewDoc), icon: "trash" }); + !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "compass" }); + cm.displayMenu(e.clientX, e.clientY); + (e.nativeEvent as any).SchemaHandled = true; // not sure why this is needed, but if you right-click quickly on a cell, the Document/Collection contextMenu handlers still fire without this. + e.stopPropagation(); + } + } @action onTablePointerDown = (e: React.PointerEvent): void => { + if (!(e.target as any)?.className?.includes?.("collectionSchemaView-cell") && !(e.target instanceof HTMLSpanElement)) { + this.setPreviewDoc(undefined); + } this.setFocused(this.props.Document); if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey && this.props.isSelected(true)) { e.stopPropagation(); } - this._pointerY = e.screenY; - this._pointerX = e.screenX; + // this.closeHeader(); } onResizedChange = (newResized: Resize[], event: any) => { @@ -611,14 +591,19 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { onKeyPress = (e: React.KeyboardEvent<HTMLInputElement>) => { } render() { + let name = "collectionSchemaView-container"; + if (this.props.Document._searchDoc !== undefined) { + name = "collectionSchemaView-searchContainer"; + } + if (!this.props.active()) setTimeout(() => this.closeHeader(), 0); TraceMobx(); const menuContent = this.renderMenuContent; - const menu = <div className="collectionSchema-header-menu" ref={this.setNode} + const menu = <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} onPointerDown={e => this.onHeaderClick(e)} style={{ - position: "fixed", background: "white", - transform: `translate(${this.menuCoordinates[0] / this.scale}px, ${this.menuCoordinates[1] / this.scale}px)` + position: "fixed", background: "white", border: "black 1px solid", + transform: `translate(${(this.menuCoordinates[0])}px, ${(this.menuCoordinates[1])}px)` }}> <Measure offset onResize={action((r: any) => { const dim = this.props.ScreenToLocalTransform().inverse().transformDirection(r.offset.width, r.offset.height); @@ -627,15 +612,16 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { {({ measureRef }) => <div ref={measureRef}> {menuContent} </div>} </Measure> </div>; - return <div className="collectionSchemaView-container" + return <div className={name} style={{ - overflow: this.props.overflow === true ? "auto" : undefined, + overflow: this.props.overflow === true ? "scroll" : undefined, backgroundColor: "white", pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, - width: this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", + width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > <div className="collectionSchemaView-tableContainer" - style={{ backgroundColor: "white", width: `calc(100% - ${this.previewWidth()}px)` }} + style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onKeyPress={this.onKeyPress} + onContextMenu={this.onSpecificMenu} onPointerDown={this.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} @@ -644,7 +630,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { </div> {this.dividerDragger} {!this.previewWidth() ? (null) : this.previewPanel} - {this._headerOpen ? menu : null} + {this._headerOpen && this.props.active() ? menu : null} </div>; } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index b27af66ba..241c64f9a 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -4,7 +4,7 @@ import { CursorProperty } from "csstype"; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import Switch from 'rc-switch'; -import { DataSym, Doc, HeightSym, WidthSym, DocListCastAsync } from "../../../fields/Doc"; +import { DataSym, Doc, HeightSym, WidthSym } from "../../../fields/Doc"; import { collectionSchema, documentSchema } from "../../../fields/documentSchemas"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; @@ -28,7 +28,6 @@ import { CollectionViewType } from "./CollectionView"; import { SnappingManager } from "../../util/SnappingManager"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; import { DocUtils } from "../../documents/Documents"; -import { MainViewNotifs } from "../MainViewNotifs"; const _global = (window /* browser */ || global /* node */) as any; type StackingDocument = makeInterface<[typeof collectionSchema, typeof documentSchema]>; @@ -48,7 +47,7 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) @computed get pivotField() { return StrCast(this.layoutDoc._pivotField); } @computed get filteredChildren() { return this.childLayoutPairs.filter(pair => pair.layout instanceof Doc && !pair.layout.hidden).map(pair => pair.layout); } @computed get xMargin() { return NumCast(this.layoutDoc._xMargin, 2 * Math.min(this.gridGap, .05 * this.props.PanelWidth())); } - @computed get yMargin() { return Math.max(this.layoutDoc._showTitle && !this.layoutDoc._showTitleHover ? 30 : 0, NumCast(this.layoutDoc._yMargin, 0)); } // 2 * this.gridGap)); } + @computed get yMargin() { return Math.max(this.layoutDoc._showTitle && !this.layoutDoc._showTitleHover ? 30 : 0, NumCast(this.layoutDoc._yMargin, 5)); } // 2 * this.gridGap)); } @computed get gridGap() { return NumCast(this.layoutDoc._gridGap, 10); } @computed get isStackingView() { return BoolCast(this.layoutDoc._columnsStack, true); } @computed get numGroupColumns() { return this.isStackingView ? Math.max(1, this.Sections.size + (this.showAddAGroup ? 1 : 0)) : 1; } @@ -299,10 +298,6 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) const srcInd = docs.indexOf(doc); docs.splice(srcInd, 1); docs.splice((targInd > srcInd ? targInd - 1 : targInd) + plusOne, 0, doc); - DocListCastAsync(docs).then(resolvedDocs => { - const pos = resolvedDocs?.findIndex(shareDoc => shareDoc.icon === "users") || 0; // hopefully find out if the sharing doc has been moved - if (MainViewNotifs.NotifsCol && pos !== -1) MainViewNotifs.NotifsCol.position = pos; - }); } else if (i < (newDocs.length / 2)) { //glr: for some reason dragged documents are duplicated if (targInd === -1) targInd = docs.length; else targInd = docs.indexOf(newDocs[0]) + 1; diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index f193a9787..ede75fba8 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -289,7 +289,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC const heading = this._heading; const style = this.props.parent; const singleColumn = style.isStackingView; - const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.props.Document._yMargin); + const columnYMargin = this.props.headingObject ? 0 : NumCast(this.props.parent.props.Document._yMargin, 5); const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; const headerEditableViewProps = { @@ -310,7 +310,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC const headingView = this.props.headingObject ? <div key={heading} className="collectionStackingView-sectionHeader" ref={this._headerRef} style={{ - marginTop: NumCast(this.props.parent.props.Document._yMargin), + marginTop: NumCast(this.props.parent.props.Document._yMargin, 5), width: (style.columnWidth) / ((uniqueHeadings.length + ((this.props.parent.props.Document._chromeStatus !== 'view-mode' && this.props.parent.props.Document._chromeStatus !== 'disabled') ? 1 : 0)) || 1) diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index d1aeb1b65..a1dd905c2 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -112,10 +112,8 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: [...this.props.docFilters(), ...Cast(this.props.Document._docFilters, listSpec("string"), [])]; } @computed get childDocs() { - let rawdocs: (Doc | Promise<Doc>)[] = DocListCast(this.props.Document._searchDocs); - - if (rawdocs.length !== 0) { - } else if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; + let rawdocs: (Doc | Promise<Doc>)[] = []; + if (this.dataField instanceof Doc) { // if collection data is just a document, then promote it to a singleton list; rawdocs = [this.dataField]; } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. rawdocs = Cast(this.dataField, listSpec(Doc), null); @@ -128,63 +126,41 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const docs = rawdocs.filter(d => !(d instanceof Promise)).map(d => d as Doc); const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); - let childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + + let searchDocs = DocListCast(this.props.Document._searchDocs); - const searchDocs = DocListCast(this.props.Document._searchDocs); - // if (searchDocs !== undefined && searchDocs.length > 0) { - // let newdocs: Doc[] = []; - // childDocs.forEach((el) => { - // searchDocs.includes(el) ? newdocs.push(el) : undefined; - // }); - // childDocs = newdocs; - // } let docsforFilter: Doc[] = childDocs; + if (searchDocs !== undefined && searchDocs.length > 0) { docsforFilter = []; - // let newdocs: Doc[] = []; - // let newarray: Doc[] = []; - //while (childDocs.length > 0) { - //newarray = []; + const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); + console.log(searchDocs); + searchDocs = DocUtils.FilterDocs(searchDocs, this.docFilters(), docRangeFilters, viewSpecScript); childDocs.forEach((d) => { if (d.data !== undefined) { - console.log(d); let newdocs = DocListCast(d.data); if (newdocs.length > 0) { - let vibecheck: boolean | undefined = undefined; + let displaycheck = false; let newarray: Doc[] = []; while (newdocs.length > 0) { newarray = []; newdocs.forEach((t) => { - if (d.data !== undefined) { - const newdocs = DocListCast(t.data); - newdocs.forEach((newdoc) => { - newarray.push(newdoc); - }); - } - if (searchDocs.includes(t)) { - vibecheck = true; - } + DocListCast(t.data).forEach((newdoc) => newarray.push(newdoc)); + displaycheck = displaycheck || searchDocs.includes(t); }); newdocs = newarray; } - if (vibecheck === true) { - docsforFilter.push(d); - } + displaycheck && docsforFilter.push(d); } } - if (searchDocs.includes(d)) { - docsforFilter.push(d); - } + searchDocs.includes(d) && docsforFilter.push(d); }); - //childDocs = newarray; - //} + return docsforFilter; } - childDocs = docsforFilter; - const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - - return this.props.Document.dontRegisterView ? docs : DocUtils.FilterDocs(docs, this.docFilters(), docRangeFilters, viewSpecScript); + return this.props.Document.dontRegisterView ? childDocs : DocUtils.FilterDocs(childDocs, this.docFilters(), docRangeFilters, viewSpecScript); } @action @@ -251,11 +227,13 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: Doc.AreProtosEqual(Cast(movedDocs[0].annotationOn, Doc, null), this.props.Document); added = docDragData.moveDocument(movedDocs, this.props.Document, canAdd ? this.addDocument : returnFalse); } else added = res; + e.stopPropagation(); } else { ScriptCast(this.props.Document.dropConverter)?.script.run({ dragData: docDragData }); added = this.addDocument(docDragData.droppedDocuments); } - added && e.stopPropagation(); + !added && alert("You cannot perform this move"); + e.stopPropagation(); return added; } else if (de.complete.annoDragData) { @@ -343,7 +321,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: let srcWeb: Doc | undefined; if (SelectionManager.SelectedDocuments().length) { srcWeb = SelectionManager.SelectedDocuments()[0].props.Document; - srcUrl = (srcWeb.data as WebField).url.href?.match(/http[s]?:\/\/[^/]*/)?.[0]; + srcUrl = (srcWeb.data as WebField).url?.href?.match(/http[s]?:\/\/[^/]*/)?.[0]; } const reg = new RegExp(Utils.prepend(""), "g"); const modHtml = srcUrl ? html.replace(reg, srcUrl) : html; @@ -351,7 +329,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: Doc.GetProto(htmlDoc)["data-text"] = Doc.GetProto(htmlDoc).text = text; this.props.addDocument(htmlDoc); if (srcWeb) { - const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")[0].contentDocument?.getSelection()?.focusNode as any); + const focusNode = (SelectionManager.SelectedDocuments()[0].ContentDiv?.getElementsByTagName("iframe")?.[0].contentDocument?.getSelection()?.focusNode as any); if (focusNode) { const rect = "getBoundingClientRect" in focusNode ? focusNode.getBoundingClientRect() : focusNode?.parentElement.getBoundingClientRect(); const x = (rect?.x || 0); @@ -481,7 +459,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: completed?.(); } else { if (text && !text.includes("https://")) { - this.addDocument(Docs.Create.TextDocument(text, { ...options, _width: 400, _height: 315 })); + UndoManager.RunInBatch(() => this.addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop"); } } disposer(); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 3c7471d7c..0f6274663 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -53,7 +53,7 @@ export interface TreeViewProps { indentDocument?: () => void; outdentDocument?: () => void; ScreenToLocalTransform: () => Transform; - backgroundColor?: (doc: Doc) => string | undefined; + backgroundColor?: (doc: Doc, renderDepth: number) => string | undefined; outerXf: () => { translateX: number, translateY: number }; treeViewDoc: Doc; parentKey: string; @@ -88,7 +88,7 @@ class TreeView extends React.Component<TreeViewProps> { get doc() { return this.props.document; } get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); } get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive - get defaultExpandedView() { return this.childDocs ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); } + get defaultExpandedView() { return this.childDocs.length ? this.fieldKey : StrCast(this.doc.defaultExpandedView, this.noviceMode ? "layout" : "fields"); } @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state set treeViewOpen(c: boolean) { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; @@ -108,6 +108,7 @@ class TreeView extends React.Component<TreeViewProps> { } @computed get childDocs() { return this.childDocList(this.fieldKey); } @computed get childLinks() { return this.childDocList("links"); } + @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } @computed get boundsOfCollectionDocument() { return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 || !DocListCast(this.props.document[this.fieldKey]).length ? undefined : Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey])); @@ -127,7 +128,7 @@ class TreeView extends React.Component<TreeViewProps> { constructor(props: any) { super(props); - const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); selectDoc(self);} `); + const script = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); this._editTitleScript = script && (() => script); if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false); } @@ -313,11 +314,11 @@ class TreeView extends React.Component<TreeViewProps> { @computed get renderContent() { TraceMobx(); const expandKey = this.treeViewExpandedView; - if (["links", this.fieldKey].includes(expandKey)) { + if (["links", "annotations", this.fieldKey].includes(expandKey)) { const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey); const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true), true); - const docs = expandKey === "links" ? this.childLinks : this.childDocs; + const docs = expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; const sortKey = `${this.fieldKey}-sortAscending`; return <ul key={expandKey + "more"} onClick={(e) => { this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true)); @@ -397,7 +398,7 @@ class TreeView extends React.Component<TreeViewProps> { title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} onClick={this.bulletClick} style={{ color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), opacity: checked === "unchecked" ? undefined : 0.4 }}> - {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down"))} />} + {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs?.length ? "caret-square-right" : "caret-right") : (this.childDocs?.length ? "caret-square-down" : "caret-down"))} />} </div>; } @@ -424,7 +425,8 @@ class TreeView extends React.Component<TreeViewProps> { this.doc.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode ? "layout" : "fields") : this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" : this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" : - this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields"); + (this.treeViewExpandedView === "links" || this.treeViewExpandedView === "layout") && DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : + this.childDocs.length ? this.fieldKey : (Doc.UserDoc().noviceMode ? "layout" : "fields"); } this.treeViewOpen = true; })}> @@ -453,6 +455,7 @@ class TreeView extends React.Component<TreeViewProps> { NativeHeight={returnZero} NativeWidth={returnZero} contextMenuItems={this.contextMenuItems} + opacity={returnOne} renderDepth={1} focus={returnTrue} parentActive={returnTrue} @@ -466,7 +469,7 @@ class TreeView extends React.Component<TreeViewProps> { return <> <div className="docContainer" ref={this._tref} title="click to edit title" id={`docContainer-${this.props.parentKey}`} style={{ - fontWeight: this.doc.searchMatch ? "bold" : undefined, + fontWeight: this.doc.searchMatch !== undefined ? "bold" : undefined, textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined, outline: BoolCast(this.doc.workspaceBrush) ? "dashed 1px #06123232" : undefined, pointerEvents: this.props.active() || SnappingManager.GetIsDragging() ? undefined : "none" @@ -475,7 +478,7 @@ class TreeView extends React.Component<TreeViewProps> { </div > {headerElements} <div className="treeViewItem-openRight" onClick={this.openRight}> - <FontAwesomeIcon title="open in pane on right" icon="external-link-alt" size="sm" /> + <FontAwesomeIcon title="open in a new pane" icon="external-link-alt" size="sm" /> </div> </>; } @@ -494,7 +497,7 @@ class TreeView extends React.Component<TreeViewProps> { } } } else this._editMaxWidth = ""; - return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active() && SelectionManager.DeselectAll()}> + return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}> <li className="collection-child"> <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => { if (this.props.active(true)) { @@ -533,7 +536,7 @@ class TreeView extends React.Component<TreeViewProps> { dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => boolean, pinToPres: (document: Doc) => void, - backgroundColor: undefined | ((document: Doc) => string | undefined), + backgroundColor: undefined | ((document: Doc, renderDepth: number) => string | undefined), screenToLocalXf: () => Transform, outerXf: () => { translateX: number, translateY: number }, active: (outsideReaction?: boolean) => boolean, @@ -813,7 +816,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}> <div className="collectionTreeView-dropTarget" id="body" style={{ - background: this.props.backgroundColor?.(this.doc), + background: this.props.backgroundColor?.(this.doc, this.props.renderDepth), paddingLeft: `${NumCast(this.doc._xPadding, 10)}px`, paddingRight: `${NumCast(this.doc._xPadding, 10)}px`, paddingTop: `${NumCast(this.doc._yPadding, 20)}px`, @@ -866,6 +869,7 @@ Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey }); const facetValueDocSet = (nonNumbers / facetValues.length > .1 ? facetValues.sort() : facetValues.sort((n1: string, n2: string) => Number(n1) - Number(n2))).map(facetValue => { const doc = new Doc(); + doc.system = true; doc.title = facetValue.toString(); doc.treeViewChecked = ComputedField.MakeFunction("determineCheckedState(layoutDoc, facetHeader, facetValue)", {}, { layoutDoc, facetHeader, facetValue }); return doc; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 0feec3fbd..0aaceb7f4 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -46,6 +46,7 @@ import { CollectionTimeView } from './CollectionTimeView'; import { CollectionTreeView } from "./CollectionTreeView"; import './CollectionView.scss'; import { ContextMenuProps } from '../ContextMenuItem'; +import { table } from 'console'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -68,7 +69,7 @@ export enum CollectionViewType { Carousel = "carousel", Carousel3D = "3D Carousel", Linear = "linear", - Staff = "staff", + //Staff = "staff", Map = "map", Grid = "grid", Pile = "pileup" @@ -138,10 +139,13 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus } const docs = doc instanceof Doc ? [doc] : doc; + + + if (docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document))) return false; 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); + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); if (added.length) { if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { @@ -176,12 +180,15 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus const pushpinLink = DocUtils.MakeLink({ doc: pushpin }, { doc: doc }, "pushpin", ""); doc.displayTimecode = undefined; } + doc._stayInCollection = 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] as List<Doc>).push(...added); targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); + const lastModified = "lastModified"; + targetDataDoc[lastModified] = new DateField(new Date(Date.now())); } } } @@ -190,16 +197,18 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus @action.bound removeDocument = (doc: any): boolean => { - const collectionEffectiveAcl = GetEffectiveAcl(this.props.Document); - const docEffectiveAcl = GetEffectiveAcl(doc); - // you can remove the document if you either have Admin/Edit access to the collection or to the specific document - if (collectionEffectiveAcl === AclEdit || collectionEffectiveAcl === AclAdmin || docEffectiveAcl === AclAdmin || docEffectiveAcl === AclEdit) { + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); + if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { const docs = doc instanceof Doc ? [doc] : doc as Doc[]; const targetDataDoc = this.props.Document[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); const toRemove = value.filter(v => docs.includes(v)); if (toRemove.length !== 0) { - toRemove.forEach(doc => Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc)); + const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; + toRemove.forEach(doc => { + Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc); + recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); + }); return true; } } @@ -216,7 +225,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return true; } const first = doc instanceof Doc ? doc : doc[0]; - if (!first?.stayInCollection && addDocument !== returnFalse) { + if (!first?._stayInCollection && addDocument !== returnFalse) { if (UndoManager.RunInTempBatch(() => this.removeDocument(doc))) { const added = addDocument(doc); if (!added) UndoManager.UndoTempBatch(); @@ -247,7 +256,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus case CollectionViewType.Schema: return (<CollectionSchemaView key="collview" {...props} />); case CollectionViewType.Docking: return (<CollectionDockingView key="collview" {...props} />); case CollectionViewType.Tree: return (<CollectionTreeView key="collview" {...props} />); - case CollectionViewType.Staff: return (<CollectionStaffView key="collview" {...props} />); + //case CollectionViewType.Staff: return (<CollectionStaffView key="collview" {...props} />); case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />); case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />); case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); } @@ -280,7 +289,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" }); subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking), icon: "ellipsis-v" }); subItems.push({ description: "Stacking (AutoHeight)", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" }); - subItems.push({ description: "Staff", event: () => func(CollectionViewType.Staff), icon: "music" }); subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" }); subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" }); subItems.push({ description: "Masonry", event: () => func(CollectionViewType.Masonry), icon: "columns" }); @@ -474,6 +482,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet); } else { newFacet = new Doc(); + newFacet.sytem = true; newFacet.title = facetHeader; newFacet.treeViewOpen = true; newFacet.type = DocumentType.COL; @@ -510,56 +519,56 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus </label>)} </div> ); - return !this._facetWidth || this.props.dontRegisterView ? (null) : - <div className="collectionTimeView-treeView" style={{ width: `${this.facetWidth()}px`, overflow: this.facetWidth() < 15 ? "hidden" : undefined }}> - <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> - <div className="collectionTimeView-button"> - <FontAwesomeIcon icon={faEdit} size={"lg"} /> - <span className="collectionTimeView-span">Facet Filters</span> - </div> - </Flyout> - </div> - <div className="collectionTimeView-tree" key="tree"> - <CollectionTreeView - PanelPosition={""} - Document={facetCollection} - DataDoc={facetCollection} - fieldKey={`${this.props.fieldKey}-filter`} - CollectionView={this} - docFilters={returnEmptyFilter} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - ContainingCollectionView={this.props.ContainingCollectionView} - PanelWidth={this.facetWidth} - PanelHeight={this.props.PanelHeight} - NativeHeight={returnZero} - NativeWidth={returnZero} - LibraryPath={emptyPath} - rootSelected={this.props.rootSelected} - renderDepth={1} - dropAction={this.props.dropAction} - ScreenToLocalTransform={this.props.ScreenToLocalTransform} - addDocTab={returnFalse} - pinToPres={returnFalse} - isSelected={returnFalse} - select={returnFalse} - bringToFront={emptyFunction} - active={this.props.active} - whenActiveChanged={returnFalse} - treeViewHideTitle={true} - ContentScaling={returnOne} - focus={returnFalse} - treeViewHideHeaderFields={true} - onCheckedClick={this.scriptField} - ignoreFields={this.ignoreFields} - annotationsKey={""} - dontRegisterView={true} - backgroundColor={this.filterBackground} - moveDocument={returnFalse} - removeDocument={returnFalse} - addDocument={returnFalse} /> - </div> - </div>; + + return !this._facetWidth || this.props.dontRegisterView ? (null) : <div className="collectionTimeView-treeView" style={{ width: `${this.facetWidth()}px`, overflow: this.facetWidth() < 15 ? "hidden" : undefined }}> + <div className="collectionTimeView-addFacet" style={{ width: `${this.facetWidth()}px` }} onPointerDown={e => e.stopPropagation()}> + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> + <div className="collectionTimeView-button"> + <FontAwesomeIcon icon={faEdit} size={"lg"} /> + <span className="collectionTimeView-span">Facet Filters</span> + </div> + </Flyout> + </div> + <div className="collectionTimeView-tree" key="tree"> + <CollectionTreeView + PanelPosition={""} + Document={facetCollection} + DataDoc={facetCollection} + fieldKey={`${this.props.fieldKey}-filter`} + CollectionView={this} + docFilters={returnEmptyFilter} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + ContainingCollectionView={this.props.ContainingCollectionView} + PanelWidth={this.facetWidth} + PanelHeight={this.props.PanelHeight} + NativeHeight={returnZero} + NativeWidth={returnZero} + LibraryPath={emptyPath} + rootSelected={this.props.rootSelected} + renderDepth={1} + dropAction={this.props.dropAction} + ScreenToLocalTransform={this.props.ScreenToLocalTransform} + addDocTab={returnFalse} + pinToPres={returnFalse} + isSelected={returnFalse} + select={returnFalse} + bringToFront={emptyFunction} + active={this.props.active} + whenActiveChanged={returnFalse} + treeViewHideTitle={true} + ContentScaling={returnOne} + focus={returnFalse} + treeViewHideHeaderFields={true} + onCheckedClick={this.scriptField} + ignoreFields={this.ignoreFields} + annotationsKey={""} + dontRegisterView={true} + backgroundColor={this.filterBackground} + moveDocument={returnFalse} + removeDocument={returnFalse} + addDocument={returnFalse} /> + </div> + </div>; } childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx index 4c8cac3ed..149d4927b 100644 --- a/src/client/views/collections/ParentDocumentSelector.tsx +++ b/src/client/views/collections/ParentDocumentSelector.tsx @@ -15,6 +15,7 @@ import { faCog, faChevronCircleUp } from "@fortawesome/free-solid-svg-icons"; import { library } from "@fortawesome/fontawesome-svg-core"; import { DocumentView } from "../nodes/DocumentView"; import { SelectionManager } from "../../util/SelectionManager"; +import { Tooltip } from "@material-ui/core"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -121,16 +122,17 @@ export class DockingViewButtonSelector extends React.Component<{ views: () => Do } render() { - return <span title="Tap for menu, drag tab as document" - onPointerDown={e => { + return <Tooltip title={<><div className="dash-tooltip">Tap for toolbar, drag to create alias in another pane</div></>} placement="bottom"> + <span onPointerDown={e => { if (getComputedStyle(this._ref.current!).width !== "100%") { e.stopPropagation(); e.preventDefault(); } this.props.views()[0]?.select(false); }} className="buttonSelector"> - <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}> - <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} /> - </Flyout> - </span>; + <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={this.flyout} stylesheet={this.customStylesheet}> + <FontAwesomeIcon icon={"arrows-alt"} size={"sm"} /> + </Flyout> + </span> + </Tooltip>; } } diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index a974c5496..6ec9783e2 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -63,18 +63,19 @@ export interface SchemaTableProps { addDocument: (document: Doc | Doc[]) => boolean; moveDocument: (document: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; ScreenToLocalTransform: () => Transform; - active: (outsideReaction: boolean) => boolean; + active: (outsideReaction: boolean | undefined) => boolean; onDrop: (e: React.DragEvent<Element>, options: DocumentOptions, completed?: (() => void) | undefined) => void; addDocTab: (document: Doc, where: string) => boolean; pinToPres: (document: Doc) => void; isSelected: (outsideReaction?: boolean) => boolean; - isFocused: (document: Doc) => boolean; + isFocused: (document: Doc, outsideReaction: boolean) => boolean; setFocused: (document: Doc) => void; - setPreviewDoc: (document: Doc) => void; + setPreviewDoc: (document: Opt<Doc>) => void; columns: SchemaHeaderField[]; documentKeys: any[]; headerIsEditing: boolean; openHeader: (column: any, screenx: number, screeny: number) => void; + onClick: (e: React.MouseEvent) => void; onPointerDown: (e: React.PointerEvent) => void; onResizedChange: (newResized: Resize[], event: any) => void; setColumns: (columns: SchemaHeaderField[]) => void; @@ -155,30 +156,36 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const possibleKeys = this.props.documentKeys.filter(key => this.props.columns.findIndex(existingKey => existingKey.heading.toUpperCase() === key.toUpperCase()) === -1); const columns: Column<Doc>[] = []; - const tableIsFocused = this.props.isFocused(this.props.Document); + const tableIsFocused = this.props.isFocused(this.props.Document, false); const focusedRow = this._focusedCell.row; const focusedCol = this._focusedCell.col; const isEditable = !this.props.headerIsEditing; - if (this.childDocs.reduce((found, doc) => found || doc.type === DocumentType.COL, false)) { - columns.push( - { - expander: true, - Header: "", - width: 30, - Expander: (rowInfo) => { - if (rowInfo.original.type === "collection") { - if (rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onCloseCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-up"} size="sm" /></div>; - if (!rowInfo.isExpanded) return <div className="collectionSchemaView-expander" onClick={() => this.onExpandCollection(rowInfo.original)}><FontAwesomeIcon icon={"sort-down"} size="sm" /></div>; - } else { - return null; - } + //if (this.childDocs.reduce((found, doc) => found || doc.type === DocumentType.COL, false)) { + columns.push( + { + expander: true, + Header: "", + width: 60, + Expander: (rowInfo) => { + if (rowInfo.original.type === "collection") { + return rowInfo.isExpanded ? + <div className="collectionSchemaView-expander" onClick={() => this.onCloseCollection(rowInfo.original)}><FontAwesomeIcon icon={"caret-down"} size="lg" /></div> : + <div className="collectionSchemaView-expander" onClick={() => this.onExpandCollection(rowInfo.original)}><FontAwesomeIcon icon={"caret-right"} size="lg" /></div>; + } else { + return null; } } - ); - } + } + ); + // } + this.props.active; const cols = this.props.columns.map(col => { + const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : + this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : + this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : + this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; const keysDropdown = <KeysDropdown keyValue={col.heading} @@ -189,24 +196,20 @@ export class SchemaTable extends React.Component<SchemaTableProps> { onSelect={this.props.changeColumns} setIsEditing={this.props.setHeaderIsEditing} docs={this.props.childDocs} + Document={this.props.Document} + dataDoc={this.props.dataDoc} + fieldKey={this.props.fieldKey} + ContainingCollectionDoc={this.props.ContainingCollectionDoc} + ContainingCollectionView={this.props.ContainingCollectionView} + active={this.props.active} + openHeader={this.props.openHeader} + icon={icon} + col={col} // try commenting this out width={"100%"} />; - const icon: IconProp = this.getColumnType(col) === ColumnType.Number ? "hashtag" : this.getColumnType(col) === ColumnType.String ? "font" : - this.getColumnType(col) === ColumnType.Boolean ? "check-square" : this.getColumnType(col) === ColumnType.Doc ? "file" : - this.getColumnType(col) === ColumnType.Image ? "image" : this.getColumnType(col) === ColumnType.List ? "list-ul" : - this.getColumnType(col) === ColumnType.Date ? "calendar" : "align-justify"; - const headerText = this._showTitleDropdown ? keysDropdown : <div - onClick={this.changeTitleMode} - style={{ - background: col.color, padding: "2px", - letterSpacing: "2px", - textTransform: "uppercase", - display: "flex" - }}> - {col.heading}</div>; const sortIcon = col.desc === undefined ? "caret-right" : col.desc === true ? "caret-down" : "caret-up"; @@ -218,24 +221,17 @@ export class SchemaTable extends React.Component<SchemaTableProps> { background: col.color, padding: "2px", display: "flex", cursor: "default", height: "100%", }}> - <FontAwesomeIcon icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px" }} /> - {/* <div className="keys-dropdown" - style={{ display: "inline", zIndex: 1000 }}> */} + {/* <FontAwesomeIcon onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} icon={icon} size="lg" style={{ display: "inline", paddingBottom: "1px", paddingTop: "4px", cursor: "hand" }} /> */} {keysDropdown} - {/* </div> */} <div onClick={e => this.changeSorting(col)} - style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit" }}> + style={{ width: 21, padding: 1, display: "inline", zIndex: 1, background: "inherit", cursor: "hand" }}> <FontAwesomeIcon icon={sortIcon} size="lg" /> </div> - {/* <div onClick={e => this.props.openHeader(col, e.clientX, e.clientY)} - style={{ float: "right", paddingRight: "6px", zIndex: 1, background: "inherit" }}> - <FontAwesomeIcon icon={"compass"} size="sm" /> - </div> */} </div>; return { Header: <MovableColumn columnRenderer={header} columnValue={col} allColumns={this.props.columns} reorderColumns={this.props.reorderColumns} ScreenToLocalTransform={this.props.ScreenToLocalTransform} />, - accessor: (doc: Doc) => doc ? doc[col.heading] : 0, + accessor: (doc: Doc) => doc ? Field.toString(doc[col.heading] as Field) : 0, id: col.heading, Cell: (rowProps: CellInfo) => { const rowIndex = rowProps.index; @@ -318,27 +314,6 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } - - @action - nextHighlight = (e: React.MouseEvent, doc: Doc) => { - e.preventDefault(); - e.stopPropagation(); - doc.searchMatch = false; - console.log(doc.searchMatch); - setTimeout(() => doc.searchMatch = true, 0); - console.log(doc.searchMatch); - - doc.searchIndex = NumCast(doc.searchIndex); - } - - @action - nextHighlight2 = (doc: Doc) => { - - doc.searchMatchAlt = false; - setTimeout(() => doc.searchMatchAlt = true, 0); - doc.searchIndex = NumCast(doc.searchIndex); - } - constructor(props: SchemaTableProps) { super(props); // convert old schema columns (list of strings) into new schema columns (list of schema header fields) @@ -347,7 +322,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const newSchemaHeaders = oldSchemaHeaders.map(i => typeof i === "string" ? new SchemaHeaderField(i, "#f1efeb") : i); this.props.Document._schemaHeaders = new List<SchemaHeaderField>(newSchemaHeaders); } else if (this.props.Document._schemaHeaders === undefined) { - this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb")]); + this.props.Document._schemaHeaders = new List<SchemaHeaderField>([new SchemaHeaderField("title", "#f1efeb"), new SchemaHeaderField("author", "#f1efeb"), new SchemaHeaderField("*lastModified", "#f1efeb", ColumnType.Date), + new SchemaHeaderField("text", "#f1efeb", ColumnType.String), new SchemaHeaderField("type", "#f1efeb"), new SchemaHeaderField("context", "#f1efeb", ColumnType.Doc)]); } } @@ -369,7 +345,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { addDoc: this.tableAddDoc, removeDoc: this.props.deleteDocument, rowInfo, - rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document), + rowFocused: !this.props.headerIsEditing && rowInfo.index === this._focusedCell.row && this.props.isFocused(this.props.Document, true), textWrapRow: this.toggleTextWrapRow, rowWrapped: this.textWrappedRows.findIndex(id => rowInfo.original[Id] === id) > -1, dropAction: StrCast(this.props.Document.childDropAction), @@ -383,7 +359,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { const row = rowInfo.index; //@ts-ignore const col = this.columns.map(c => c.heading).indexOf(column!.id); - const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document); + const isFocused = this._focusedCell.row === row && this._focusedCell.col === col && this.props.isFocused(this.props.Document, true); // TODO: editing border doesn't work :( return { style: { @@ -403,12 +379,16 @@ export class SchemaTable extends React.Component<SchemaTableProps> { @action onKeyDown = (e: KeyboardEvent): void => { - if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document)) {// && this.props.isSelected(true)) { + if (!this._cellIsEditing && !this.props.headerIsEditing && this.props.isFocused(this.props.Document, true)) {// && this.props.isSelected(true)) { const direction = e.key === "Tab" ? "tab" : e.which === 39 ? "right" : e.which === 37 ? "left" : e.which === 38 ? "up" : e.which === 40 ? "down" : ""; this._focusedCell = this.changeFocusedCellByDirection(direction, this._focusedCell.row, this._focusedCell.col); const pdoc = FieldValue(this.childDocs[this._focusedCell.row]); pdoc && this.props.setPreviewDoc(pdoc); + e.stopPropagation(); + } else if (e.keyCode === 27) { + this.props.setPreviewDoc(undefined); + e.stopPropagation(); // stopPropagation for left/right arrows } } @@ -432,9 +412,10 @@ export class SchemaTable extends React.Component<SchemaTableProps> { } @undoBatch - createRow = () => { + createRow = action(() => { this.props.addDocument(Docs.Create.TextDocument("", { title: "", _width: 100, _height: 30 })); - } + this._focusedCell = { row: this.childDocs.length, col: this._focusedCell.col }; + }); @undoBatch @action @@ -586,10 +567,8 @@ export class SchemaTable extends React.Component<SchemaTableProps> { setComputed = (script: string, doc: Doc, field: string, row: number, col: number): boolean => { script = `const $ = (row:number, col?:number) => { - if(col === undefined) { - return (doc as any)[key][row + ${row}]; - } - return (doc as any)[key][row + ${row}][(doc as any)._schemaHeaders[col + ${col}].heading]; + const rval = (doc as any)[key][row + ${row}]; + return col === undefined ? rval : rval[(doc as any)._schemaHeaders[col + ${col}].heading]; } return ${script}`; const compiled = CompileScript(script, { params: { this: Doc.name }, capturedVariables: { doc: this.props.Document, key: this.props.fieldKey }, typecheck: false, transformer: this.createTransformer(row, col) }); @@ -620,10 +599,11 @@ export class SchemaTable extends React.Component<SchemaTableProps> { render() { const preview = ""; - return <div className="collectionSchemaView-table" onPointerDown={this.props.onPointerDown} onWheel={e => this.props.active(true) && e.stopPropagation()} + return <div className="collectionSchemaView-table" style={{ overflow: this.props.Document._searchDoc ? undefined : "auto" }} + onPointerDown={this.props.onPointerDown} onClick={this.props.onClick} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > {this.reactTable} - {StrCast(this.props.Document.type) !== "search" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div> + {StrCast(this.props.Document._chromeStatus) !== "disabled" ? <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div> : undefined} {!this._showDoc ? (null) : <div className="collectionSchemaView-documentPreview" //onClick={() => { this.onOpenClick(); }} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2b8e949b1..f6bb375fc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -15,7 +15,7 @@ import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils } from "../../../../Utils"; +import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, Utils, setupMoveUpEvents } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; @@ -179,7 +179,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } if (this.Document.currentFrame !== undefined && !this.props.isAnnotationOverlay) { - CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentFrame); + CollectionFreeFormDocumentView.setupKeyframes(newBoxes, this.Document.currentFrame, true); } } return retVal; @@ -214,7 +214,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const layoutDoc = Doc.Layout(d); if (this.Document.currentFrame !== undefined) { const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.opacity); + CollectionFreeFormDocumentView.setValues(this.Document.currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.scroll, vals.opacity); } else { d.x = x + NumCast(d.x) - dropPos[0]; d.y = y + NumCast(d.y) - dropPos[1]; @@ -378,7 +378,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } getClusterColor = (doc: Doc) => { - let clusterColor = this.props.backgroundColor?.(doc); + let clusterColor = this.props.backgroundColor?.(doc, this.props.renderDepth + 1); const cluster = NumCast(doc.cluster); if (this.Document.useClusters) { if (this._clusterSets.length <= cluster) { @@ -605,6 +605,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P pan = (e: PointerEvent | React.Touch | { clientX: number, clientY: number }): void => { // bcz: theres should be a better way of doing these than referencing these static instances directly MarqueeOptionsMenu.Instance?.fadeOut(true);// I think it makes sense for the marquee menu to go away when panned. -syip2 + // PDFMenu.Instance.fadeOut(true); (commented out for mobile) const [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); this.setPan((this.Document._panX || 0) - dx, (this.Document._panY || 0) - dy, undefined, true); @@ -849,7 +850,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } else { const docs = this.childLayoutPairs.map(pair => pair.layout); docs.slice().sort((doc1, doc2) => NumCast(doc1.zIndex) - NumCast(doc2.zIndex)); - let zlast = docs.length ? NumCast(docs[docs.length - 1].zIndex) : 1; + let zlast = docs.length ? Math.max(docs.length, NumCast(docs[docs.length - 1].zIndex)) : 1; if (zlast - docs.length > 100) { for (let i = 0; i < docs.length; i++) doc.zIndex = i + 1; zlast = docs.length + 1; @@ -1199,27 +1200,29 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.props.addDocTab(childDocs as any as Doc, "inParent"); this.props.ContainingCollectionView?.removeDocument(this.props.Document); })); - layoutDocsInGrid = () => { - UndoManager.RunInBatch(() => { - const docs = this.childLayoutPairs; - const startX = this.Document._panX || 0; - let x = startX; - let y = this.Document._panY || 0; - let i = 0; - const width = Math.max(...docs.map(doc => NumCast(doc.layout._width))); - const height = Math.max(...docs.map(doc => NumCast(doc.layout._height))); - docs.forEach(pair => { - pair.layout.x = x; - pair.layout.y = y; - x += width + 20; - if (++i === 6) { - i = 0; - x = startX; - y += height + 20; - } - }); - }, "arrange contents"); - } + + + @undoBatch + layoutDocsInGrid = action(() => { + const docs = this.childLayoutPairs; + const startX = this.Document._panX || 0; + let x = startX; + let y = this.Document._panY || 0; + let i = 0; + const width = Math.max(...docs.map(doc => NumCast(doc.layout._width))); + const height = Math.max(...docs.map(doc => NumCast(doc.layout._height))); + docs.forEach(pair => { + pair.layout.x = x; + pair.layout.y = y; + x += width + 20; + if (++i === 6) { + i = 0; + x = startX; + y += height + 20; + } + }); + }); + @undoBatch @action toggleNativeDimensions = () => { @@ -1312,7 +1315,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } @action - setupDragLines = () => { + setupDragLines = (snapToDraggedDoc: boolean = false) => { const activeDocs = this.getActiveDocuments(); if (activeDocs.length > 50) { DragManager.SetSnapLines([], []); @@ -1334,7 +1337,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const horizLines: number[] = []; const vertLines: number[] = []; - snappableDocs.filter(doc => !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { + snappableDocs.filter(doc => snapToDraggedDoc || !DragManager.docsBeingDragged.includes(Cast(doc.rootDocument, Doc, null) || doc)).forEach(doc => { const { left, top, width, height } = docDims(doc); const topLeftInScreen = this.getTransform().inverse().transformPoint(left, top); const docSize = this.getTransform().inverse().transformDirection(width, height); @@ -1346,7 +1349,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } onPointerOver = (e: React.PointerEvent) => { if (SnappingManager.GetIsDragging()) { - this.setupDragLines(); + this.setupDragLines(e.ctrlKey || e.shiftKey); } e.stopPropagation(); } @@ -1494,8 +1497,123 @@ interface CollectionFreeFormViewPannableContentsProps { @observer class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{ + @observable private _drag: string = ''; + + //Adds event listener so knows pointer is down and moving + onPointerDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + const corner = e.target as any; + console.log(corner.id); + if (corner) this._drag = corner.id; + const rect = document.getElementById(this._drag); + if (rect) { + console.log(this._drag); + setupMoveUpEvents(e.target, e, this.onPointerMove, (e) => { }, (e) => { }); + } + } + + //Removes all event listeners + onPointerUp = (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + this._drag = ""; + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + } + + //Adjusts the value in NodeStore + @action + onPointerMove = (e: PointerEvent) => { + const doc = document.getElementById('resizable'); + const rect = doc!.getBoundingClientRect(); + const toNumber = (original: number, delta: number): number => { + return original + (delta * this.props.zoomScaling()); + }; + if (doc) { + const height = doc.offsetHeight; + const width = doc.offsetWidth; + const top = doc.offsetTop; + const left = doc.offsetLeft; + switch (this._drag) { + case "": break; + case "resizer-br": + doc.style.width = toNumber(width, e.movementX) + 'px'; + doc.style.height = toNumber(height, e.movementY) + 'px'; + break; + case "resizer-bl": + doc.style.width = toNumber(width, -e.movementX) + 'px'; + doc.style.height = toNumber(height, e.movementY) + 'px'; + doc.style.left = toNumber(left, e.movementX) + 'px'; + break; + case "resizer-tr": + doc.style.width = toNumber(width, -e.movementX) + 'px'; + doc.style.height = toNumber(height, -e.movementY) + 'px'; + doc.style.top = toNumber(top, e.movementY) + 'px'; + case "resizer-tl": + doc.style.width = toNumber(width, -e.movementX) + 'px'; + doc.style.height = toNumber(height, -e.movementY) + 'px'; + doc.style.top = toNumber(top, e.movementY) + 'px'; + doc.style.left = toNumber(left, e.movementX) + 'px'; + case "resizable": + doc.style.top = toNumber(top, e.movementY) + 'px'; + doc.style.left = toNumber(left, e.movementX) + 'px'; + } + this.updateAll(height, width, top, left); + return false; + } + return true; + } + + @action + updateAll = (width: number, height: number, top: number, left: number) => { + const activeItem = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + this.updateList(targetDoc, activeItem["viewfinder-width-indexed"], width); + this.updateList(targetDoc, activeItem["viewfinder-height-indexed"], height); + this.updateList(targetDoc, activeItem["viewfinder-top-indexed"], top); + this.updateList(targetDoc, activeItem["viewfinder-left-indexed"], left); + } + + @action + updateList = (doc: Doc, list: any, val: number) => { + const x: List<number> = list; + if (x && x.length >= NumCast(doc.currentFrame) + 1) { + x[NumCast(doc.currentFrame)] = val; + list = x; + } else if (doc && x) { + x.length = NumCast(doc.currentFrame) + 1; + x[NumCast(doc.currentFrame)] = val; + list = x; + } + } + + // scale: NumCast(targetDoc._viewScale), + @computed get zoomProgressivizeContainer() { + const activeItem = Cast(PresBox.Instance.childDocs[PresBox.Instance.itemIndex], Doc, null); + const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); + if (activeItem && activeItem.zoomProgressivize) { + const vfLeft: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-left-indexed"]); + const vfWidth: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-width-indexed"]); + const vfTop: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-top-indexed"]); + const vfHeight: number = PresBox.Instance.checkList(targetDoc, activeItem["viewfinder-height-indexed"]); + return ( + <> + {!activeItem.editZoomProgressivize ? (null) : <div id="resizable" className="resizable" onPointerDown={this.onPointerDown} style={{ width: vfWidth, height: vfHeight, top: vfTop, left: vfLeft, position: 'absolute' }}> + <div className='resizers'> + <div id="resizer-tl" className='resizer top-left' onPointerDown={this.onPointerDown}></div> + <div id="resizer-tr" className='resizer top-right' onPointerDown={this.onPointerDown}></div> + <div id="resizer-bl" className='resizer bottom-left' onPointerDown={this.onPointerDown}></div> + <div id="resizer-br" className='resizer bottom-right' onPointerDown={this.onPointerDown}></div> + </div> + </div>} + </> + ); + } + } + @computed get zoomProgressivize() { - return PresBox.Instance && this.props.zoomProgressivize ? PresBox.Instance.zoomProgressivizeContainer : (null); + return PresBox.Instance && this.props.zoomProgressivize ? this.zoomProgressivizeContainer : (null); } @computed get progressivize() { diff --git a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx index 1ffa2fbed..a7f44bbbf 100644 --- a/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx +++ b/src/client/views/collections/collectionFreeForm/FormatShapePane.tsx @@ -9,13 +9,13 @@ 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 AntimodeMenu, { AntimodeMenuProps } from "../../AntimodeMenu"; import "./FormatShapePane.scss"; import { undoBatch } from "../../../util/UndoManager"; import { ColorState, SketchPicker } from 'react-color'; @observer -export default class FormatShapePane extends AntimodeMenu { +export default class FormatShapePane extends AntimodeMenu<AntimodeMenuProps> { static Instance: FormatShapePane; private _lastFill = "#D0021B"; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index f1df7998b..2cfe0183c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import AntimodeMenu from "../../AntimodeMenu"; +import AntimodeMenu, { AntimodeMenuProps } from "../../AntimodeMenu"; import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { unimplementedFunction } from "../../../../Utils"; @@ -7,7 +7,7 @@ import { undoBatch } from "../../../util/UndoManager"; import { Tooltip } from "@material-ui/core"; @observer -export default class MarqueeOptionsMenu extends AntimodeMenu { +export default class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { static Instance: MarqueeOptionsMenu; public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3606d5402..0918e8389 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -12,7 +12,7 @@ import { CognitiveServices } from "../../../cognitive_services/CognitiveServices import { Docs, DocumentOptions, DocUtils } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; -import { undoBatch } from "../../../util/UndoManager"; +import { undoBatch, UndoManager } from "../../../util/UndoManager"; import { ContextMenu } from "../../ContextMenu"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { PreviewCursor } from "../../PreviewCursor"; @@ -21,6 +21,7 @@ import { CollectionView } from "../CollectionView"; import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import "./MarqueeView.scss"; import React = require("react"); +import { ContextMenuItem } from "../../ContextMenuItem"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -70,23 +71,19 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque onKeyPress = (e: KeyboardEvent) => { //make textbox and add it to this collection // tslint:disable-next-line:prefer-const - let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); + const cm = ContextMenu.Instance; + const [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); if (e.key === "?") { - ContextMenu.Instance.setDefaultItem("?", (str: string) => { - const textDoc = Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { - _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, - title: "bing", UseCors: true - }); - this.props.addDocTab(textDoc, "onRight"); - }); + cm.setDefaultItem("?", (str: string) => this.props.addDocTab( + Docs.Create.WebDocument(`https://bing.com/search?q=${str}`, { _width: 200, x, y, _nativeHeight: 962, _nativeWidth: 850, isAnnotating: false, title: "bing", UseCors: true }), "onRight")); - ContextMenu.Instance.displayMenu(this._downX, this._downY); + cm.displayMenu(this._downX, this._downY); e.stopPropagation(); } else if (e.key === ":") { DocUtils.addDocumentCreatorMenuItems(this.props.addLiveTextDocument, this.props.addDocument, x, y); - ContextMenu.Instance.displayMenu(this._downX, this._downY); + cm.displayMenu(this._downX, this._downY); e.stopPropagation(); } else if (e.key === "a" && (e.ctrlKey || e.metaKey)) { e.preventDefault(); @@ -108,11 +105,12 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (br) break; } } + let ypos = y; ns.map(line => { const indent = line.search(/\S|$/); - const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: y, title: line }); + const newBox = Docs.Create.TextDocument(line, { _width: 200, _height: 35, x: x + indent / 3 * 10, y: ypos, title: line }); this.props.addDocument(newBox); - y += 40 * this.props.getTransform().Scale; + ypos += 40 * this.props.getTransform().Scale; }); })(); e.stopPropagation(); @@ -140,6 +138,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque tbox.layoutKey = "layout_" + StrCast(template.title); Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template; } + FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch"); this.props.addLiveTextDocument(tbox); e.stopPropagation(); } @@ -280,7 +279,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } else { this._downX = x; this._downY = y; - const effectiveAcl = GetEffectiveAcl(this.props.Document); + const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); if ([AclAdmin, AclEdit, AclAddonly].includes(effectiveAcl)) PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument, this.props.nudge); this.clearSelection(); } @@ -342,41 +341,33 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque @undoBatch @action delete = () => { - const recent = Cast(Doc.UserDoc().myRecentlyClosed, Doc) as Doc; const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); - - selected.map(doc => { - const effectiveAcl = GetEffectiveAcl(doc); - if (effectiveAcl === AclEdit || effectiveAcl === AclAdmin) { // deletes whatever you have the right to delete - recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); - this.props.removeDocument(doc); - } - }); + selected.forEach(doc => this.props.removeDocument(doc)); this.cleanupInteractions(false); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); } - getCollection = (selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => { - const bounds = this.Bounds; - // const inkData = this.ink ? this.ink.inkData : undefined; - const newCollection = (creator || Docs.Create.FreeformDocument)(selected, { - x: bounds.left, - y: bounds.top, - _panX: 0, - _panY: 0, - isBackground, - backgroundColor: this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined, - _width: bounds.width, - _height: bounds.height, - title: "a nested collection", - }); + getCollection = action((selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => { + const newCollection = creator ? creator(selected, { title: "nested stack", }) : ((doc: Doc) => { + Doc.GetProto(doc).data = new List<Doc>(selected); + Doc.GetProto(doc).title = "nested freeform"; + doc._panX = doc._panY = 0; + return doc; + })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); + newCollection.system = undefined; + newCollection.isBackground = isBackground; + newCollection.backgroundColor = this.props.isAnnotationOverlay ? "#00000015" : isBackground ? "cyan" : undefined; + newCollection._width = this.Bounds.width; + newCollection._height = this.Bounds.height; + newCollection.x = this.Bounds.left; + newCollection.y = this.Bounds.top; selected.forEach(d => d.context = newCollection); this.hideMarquee(); return newCollection; - } + }); @action pileup = (e: KeyboardEvent | React.PointerEvent | undefined) => { diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.scss b/src/client/views/collections/collectionFreeForm/PropertiesView.scss index aee28366a..535581f2e 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.scss +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.scss @@ -121,6 +121,19 @@ padding: 10px; margin-left: 5px; + .propertiesView-acls-checkbox { + float: right; + height: 20px; + margin-top: -20px; + margin-right: -15; + + .propertiesView-acls-checkbox-text { + font-size: 7px; + margin-top: -10px; + margin-left: 6px; + } + } + .change-buttons { display: flex; @@ -259,6 +272,7 @@ background-color: #ececec; max-height: 130px; overflow-y: scroll; + width: 92%; .propertiesView-sharingTable-item { @@ -267,7 +281,6 @@ padding: 3px; align-items: center; border-bottom: 0.5px solid grey; - cursor: pointer; &:hover .propertiesView-sharingTable-item-name { overflow-x: unset; diff --git a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx index e0f3eca44..fb138ecc0 100644 --- a/src/client/views/collections/collectionFreeForm/PropertiesView.tsx +++ b/src/client/views/collections/collectionFreeForm/PropertiesView.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { observer } from "mobx-react"; import "./PropertiesView.scss"; import { observable, action, computed, runInAction } from "mobx"; -import { Doc, Field, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt, DocCastAsync } from "../../../../fields/Doc"; +import { Doc, Field, WidthSym, HeightSym, AclSym, AclPrivate, AclReadonly, AclAddonly, AclEdit, AclAdmin, Opt, DocCastAsync, DataSym } from "../../../../fields/Doc"; import { ComputedField } from "../../../../fields/ScriptField"; import { EditableView } from "../../EditableView"; import { KeyValueBox } from "../../nodes/KeyValueBox"; @@ -49,6 +49,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @computed get MAX_EMBED_HEIGHT() { return 200; } + @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } @computed get selectedDocumentView() { if (SelectionManager.SelectedDocuments().length) { return SelectionManager.SelectedDocuments()[0]; @@ -60,7 +61,6 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { if (this.selectedDoc?.type === DocumentType.PRES) return true; return false; } - @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @computed get dataDoc() { return this.selectedDocumentView?.dataDoc; } @observable layoutFields: boolean = false; @@ -73,6 +73,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @observable openTransform: boolean = true; // @observable selectedUser: string = ""; // @observable addButtonPressed: boolean = false; + @observable layoutDocAcls: boolean = false; //Pres Trails booleans: @observable openPresTransitions: boolean = false; @@ -316,7 +317,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { */ getPermissionsSelect(user: string, permission: string) { return <select className="permissions-select" - defaultValue={permission} + value={permission} onChange={e => this.changePermissions(e, user)}> {Object.values(SharingPermissions).map(permission => { return ( @@ -344,8 +345,8 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @computed get expansionIcon() { return <Tooltip title={<div className="dash-tooltip">{"Show more permissions"}</div>}> <div className="expansion-button" onPointerDown={() => { - if (this.selectedDocumentView) { - SharingManager.Instance.open(this.selectedDocumentView); + if (this.selectedDocumentView || this.selectedDoc) { + SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc); } }}> <FontAwesomeIcon className="expansion-button-icon" icon="ellipsis-h" color="black" size="sm" /> @@ -382,15 +383,17 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { [AclAdmin, SharingPermissions.Admin] ]); - const effectiveAcl = GetEffectiveAcl(this.selectedDoc!); + const target = this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym]; + + const effectiveAcl = GetEffectiveAcl(target); const tableEntries = []; // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { - if (this.selectedDoc![AclSym]) { - for (const [key, value] of Object.entries(this.selectedDoc![AclSym])) { + if (target[AclSym]) { + for (const [key, value] of Object.entries(target[AclSym])) { const name = key.substring(4).replace("_", "."); - if (name !== Doc.CurrentUserEmail && name !== this.selectedDoc!.author/* && sidebarUsersDisplayed![name] !== false*/) { - tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value)!)); + if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public"/* && sidebarUsersDisplayed![name] !== false*/) { + tableEntries.push(this.sharingItem(name, effectiveAcl, AclMap.get(value as symbol)!)); } } } @@ -402,9 +405,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { // } // }) - // shifts the current user and the owner to the top of the doc. - tableEntries.unshift(this.sharingItem("Me", effectiveAcl, Doc.CurrentUserEmail === this.selectedDoc!.author ? "Owner" : StrCast(this.selectedDoc![`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]))); - if (Doc.CurrentUserEmail !== this.selectedDoc!.author) tableEntries.unshift(this.sharingItem(StrCast(this.selectedDoc!.author), effectiveAcl, "Owner")); + // shifts the current user, owner, public to the top of the doc. + tableEntries.unshift(this.sharingItem("Public", effectiveAcl, (AclMap.get(target[AclSym]?.["ACL-Public"]) || SharingPermissions.None))); + tableEntries.unshift(this.sharingItem("Me", effectiveAcl, Doc.CurrentUserEmail === target.author ? "Owner" : AclMap.get(effectiveAcl)!)); + if (Doc.CurrentUserEmail !== target.author) tableEntries.unshift(this.sharingItem(StrCast(target.author), effectiveAcl, "Owner")); return <div className="propertiesView-sharingTable"> {tableEntries} @@ -866,6 +870,14 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { </div> {!this.openSharing ? (null) : <div className="propertiesView-sharing-content"> + <div className="propertiesView-acls-checkbox"> + <Checkbox + color="primary" + onChange={action(() => this.layoutDocAcls = !this.layoutDocAcls)} + checked={this.layoutDocAcls} + />; + <div className="propertiesView-acls-checkbox-text">Layout</div> + </div> {this.sharingTable} {/* <div className="change-buttons"> <button @@ -963,22 +975,22 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } if (this.isPres) { const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0; - return <div className="propertiesView"> - <div className="propertiesView-title"> + return <div className="propertiesView" style={{ width: this.props.width }}> + <div className="propertiesView-title" style={{ width: this.props.width }}> Presentation </div> <div className="propertiesView-name"> {this.editableTitle} <div className="propertiesView-presSelected"> - {PresBox.Instance._selectedArray.length} selected + {PresBox.Instance?._selectedArray.length} selected <div className="propertiesView-selectedList"> - {PresBox.Instance.listOfSelected} + {PresBox.Instance?.listOfSelected} </div> </div> </div> {!selectedItem ? (null) : <div className="propertiesView-presTrails"> <div className="propertiesView-presTrails-title" - onPointerDown={() => runInAction(() => { this.openPresTransitions = !this.openPresTransitions; })} + onPointerDown={action(() => { this.openPresTransitions = !this.openPresTransitions; })} style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> <FontAwesomeIcon icon={"rocket"} /> Transitions <div className="propertiesView-presTrails-title-icon"> @@ -1028,7 +1040,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {PresBox.Instance.newDocumentDropdown} </div> : null} </div> - <div className="propertiesView-sharing"> + {/* <div className="propertiesView-sharing"> <div className="propertiesView-sharing-title" onPointerDown={() => runInAction(() => { this.openSharing = !this.openSharing; })} style={{ backgroundColor: this.openSharing ? "black" : "" }}> @@ -1040,7 +1052,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {this.openSharing ? <div className="propertiesView-sharing-content"> {this.sharingTable} </div> : null} - </div> + </div> */} </div>; } } diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index e6ac7021a..70ebca5e7 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -94,8 +94,8 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { */ unflexedPosition(index: number): Omit<Layout, "i"> { return { - x: (index % Math.floor(this.numCols / this.defaultW)) * this.defaultW, - y: Math.floor(index / Math.floor(this.numCols / this.defaultH)) * this.defaultH, + x: (index % (Math.floor(this.numCols / this.defaultW) || 1)) * this.defaultW, + y: Math.floor(index / (Math.floor(this.numCols / this.defaultH) || 1)) * this.defaultH, w: this.defaultW, h: this.defaultH, static: true |
