diff options
29 files changed, 283 insertions, 417 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fbcecbec6..8d1f8a04e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -108,7 +108,6 @@ export interface DocumentOptions { layout?: string | Doc; // default layout string for a document childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view) childLayoutString?: string; // template string for collection to use to render its children - hideFilterView?: boolean; // whether to hide the filter popout on collections hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden _columnsHideIfEmpty?: boolean; // whether stacking view column headings should be hidden @@ -762,7 +761,7 @@ export namespace Docs { } export function PileDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", hideFilterView: true, forceActive: true, ...options, _viewType: CollectionViewType.Pile }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", backgroundColor: "black", forceActive: true, ...options, _viewType: CollectionViewType.Pile }, id); } export function LinearDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { @@ -835,7 +834,7 @@ export namespace Docs { } export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) { - const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + const inst = InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { freezeChildren: "remove|add", treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); const tabs = TreeDocument(documents, { title: "On-Screen Tabs", freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true }); const all = TreeDocument([], { title: "Off-Screen Tabs", freezeChildren: "add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true }); Doc.GetProto(inst).data = new List<Doc>([tabs, all]); @@ -889,19 +888,21 @@ export namespace DocUtils { if (d.z) return true; for (const facetKey of Object.keys(filterFacets)) { const facet = filterFacets[facetKey]; - const satisfiesFacet = Object.keys(facet).some(value => { - if (facet[value] === "match") { - if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc - const allKeys = Array.from(Object.keys(d)); - allKeys.push(...Object.keys(Doc.GetProto(d))); - const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); - return keys.some(key => Field.toString(d[key] as Field).includes(value)); - } - return d[facetKey] === undefined || Field.toString(d[facetKey] as Field).includes(value); + const matches = Object.keys(facet).filter(value => facet[value] === "match"); + const checks = Object.keys(facet).filter(value => facet[value] === "check"); + const xs = Object.keys(facet).filter(value => facet[value] === "x"); + const failsNotEqualFacets = !xs.length ? false : xs.some(value => Doc.matchFieldValue(d, facetKey, value)); + const satisfiesCheckFacets = !checks.length ? true : checks.some(value => Doc.matchFieldValue(d, facetKey, value)); + const satisfiesMatchFacets = !matches.length ? true : matches.some(value => { + if (facetKey.startsWith("*")) { // fields starting with a '*' are used to match families of related fields. ie, *lastModified will match text-lastModified, data-lastModified, etc + const allKeys = Array.from(Object.keys(d)); + allKeys.push(...Object.keys(Doc.GetProto(d))); + const keys = allKeys.filter(key => key.includes(facetKey.substring(1))); + return keys.some(key => Field.toString(d[key] as Field).includes(value)); } - return (facet[value] === "x") !== Doc.matchFieldValue(d, facetKey, value); + return Field.toString(d[facetKey] as Field).includes(value); }); - if (!satisfiesFacet) { + if (!satisfiesCheckFacets || !satisfiesMatchFacets || failsNotEqualFacets) { return false; } } diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 164c3ab67..7a1c193c1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -55,7 +55,7 @@ export class CurrentUserUtils { Docs.Create.SearchDocument({ _viewType: CollectionViewType.Schema, ignoreClick: true, forceActive: true, _chromeStatus: "disabled", lockedPosition: true, title: "query", _height: 200, system: true }), Docs.Create.FreeformDocument([], { title: "data", _height: 100, system: true }) ], - { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true } + { _width: 400, _height: 300, title: "queryView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, system: true } ); queryTemplate.isTemplateDoc = makeTemplate(queryTemplate); doc["template-button-query"] = CurrentUserUtils.ficon({ @@ -90,7 +90,7 @@ export class CurrentUserUtils { Docs.Create.MulticolumnDocument([], { title: "data", _height: 200, system: true }), Docs.Create.TextDocument("", { title: "text", _height: 100, system: true }) ], - { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, hideFilterView: true, system: true } + { _width: 400, _height: 300, title: "slideView", _chromeStatus: "disabled", _xMargin: 3, _yMargin: 3, system: true } ); slideTemplate.isTemplateDoc = makeTemplate(slideTemplate); doc["template-button-slides"] = CurrentUserUtils.ficon({ @@ -720,7 +720,7 @@ export class CurrentUserUtils { if (doc.myTools === undefined) { const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - title: "My Tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", hideFilterView: true, forceActive: true, system: true + title: "My Tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", forceActive: true, system: true })) as any as Doc; doc.myTools = toolsStack; @@ -734,7 +734,7 @@ export class CurrentUserUtils { doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Dashboards", _height: 400, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); @@ -750,7 +750,7 @@ export class CurrentUserUtils { doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Presentations", _height: 100, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); @@ -768,7 +768,7 @@ export class CurrentUserUtils { doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "Recently Closed", _height: 500, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const clearAll = ScriptField.MakeScript(`self.data = new List([])`); @@ -783,7 +783,7 @@ export class CurrentUserUtils { doc.myFilter = new PrefetchProxy(Docs.Create.FilterDocument({ title: "FilterDoc", _height: 500, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const clearAll = ScriptField.MakeScript(`self.data = new List([])`); @@ -799,7 +799,7 @@ export class CurrentUserUtils { doc.treeViewExpandedView = "fields"; doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "My UserDoc", - treeViewTruncateTitleWidth: 150, hideFilterView: true, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } @@ -867,14 +867,14 @@ export class CurrentUserUtils { // Sharing sidebar is where shared documents are contained static setupSharingSidebar(doc: Doc) { if (doc.mySharedDocs === undefined) { - doc.mySharedDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, _yMargin: 30, _showTitle: "title", ignoreClick: true, lockedPosition: true })); + doc.mySharedDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true })); } } // Import sidebar is where shared documents are contained static setupImportSidebar(doc: Doc) { if (doc.myImportDocs === undefined) { - doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My ImportDocuments", forceActive: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 30, lockedPosition: true, _chromeStatus: "disabled", system: true })); + doc.myImportDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My ImportDocuments", forceActive: true, ignoreClick: true, _showTitle: "title", childDropAction: "alias", _autoHeight: true, _yMargin: 50, _gridGap: 15, lockedPosition: true, _chromeStatus: "disabled", system: true })); } if (doc.myImportPanel === undefined) { const uploads = Cast(doc.myImportDocs, Doc, null); @@ -1063,7 +1063,8 @@ export class CurrentUserUtils { const importDocs = Cast(Doc.UserDoc().myImportDocs, Doc, null); const disposer = OverlayView.ShowSpinner(); DocListCastAsync(importDocs.data).then(async list => { - list?.push(... await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {})); + const results = await DocUtils.uploadFilesToDocs(Array.from(input.files || []), {}); + list?.splice(0, 0, ...results); disposer(); }); } else { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 2c7dcf49b..420b29559 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -215,8 +215,8 @@ export class DocumentManager { public async FollowLink(link: Opt<Doc>, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { const linkDocs = link ? [link] : DocListCast(doc.links); SelectionManager.DeselectAll(); - const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); // link docs where 'doc' is anchor1 - const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); // link docs where 'doc' is anchor2 + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1 + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2 const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; @@ -225,10 +225,10 @@ export class DocumentManager { followLinks.forEach(async linkDoc => { if (linkDoc) { const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 : - (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; const targetTimecode = (doc === linkDoc.anchor1 ? Cast(linkDoc.anchor2_timecode, "number") : doc === linkDoc.anchor2 ? Cast(linkDoc.anchor1_timecode, "number") : - (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number"))); + (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? Cast(linkDoc.anchor2_timecode, "number") : Cast(linkDoc.anchor1_timecode, "number"))); if (target) { const containerDoc = (await Cast(target.annotationOn, Doc)) || target; containerDoc._currentTimecode = targetTimecode; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 8bf6faf03..9797f5488 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -144,7 +144,7 @@ export namespace DragManager { linkSourceDocument: Doc; dontClearTextBox?: boolean; linkDocument?: Doc; - linkDropCallback?: (data: LinkDragData) => void; + linkDropCallback?: (data: { linkDocument?: Doc }) => void; } export class ColumnDragData { constructor(colKey: SchemaHeaderField) { @@ -161,7 +161,7 @@ export namespace DragManager { this.annotationDocument = annotationDoc; this.offset = [0, 0]; } - linkedToDoc?: boolean; + linkDocument?: Doc; targetContext: Doc | undefined; dragDocument: Doc; annotationDocument: Doc; @@ -169,6 +169,7 @@ export namespace DragManager { offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; + linkDropCallback?: (data: { linkDocument?: Doc }) => void; } export function MakeDropTarget( @@ -435,7 +436,7 @@ export namespace DragManager { const target = document.elementFromPoint(e.x, e.y); - if (target && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) { + if (target && !Doc.UserDoc()._noAutoscroll && !options?.noAutoscroll && !dragData.draggedDocuments?.some((d: any) => d._noAutoscroll)) { const autoScrollHandler = () => { target.dispatchEvent( new CustomEvent<React.DragEvent>("dashDragAutoScroll", { diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 6d4f2c190..730db4f29 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -4,6 +4,7 @@ import { DocumentView } from "../views/nodes/DocumentView"; import { computedFn } from "mobx-utils"; import { List } from "../../fields/List"; import { CollectionSchemaView } from "../views/collections/CollectionSchemaView"; +import { CollectionViewType } from "../views/collections/CollectionView"; export namespace SelectionManager { @@ -93,7 +94,7 @@ export namespace SelectionManager { } export function SelectedDocuments(): Array<DocumentView> { - return Array.from(manager.SelectedDocuments.keys()); + return Array.from(manager.SelectedDocuments.keys()).filter(dv => dv.props.Document._viewType !== CollectionViewType.Docking); } export function SelectedSchemaDoc(): Doc | undefined { return manager.SelectedSchemaDocument; diff --git a/src/client/util/SettingsManager.scss b/src/client/util/SettingsManager.scss index b855d58bc..68e0b91b0 100644 --- a/src/client/util/SettingsManager.scss +++ b/src/client/util/SettingsManager.scss @@ -4,7 +4,6 @@ //background-color: whitesmoke !important; color: grey; width: 450px; - height: 300px; button { background: #315a96; diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index a0281bc5a..0a1890ca4 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -105,6 +105,11 @@ export class SettingsManager extends React.Component<{}> { <input type="checkbox" onChange={e => Doc.UserDoc()["documentLinksButton-hideEnd"] = !Doc.UserDoc()["documentLinksButton-hideEnd"]} checked={BoolCast(Doc.UserDoc()["documentLinksButton-hideEnd"])} /> </div> + <div> + Autoscroll + <input type="checkbox" onChange={e => Doc.UserDoc()._noAutoscroll = !Doc.UserDoc()._noAutoscroll} + checked={!BoolCast(Doc.UserDoc()._noAutoscroll)} /> + </div> </div> </div>; } @@ -188,6 +193,6 @@ export class SettingsManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: "600px", height: "340px" }} />; + dialogueBoxStyle={{ width: "600px" }} />; } }
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7781d6069..81c1676b0 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -598,16 +598,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return collectionAcl === AclAdmin || collectionAcl === AclEdit; }); const minimal = bounds.r - bounds.x < 100 ? true : false; - const maximizeIcon = minimal ? ( - <Tooltip title={<div className="dash-tooltip">Show context menu</div>} placement="top"> - <div className="documentDecorations-contextMenu" onPointerDown={this.onSettingsDown}> - <FontAwesomeIcon size="lg" icon="bars" /> - </div></Tooltip>) : canDelete ? ( - <Tooltip title={<div className="dash-tooltip">Close</div>} placement="top"> - <div className="documentDecorations-closeButton" onClick={this.onCloseClick}> - {/* Currently, this is set to be enabled if there is no ink selected. It might be interesting to think about minimizing ink if it's useful? -syip2*/} - <FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" /> - </div></Tooltip>) : (null); + const closeIcon = canDelete ? ( + <Tooltip title={<div className="dash-tooltip">Close</div>} placement="top"> + <div className="documentDecorations-closeButton" onClick={this.onCloseClick}> + <FontAwesomeIcon className="documentdecorations-times" icon={"times"} size="lg" /> + </div></Tooltip>) : (null); const titleArea = this._edtingTitle ? <input ref={this._keyinput} className="documentDecorations-title" type="text" name="dynbox" autoComplete="on" value={this._accumulatedTitle} @@ -616,7 +611,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> {minimal ? (null) : <Tooltip title={<div className="dash-tooltip">Show context menu</div>} placement="top"><div className="documentDecorations-contextMenu" key="menu" onPointerDown={this.onSettingsDown}> <FontAwesomeIcon size="lg" icon="bars" /> </div></Tooltip>} - <div className="documentDecorations-title" key="title" onPointerDown={this.onTitleDown} > + <div className="documentDecorations-title" style={{ gridColumnEnd: minimal ? 4 : 5, gridColumnStart: minimal ? 2 : 3 }} key="title" onPointerDown={this.onTitleDown} > <span style={{ width: "100%", display: "inline-block", cursor: "move" }}>{`${this.selectionTitle}`}</span> </div> </>; @@ -651,9 +646,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> - {maximizeIcon} + {closeIcon} {titleArea} - {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) : + {SelectionManager.SelectedDocuments().length !== 1 || seldoc.Document.type === DocumentType.INK || minimal ? (null) : <Tooltip title={<div className="dash-tooltip">{`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}</div>} placement="top"> <div className="documentDecorations-iconifyButton" onPointerDown={this.onIconifyDown}> <FontAwesomeIcon icon={seldoc.finalLayoutKey.includes("icon") ? "window-restore" : "window-minimize"} className="documentView-minimizedIcon" /> diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index e6e80d9fb..f9b3b1da8 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -83,6 +83,7 @@ export class MainView extends React.Component { componentDidMount() { new InkStrokeProperties(); + this._sidebarContent.proto = undefined; DocServer.setPlaygroundFields(["dataTransition", "_viewTransition", "_panX", "_panY", "_viewScale", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's DocServer.GetRefField("rtfProto").then(proto => (proto instanceof Doc) && reaction(() => StrCast(proto.BROADCAST_MESSAGE), msg => msg && alert(msg))); @@ -169,6 +170,14 @@ export class MainView extends React.Component { window.addEventListener("drop", e => e.preventDefault(), false); // prevent default behavior of navigating to a new web page window.addEventListener("dragover", e => e.preventDefault(), false); document.addEventListener("pointerdown", this.globalPointerDown); + document.addEventListener("click", (e: MouseEvent) => { + if (!e.cancelBubble) { + const pathstr = (e as any)?.path.map((p: any) => p.classList?.toString()).join(); + if (pathstr.includes("libraryFlyout")) { + SelectionManager.DeselectAll(); + } + } + }); } initAuthenticationRouters = async () => { @@ -527,7 +536,7 @@ export class MainView extends React.Component { TraceMobx(); return <div className="mainView-searchPanel"> <SearchBox Document={CurrentUserUtils.MySearchPanelDoc} - DataDoc={undefined} + DataDoc={CurrentUserUtils.MySearchPanelDoc} fieldKey="data" dropAction="move" isSelected={returnTrue} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index bb4657a2b..46f38795c 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -45,11 +45,9 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } @computed get selectedDocumentView() { - if (SelectionManager.SelectedDocuments().length) { - return SelectionManager.SelectedDocuments()[0]; - } else if (PresBox.Instance && PresBox.Instance._selectedArray.length) { - return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); - } else { return undefined; } + if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; + if (PresBox.Instance && PresBox.Instance._selectedArray.length) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); + return undefined; } @computed get isPres(): boolean { if (this.selectedDoc?.type === DocumentType.PRES) return true; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index a3d58f31d..c891d2035 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -140,6 +140,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { // @undoBatch public static AddSplit(document: Doc, pullSide: string, stack?: any, panelName?: string) { + if (document._viewType === CollectionViewType.Docking) return CurrentUserUtils.openDashboard(Doc.UserDoc(), document); const instance = CollectionDockingView.Instance; if (!instance) return false; const docContentConfig = CollectionDockingView.makeDocumentConfig(document, panelName); diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 2e4055256..522f93b88 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -28,10 +28,8 @@ export class CollectionPileView extends CollectionSubView(doc => doc) { } this._originalChrome = StrCast(this.layoutDoc._chromeStatus); this.layoutDoc._chromeStatus = "disabled"; - this.layoutDoc.hideFilterView = true; } componentWillUnmount() { - this.layoutDoc.hideFilterView = false; this.layoutDoc._chromeStatus = this._originalChrome; } diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index 94f9d4f92..d2a1234ed 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -380,7 +380,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { @action renderFilterOptions = (): JSX.Element[] | JSX.Element => { - if (!this._isOpen) { + if (!this._isOpen || !this.props.dataDoc) { this.defaultMenuHeight = 0; return <></>; } @@ -388,7 +388,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { 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]); + this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]); } const docs = this.docSafe; docs.forEach((doc) => { @@ -476,8 +476,8 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { removeFilters = (e: React.PointerEvent): void => { const keyOptions: string[] = []; - if (this.docSafe.length === 0) { - this.docSafe = DocListCast(this.props.dataDoc![this.props.fieldKey]); + if (this.docSafe.length === 0 && this.props.dataDoc) { + this.docSafe = DocListCast(this.props.dataDoc[this.props.fieldKey]); } const docs = this.docSafe; docs.forEach((doc) => { diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index e4bb8302f..fb0bce53e 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -77,6 +77,9 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) const dxf = () => this.getDocTransform(d, dref.current!); this._docXfs.push({ dxf, width, height }); const rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); + if (height() < 5) { + console.log("here" + height()); + } const style = this.isStackingView ? { width: width(), marginTop: i ? this.gridGap : 0, height: height() } : { gridRowEnd: `span ${rowSpan}` }; return <div className={`collectionStackingView-${this.isStackingView ? "columnDoc" : "masonryDoc"}`} key={d[Id]} ref={dref} style={style} > {this.getDisplayDoc(d, (!d.isTemplateDoc && !d.isTemplateForField && !d.PARAMS) ? undefined : this.props.DataDoc, dxf, width)} @@ -253,8 +256,10 @@ export class CollectionStackingView extends CollectionSubView(StackingDocument) if (!(this.layoutDoc._columnsFill)) wid = Math.min(layoutDoc[WidthSym](), wid); return wid * aspect; } - return layoutDoc._fitWidth ? !nh ? this.props.PanelHeight() - 2 * this.yMargin : - Math.min(wid * NumCast(layoutDoc.scrollHeight, nh) / (nw || 1), this.props.PanelHeight() - 2 * this.yMargin) : Math.max(20, layoutDoc[HeightSym]()); + return layoutDoc._fitWidth ? + (!nh ? this.props.PanelHeight() - 2 * this.yMargin : + Math.min(wid * nh / (nw || 1), this.layoutDoc._autoHeight ? 100000 : this.props.PanelHeight() - 2 * this.yMargin)) : + Math.max(20, layoutDoc[HeightSym]()); } columnDividerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 54e7f790f..8f0710f4b 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -131,42 +131,43 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: const viewSpecScript = Cast(this.props.Document.viewSpecScript, ScriptField); const childDocs = viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; + const docFilters = this.docFilters(); let searchDocs = this.searchFilterDocs(); + if (this.props.Document.dontRegisterView || (!docFilters.length && !searchDocs.length)) return childDocs; - let docsforFilter: Doc[] = childDocs; - - if (searchDocs.length > 0) { - searchDocs = [...searchDocs, ...docs.filter(d => d.z)]; - docsforFilter = []; - const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - searchDocs = DocUtils.FilterDocs(searchDocs, this.docFilters(), docRangeFilters, viewSpecScript); - childDocs.forEach((d) => { - let notFiltered = searchDocs.includes(d) || d.z; - const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; - if (data !== undefined) { - let subDocs = DocListCast(data); - if (subDocs.length > 0) { - let newarray: Doc[] = []; - while (subDocs.length > 0 && !notFiltered) { - newarray = []; - subDocs.forEach((t) => { - const fieldKey = Doc.LayoutFieldKey(t); - const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView"); - notFiltered = notFiltered || searchDocs.includes(t); - DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc)); - }); - subDocs = newarray; - } + const docsforFilter: Doc[] = []; + const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); + childDocs.forEach((d) => { + if (this.props.Document.title === "lose this") { + console.log('here"') + } + if (d.title === "lose this") { + console.log('here"') + } + let notFiltered = d.z || ((!searchDocs.length || searchDocs.includes(d)) && (!docFilters.length || DocUtils.FilterDocs([d], docFilters, docRangeFilters, viewSpecScript).length > 0)); + const fieldKey = Doc.LayoutFieldKey(d); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); + const data = d[annos ? fieldKey + "-annotations" : fieldKey]; + if (data !== undefined) { + let subDocs = DocListCast(data); + if (subDocs.length > 0) { + let newarray: Doc[] = []; + notFiltered = notFiltered || (!searchDocs.length && docFilters.length && DocUtils.FilterDocs(subDocs, docFilters, docRangeFilters, viewSpecScript).length); + while (subDocs.length > 0 && !notFiltered) { + newarray = []; + subDocs.forEach((t) => { + const fieldKey = Doc.LayoutFieldKey(t); + const annos = !Field.toString(Doc.LayoutField(t) as Field).includes("CollectionView"); + notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && (!docFilters.length || DocUtils.FilterDocs([t], docFilters, docRangeFilters, viewSpecScript).length)); + DocListCast(t[annos ? fieldKey + "-annotations" : fieldKey]).forEach((newdoc) => newarray.push(newdoc)); + }); + subDocs = newarray; } } - notFiltered && docsforFilter.push(d); - }); - return docsforFilter; - } - const docRangeFilters = this.props.ignoreFields?.includes("_docRangeFilters") ? [] : Cast(this.props.Document._docRangeFilters, listSpec("string"), []); - return this.props.Document.dontRegisterView ? childDocs : DocUtils.FilterDocs(childDocs, this.docFilters(), docRangeFilters, viewSpecScript); + } + notFiltered && docsforFilter.push(d); + }); + return docsforFilter; } @action diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 52c3b2793..f13fee776 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -32,6 +32,7 @@ import React = require("react"); import { makeTemplate } from '../../util/DropConverter'; import { TraceMobx } from '../../../fields/util'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; +import { CollectionDockingView } from './CollectionDockingView'; export interface TreeViewProps { document: Doc; @@ -488,7 +489,7 @@ class TreeView extends React.Component<TreeViewProps> { }} > {view} </div > - {Doc.IsSystem(this.doc) ? (null) : headerElements} + {Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode ? (null) : headerElements} </>; } @@ -875,28 +876,6 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll } } -Scripting.addGlobal(function readFacetData(layoutDoc: Doc, dataDoc: Doc, dataKey: string, facetHeader: string) { - const allCollectionDocs = DocListCast(dataDoc[dataKey]); - const facetValues = Array.from(allCollectionDocs.reduce((set, child) => - set.add(Field.toString(child[facetHeader] as Field)), new Set<string>())); - - let nonNumbers = 0; - facetValues.map(val => { - const num = Number(val); - if (Number.isNaN(num)) { - nonNumbers++; - } - }); - 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; - }); - return new List<Doc>(facetValueDocSet); -}); - Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []); for (let i = 0; i < docFilters.length; i += 3) { diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index cb053e85c..ba8e23447 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -271,7 +271,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return this.SubViewHelper(type, renderProps); } - setupViewTypes(category: string, func: (viewType: CollectionViewType) => Doc, addExtras: boolean) { const subItems: ContextMenuProps[] = []; @@ -367,6 +366,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus onMovePrevRequest={action(() => this._curLightboxImg = (this._curLightboxImg + images.length - 1) % images.length)} onMoveNextRequest={action(() => this._curLightboxImg = (this._curLightboxImg + 1) % images.length)} />); } + get _facetWidth() { return NumCast(this.props.Document._facetWidth); } set _facetWidth(value) { this.props.Document._facetWidth = value; } @@ -390,9 +390,11 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, DataDoc, doc)).filter(pair => pair.layout); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } + get childDocList() { return Cast(this.dataField, listSpec(Doc)); } + get childDocs() { const dfield = this.dataField; const rawdocs = (dfield instanceof Doc) ? [dfield] : Cast(dfield, listSpec(Doc), Cast(this.props.Document.rootDocument, Doc, null) ? [Cast(this.props.Document.rootDocument, Doc, null)] : []); @@ -401,99 +403,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus return viewSpecScript ? docs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result) : docs; } - @computed get _allFacets() { - TraceMobx(); - return ["author", "creationDate", "type", "text", "context"]; - const noviceReqFields = ["author", "creationDate", "type", "text", "context"]; - const noviceLayoutFields: string[] = [];//["_curPage"]; - const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; - - const facets = new Set<string>([...noviceReqFields, ...noviceLayoutFields]); - this.childDocs.filter(child => child).forEach(child => child && Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); - Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key))); - - return Array.from(facets).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort(); - } - - /** - * Responds to clicking the check box in the flyout menu - */ - facetClick = (facetHeader: string) => { - const facetCollection = this.props.Document; - const found = DocListCast(facetCollection[this.props.fieldKey + "-filter"]).findIndex(doc => doc.title === facetHeader); - if (found !== -1) { - (facetCollection[this.props.fieldKey + "-filter"] as List<Doc>).splice(found, 1); - const docFilter = Cast(this.props.Document._docFilters, listSpec("string")); - if (docFilter) { - let index: number; - while ((index = docFilter.findIndex(item => item === facetHeader)) !== -1) { - docFilter.splice(index, 3); - } - } - const docRangeFilters = Cast(this.props.Document._docRangeFilters, listSpec("string")); - if (docRangeFilters) { - let index: number; - while ((index = docRangeFilters.findIndex(item => item === facetHeader)) !== -1) { - docRangeFilters.splice(index, 3); - } - } - } else { - const allCollectionDocs = DocListCast(this.dataDoc[this.props.fieldKey]); - var rtfields = 0; - const facetValues = Array.from(allCollectionDocs.reduce((set, child) => { - const field = child[facetHeader] as Field; - const fieldStr = Field.toString(field); - if (field instanceof RichTextField || (typeof (field) === "string" && fieldStr.split(" ").length > 2)) rtfields++; - return set.add(fieldStr); - }, new Set<string>())); - - let nonNumbers = 0; - let minVal = Number.MAX_VALUE, maxVal = -Number.MAX_VALUE; - facetValues.map(val => { - const num = Number(val); - if (Number.isNaN(num)) { - nonNumbers++; - } else { - minVal = Math.min(num, minVal); - maxVal = Math.max(num, maxVal); - } - }); - let newFacet: Opt<Doc>; - if (facetHeader === "text" || rtfields / allCollectionDocs.length > 0.1) { - newFacet = Docs.Create.TextDocument("", { _width: 100, _height: 25, treeViewExpandedView: "layout", title: facetHeader, treeViewOpen: true, forceActive: true, ignoreClick: true }); - Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - newFacet.target = this.props.Document; - newFacet._textBoxPadding = 4; - const scriptText = `setDocFilter(this.target, "${facetHeader}", text, "match")`; - newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" }); - } else if (nonNumbers / facetValues.length < .1) { - newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true }); - const newFacetField = Doc.LayoutFieldKey(newFacet); - const ranged = Doc.readDocRangeFilter(this.props.Document, facetHeader); - Doc.GetProto(newFacet).type = DocumentType.COL; // forces item to show an open/close button instead ofa checkbox - const extendedMinVal = minVal - Math.min(1, Math.abs(maxVal - minVal) * .05); - const extendedMaxVal = maxVal + Math.min(1, Math.abs(maxVal - minVal) * .05); - newFacet[newFacetField + "-min"] = ranged === undefined ? extendedMinVal : ranged[0]; - newFacet[newFacetField + "-max"] = ranged === undefined ? extendedMaxVal : ranged[1]; - Doc.GetProto(newFacet)[newFacetField + "-minThumb"] = extendedMinVal; - Doc.GetProto(newFacet)[newFacetField + "-maxThumb"] = extendedMaxVal; - newFacet.target = this.props.Document; - const scriptText = `setDocFilterRange(this.target, "${facetHeader}", range)`; - newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); - 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; - const capturedVariables = { layoutDoc: this.props.Document, dataDoc: this.dataDoc }; - newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables); - } - newFacet && Doc.AddDocToList(facetCollection, this.props.fieldKey + "-filter", newFacet); - } - } - onPointerDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, action((e: PointerEvent, down: number[], delta: number[]) => { this._facetWidth = this.props.PanelWidth() - Math.max(this.props.ScreenToLocalTransform().transformPoint(e.clientX, 0)[0], 0); @@ -501,78 +410,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus }), returnFalse, action(() => this._facetWidth = this.facetWidth() < 15 ? Math.min(this.props.PanelWidth() - 25, 200) : 0), false); } - filterBackground = () => "rgba(105, 105, 105, 0.432)"; - get ignoreFields() { return ["_docFilters", "_docRangeFilters"]; } // this makes the tree view collection ignore these filters (otherwise, the filters would filter themselves) - @computed get scriptField() { - 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; - } - @computed get filterView() { - TraceMobx(); - const facetCollection = this.props.Document; - const flyout = ( - <div className="collectionTimeView-flyout" style={{ width: `${this.facetWidth()}`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}> - {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}> - <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey + "-filter"]).some(d => d.title === facet)} /> - <span className="checkmark" /> - {facet} - </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={"edit"} 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} - searchFilterDocs={returnEmptyDoclist} - 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); childLayoutString = this.props.childLayoutString || StrCast(this.props.Document.childLayoutString); @@ -603,11 +440,6 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus Utils.CorsProxy(Cast(d.data, ImageField)!.url.href) : Cast(d.data, ImageField)!.url.href : ""))} - {(Doc.UserDoc()?.noviceMode || !this.props.isSelected() && !this.props.Document.forceActive) || this.props.Document.hideFilterView ? (null) : - <div className="collectionView-filterDragger" title="library View Dragger" onPointerDown={this.onPointerDown} - style={{ right: this.facetWidth() - 1, top: this.props.Document._viewType === CollectionViewType.Docking ? "25%" : "60%" }} /> - } - {Doc.UserDoc()?.noviceMode || this.facetWidth() < 10 ? (null) : this.filterView} </div>); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 30a71398b..306dfe797 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1167,7 +1167,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if ((e as any).handlePan || this.props.isAnnotationOverlay) return; (e as any).handlePan = true; - if (this._marqueeRef?.current) { + if (!this.props.Document._noAutoscroll && this._marqueeRef?.current) { const dragX = e.detail.clientX; const dragY = e.detail.clientY; const bounds = this._marqueeRef.current?.getBoundingClientRect(); diff --git a/src/client/views/linking/LinkMenuGroup.tsx b/src/client/views/linking/LinkMenuGroup.tsx index 2ae87ac13..8e09052a3 100644 --- a/src/client/views/linking/LinkMenuGroup.tsx +++ b/src/client/views/linking/LinkMenuGroup.tsx @@ -66,7 +66,8 @@ export class LinkMenuGroup extends React.Component<LinkMenuGroupProps> { } render() { - const groupItems = this.props.group.map(linkDoc => { + const set = new Set<Doc>(this.props.group); + const groupItems = Array.from(set.keys()).map(linkDoc => { const destination = LinkManager.Instance.getOppositeAnchor(linkDoc, this.props.sourceDoc) || LinkManager.Instance.getOppositeAnchor(linkDoc, Cast(linkDoc.anchor2, Doc, null).annotationOn === this.props.sourceDoc ? Cast(linkDoc.anchor2, Doc, null) : Cast(linkDoc.anchor1, Doc, null)); if (destination && this.props.sourceDoc) { diff --git a/src/client/views/nodes/DocumentLinksButton.tsx b/src/client/views/nodes/DocumentLinksButton.tsx index 1d346894c..ab6cae0ad 100644 --- a/src/client/views/nodes/DocumentLinksButton.tsx +++ b/src/client/views/nodes/DocumentLinksButton.tsx @@ -220,7 +220,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp @computed get linkButton() { TraceMobx(); - const links = this.props.links; + const links = new Set<Doc>(this.props.links); const menuTitle = this.props.StartLink ? "Drag or tap to start link" : "Tap to complete link"; const buttonTitle = "Tap to view links"; @@ -265,7 +265,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp <FontAwesomeIcon className="documentdecorations-icon" icon="hand-paper" size="sm" /> : links.length} */} {this.props.InMenu ? this.props.StartLink ? <FontAwesomeIcon className="documentdecorations-icon" icon="link" size="sm" /> : - link : links.length} + link : Array.from(links).length} </div> {this.props.InMenu && !this.props.StartLink && DocumentLinksButton.StartLink !== this.props.View.props.Document ? @@ -287,7 +287,7 @@ export class DocumentLinksButton extends React.Component<DocumentLinksButtonProp } </div >; - return (!links.length) && !this.props.AlwaysOn ? (null) : + return (!Array.from(links).length) && !this.props.AlwaysOn ? (null) : this.props.InMenu && (DocumentLinksButton.StartLink || this.props.StartLink) ? <Tooltip title={<><div className="dash-tooltip">{title}</div></>}> {linkButton} diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 41e6d9603..f6360fc87 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -661,10 +661,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (de.complete.annoDragData) { /// this whole section for handling PDF annotations looks weird. Need to rethink this to make it cleaner e.stopPropagation(); - de.complete.annoDragData.linkedToDoc = true; - - const linkDoc = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link"); - linkDoc && makeLink(linkDoc); + de.complete.annoDragData.linkDocument = DocUtils.MakeLink({ doc: de.complete.annoDragData.annotationDocument }, { doc: this.props.Document }, "link"); + de.complete.annoDragData.linkDocument && makeLink(de.complete.annoDragData.linkDocument); } if (de.complete.linkDragData) { e.stopPropagation(); diff --git a/src/client/views/nodes/FilterBox.scss b/src/client/views/nodes/FilterBox.scss index c5d6e2505..b39793f01 100644 --- a/src/client/views/nodes/FilterBox.scss +++ b/src/client/views/nodes/FilterBox.scss @@ -1,44 +1,54 @@ - .collectionTimeView-treeView { - display: flex; - flex-direction: column; - width: 200px; - height: 100%; - position: absolute; - right: 0; - top: 0; - border-left: solid 1px; - z-index: 1; - - .collectionTimeView-addfacet { - display: inline-block; - width: 200px; - height: 30px; - background: darkGray; +.filterBox-flyout { + width: 400px; + display: block; + text-align: left; + .filterBox-flyout-facet { + background-color: lightgray; text-align: left; + display: inline-block; + position: relative; + width: 100%; + } +} +.filterBox-treeView { + display: flex; + flex-direction: column; + width: 200px; + height: 100%; + position: absolute; + right: 0; + top: 0; + border-left: solid 1px; + z-index: 1; - .collectionTimeView-button { - align-items: center; - display: flex; - width: 100%; - height: 100%; + .filterBox-addfacet { + display: inline-block; + width: 200px; + height: 30px; + background: darkGray; + text-align: left; - .collectionTimeView-span { - margin: auto; - } - } + .filterBox-addFacetButton { + display: flex; + margin: auto; - >div, - >div>div { - width: 100%; - height: 100%; + .filterBox-span { + margin-right: 15px; } } - .collectionTimeView-tree { - display: inline-block; + >div, + >div>div { width: 100%; - height: calc(100% - 30px); + height: 100%; } - }
\ No newline at end of file + } + + .filterBox-tree { + display: inline-block; + width: 100%; + height: calc(100% - 30px); + } +}
\ No newline at end of file diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 7bffc9184..790901a29 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -1,27 +1,24 @@ import React = require("react"); -import { action, computed } from "mobx"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { computed } from "mobx"; import { observer } from "mobx-react"; -import { ColorState, SketchPicker } from 'react-color'; -import { Doc, Opt, DocListCast, Field, DataSym } from "../../../fields/Doc"; -import { Utils, returnEmptyFilter, returnEmptyDoclist, returnZero, emptyPath, returnFalse, emptyFunction, returnOne } from "../../../Utils"; +import { DataSym, Doc, DocListCast, Field, Opt } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; -import { InkTool } from "../../../fields/InkField"; -import { makeInterface, listSpec } from "../../../fields/Schema"; -import { StrCast, Cast } from "../../../fields/Types"; -import { SelectionManager } from "../../util/SelectionManager"; -import { undoBatch } from "../../util/UndoManager"; -import { ViewBoxBaseComponent } from "../DocComponent"; -import "./ColorBox.scss"; -import { FieldView, FieldViewProps } from './FieldView'; +import { List } from "../../../fields/List"; +import { RichTextField } from "../../../fields/RichTextField"; +import { listSpec, makeInterface } from "../../../fields/Schema"; +import { ComputedField, ScriptField } from "../../../fields/ScriptField"; +import { Cast } from "../../../fields/Types"; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionTreeView } from "../collections/CollectionTreeView"; -import { ScriptField, ComputedField } from "../../../fields/ScriptField"; -import { Docs } from "../../documents/Documents"; -import { RichTextField } from "../../../fields/RichTextField"; -import { List } from "../../../fields/List"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { ViewBoxBaseComponent } from "../DocComponent"; +import { SearchBox } from "../search/SearchBox"; +import { FieldView, FieldViewProps } from './FieldView'; import './FilterBox.scss'; -import { CollectionDockingView } from "../collections/CollectionDockingView"; +import { Scripting } from "../../util/Scripting"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -33,17 +30,24 @@ const FilterBoxDocument = makeInterface(documentSchema); export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDocument>(FilterBoxDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(FilterBox, fieldKey); } - @computed get _allFacets() { - return ["author", "creationDate", "type", "text", "context"]; - // const noviceReqFields = ["author", "creationDate", "type", "text", "context"]; - // const noviceLayoutFields: string[] = [];//["_curPage"]; - // const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; + @computed get allDocs() { + const allDocs = new Set<Doc>(); + if (CollectionDockingView.Instance) { + const activeTabs = DocListCast(CollectionDockingView.Instance.props.Document.data); + SearchBox.foreachRecursiveDoc(activeTabs, (doc: Doc) => allDocs.add(doc)); + setTimeout(() => CollectionDockingView.Instance.props.Document.allDocuments = new List<Doc>(Array.from(allDocs))); + } + return allDocs; + } - // const facets = new Set<string>([...noviceReqFields, ...noviceLayoutFields]); - // this.childDocs.filter(child => child).forEach(child => child && Object.keys(Doc.GetProto(child)).forEach(key => facets.add(key))); - // Doc.AreProtosEqual(this.dataDoc, this.props.Document) && this.childDocs.filter(child => child).forEach(child => Object.keys(child).forEach(key => facets.add(key))); + @computed get _allFacets() { + const noviceReqFields = ["author", "tags", "text", "type"]; + const noviceLayoutFields: string[] = [];//["_curPage"]; + const noviceFields = [...noviceReqFields, ...noviceLayoutFields]; - // return Array.from(facets).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort(); + const keys = new Set<string>(noviceFields); + this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); + return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort(); } /** * Responds to clicking the check box in the flyout menu @@ -95,7 +99,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc newFacet._textBoxPadding = 4; const scriptText = `setDocFilter(this?.target, "${facetHeader}", text, "match")`; newFacet.onTextChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, text: "string" }); - } else if (nonNumbers / facetValues.length < .1) { + } else if (facetHeader !== "tags" && nonNumbers / facetValues.length < .1) { newFacet = Docs.Create.SliderDocument({ title: facetHeader, treeViewExpandedView: "layout", treeViewOpen: true }); const newFacetField = Doc.LayoutFieldKey(newFacet); const ranged = Doc.readDocRangeFilter(targetDoc, facetHeader); @@ -115,7 +119,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc newFacet.treeViewOpen = true; newFacet.type = DocumentType.COL; const capturedVariables = { layoutDoc: targetDoc, dataDoc: (targetDoc.data as any)[0][DataSym] }; - newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, dataDoc, "${this.props.fieldKey}", "${facetHeader}")`, {}, capturedVariables); + newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, "${facetHeader}")`, {}, capturedVariables); } newFacet && Doc.AddDocToList(this.dataDoc, this.props.fieldKey, newFacet); } @@ -128,28 +132,26 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc return script ? () => script : undefined; } - @computed get filterView() { + render() { const facetCollection = this.props.Document.proto as Doc; - const flyout = ( - <div className="collectionTimeView-flyout" style={{ width: `100%`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}> - {this._allFacets.map(facet => <label className="collectionTimeView-flyout-item" key={`${facet}`} onClick={e => this.facetClick(facet)}> - <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey]).some(d => d.title === facet)} /> - <span className="checkmark" /> - {facet} - </label>)} - </div> - ); + const flyout = <div className="filterBox-flyout" style={{ width: `100%`, height: this.props.PanelHeight() - 30 }} onWheel={e => e.stopPropagation()}> + {this._allFacets.map(facet => <label className="filterBox-flyout-facet" key={`${facet}`} onClick={e => this.facetClick(facet)}> + <input type="checkbox" onChange={e => { }} checked={DocListCast(this.props.Document[this.props.fieldKey]).some(d => d.title === facet)} /> + <span className="checkmark" /> + {facet} + </label>)} + </div>; - return this.props.dontRegisterView ? (null) : <div className="collectionTimeView-treeView" style={{ width: "100%" }}> - <div className="collectionTimeView-addFacet" style={{ width: "100%" }} onPointerDown={e => e.stopPropagation()}> + return this.props.dontRegisterView ? (null) : <div className="filterBox-treeView" style={{ width: "100%" }}> + <div className="filterBox-addFacet" style={{ width: "100%" }} onPointerDown={e => e.stopPropagation()}> <Flyout anchorPoint={anchorPoints.LEFT_TOP} content={flyout}> - <div className="collectionTimeView-button"> + <div className="filterBox-addFacetButton"> <FontAwesomeIcon icon={"edit"} size={"lg"} /> - <span className="collectionTimeView-span">Facet Filters</span> + <span className="filterBox-span">Choose Facets</span> </div> </Flyout> </div> - <div className="collectionTimeView-tree" key="tree"> + <div className="filterBox-tree" key="tree"> <CollectionTreeView PanelPosition={""} Document={facetCollection} @@ -191,7 +193,23 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc </div> </div>; } - render() { - return this.filterView; - } -}
\ No newline at end of file +} + +Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) { + const allCollectionDocs = DocListCast(CollectionDockingView.Instance?.props.Document.allDocuments); + const set = new Set<string>(); + if (facetHeader === "tags") allCollectionDocs.forEach(child => Field.toString(child[facetHeader] as Field).split(":").forEach(key => set.add(key))); + else allCollectionDocs.forEach(child => set.add(Field.toString(child[facetHeader] as Field))); + let facetValues = Array.from(set).filter(v => v); + + let nonNumbers = 0; + facetValues.map(val => Number.isNaN(Number(val)) && nonNumbers++); + 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; + }); + return new List<Doc>(facetValueDocSet); +});
\ No newline at end of file diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index ce8df5195..dddefc17f 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -22,6 +22,7 @@ interface Props { } @observer export class LinkDocPreview extends React.Component<Props> { + static TargetDoc: Doc | undefined; @observable public static LinkInfo: Opt<{ linkDoc?: Doc; addDocTab: (document: Doc, where: string) => boolean, linkSrc: Doc; href?: string; Location: number[] }>; @observable _targetDoc: Opt<Doc>; @observable _toolTipText = ""; @@ -42,12 +43,14 @@ export class LinkDocPreview extends React.Component<Props> { LinkDocPreview.LinkInfo = undefined; this._targetDoc ? DocumentManager.Instance.FollowLink(this.props.linkDoc, this._targetDoc, doc => this.props.addDocTab(doc, "add:right"), false) : null; } + componentWillUnmount() { LinkDocPreview.TargetDoc = undefined; } componentDidUpdate() { this.updatePreview(); } componentDidMount() { this.updatePreview(); } async updatePreview() { const linkDoc = this.props.linkDoc; const linkSrc = this.props.linkSrc; + LinkDocPreview.TargetDoc = undefined; if (this.props.href) { if (this.props.href.startsWith("https://en.wikipedia.org/wiki/")) { wiki().page(this.props.href.replace("https://en.wikipedia.org/wiki/", "")).then(page => page.summary().then(action(summary => this._toolTipText = summary.substring(0, 500)))); @@ -59,7 +62,7 @@ export class LinkDocPreview extends React.Component<Props> { const target = anchor?.annotationOn ? await DocCastAsync(anchor.annotationOn) : anchor; runInAction(() => { this._toolTipText = ""; - this._targetDoc = target; + LinkDocPreview.TargetDoc = this._targetDoc = target; if (anchor !== this._targetDoc && anchor && this._targetDoc) { this._targetDoc._scrollY = NumCast(anchor?.y); } @@ -74,8 +77,8 @@ export class LinkDocPreview extends React.Component<Props> { this.props.addDocTab(Docs.Create.WebDocument(this.props.href, { title: this.props.href, _width: 200, _height: 400, useCors: true }), "add:right"); } } - width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)); - height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)); + width = () => Math.min(225, NumCast(this._targetDoc?.[WidthSym](), 225)) - 16; + height = () => Math.min(225, NumCast(this._targetDoc?.[HeightSym](), 225)) - 16 @computed get targetDocView() { return !this._targetDoc ? <div style={{ @@ -105,8 +108,8 @@ export class LinkDocPreview extends React.Component<Props> { ContainingCollectionDoc={undefined} ContainingCollectionView={undefined} renderDepth={0} - PanelWidth={() => this.width() - 16} //Math.min(350, NumCast(target._width, 350))} - PanelHeight={() => this.height() - 16} //Math.min(250, NumCast(target._height, 250))} + PanelWidth={this.width} //Math.min(350, NumCast(target._width, 350))} + PanelHeight={this.height} //Math.min(250, NumCast(target._height, 250))} focus={emptyFunction} whenActiveChanged={returnFalse} bringToFront={returnFalse} @@ -120,7 +123,7 @@ export class LinkDocPreview extends React.Component<Props> { return <div className="linkDocPreview" style={{ position: "absolute", left: this.props.location[0], - top: this.props.location[1], width: this.width(), height: this.height(), + top: this.props.location[1], width: this.width() + 16, height: this.height() + 16, zIndex: 1000, border: "8px solid white", borderRadius: "7px", boxShadow: "3px 3px 1.5px grey", diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f9e6227d7..c5d7c3c9f 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -499,8 +499,8 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum if (annotationDoc) { DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, { dragComplete: e => { - if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) { - DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); + if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) { + e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); annotationDoc.isLinkButton = true; } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 436538eba..311143ff7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -460,8 +460,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } else if (de.complete.linkDragData) { de.complete.linkDragData.linkDropCallback = this.linkDrop; } + else if (de.complete.annoDragData) { + de.complete.annoDragData.linkDropCallback = this.linkDrop; + } } - linkDrop = (data: DragManager.LinkDragData) => { + linkDrop = (data: { linkDocument?: Doc }) => { const linkDoc = data.linkDocument!; const anchor1Title = linkDoc.anchor1 instanceof Doc ? StrCast(linkDoc.anchor1.title) : "-untitled-"; const anchor1Id = linkDoc.anchor1 instanceof Doc ? linkDoc.anchor1[Id] : ""; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0a52f6ad5..18be9b679 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -32,6 +32,7 @@ import { PDFMenu } from "./PDFMenu"; import "./PDFViewer.scss"; const pdfjs = require('pdfjs-dist/es5/build/pdf.js'); import React = require("react"); +import { LinkDocPreview } from "../nodes/LinkDocPreview"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); @@ -98,6 +99,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @observable private _zoomed = 1; private _pdfViewer: any; + private _styleRule: any; // stylesheet rule for making hyperlinks clickable private _retries = 0; // number of times tried to create the PDF viewer private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); @@ -166,7 +168,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu (scrollY) => { if (scrollY !== undefined) { (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); - this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0)); + (!LinkDocPreview.TargetDoc) && this._mainCont.current && smoothScroll(1000, this._mainCont.current, (this.Document._scrollY || 0)); setTimeout(() => this.Document._scrollY = undefined, 1000); } }, @@ -435,11 +437,10 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu // if alt+left click, drag and annotate this._downX = e.clientX; this._downY = e.clientY; - addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" }); + (e.target as any).tagName === "SPAN" && (this._styleRule = addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" })); if ((this.Document._viewScale || 1) !== 1) return; if ((e.button !== 0 || e.altKey) && this.active(true)) { this._setPreviewCursor?.(e.clientX, e.clientY, true); - //e.stopPropagation(); } this._marqueeing = false; if (!e.altKey && e.button === 0 && this.active(true)) { @@ -461,12 +462,15 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this._marqueeHeight = this._marqueeWidth = 0; this._marqueeing = true; } - document.removeEventListener("pointermove", this.onSelectMove); document.addEventListener("pointermove", this.onSelectMove); - document.removeEventListener("pointerup", this.onSelectEnd); document.addEventListener("pointerup", this.onSelectEnd); + document.addEventListener("pointerup", this.removeStyle, true); } } + removeStyle = () => { + clearStyleSheetRules(PDFViewer._annotationStyle); + document.removeEventListener("pointerup", this.removeStyle); + } @action onSelectMove = (e: PointerEvent): void => { @@ -605,11 +609,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu if (annotationDoc) { DragManager.StartPdfAnnoDrag([ele], new DragManager.PdfAnnoDragData(this.props.Document, annotationDoc, targetDoc), e.pageX, e.pageY, { dragComplete: e => { - if (!e.aborted && e.annoDragData && !e.annoDragData.linkedToDoc) { - const link = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); - annotationDoc.isLinkButton = true; - if (link) Doc.GetProto(link).followLinkLocation = "default"; + if (!e.aborted && e.annoDragData && !e.annoDragData.linkDocument) { + e.annoDragData.linkDocument = DocUtils.MakeLink({ doc: annotationDoc }, { doc: e.annoDragData.dropDocument }, "Annotation"); + annotationDoc.isLinkButton = true; // prevents link button fro showing up --- maybe not a good thing? } + e.annoDragData && e.annoDragData.linkDocument && e.annoDragData?.linkDropCallback?.({ linkDocument: e.annoDragData.linkDocument }); } }); } diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index 3907d2d5c..b37ae02c3 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -179,6 +179,21 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc return finalDocs; } + static foreachRecursiveDoc(docs: Doc[], func: (doc: Doc) => void) { + let newarray: Doc[] = []; + while (docs.length > 0) { + newarray = []; + docs.forEach(d => { + const fieldKey = Doc.LayoutFieldKey(d); + const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); + const data = d[annos ? fieldKey + "-annotations" : fieldKey]; + data && newarray.push(...DocListCast(data)); + func(d); + }); + docs = newarray; + } + } + @action searchCollection(query: string) { const selectedCollection = this.currentSelectedCollection;//SelectionManager.SelectedDocuments()[0]; @@ -186,26 +201,14 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc if (selectedCollection !== undefined) { // this._currentSelectedCollection = selectedCollection; - let docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); + const docs = DocListCast(selectedCollection.dataDoc[Doc.LayoutFieldKey(selectedCollection.dataDoc)]); const found: [Doc, string[], string[]][] = []; - let newarray: Doc[] = []; - - while (docs.length > 0) { - newarray = []; - docs.forEach(d => { - const fieldKey = Doc.LayoutFieldKey(d); - const annos = !Field.toString(Doc.LayoutField(d) as Field).includes("CollectionView"); - const data = d[annos ? fieldKey + "-annotations" : fieldKey]; - data && newarray.push(...DocListCast(data)); - const hlights = new Set<string>(); - this.documentKeys(d).forEach(key => - Field.toString(d[key] as Field).toLowerCase().includes(query) && hlights.add(key)); - if (Array.from(hlights.keys()).length > 0) { - found.push([d, Array.from(hlights.keys()), []]); - } - }); - docs = newarray; - } + SearchBox.foreachRecursiveDoc(docs, (doc: Doc) => { + const hlights = new Set<string>(); + SearchBox.documentKeys(doc).forEach(key => Field.toString(doc[key] as Field).toLowerCase().includes(query) && hlights.add(key)); + Array.from(hlights.keys()).length > 0 && found.push([doc, Array.from(hlights.keys()), []]); + }); + this._results = found; this.docsforfilter = this._results.map(r => r[0]); this.setSearchFilter(selectedCollection, this.filter && found.length ? this.docsforfilter : undefined); @@ -218,7 +221,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc } - documentKeys(doc: Doc) { + static documentKeys(doc: Doc) { const keys: { [key: string]: boolean } = {}; // bcz: ugh. this is untracked since otherwise a large collection of documents will blast the server for all their fields. // then as each document's fields come back, we update the documents _proxies. Each time we do this, the whole schema will be diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 76b26a9aa..71294c59c 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -95,7 +95,7 @@ export const documentSchema = createSchema({ onPointerDown: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onPointerUp: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab, ) + followLinkLocation: "string",// flag for where to place content when following a click interaction (e.g., add:right, inPlace, default, ) hideLinkButton: "boolean", // whether the blue link counter button should be hidden hideAllLinks: "boolean", // whether all individual blue anchor dots should be hidden linkDisplay: "boolean", // whether a link connection should be shown between link anchor endpoints. |