From e3709b4445732791f696cdf26274ab09294ce208 Mon Sep 17 00:00:00 2001 From: bobzel Date: Sat, 27 Jan 2024 04:21:08 -0500 Subject: made dataViz nodes linked to schema nodes update automatically as cahnges are made. fixed user created templates from disappearing from menu, and made them work. added toJavascriptString and made DashField views convert to text. added support for turning text into javascript rendering (paint) code. --- src/client/views/linking/LinkMenuItem.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index dc4aee1ca..ad6deeefb 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -136,7 +136,7 @@ export class LinkMenuItem extends ObservableReactComponent { deleteLink = (e: React.PointerEvent): void => setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.deleteLink(this._props.linkDoc)))); @observable _hover = false; - docView = () => this.props.docView; + docView = () => this._props.docView; render() { const destinationIcon = Doc.toIcon(this._props.destinationDoc) as any as IconProp; -- cgit v1.2.3-70-g09d2 From a888150f2e220eff4629551aa03813f92aa0b12f Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 5 Feb 2024 22:56:04 -0500 Subject: changed backgroundColor to set on dataDocs. fixed pivoting on tags. fixed link description editing popup. fixed showing link editor in property view - still some weirdness in what is selected. fixed dragging tree view items to set dragData.treeview and be able to drop at bottom of tree. fixed addFolder menu option for TreeViews to add locally.. added a function to collect all docs of a given tag into a collection. fixed setting default font size to update autolayouts. changed dropping link onto same collection to not leave pushpin. fixed minimap thumb updating. added fieldvalue dropdown for dashFieldViews in text. --- src/client/documents/Documents.ts | 4 +- src/client/util/CurrentUserUtils.ts | 22 ++----- src/client/util/DragManager.ts | 2 +- src/client/util/LinkManager.ts | 4 +- src/client/util/SearchUtil.ts | 7 +- src/client/util/SelectionManager.ts | 4 +- src/client/util/SettingsManager.tsx | 12 +++- src/client/views/DocComponent.tsx | 4 +- src/client/views/DocumentDecorations.tsx | 3 + src/client/views/FilterPanel.tsx | 6 +- .../views/PropertiesDocBacklinksSelector.tsx | 2 +- src/client/views/PropertiesSection.scss | 26 ++++---- src/client/views/PropertiesView.tsx | 74 ++++++++++++---------- src/client/views/StyleProvider.tsx | 2 +- .../views/collections/CollectionTimeView.tsx | 7 +- .../views/collections/CollectionTreeView.tsx | 73 ++++++++++++++------- src/client/views/collections/TabDocView.tsx | 1 + src/client/views/collections/TreeView.scss | 6 +- src/client/views/collections/TreeView.tsx | 34 +++++----- .../CollectionFreeFormLinkView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 30 +++------ src/client/views/global/globalScripts.ts | 7 +- src/client/views/linking/LinkMenuItem.tsx | 6 +- src/client/views/nodes/DocumentLinksButton.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 34 ++++++++-- src/client/views/nodes/LabelBox.tsx | 2 +- src/client/views/nodes/LinkBox.scss | 25 ++++++++ src/client/views/nodes/LinkBox.tsx | 69 +++++++++++++------- src/client/views/nodes/LinkDescriptionPopup.tsx | 18 ++++-- src/client/views/nodes/LinkDocPreview.tsx | 4 +- .../views/nodes/formattedText/DashFieldView.scss | 10 +++ .../views/nodes/formattedText/DashFieldView.tsx | 27 ++++++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 11 ++-- .../views/nodes/formattedText/RichTextRules.ts | 2 +- src/fields/Doc.ts | 2 +- src/fields/List.ts | 2 +- 36 files changed, 343 insertions(+), 207 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index cc983ffa7..95058da22 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -437,7 +437,7 @@ export class DocumentOptions { clickFactory?: DOCt = new DocInfo('document to create when clicking on a button with a suitable onClick script', false); onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop target?: Doc; // available for use in scripts. used to provide a document parameter to the script (Note, this is a convenience entry since any field could be used for parameterizing a script) - + tags?: LISTt = new ListInfo('hashtags added to document, typically using a text view', true); treeView_HideTitle?: BOOLt = new BoolInfo('whether to hide the top document title of a tree view'); treeView_HideUnrendered?: BOOLt = new BoolInfo("tells tree view not to display documents that have an 'layout_unrendered' tag unless they also have a treeView_FieldKey tag (presBox)"); treeView_HideHeaderIfTemplate?: BOOLt = new BoolInfo('whether to hide the header for a document in a tree view only if a childLayoutTemplate is provided (presBox)'); @@ -1459,7 +1459,7 @@ export namespace DocUtils { const makeLink = action((linkDoc: Doc, showPopup?: number[]) => { if (showPopup) { - LinkManager.currentLink = linkDoc; + LinkManager.Instance.currentLink = linkDoc; TaskCompletionBox.textDisplayed = 'Link Created'; TaskCompletionBox.popupX = showPopup[0]; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 31f0308b7..714e33d25 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -17,7 +17,7 @@ import { CollectionViewType, DocumentType } from "../documents/DocumentTypes"; import { DocUtils, Docs, DocumentOptions, FInfo } from "../documents/Documents"; import { DashboardView } from "../views/DashboardView"; import { OverlayView } from "../views/OverlayView"; -import { TreeViewType } from "../views/collections/CollectionTreeView"; +import { CollectionTreeView, TreeViewType } from "../views/collections/CollectionTreeView"; import { Colors } from "../views/global/globalEnums"; import { media_state } from "../views/nodes/AudioBox"; import { OpenWhere } from "../views/nodes/DocumentView"; @@ -78,7 +78,7 @@ export class CurrentUserUtils { const tempClicks = DocCast(doc[field]); const reqdClickOpts:DocumentOptions = {_width: 300, _height:200, isSystem: true}; const reqdTempOpts:{opts:DocumentOptions, script: string}[] = [ - { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([self])))"}, + { opts: { title: "Open In Target", targetScriptKey: "onChildClick"}, script: "docCastAsync(documentView?.containerViewPath().lastElement()?.Document.target).then((target) => target && (target.proto.data = new List([this])))"}, { opts: { title: "Open Detail On Right", targetScriptKey: "onChildDoubleClick"}, script: `openDoc(this.doubleClickView.${OpenWhere.addRight})`}]; const reqdClickList = reqdTempOpts.map(opts => { const allOpts = {...reqdClickOpts, ...opts.opts}; @@ -502,29 +502,21 @@ export class CurrentUserUtils { static setupFilesystem(doc: Doc, field:string) { var myFilesystem = DocCast(doc[field]); - const newFolder = `TreeView_addNewFolder()`; const newFolderOpts: DocumentOptions = { - _forceActive: true, _dragOnlyWithinContainer: true, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List(['treeView_SortCriterion']), + _forceActive: true, _dragOnlyWithinContainer: true, _embedContainer: Doc.MyFilesystem, _layout_hideContextMenu: true, _width: 30, _height: 30, undoIgnoreFields:new List(['treeView_SortCriterion']), title: "New folder", color: Colors.BLACK, btnType: ButtonType.ClickButton, toolTip: "Create new folder", buttonText: "New folder", icon: "folder-plus", isSystem: true }; - const newFolderScript = { onClick: newFolder}; + const newFolderScript = { onClick: CollectionTreeView.AddTreeFunc}; const newFolderButton = DocUtils.AssignScripts(DocUtils.AssignOpts(DocCast(myFilesystem?.layout_headerButton), newFolderOpts) ?? Docs.Create.FontIconDocument(newFolderOpts), newFolderScript); const reqdOpts:DocumentOptions = { _layout_showTitle: "title", _height: 100, _forceActive: true, title: "My Documents", layout_headerButton: newFolderButton, treeView_HideTitle: true, dropAction: 'add', isSystem: true, isFolder: true, treeView_Type: TreeViewType.fileSystem, childHideLinkButton: true, layout_boxShadow: "0 0", childDontRegisterViews: true, treeView_TruncateTitleWidth: 350, ignoreClick: true, childDragAction: "embed", - childContextMenuLabels: new List(["Create new folder"]), - childContextMenuIcons: new List(["plus"]), layout_explainer: "This is your file manager where you can create folders to keep track of documents independently of your dashboard." }; const fileFolders = new Set(DocListCast(DocCast(doc[field])?.data)); - myFilesystem = DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders)); - const childContextMenuScripts = [newFolder]; - if (Cast(myFilesystem.childContextMenuScripts, listSpec(ScriptField), null)?.length !== childContextMenuScripts.length) { - myFilesystem.childContextMenuScripts = new List(childContextMenuScripts.map(script => ScriptField.MakeFunction(script)!)); - } - return myFilesystem; + return DocUtils.AssignDocField(doc, field, (opts, items) => Docs.Create.TreeDocument(items??[], opts), reqdOpts, Array.from(fileFolders)); } /// initializes the panel displaying docs that have been recently closed @@ -544,8 +536,8 @@ export class CurrentUserUtils { toolTip: "Empty recently closed",}; DocUtils.AssignDocField(recentlyClosed, "layout_headerButton", (opts) => Docs.Create.FontIconDocument(opts), clearBtnsOpts, undefined, {onClick: clearAll("this.target")}); - if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("self"))) { - recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("self"))!]) + if (!Cast(recentlyClosed.contextMenuScripts, listSpec(ScriptField),null)?.find((script) => script?.script.originalScript === clearAll("this"))) { + recentlyClosed.contextMenuScripts = new List([ScriptField.MakeScript(clearAll("this"))!]) } return recentlyClosed; } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index a6ad0f1b3..071b25a0e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -243,7 +243,7 @@ export namespace DragManager { }; dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, finishDrag); - dragData.draggedViews.forEach(view => view.props.dragStarting?.()); + dragData.draggedViews.forEach(view => view.props.dragStarting?.(dragData)); return true; } diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 353f28a92..dd3b9bd07 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -23,8 +23,8 @@ import { ScriptingGlobals } from './ScriptingGlobals'; export class LinkManager { @observable static _instance: LinkManager; @observable.shallow userLinkDBs: Doc[] = []; - @observable public static currentLink: Opt = undefined; - @observable public static currentLinkAnchor: Opt = undefined; + @observable public currentLink: Opt = undefined; + @observable public currentLinkAnchor: Opt = undefined; public static get Instance() { return LinkManager._instance; } diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 218667d3e..fff2737b6 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -8,7 +8,7 @@ import { DocOptions, FInfo } from '../documents/Documents'; export namespace SearchUtil { export type HighlightingResult = { [id: string]: { [key: string]: string[] } }; - export function SearchCollection(collectionDoc: Opt, query: string, matchKeyNames: boolean) { + export function SearchCollection(collectionDoc: Opt, query: string, matchKeyNames: boolean, onlyKeys?: string[]) { const blockedTypes = [DocumentType.PRESELEMENT, DocumentType.CONFIG, DocumentType.KVP, DocumentType.FONTICON, DocumentType.BUTTON, DocumentType.SCRIPTING]; const blockedKeys = matchKeyNames ? [] @@ -27,8 +27,9 @@ export namespace SearchUtil { const dtype = StrCast(doc.type) as DocumentType; if (dtype && !blockedTypes.includes(dtype) && !docIDs.includes(doc[Id]) && depth >= 0) { const hlights = new Set(); - SearchUtil.documentKeys(doc).forEach( - key => (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))( + (onlyKeys ?? SearchUtil.documentKeys(doc)).forEach( + key => + (val => (exact ? val === query.toLowerCase() : val.includes(query.toLowerCase())))( matchKeyNames ? key : Field.toString(doc[key] as Field)) && hlights.add(key) ); // prettier-ignore diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index f2a327445..b79281802 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -51,8 +51,8 @@ export class SelectionManager { public static DeselectAll = (except?: Doc): void => { const found = this.Instance.SelectedViews.find(dv => dv.Document === except); - LinkManager.currentLink = undefined; - LinkManager.currentLinkAnchor = undefined; + LinkManager.Instance.currentLink = undefined; + LinkManager.Instance.currentLinkAnchor = undefined; runInAction(() => (this.Instance.SelectedSchemaDocument = undefined)); this.Instance.SelectedViews.forEach(dv => { dv.IsSelected = false; diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index 5bf9e5b00..682704770 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -317,7 +317,17 @@ export class SettingsManager extends React.Component<{}> {
{/* */} - {}} /> + (Doc.UserDoc().fontSize = val + 'px')} + /> { return { diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 58be41f53..3c772bd42 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -47,7 +47,7 @@ export interface ViewBoxInterface { setData?: (data: Field | Promise) => boolean; componentUI?: (boundsLeft: number, boundsTop: number) => JSX.Element | null; dragStarting?: (snapToDraggedDoc: boolean, showGroupDragTarget: boolean, visited: Set) => void; - dragConfig?: (dragData: DragManager.DocumentDragData) => void; + dragConfig?: (dragData: DragManager.DocumentDragData) => void; // function to setup dragData in custom way (see TreeViews which add a tree view flag) incrementalRendering?: () => void; infoUI?: () => JSX.Element | null; screenBounds?: () => Opt<{ left: number; top: number; right: number; bottom: number; center?: { X: number; Y: number } }>; @@ -57,7 +57,7 @@ export interface ViewBoxInterface { search?: (str: string, bwd?: boolean, clear?: boolean) => boolean; } /** - * DocComponent returns a React base class used by Doc views with accessors for unpacking he Document,layoutDoc, and dataDoc's + * DocComponent returns a React base class used by Doc views with accessors for unpacking the Document,layoutDoc, and dataDoc's * (note: this should not be used for the 'Box' views that render the contents of Doc views) * Example derived views: CollectionFreeFormDocumentView, DocumentView, DocumentViewInternal) * */ diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index dec109d7b..e9c4d9cc5 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -114,6 +114,9 @@ export class DocumentDecorations extends ObservableReactComponent#')) { + SelectionManager.Docs.forEach(doc => (doc[DocData].onViewMounted = ScriptField.MakeScript(`updateTagsCollection(this)`))); + } const titleFieldKey = this._titleControlString.substring(1); UndoManager.RunInBatch( () => diff --git a/src/client/views/FilterPanel.tsx b/src/client/views/FilterPanel.tsx index c4f65a5ca..818c81c9a 100644 --- a/src/client/views/FilterPanel.tsx +++ b/src/client/views/FilterPanel.tsx @@ -117,8 +117,8 @@ export class FilterPanel extends ObservableReactComponent { // return this.activeFilters.map(filter => filter.split(Doc.FilterSep)[0]); // } - gatherFieldValues(childDocs: Doc[], facetKey: string) { - const valueSet = new Set(StrListCast(this.targetDoc.childFilters).map(filter => filter.split(Doc.FilterSep)[1])); + static gatherFieldValues(childDocs: Doc[], facetKey: string, childFilters: string[]) { + const valueSet = new Set(childFilters.map(filter => filter.split(Doc.FilterSep)[1])); let rtFields = 0; let subDocs = childDocs; if (subDocs.length > 0) { @@ -165,7 +165,7 @@ export class FilterPanel extends ObservableReactComponent { @computed get activeRenderedFacetInfos() { return new Set( Array.from(new Set(Array.from(this._selectedFacetHeaders).concat(this.activeFacetHeaders))).map(facetHeader => { - const facetValues = this.gatherFieldValues(this.targetDocChildren, facetHeader); + const facetValues = FilterPanel.gatherFieldValues(this.targetDocChildren, facetHeader, StrListCast(this.targetDoc.childFilters)); let nonNumbers = 0; let minVal = Number.MAX_VALUE, diff --git a/src/client/views/PropertiesDocBacklinksSelector.tsx b/src/client/views/PropertiesDocBacklinksSelector.tsx index 244cd4aa0..cf5105efc 100644 --- a/src/client/views/PropertiesDocBacklinksSelector.tsx +++ b/src/client/views/PropertiesDocBacklinksSelector.tsx @@ -25,7 +25,7 @@ export class PropertiesDocBacklinksSelector extends React.Component LinkManager.currentLink, + () => LinkManager.Instance.currentLink, link => { link && this.CloseAll(); link && (this.openLinks = true); @@ -173,10 +173,14 @@ export class PropertiesView extends ObservableReactComponent(reqdKeys); const docs: Doc[] = SelectionManager.Views.length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.Views.map(dv => (this.layoutFields ? dv.layoutDoc : dv.dataDoc)); - docs.forEach(doc => Object.keys(doc).forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key))); + docs.forEach(doc => + Object.keys(doc) + .filter(filter) + .forEach(key => doc[key] !== ComputedField.undefined && key && ids.add(key)) + ); // prettier-ignore - Array.from(ids).filter(filter).sort().map(key => { + Array.from(ids).sort().map(key => { const multiple = Array.from(docs.reduce((set,doc) => set.add(doc[key]), new Set()).keys()).length > 1; const editableContents = multiple ? '-multiple-' : Field.toKeyValueString(docs[0], key); const displayContents = multiple ? '-multiple-' : Field.toString(docs[0][key] as Field); @@ -270,12 +274,12 @@ export class PropertiesView extends ObservableReactComponent; } @computed get linkCount() { - const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor ?? this.selectedDoc; + const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor ?? this.selectedDoc; var counter = 0; LinkManager.Links(selAnchor).forEach((l, i) => counter++); @@ -571,19 +575,19 @@ export class PropertiesView extends ObservableReactComponent - {LinkManager.currentLinkAnchor ? ( + {LinkManager.Instance.currentLinkAnchor ? (

<> Anchor: - {LinkManager.currentLinkAnchor.title} + {LinkManager.Instance.currentLinkAnchor.title}

) : null} - {LinkManager.currentLink?.title ? ( + {LinkManager.Instance.currentLink?.title ? (

<> Link: - {LinkManager.currentLink.title} + {LinkManager.Instance.currentLink.title}

) : null} @@ -1222,10 +1226,10 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.currentLink && this.selectedDoc) { + if (LinkManager.Instance.currentLink && this.selectedDoc) { this.setDescripValue(value); } }), @@ -1244,7 +1248,7 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.currentLink && this.selectedDoc) { + if (LinkManager.Instance.currentLink && this.selectedDoc) { this.setlinkRelationshipValue(value); } }), @@ -1253,17 +1257,17 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.currentLink) { - Doc.GetProto(LinkManager.currentLink).link_description = value; + if (LinkManager.Instance.currentLink) { + Doc.GetProto(LinkManager.Instance.currentLink).link_description = value; } }); @undoBatch setlinkRelationshipValue = action((value: string) => { - if (LinkManager.currentLink) { - const prevRelationship = StrCast(LinkManager.currentLink.link_relationship); - LinkManager.currentLink.link_relationship = value; - Doc.GetProto(LinkManager.currentLink).link_relationship = value; + if (LinkManager.Instance.currentLink) { + const prevRelationship = StrCast(LinkManager.Instance.currentLink.link_relationship); + LinkManager.Instance.currentLink.link_relationship = value; + Doc.GetProto(LinkManager.Instance.currentLink).link_relationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList); const linkRelationshipSizes = NumListCast(Doc.UserDoc().link_relationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().link_ColorList); @@ -1347,20 +1351,20 @@ export class PropertiesView extends ObservableReactComponent { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.currentLink && (LinkManager.currentLink[prop] = !LinkManager.currentLink[prop])))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[prop] = !LinkManager.Instance.currentLink[prop])))); }; @computed get destinationAnchor() { - const ldoc = LinkManager.currentLink; - const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; + const ldoc = LinkManager.Instance.currentLink; + const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor; if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch; return ldoc ? DocCast(ldoc.link_anchor_2) : ldoc; } @computed get sourceAnchor() { - const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.currentLinkAnchor; + const selAnchor = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor; - return selAnchor ?? (LinkManager.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.currentLink, this.destinationAnchor) : LinkManager.currentLink); + return selAnchor ?? (LinkManager.Instance.currentLink && this.destinationAnchor ? LinkManager.getOppositeAnchor(LinkManager.Instance.currentLink, this.destinationAnchor) : LinkManager.Instance.currentLink); } toggleAnchorProp = (e: React.PointerEvent, prop: string, anchor?: Doc, value: any = true, ovalue: any = false, cb: (val: any) => any = val => val) => { @@ -1386,7 +1390,7 @@ export class PropertiesView extends ObservableReactComponent this.handlelinkRelationshipChange(e.currentTarget.value)} @@ -1403,7 +1407,7 @@ export class PropertiesView extends ObservableReactComponent this.handleDescriptionChange(e.currentTarget.value)} @@ -1425,7 +1429,7 @@ export class PropertiesView extends ObservableReactComponent @@ -1441,7 +1445,7 @@ export class PropertiesView extends ObservableReactComponent

Show link

@@ -1671,7 +1675,7 @@ export class PropertiesView extends ObservableReactComponent @@ -1692,7 +1696,7 @@ export class PropertiesView extends ObservableReactComponent
-
+
Properties
window.open('https://brown-dash.github.io/Dash-Documentation/properties')}> } color={SettingsManager.userColor} /> @@ -1702,12 +1706,12 @@ export class PropertiesView extends ObservableReactComponent{this.editableTitle}
{this.currentType}
+ {this.fieldsSubMenu} {this.optionsSubMenu} {this.linksSubMenu} - {!LinkManager.currentLink || !this.openLinks ? null : this.linkProperties} + {!LinkManager.Instance.currentLink || !this.openLinks ? null : this.linkProperties} {this.inkSubMenu} {this.contextsSubMenu} - {this.fieldsSubMenu} {isNovice ? null : this.sharingSubMenu} {this.filtersSubMenu} {isNovice ? null : this.layoutSubMenu} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index fa0be225e..0e38790b6 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -191,7 +191,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?._backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : '')); + let docColor: Opt = StrCast(doc?.[fieldKey + 'backgroundColor'], StrCast(doc?.backgroundColor, isCaption ? 'rgba(0,0,0,0.4)' : '')); // prettier-ignore switch (doc?.type) { case DocumentType.PRESELEMENT: docColor = docColor || ""; break; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index ee5147428..38f6aa3e7 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -200,8 +200,7 @@ export class CollectionTimeView extends CollectionSubView() { } menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); - const docItems: ContextMenuProps[] = []; - const keySet: Set = new Set(); + const keySet: Set = new Set(['tags']); this.childLayoutPairs.map(pair => this._allFacets @@ -209,7 +208,9 @@ export class CollectionTimeView extends CollectionSubView() { .filter(fieldKey => fieldKey[0] !== '_' && (fieldKey === 'tags' || fieldKey[0] === toUpper(fieldKey)[0])) .map(fieldKey => keySet.add(fieldKey)) ); - Array.from(keySet).map(fieldKey => docItems.push({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); + + const docItems: ContextMenuProps[] = Array.from(keySet).map(fieldKey => + ({ description: ':' + fieldKey, event: () => (this.layoutDoc._pivotField = fieldKey), icon: 'compress-arrows-alt' })); // prettier-ignore docItems.push({ description: ':default', event: () => (this.layoutDoc._pivotField = undefined), icon: 'compress-arrows-alt' }); ContextMenu.Instance.addItem({ description: 'Pivot Fields ...', subitems: docItems, icon: 'eye' }); ContextMenu.Instance.displayMenu(x, y, ':'); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 741013148..786301136 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -8,8 +8,8 @@ import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero } from '../../../Utils'; -import { DocUtils } from '../../documents/Documents'; +import { emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnOne, returnTrue, returnZero, Utils } from '../../../Utils'; +import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; @@ -27,6 +27,7 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { @@ -51,6 +52,7 @@ export enum TreeViewType { @observer export class CollectionTreeView extends CollectionSubView>() { + public static AddTreeFunc = 'addTreeFolder(this.embedContainer)'; private _treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; private _titleRef?: HTMLDivElement | HTMLInputElement | null; @@ -140,6 +142,17 @@ export class CollectionTreeView extends CollectionSubView { + if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc); + }); + } + return res; + } + protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, dropAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData) { @@ -150,9 +163,7 @@ export class CollectionTreeView extends CollectionSubView { - dragData.treeViewDoc = this.Document; - }; + dragConfig = (dragData: DragManager.DocumentDragData) => (dragData.treeViewDoc = this.Document); screenToLocalTransform = () => this.ScreenToLocalBoxXf().translate(0, -this._headerHeight); @@ -163,34 +174,41 @@ export class CollectionTreeView extends CollectionSubView !docs.includes(v)); if ((doc instanceof Doc ? [doc] : doc).some(doc => SelectionManager.Views.some(dv => Doc.AreProtosEqual(dv.Document, doc)))) SelectionManager.DeselectAll(); - if (result.length !== value.length && doc instanceof Doc) { - const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc); - const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1]; - this._props.removeDocument?.(doc); - if (ind > 0 && prev) { - FormattedTextBox.SetSelectOnLoad(prev); - DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false); + if (result.length !== value.length) { + if (doc instanceof Doc) { + const ind = DocListCast(targetDataDoc[this._props.fieldKey]).indexOf(doc); + const prev = ind && DocListCast(targetDataDoc[this._props.fieldKey])[ind - 1]; + this._props.removeDocument?.(doc); + if (ind > 0 && prev) { + FormattedTextBox.SetSelectOnLoad(prev); + DocumentManager.Instance.getDocumentView(prev, this.DocumentView?.())?.select(false); + } + return true; } - return true; + return this._props.removeDocument?.(doc) ?? false; } return false; }; @action addDoc = (docs: Doc | Doc[], relativeTo: Opt, before?: boolean): boolean => { - const doAddDoc = (doc: Doc | Doc[]) => - (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => { - const res = flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before); - res && Doc.SetContainer(doc, this.Document); - return res; - }, true); + const doclist = docs instanceof Doc ? [docs] : docs; + const addDocRelativeTo = (doc: Doc | Doc[]) => doclist.reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true); if (this.Document.resolvedDataDoc instanceof Promise) return false; - return relativeTo === undefined ? this._props.addDocument?.(docs) || false : doAddDoc(docs); + const res = relativeTo === undefined ? this._props.addDocument?.(docs) || false : addDocRelativeTo(docs); + res && + doclist.forEach(doc => { + Doc.SetContainer(doc, this.Document); + if (this.Document !== Doc.MyRecentlyClosed) Doc.RemoveDocFromList(Doc.MyRecentlyClosed, undefined, doc); + }); + return res; }; onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout + const layoutItems: ContextMenuProps[] = []; + const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick).script.originalScript === CollectionTreeView.AddTreeFunc; + menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' }); if (!Doc.noviceMode) { - const layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: 'Make tree state ' + (this.Document.treeView_OpenIsTransient ? 'persistent' : 'transient'), event: () => (this.Document.treeView_OpenIsTransient = !this.Document.treeView_OpenIsTransient), @@ -198,7 +216,9 @@ export class CollectionTreeView extends CollectionSubView (this.Document.treeView_HideHeaderFields = !this.Document.treeView_HideHeaderFields), icon: 'paint-brush' }); layoutItems.push({ description: (this.Document.treeView_HideTitle ? 'Show' : 'Hide') + ' Title', event: () => (this.Document.treeView_HideTitle = !this.Document.treeView_HideTitle), icon: 'paint-brush' }); - ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); + } + ContextMenu.Instance.addItem({ description: 'Options...', subitems: layoutItems, icon: 'eye' }); + if (!Doc.noviceMode) { const existingOnClick = ContextMenu.Instance.findByDescription('OnClick...'); const onClicks: ContextMenuProps[] = existingOnClick && 'subitems' in existingOnClick ? existingOnClick.subitems : []; onClicks.push({ description: 'Edit onChecked Script', event: () => UndoManager.RunInBatch(() => DocUtils.makeCustomViewClicked(this.Document, undefined, 'onCheckedClick'), 'edit onCheckedClick'), icon: 'edit' }); @@ -467,4 +487,13 @@ export class CollectionTreeView extends CollectionSubView ); } + static addTreeFolder(container: Doc) { + TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; + const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }; + return Doc.AddDocToList(container, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); + } } + +ScriptingGlobals.add(function addTreeFolder(doc: Doc) { + CollectionTreeView.addTreeFolder(doc); +}); diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 9bc3ef822..02aa76d82 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -520,6 +520,7 @@ interface TabMiniThumbProps { miniLeft: () => number; } +@observer class TabMiniThumb extends React.Component { render() { return
; diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 09701ddb5..2ab1a5ac1 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -224,13 +224,13 @@ } .treeView-header-above { - border-top: black 1px solid; + border-top: red 1px solid; } .treeView-header-below { - border-bottom: black 1px solid; + border-bottom: red 1px solid; } .treeView-header-inside { - border: black 1px solid; + border: red 1px solid; } diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index be5737a25..85f7cf7fe 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -383,7 +383,7 @@ export class TreeView extends ObservableReactComponent { makeFolder = () => { const folder = Docs.Create.TreeDocument([], { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }); TreeView._editTitleOnLoad = { id: folder[Id], parent: this._props.parentTreeView }; - return this._props.addDocument(folder); + return this.localAdd(folder); }; preTreeDrop = (e: Event, de: DragManager.DropEvent, docDropAction: dropActionType) => { @@ -424,6 +424,16 @@ export class TreeView extends ObservableReactComponent { return false; }; + localAdd = (doc: Doc | Doc[]) => { + const innerAdd = (doc: Doc) => { + const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; + const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); + dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); + return added; + }; + return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); + }; + dropping: boolean = false; dropDocuments( droppedDocuments: Doc[], @@ -436,16 +446,8 @@ export class TreeView extends ObservableReactComponent { canEmbed?: boolean ) { const parentAddDoc = (doc: Doc | Doc[]) => this._props.addDocument(doc, undefined, undefined, before); - const localAdd = (doc: Doc | Doc[]) => { - const innerAdd = (doc: Doc) => { - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; - const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); - return added; - }; - return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); - }; - const addDoc = inside ? localAdd : parentAddDoc; + + const addDoc = inside ? this.localAdd : parentAddDoc; const canAdd = !StrCast((inside ? this.Document : this._props.treeViewParent)?.treeView_FreezeChildren).includes('add') || forceAdd; if (canAdd && (dropAction !== 'inSame' || droppedDocuments.every(d => d.embedContainer === this._props.parentTreeView?.Document))) { const move = (!dropAction || canEmbed || dropAction === 'proto' || dropAction === 'move' || dropAction === 'same' || dropAction === 'inSame') && moveDocument; @@ -839,14 +841,13 @@ export class TreeView extends ObservableReactComponent { }; contextMenuItems = () => { const makeFolder = { script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: 'any' })!, icon: 'folder-plus', label: 'New Folder' }; - const folderOp = this.childDocs?.length ? [makeFolder] : []; const openEmbedding = { script: ScriptField.MakeFunction(`openDoc(getEmbedding(this), "${OpenWhere.addRight}")`)!, icon: 'copy', label: 'Open New Embedding' }; const focusDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Focus or Open' }; const reopenDoc = { script: ScriptField.MakeFunction(`DocFocusOrOpen(this)`)!, icon: 'eye', label: 'Reopen' }; return [ ...(this._props.contextMenuItems ?? []).filter(mi => (!mi.filter ? true : mi.filter.script.run({ doc: this.Document })?.result)), ...(this.Document.isFolder - ? folderOp + ? [makeFolder] : Doc.IsSystem(this.Document) ? [] : this.treeView.fileSysMode && this.Document === this.Document[DocData] @@ -993,6 +994,7 @@ export class TreeView extends ObservableReactComponent { onClickScript={this.onChildClick} onDoubleClickScript={this.onChildDoubleClick} dragAction={this._props.dragAction} + dragConfig={this.treeView.dragConfig} moveDocument={this.move} removeDocument={this._props.removeDoc} ScreenToLocalTransform={this.getTransform} @@ -1328,9 +1330,3 @@ export class TreeView extends ObservableReactComponent { }); } } - -ScriptingGlobals.add(function TreeView_addNewFolder() { - TreeView._editTitleOnLoad = { id: Utils.GenerateGuid(), parent: undefined }; - const opts = { title: 'Untitled folder', _dragOnlyWithinContainer: true, isFolder: true }; - return Doc.AddDocToList(Doc.MyFilesystem, 'data', Docs.Create.TreeDocument([], opts, TreeView._editTitleOnLoad.id)); -}); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index f0a31a8c6..a45a1fb0f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -127,7 +127,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent { SelectionManager.DeselectAll(); SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); - LinkManager.currentLink = this._props.LinkDocs[0]; + LinkManager.Instance.currentLink = this._props.LinkDocs[0]; this.toggleProperties(); // OverlayView.Instance.addElement( // { SelectionManager.DeselectAll(); SelectionManager.SelectSchemaViewDoc(this._props.LinkDocs[0], true); - LinkManager.currentLink = this._props.LinkDocs[0]; + LinkManager.Instance.currentLink = this._props.LinkDocs[0]; this.toggleProperties(); }; @@ -295,7 +295,7 @@ export class CollectionFreeFormLinkView extends ObservableReactComponent { this.dataDoc[this.autoResetFieldKey] = !this.dataDoc[this.autoResetFieldKey]; if (this.dataDoc[this.autoResetFieldKey]) { - this.dataDoc[this.panXFieldKey + '_reset'] = this.dataDoc[this.panXFieldKey]; - this.dataDoc[this.panYFieldKey + '_reset'] = this.dataDoc[this.panYFieldKey]; - this.dataDoc[this.scaleFieldKey + '_reset'] = this.dataDoc[this.scaleFieldKey]; + this.dataDoc[this.panXFieldKey + '_reset'] = this.layoutDoc[this.panXFieldKey]; + this.dataDoc[this.panYFieldKey + '_reset'] = this.layoutDoc[this.panYFieldKey]; + this.dataDoc[this.scaleFieldKey + '_reset'] = this.layoutDoc[this.scaleFieldKey]; } }; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 3084a7972..813cb9338 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -20,6 +20,7 @@ import { DocumentView } from '../nodes/DocumentView'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { WebBox } from '../nodes/WebBox'; import { VideoBox } from '../nodes/VideoBox'; +import { DocData } from '../../../fields/DocSymbols'; ScriptingGlobals.add(function IsNoneSelected() { return SelectionManager.Views.length <= 0; @@ -57,16 +58,16 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b obj[fieldKey] = color; CollectionFreeFormDocumentView.setStringValues(contentFrameNumber, dv.Document, obj); } else { - dv.Document['_' + fieldKey] = color; + dv.Document[DocData][fieldKey] = color; } }); } else { - const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.currentLink ? [LinkManager.currentLink] : []; + const selected = SelectionManager.Docs.length ? SelectionManager.Docs : LinkManager.Instance.currentLink ? [LinkManager.Instance.currentLink] : []; if (checkResult) { return selected.lastElement()?._backgroundColor ?? 'transparent'; } SetActiveFillColor(color ?? 'transparent'); - selected.forEach(doc => (doc._backgroundColor = color)); + selected.forEach(doc => (doc[DocData].backgroundColor = color)); } }); diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index ad6deeefb..7427f4310 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -92,8 +92,8 @@ export class LinkMenuItem extends ObservableReactComponent { DocumentViewInternal.addDocTabFunc(trail, OpenWhere.replaceRight); } else { SelectionManager.SelectView(this._props.docView, false); - LinkManager.currentLink = this._props.linkDoc === LinkManager.currentLink ? undefined : this._props.linkDoc; - LinkManager.currentLinkAnchor = LinkManager.currentLink ? this.sourceAnchor : undefined; + LinkManager.Instance.currentLink = this._props.linkDoc === LinkManager.Instance.currentLink ? undefined : this._props.linkDoc; + LinkManager.Instance.currentLinkAnchor = LinkManager.Instance.currentLink ? this.sourceAnchor : undefined; if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { setTimeout(action(() => (SettingsManager.Instance.propertiesWidth = 250))); @@ -159,7 +159,7 @@ export class LinkMenuItem extends ObservableReactComponent { style={{ fontSize: this._hover ? 'larger' : undefined, fontWeight: this._hover ? 'bold' : undefined, - background: LinkManager.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, + background: LinkManager.Instance.currentLink === this._props.linkDoc ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, }}>
number; NativeHeight?: () => number; contextMenuItems?: () => { script: ScriptField; filter?: ScriptField; label: string; icon: string }[]; + dragConfig?: (data: DragManager.DocumentDragData) => void; dragStarting?: () => void; dragEnding?: () => void; } @@ -288,7 +290,7 @@ export class DocumentViewInternal extends DocComponent dv.ContentDiv!), dragData, @@ -1466,7 +1468,7 @@ ScriptingGlobals.add(function toggleDetail(dv: DocumentView, detailLayoutKeySuff }); ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSource: Doc) { - const collectedLinks = DocListCast(Doc.GetProto(linkCollection).data); + const collectedLinks = DocListCast(linkCollection[DocData].data); let wid = NumCast(linkSource._width); let embedding: Doc | undefined; const links = LinkManager.Links(linkSource); @@ -1485,3 +1487,27 @@ ScriptingGlobals.add(function updateLinkCollection(linkCollection: Doc, linkSour embedding && DocServer.UPDATE_SERVER_CACHE(); // if a new embedding was made, update the client's server cache so that it will not come back as a promise return links; }); +ScriptingGlobals.add(function updateTagsCollection(collection: Doc) { + const tag = StrCast(collection.title).split('-->')[1]; + const matchedTags = Array.from(SearchUtil.SearchCollection(Doc.MyFilesystem, tag, false, ['tags']).keys()); + const collectionDocs = DocListCast(collection[DocData].data).concat(collection); + let wid = 100; + let created = false; + const matchedDocs = matchedTags + .filter(tagDoc => !Doc.AreProtosEqual(collection, tagDoc)) + .map(tagDoc => { + let embedding = collectionDocs.find(doc => Doc.AreProtosEqual(tagDoc, doc)); + if (!embedding) { + embedding = Doc.MakeEmbedding(tagDoc); + embedding.x = wid; + embedding.y = 0; + embedding._lockedPosition = false; + wid += NumCast(tagDoc._width); + created = true; + } + return embedding; + }); + + created && (collection[DocData].data = new List(matchedDocs)); + return true; +}); diff --git a/src/client/views/nodes/LabelBox.tsx b/src/client/views/nodes/LabelBox.tsx index 10eeff08d..fd3074a88 100644 --- a/src/client/views/nodes/LabelBox.tsx +++ b/src/client/views/nodes/LabelBox.tsx @@ -88,7 +88,7 @@ export class LabelBox extends ViewBoxBaseComponent() { @observable _mouseOver = false; @computed get hoverColor() { - return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.BackgroundColor); + return this._mouseOver ? StrCast(this.layoutDoc._hoverBackgroundColor) : this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor); } getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index 767f0291b..484fb301e 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -5,3 +5,28 @@ .linkBox-container { width: 100%; } + +.linkBox { + transition: inherit; + pointer-events: none; + position: absolute; + width: 100%; + height: 100%; + path { + transition: inherit; + fill: transparent; + } + svg { + transition: inherit; + overflow: visible; + } + text { + cursor: default; + text-anchor: middle; + font-size: 12; + stroke: black; + } + circle { + cursor: default; + } +} diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 8b6293806..7ade846e7 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -4,7 +4,7 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { Id } from '../../../fields/FieldSymbols'; import { DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { aggregateBounds, emptyFunction, returnAlways, returnFalse, Utils } from '../../../Utils'; +import { aggregateBounds, emptyFunction, returnAlways, returnFalse, setupMoveUpEvents, Utils } from '../../../Utils'; import { DocumentManager } from '../../util/DocumentManager'; import { Transform } from '../../util/Transform'; import { CollectionFreeFormView } from '../collections/collectionFreeForm'; @@ -13,6 +13,8 @@ import { StyleProp } from '../StyleProvider'; import { ComparisonBox } from './ComparisonBox'; import { FieldView, FieldViewProps } from './FieldView'; import './LinkBox.scss'; +import { LinkDescriptionPopup } from './LinkDescriptionPopup'; +import { LinkManager } from '../../util/LinkManager'; @observer export class LinkBox extends ViewBoxBaseComponent() { @@ -97,8 +99,8 @@ export class LinkBox extends ViewBoxBaseComponent() { ) { this.layoutDoc.x = params?.lx; this.layoutDoc.y = params?.ty; - this.layoutDoc._width = params.rx - params?.lx; - this.layoutDoc._height = params?.by - params?.ty; + this.layoutDoc._width = Math.max(1, params.rx - params?.lx); + this.layoutDoc._height = Math.max(1, params?.by - params?.ty); } } else { this.layoutDoc._width = Math.max(50, NumCast(this.layoutDoc._width)); @@ -108,6 +110,22 @@ export class LinkBox extends ViewBoxBaseComponent() { { fireImmediately: true } ); } + descriptionDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, () => { + LinkManager.Instance.currentLink = this.Document; + LinkDescriptionPopup.Instance.popupX = e.clientX; + LinkDescriptionPopup.Instance.popupY = e.clientY; + LinkDescriptionPopup.Instance.display = true; + + const rect = document.body.getBoundingClientRect(); + if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { + LinkDescriptionPopup.Instance.popupX -= 190; + } + if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { + LinkDescriptionPopup.Instance.popupY -= 40; + } + }); + }; componentWillUnmount(): void { this.disposer?.(); } @@ -117,19 +135,21 @@ export class LinkBox extends ViewBoxBaseComponent() { const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting); const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined; - const bez = new Bezier(this.renderProps.pts.map(p => ({ x: p[0], y: p[1] }))); - const text = bez.get(0.5); - const linkDesc = StrCast(this.dataDoc.link_description) || 'description'; + const { pts, lx, ty, rx, by } = this.renderProps; + const bez = new Bezier(pts.map(p => ({ x: p[0] - lx, y: p[1] - ty }))); + const { x, y } = bez.get(0.5); + const linkDesc = StrCast(this.dataDoc.link_description) || ' '; const strokeWidth = NumCast(this.dataDoc.stroke_width, 4); const dash = StrCast(this.Document.stroke_dash); const strokeDasharray = dash && Number(dash) ? String(strokeWidth * Number(dash)) : undefined; - const { pts, lx, ty, rx, by } = this.renderProps; + const pointerEvents = this._props.pointerEvents?.() === 'none' ? 'none' : 'all'; + const stroke = highlightColor ?? 'lightblue'; return ( -
- +
+ - + @@ -137,26 +157,27 @@ export class LinkBox extends ViewBoxBaseComponent() { - -   - {linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')} -   - + {!linkDesc.trim().length ? ( + + ) : ( + +  {linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : '')}  + + )}
); diff --git a/src/client/views/nodes/LinkDescriptionPopup.tsx b/src/client/views/nodes/LinkDescriptionPopup.tsx index 13f0ac4fc..1645d0813 100644 --- a/src/client/views/nodes/LinkDescriptionPopup.tsx +++ b/src/client/views/nodes/LinkDescriptionPopup.tsx @@ -1,10 +1,11 @@ -import { action, makeObservable, observable } from 'mobx'; +import { action, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DocData } from '../../../fields/DocSymbols'; import { LinkManager } from '../../util/LinkManager'; import './LinkDescriptionPopup.scss'; import { TaskCompletionBox } from './TaskCompletedBox'; +import { StrCast } from '../../../fields/Types'; @observer export class LinkDescriptionPopup extends React.Component<{}> { @@ -31,21 +32,26 @@ export class LinkDescriptionPopup extends React.Component<{}> { onDismiss = (add: boolean) => { this.display = false; if (add) { - LinkManager.currentLink && (LinkManager.currentLink[DocData].link_description = this.description); + LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[DocData].link_description = this.description); } + this.description = ''; }; @action onClick = (e: PointerEvent) => { if (this.popupRef && !!!this.popupRef.current?.contains(e.target as any)) { this.display = false; + this.description = ''; TaskCompletionBox.taskCompleted = false; } }; - @action componentDidMount() { document.addEventListener('pointerdown', this.onClick, true); + reaction( + () => this.display, + display => display && (this.description = StrCast(LinkManager.Instance.currentLink?.link_description)) + ); } componentWillUnmount() { @@ -65,8 +71,10 @@ export class LinkDescriptionPopup extends React.Component<{}> { className="linkDescriptionPopup-input" onKeyDown={e => e.stopPropagation()} onKeyPress={e => e.key === 'Enter' && this.onDismiss(true)} - placeholder={'(Optional) Enter link description...'} - onChange={e => this.descriptionChanged(e)}> + value={this.description} + placeholder={this.description || '(Optional) Enter link description...'} + onChange={e => this.descriptionChanged(e)} + />
this.onDismiss(false)}> {' '} diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 3f793b85e..ae25ff179 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -152,8 +152,8 @@ export class LinkDocPreview extends ObservableReactComponent { - LinkManager.currentLink = this._linkDoc; - LinkManager.currentLinkAnchor = this._linkSrc; + LinkManager.Instance.currentLink = this._linkDoc; + LinkManager.Instance.currentLinkAnchor = this._linkSrc; this._props.DocumentView?.().select(false); if ((SettingsManager.Instance.propertiesWidth ?? 0) < 100) { SettingsManager.Instance.propertiesWidth = 250; diff --git a/src/client/views/nodes/formattedText/DashFieldView.scss b/src/client/views/nodes/formattedText/DashFieldView.scss index 3426ba1a7..7a0ff8776 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.scss +++ b/src/client/views/nodes/formattedText/DashFieldView.scss @@ -5,6 +5,16 @@ display: inline-flex; align-items: center; + select { + display: none; + } + + &:hover { + select { + display: unset; + } + } + .dashFieldView-enumerables { width: 10px; height: 10px; diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index 18286267a..ec0b76aa8 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -4,21 +4,24 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; -import { Doc, Field } from '../../../../fields/Doc'; +import Select from 'react-select'; +import { Doc, DocListCast, Field } from '../../../../fields/Doc'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { SchemaHeaderField } from '../../../../fields/SchemaHeaderField'; -import { Cast } from '../../../../fields/Types'; +import { Cast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, returnZero, setupMoveUpEvents } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; import { CollectionViewType } from '../../../documents/DocumentTypes'; import { Transform } from '../../../util/Transform'; +import { undoable, undoBatch } from '../../../util/UndoManager'; import { AntimodeMenu, AntimodeMenuProps } from '../../AntimodeMenu'; import { SchemaTableCell } from '../../collections/collectionSchema/SchemaTableCell'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { OpenWhere } from '../DocumentView'; import './DashFieldView.scss'; import { FormattedTextBox } from './FormattedTextBox'; +import { FilterPanel } from '../../FilterPanel'; export class DashFieldView { dom: HTMLDivElement; // container for label and value @@ -97,7 +100,7 @@ export class DashFieldViewInternal extends ObservableReactComponent(); + _fieldRef = React.createRef(); @observable _dashDoc: Doc | undefined = undefined; @observable _expanded = false; @@ -180,10 +183,22 @@ export class DashFieldViewInternal extends ObservableReactComponent | undefined) => { + event && this._dashDoc && (this._dashDoc[this._fieldKey] = event.target.value); + }; + + @computed get values() { + const vals = FilterPanel.gatherFieldValues(DocListCast(Doc.ActiveDashboard?.data), this._fieldKey, []); + + return vals.strings.map(facet => ({ value: facet, label: facet })); + } + render() { return (
)} - {this._props.fieldKey.startsWith('#') ? null : this.fieldValueContent} +
); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index b82ab4219..f2c4c6c8f 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -13,7 +13,7 @@ import { EditorView } from 'prosemirror-view'; import * as React from 'react'; import { BsMarkdownFill } from 'react-icons/bs'; import { DateField } from '../../../../fields/DateField'; -import { Doc, DocListCast, Field, Opt } from '../../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../../fields/Doc'; import { AclAdmin, AclAugment, AclEdit, AclSelfEdit, DocCss, DocData, ForceServerWrite, UpdatingFromServer } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkTool } from '../../../../fields/InkField'; @@ -67,6 +67,7 @@ import { RichTextMenu, RichTextMenuPlugin } from './RichTextMenu'; import { RichTextRules } from './RichTextRules'; import { schema } from './schema_rts'; import { SummaryView } from './SummaryView'; +import Select from 'react-select'; // import * as applyDevTools from 'prosemirror-dev-tools'; @observer export class FormattedTextBox extends ViewBoxAnnotatableComponent() implements ViewBoxInterface { @@ -360,7 +361,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent(Array.from(new Set(accumTags))) : undefined; + if (accumTags.some(atag => !StrListCast(dataDoc.tags).includes(atag))) { + dataDoc.tags = new List(Array.from(new Set(accumTags))); + } let unchanged = true; if (this._applyingChange !== this.fieldKey && (force || removeSelection(newJson) !== removeSelection(prevData?.Data))) { @@ -1189,8 +1192,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent Doc.RecordingEvent, this.breakupDictation); this._disposers.layout_autoHeight = reaction( - () => this.layout_autoHeight, - layout_autoHeight => layout_autoHeight && this.tryUpdateScrollHeight() + () => ({ autoHeight: this.layout_autoHeight, fontSize: this.fontSize }), + (autoHeight, fontSize) => setTimeout(() => autoHeight && this.tryUpdateScrollHeight()) ); this._disposers.highlights = reaction( () => Array.from(FormattedTextBox._globalHighlights).slice(), diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 2fdd6374a..9bd41f42c 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -264,7 +264,7 @@ export class RichTextRules { return null; }), - // stop using active style + // toggle alternate text UI %/ new InputRule(new RegExp(/%\//), (state, match, start, end) => { setTimeout(this.TextBox.cycleAlternateText); return state.tr.deleteRange(start, end); diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index ab6d0390b..3cd4efcf7 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -390,7 +390,7 @@ export class Doc extends RefField { export namespace Doc { export function SetContainer(doc: Doc, container: Doc) { - doc.embedContainer = container; + container !== Doc.MyRecentlyClosed && (doc.embedContainer = container); } export function RunCachedUpdate(doc: Doc, field: string) { const update = doc[CachedUpdates][field]; diff --git a/src/fields/List.ts b/src/fields/List.ts index 9458a9611..ec31f7dae 100644 --- a/src/fields/List.ts +++ b/src/fields/List.ts @@ -317,7 +317,7 @@ class ListImpl extends ObjectField { return `new List([${(this as any).map((field: any) => Field.toScriptString(field))}])`; } [ToString]() { - return `List(${(this as any).length})`; + return `[${(this as any).map((field: any) => Field.toString(field))}]`; } } export type List = ListImpl & (T | (T extends RefField ? Promise : never))[]; -- cgit v1.2.3-70-g09d2 From e3fde25014d523c5f43a138093718899fe17d108 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 Feb 2024 16:18:16 -0500 Subject: made various render methods in DocumentView computed getters for efficiency and to avoid artifacts (LInkanchorBox dragging) when something else invalidates causing components to regenerate. fixed linklines to animate when doing a zoom transition and to be able to target texts hyperlinks. fixed link lines to share properties with ink and updated the properties panel / menus to allow editing of either. addding toggling link lines on and off from linkitemmenu --- src/client/documents/Documents.ts | 2 +- src/client/views/DocComponent.tsx | 1 + src/client/views/PropertiesButtons.tsx | 2 +- src/client/views/PropertiesView.tsx | 104 ++--------- src/client/views/StyleProvider.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/global/globalScripts.ts | 26 +-- src/client/views/linking/LinkMenuItem.tsx | 24 ++- src/client/views/nodes/DocumentView.tsx | 35 ++-- src/client/views/nodes/LinkBox.tsx | 197 +++++++++++++-------- src/client/views/nodes/formattedText/marks_rts.ts | 5 +- src/fields/Doc.ts | 3 +- src/fields/DocSymbols.ts | 1 + 13 files changed, 201 insertions(+), 202 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1978c144b..355a4c937 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -599,7 +599,7 @@ export namespace Docs { _width: 1, link: '', link_description: '', - backgroundColor: 'lightblue', // lightblue is default color for linking dot and link documents text comment area + color: 'lightBlue', // lightblue is default color for linking dot and link documents text comment area _dropPropertiesToRemove: new List(['onClick']), }, }, diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index dacd359c5..99b9c3045 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -35,6 +35,7 @@ export interface ViewBoxInterface { addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) select?: (ctrlKey: boolean, shiftKey: boolean) => void; focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt; + viewTransition?: () => Opt; // duration of a view transition animation isAnyChildContentActive?: () => boolean; // is any child content of the document active onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected getKeyFrameEditing?: () => boolean; // whether the document is in keyframe editing mode (if it is, then all hidden documents that are not active at the keyframe time will still be shown) diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index bba6285c2..cb38ab602 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -411,7 +411,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { docView.noOnClick(); switch (onClick) { case 'enterPortal': - docView.makeIntoPortal(); + DocUtils.makeIntoPortal(docView.Document, docView.layoutDoc, docView.allLinks); break; case 'toggleDetail': docView.setToggleDetail(); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 07f285eaf..3ae2362a1 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -741,7 +741,7 @@ export class PropertiesView extends ObservableReactComponent void) { @@ -943,19 +949,19 @@ export class PropertiesView extends ObservableReactComponent -
-
-
Width:
-
{this.stkInput}
-
- (this.widthStk = e.target.value))} - onMouseDown={e => { - this._widthUndo = UndoManager.StartBatch('width undo'); - }} - onMouseUp={e => { - this._widthUndo?.end(); - this._widthUndo = undefined; - }} - /> -
+
{this.getNumber('Thickness', '', 0, Math.max(50, this.strokeThk), this.strokeThk, (val: number) => !isNaN(val) && (this.strokeThk = val), 50, 1)}
+
{this.getNumber('Arrow Scale', '', 0, Math.max(10, this.markScal), this.markScal, (val: number) => !isNaN(val) && (this.markScal = val), 10, 1)}
-
-
-
Arrow Scale:
- {/*
{this.markScalInput}
*/} -
- (this.markScal = +e.target.value))} - onMouseDown={e => { - this._widthUndo = UndoManager.StartBatch('scale undo'); - }} - onMouseUp={e => { - this._widthUndo?.end(); - this._widthUndo = undefined; - }} - /> -
Arrow Head:
(this.markHead = this.markHead ? '' : 'arrow')))} /> @@ -1442,36 +1406,6 @@ export class PropertiesView extends ObservableReactComponentDescription

{this.editDescription}
-
-

Show link

- -
-
-

Auto-move anchors

- -
-
-

Display arrow

- -
{!hasSelectedAnchor ? null : (
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index b7e64d9a8..0794efe4c 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -134,7 +134,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt this._props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; + viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined); fitContentOnce = () => { const vals = this.fitToContentVals; this.layoutDoc._freeform_panX = vals.bounds.cx; diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts index 813cb9338..0541a9ca7 100644 --- a/src/client/views/global/globalScripts.ts +++ b/src/client/views/global/globalScripts.ts @@ -43,14 +43,14 @@ ScriptingGlobals.add(function setBackgroundColor(color?: string, checkResult?: b } else if (selectedViews.length) { if (checkResult) { const selView = selectedViews.lastElement(); - const fieldKey = selView.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; + const fieldKey = selView.Document._layout_isSvg ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(selView.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(selView.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed return CollectionFreeFormDocumentView.getStringValues(selView?.Document, contentFrameNumber)[fieldKey] ?? 'transparent'; } selectedViews.some(dv => dv.ComponentView instanceof InkingStroke) && SetActiveFillColor(color ?? 'transparent'); selectedViews.forEach(dv => { - const fieldKey = dv.Document.type === DocumentType.INK ? 'fillColor' : 'backgroundColor'; + const fieldKey = dv.Document._layout_isSvg ? 'fillColor' : 'backgroundColor'; const layoutFrameNumber = Cast(dv.containerViewPath?.().lastElement()?.Document?._currentFrame, 'number'); // frame number that container is at which determines layout frame values const contentFrameNumber = Cast(dv.Document?._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed if (contentFrameNumber !== undefined) { @@ -344,24 +344,24 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | ' // prettier-ignore const map: Map<'inkMask' | 'fillColor' | 'strokeWidth' | 'strokeColor', { checkResult: () => any; setInk: (doc: Doc) => void; setMode: () => void }> = new Map([ ['inkMask', { - checkResult: () => ((selected?.type === DocumentType.INK ? BoolCast(selected.stroke_isInkMask) : ActiveIsInkMask())), - setInk: (doc: Doc) => (doc.stroke_isInkMask = !doc.stroke_isInkMask), + checkResult: () => ((selected?._layout_isSvg ? BoolCast(selected[DocData].stroke_isInkMask) : ActiveIsInkMask())), + setInk: (doc: Doc) => (doc[DocData].stroke_isInkMask = !doc.stroke_isInkMask), setMode: () => selected?.type !== DocumentType.INK && SetActiveIsInkMask(!ActiveIsInkMask()), }], ['fillColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.fillColor) : ActiveFillColor() ?? "transparent"), - setInk: (doc: Doc) => (doc.fillColor = StrCast(value)), + checkResult: () => (selected?._layout_isSvg ? StrCast(selected[DocData].fillColor) : ActiveFillColor() ?? "transparent"), + setInk: (doc: Doc) => (doc[DocData].fillColor = StrCast(value)), setMode: () => SetActiveFillColor(StrCast(value)), }], [ 'strokeWidth', { - checkResult: () => (selected?.type === DocumentType.INK ? NumCast(selected.stroke_width) : ActiveInkWidth()), - setInk: (doc: Doc) => (doc.stroke_width = NumCast(value)), - setMode: () => { SetActiveInkWidth(value.toString()); setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, + checkResult: () => (selected?._layout_isSvg ? NumCast(selected[DocData].stroke_width) : ActiveInkWidth()), + setInk: (doc: Doc) => (doc[DocData].stroke_width = NumCast(value)), + setMode: () => { SetActiveInkWidth(value.toString()); selected?.type === DocumentType.INK && setActiveTool( GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, }], ['strokeColor', { - checkResult: () => (selected?.type === DocumentType.INK ? StrCast(selected.color) : ActiveInkColor()), - setInk: (doc: Doc) => (doc.color = String(value)), - setMode: () => { SetActiveInkColor(StrCast(value)); setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, + checkResult: () => (selected?._layout_isSvg? StrCast(selected[DocData].color) : ActiveInkColor()), + setInk: (doc: Doc) => (doc[DocData].color = String(value)), + setMode: () => { SetActiveInkColor(StrCast(value)); selected?.type === DocumentType.INK && setActiveTool(GestureOverlay.Instance.InkShape ?? InkTool.Pen, true, false);}, }], ]); @@ -369,7 +369,7 @@ ScriptingGlobals.add(function setInkProperty(option: 'inkMask' | 'fillColor' | ' return map.get(option)?.checkResult(); } map.get(option)?.setMode(); - SelectionManager.Docs.filter(doc => doc.type === DocumentType.INK).map(doc => map.get(option)?.setInk(doc)); + SelectionManager.Docs.filter(doc => doc._layout_isSvg).map(doc => map.get(option)?.setInk(doc)); }); /** WEB diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 7427f4310..92c63cd56 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -74,6 +74,14 @@ export class LinkMenuItem extends ObservableReactComponent { return this._props.sourceDoc; } + onIconDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, returnFalse, returnFalse, () => { + if (!this._props.docView._props.removeDocument?.(this._props.linkDoc)) { + this._props.docView._props.addDocument?.(this._props.linkDoc); + } + }); + }; + onEdit = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -196,12 +204,16 @@ export class LinkMenuItem extends ObservableReactComponent {

) : null}
-
- -
-

- {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)} -

+ Show/Hide Link
}> +
+ +
+ + Follow Link
}> +

+ {this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)} +

+
{!this._props.linkDoc.link_description ? null :

{StrCast(this._props.linkDoc.link_description).split('\n')[0].substring(0, 50)}

}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5efa028d1..042ae6e55 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -48,6 +48,7 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; +import { CollectionFreeFormView } from '../collections/collectionFreeForm'; interface Window { MediaRecorder: MediaRecorder; } @@ -726,7 +727,7 @@ export class DocumentViewInternal extends DocComponent () => (link.link_displayLine = false); - allLinkEndpoints = () => { + @computed get allLinkEndpoints() { // the small blue dots that mark the endpoints of links if (this._componentView instanceof KeyValueBox || this._props.hideLinkAnchors || this.layoutDoc.layout_hideLinkAnchors || this._props.dontRegisterView || this.layoutDoc.layout_unrendered) return null; return this.filteredLinks.map(link => ( @@ -750,9 +751,9 @@ export class DocumentViewInternal extends DocComponent
)); - }; + } - viewBoxContents = () => { + @computed get viewBoxContents() { TraceMobx(); const isInk = this.layoutDoc._layout_isSvg && !this._props.LayoutTemplateString; const noBackground = this.Document.isGroup && !this._props.LayoutTemplateString?.includes(KeyValueBox.name) && (!this.layoutDoc.backgroundColor || this.layoutDoc.backgroundColor === 'transparent'); @@ -778,10 +779,10 @@ export class DocumentViewInternal extends DocComponent - {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints()} + {this.layoutDoc.layout_hideAllLinks ? null : this.allLinkEndpoints}
); - }; + } captionStyleProvider = (doc: Opt, props: Opt, property: string) => this._props?.styleProvider?.(doc, props, property + ':caption'); fieldsDropdown = (reqdFields: string[], dropdownWidth: number, placeholder: string, onChange: (val: string | number) => void, onClose: () => void) => { @@ -814,7 +815,7 @@ export class DocumentViewInternal extends DocComponent { + @computed get titleView() { const showTitle = this.layout_showTitle?.split(':')[0]; const showTitleHover = this.layout_showTitle?.includes(':hover'); @@ -888,9 +889,9 @@ export class DocumentViewInternal extends DocComponent
); - }; + } - captionView = () => { + @computed get captionView() { return !this.layout_showCaption ? null : (
); - }; + } renderDoc = (style: object) => { TraceMobx(); @@ -933,15 +934,15 @@ export class DocumentViewInternal extends DocComponent {this._props.hideTitle || (!showTitle && !this.layout_showCaption) ? ( - this.viewBoxContents() + this.viewBoxContents ) : (
- {this.titleView()} - {this.viewBoxContents()} - {this.captionView()} + {this.titleView} + {this.viewBoxContents} + {this.captionView}
)} {this.widgetDecorations ?? null} @@ -1191,8 +1192,8 @@ export class DocumentView extends DocComponent() { if (docuBox.length) return { ...docuBox[0].getBoundingClientRect(), transition: undefined }; } // transition is returned so that the bounds will 'update' at the end of an animated transition. This is needed by xAnchor in LinkBox - const transition = this.docViewPath().find((parent: DocumentView) => parent._props.DataTransition?.() || StrCast(parent.Document.dataTransition)); - return { left, top, right, bottom, transition: transition?._props.DataTransition?.() || StrCast(transition?.Document.dataTransition) }; + const transition = this.docViewPath().find((parent: DocumentView) => parent.DataTransition?.() || parent.ComponentView?.viewTransition?.()); + return { left, top, right, bottom, transition: transition?.DataTransition?.() || transition?.ComponentView?.viewTransition?.() }; } @computed get nativeWidth() { @@ -1337,6 +1338,7 @@ export class DocumentView extends DocComponent() { } } }; + DataTransition = () => this._props.DataTransition?.() || StrCast(this.Document.dataTransition); ShouldNotScale = () => this.shouldNotScale; NativeWidth = () => this.effectiveNativeWidth; NativeHeight = () => this.effectiveNativeHeight; @@ -1402,6 +1404,7 @@ export class DocumentView extends DocComponent() { () { @@ -43,19 +46,20 @@ export class LinkBox extends ViewBoxBaseComponent() { this.disposer = reaction( () => ({ drag: SnappingManager.IsDragging, a: this.anchor1, b: this.anchor2 }), ({ drag, a, b }) => { - setTimeout( - // need to wait for drag manager to set 'hidden' flag on dragged elements - action(() => { - let a1 = a && document.getElementById(a.Guid); - let a2 = b && document.getElementById(b.Guid); - if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true; - else { - for (; a1 && !a1.hidden; a1 = a1.parentElement); - for (; a2 && !a2.hidden; a2 = a2.parentElement); - this._hide = a1 || a2 ? true : false; - } - }) - ); + !LightboxView.Contains(this.DocumentView?.()) && + setTimeout( + // need to wait for drag manager to set 'hidden' flag on dragged elements + action(() => { + let a1 = a && document.getElementById(a.Guid); + let a2 = b && document.getElementById(b.Guid); + if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true; + else { + for (; a1 && !a1.hidden; a1 = a1.parentElement); + for (; a2 && !a2.hidden; a2 = a2.parentElement); + this._hide = a1 || a2 ? true : false; + } + }) + ); }, { fireImmediately: true } ); @@ -63,89 +67,130 @@ export class LinkBox extends ViewBoxBaseComponent() { select = (ctrlKey: boolean, shiftKey: boolean) => (LinkManager.Instance.currentLink = this.Document); - descriptionDown = (e: React.PointerEvent) => { - setupMoveUpEvents( - this, - e, - returnFalse, - returnFalse, - action(() => { - LinkManager.Instance.currentLink = this.Document; - LinkDescriptionPopup.Instance.popupX = e.clientX; - LinkDescriptionPopup.Instance.popupY = e.clientY; - LinkDescriptionPopup.Instance.display = true; - - const rect = document.body.getBoundingClientRect(); - if (LinkDescriptionPopup.Instance.popupX + 200 > rect.width) { - LinkDescriptionPopup.Instance.popupX -= 190; - } - if (LinkDescriptionPopup.Instance.popupY + 100 > rect.height) { - LinkDescriptionPopup.Instance.popupY -= 40; - } - }), - false - ); - }; render() { - trace(); + if (this._hide) return null; const a = this.anchor1; const b = this.anchor2; this._forceAnimate; - if (a && b && !this._hide) { + if (a && b && !LightboxView.Contains(this.DocumentView?.())) { + // text selection bounds are not directly observable, so we have to + // force an update when anything that could affect them changes (text edits causing reflow, scrolling) + a.Document[a.LayoutFieldKey]; + b.Document[b.LayoutFieldKey]; + a.Document.layout_scrollTop; + b.Document.layout_scrollTop; + const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove) const bxf = b.screenToViewTransform(); const scale = a.CollectionFreeFormView === this.DocumentView?.().CollectionFreeFormView ? axf.Scale : bxf.Scale; const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition - const bt = b.getBounds?.transition; + const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation) + + // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext ), + // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right + // otherwise, we just use the computed nearest point on the document boundary to the target Document + const targetAhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement(); + const targetBhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement(); + + const aid = targetAhyperlink?.id || a.Document[Id]; + const bid = targetBhyperlink?.id || b.Document[Id]; + if (!document.getElementById(aid) || !document.getElementById(bid)) { + setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); + return null; + } + if (at || bt) setTimeout(action(() => (this._forceAnimate = this._forceAnimate + 0.01))); // this forces an update during a transition animation const highlight = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Highlighting); const highlightColor = highlight?.highlightIndex ? highlight?.highlightColor : undefined; + const color = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color); const fontFamily = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontFamily); const fontSize = this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.FontSize); - const color = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor)); - const { strokeWidth, stroke_startMarker, stroke_endMarker } = this.Document; - const dash = StrCast(this.Document.stroke_dash); - const stroke = highlightColor ?? 'lightblue'; + const fontColor = (c => (c !== 'transparent' ? c : undefined))(StrCast(this.layoutDoc.link_fontColor)); + const { stroke_markerScale, stroke_width, stroke_startMarker, stroke_endMarker, stroke_dash } = this.Document; + const strokeWidth = NumCast(stroke_width, 4); const linkDesc = StrCast(this.dataDoc.link_description) || ' '; const labelText = linkDesc.substring(0, 50) + (linkDesc.length > 50 ? '...' : ''); return ( - - {labelText} -
- } - passProps={{ onPointerDown: this.descriptionDown }} - /> + <> + {!highlightColor ? null : ( + + )} + + linkDesc} + SetValue={action(val => { + this.Document[DocData].link_description = val; + return true; + })} + /> + + {/* (this.Document[DocData].link_description = val))} + fillWidth + /> */} +
+ } + passProps={{}} + /> + ); } - return null; return (
(p ? p + ' ' + item.href : item.href), ''); const anchorids = node.attrs.allAnchors.reduce((p: string, item: { href: string; title: string; anchorId: string }) => (p ? p + ' ' + item.anchorId : item.anchorId), ''); - return ['a', { class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; + return ['a', { id: Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, /*'data-noPreview': 'true', */ 'data-linkdoc': node.attrs.linkDoc, title: node.attrs.title, style: `background: lightBlue` }, 0]; }, }, noAutoLinkAnchor: { @@ -104,7 +105,7 @@ export const marks: { [index: string]: MarkSpec } = { node.attrs.title, ], ] - : ['a', { class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0]; + : ['a', { id: '' + Utils.GenerateGuid(), class: anchorids, 'data-targethrefs': targethrefs, title: node.attrs.title, 'data-noPreview': node.attrs.noPreview, style: `text-decoration: underline; cursor: default` }, 0]; }, }, diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 3cd4efcf7..56d50846a 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -16,7 +16,7 @@ import { DateField } from './DateField'; import { AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks, DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight, - Initializing, Self, SelfProxy, UpdatingFromServer, Width + Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width } from './DocSymbols'; // prettier-ignore import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols'; import { InkField, InkTool } from './InkField'; @@ -309,6 +309,7 @@ export class Doc extends RefField { public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any); public [Width] = () => NumCast(this[SelfProxy]._width); public [Height] = () => NumCast(this[SelfProxy]._height); + public [TransitionTimer]: any = undefined; public [ToJavascriptString] = () => `idToDoc("${this[Self][Id]}")`; // what should go here? public [ToScriptString] = () => `idToDoc("${this[Self][Id]}")`; public [ToString] = () => `Doc(${GetEffectiveAcl(this[SelfProxy]) === AclPrivate ? '-inaccessible-' : this[SelfProxy].title})`; diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts index 9c563abbf..f8a57acd5 100644 --- a/src/fields/DocSymbols.ts +++ b/src/fields/DocSymbols.ts @@ -15,6 +15,7 @@ export const DocLayout = Symbol('DocLayout'); export const DocFields = Symbol('DocFields'); export const DocCss = Symbol('DocCss'); export const DocAcl = Symbol('DocAcl'); +export const TransitionTimer = Symbol('DocTransitionTimer'); export const DirectLinks = Symbol('DocDirectLinks'); export const AclPrivate = Symbol('DocAclOwnerOnly'); export const AclReadonly = Symbol('DocAclReadOnly'); -- cgit v1.2.3-70-g09d2 From 5fa690804ebd0b915488530882564a241315ad09 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 Feb 2024 21:15:37 -0500 Subject: changed so link docs are added to common ancestor of anchors so that they can appear above/below intermediary docs using z order. fixed dragging to tab bar to start dragging document as a tab. --- src/client/util/DocumentManager.ts | 14 ++++++++++++++ src/client/util/DragManager.ts | 2 +- src/client/views/DocComponent.tsx | 1 + src/client/views/MainView.tsx | 1 - src/client/views/linking/LinkMenuItem.tsx | 6 ++++-- src/client/views/nodes/DocumentView.tsx | 5 ++--- src/client/views/nodes/LinkBox.tsx | 6 +++--- 7 files changed, 25 insertions(+), 10 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index eada5af75..7407fa2b3 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -209,6 +209,20 @@ export class DocumentManager { finished?.(); }; + public static LinkCommonAncestor(linkDoc: Doc) { + const anchor = (which: number) => { + const anch = DocCast(linkDoc['link_anchor_' + which]); + const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch; + return DocumentManager.Instance.getDocumentView(anchor); + }; + const anchor1 = anchor(1); + const anchor2 = anchor(2); + return anchor1 + ?.docViewPath() + .reverse() + .find(ancestor => anchor2?.docViewPath().includes(ancestor)); + } + // shows a documentView by: // traverses down through the viewPath of contexts to the view: // focusing on each context diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 78356888a..1f093a33c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -506,7 +506,7 @@ export namespace DragManager { if (dragData instanceof DocumentDragData) { dragData.userDropAction = e.ctrlKey && e.altKey ? 'copy' : e.shiftKey ? 'move' : e.ctrlKey ? 'embed' : dragData.defaultDropAction; } - if (((e.target as any)?.className === 'lm_tabs' || (e.target as any)?.className === 'lm_header') && dragData.draggedDocuments.length === 1) { + if (['lm_tab', 'lm_title_wrap', 'lm_tabs', 'lm_header'].includes(typeof (e.target as any).className === 'string' ? (e.target as any)?.className : '') && dragData.draggedDocuments.length === 1) { if (!startWindowDragTimer) { startWindowDragTimer = setTimeout(async () => { startWindowDragTimer = undefined; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 99b9c3045..3d5a5b945 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -33,6 +33,7 @@ export interface ViewBoxInterface { getView?: (doc: Doc, options: FocusViewOptions) => Promise>; // returns a nested DocumentView for the specified doc or undefined addDocTab?: (doc: Doc, where: OpenWhere) => boolean; // determines how to add a document - used in following links to open the target ina local lightbox addDocument?: (doc: Doc | Doc[], annotationKey?: string) => boolean; // add a document (used only by collections) + removeDocument?: (doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean, dontAddToRemoved?: boolean) => boolean; // add a document (used only by collections) select?: (ctrlKey: boolean, shiftKey: boolean) => void; focus?: (textAnchor: Doc, options: FocusViewOptions) => Opt; viewTransition?: () => Opt; // duration of a view transition animation diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index efe906981..b6cb845a6 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -640,7 +640,6 @@ export class MainView extends ObservableReactComponent<{}> { } @computed get mainDocView() { const headerBar = this._hideUI || !this.headerBarDocHeight?.() ? null : this.headerBarDocView; - console.log('Header = ' + this._hideUI + ' ' + this.headerBarDocHeight?.() + ' ' + headerBar); return ( <> {headerBar} diff --git a/src/client/views/linking/LinkMenuItem.tsx b/src/client/views/linking/LinkMenuItem.tsx index 92c63cd56..a44ad26cd 100644 --- a/src/client/views/linking/LinkMenuItem.tsx +++ b/src/client/views/linking/LinkMenuItem.tsx @@ -9,6 +9,7 @@ import { Doc } from '../../../fields/Doc'; import { Cast, DocCast, StrCast } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { DocumentType } from '../../documents/DocumentTypes'; +import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; import { LinkFollower } from '../../util/LinkFollower'; import { LinkManager } from '../../util/LinkManager'; @@ -76,8 +77,9 @@ export class LinkMenuItem extends ObservableReactComponent { onIconDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, returnFalse, returnFalse, () => { - if (!this._props.docView._props.removeDocument?.(this._props.linkDoc)) { - this._props.docView._props.addDocument?.(this._props.linkDoc); + const ancestor = DocumentManager.LinkCommonAncestor(this._props.linkDoc); + if (!ancestor?.ComponentView?.removeDocument?.(this._props.linkDoc)) { + ancestor?.ComponentView?.addDocument?.(this._props.linkDoc); } }); }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 042ae6e55..73c13b5dd 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,7 +1,7 @@ import { IconProp } from '@fortawesome/fontawesome-svg-core'; import { Dropdown, DropdownType, Type } from 'browndash-components'; import { Howl } from 'howler'; -import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; +import { IReactionDisposer, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Bounce, Fade, Flip, JackInTheBox, Roll, Rotate, Zoom } from 'react-awesome-reveal'; @@ -48,7 +48,6 @@ import { KeyValueBox } from './KeyValueBox'; import { LinkAnchorBox } from './LinkAnchorBox'; import { FormattedTextBox } from './formattedText/FormattedTextBox'; import { PresEffect, PresEffectDirection } from './trails'; -import { CollectionFreeFormView } from '../collections/collectionFreeForm'; interface Window { MediaRecorder: MediaRecorder; } @@ -490,7 +489,7 @@ export class DocumentViewInternal extends DocComponent() { const axf = a.screenToViewTransform(); // these force re-render when a or b moves (so do NOT remove) const bxf = b.screenToViewTransform(); - const scale = !docView ? 1 : a.CollectionFreeFormView === docView.CollectionFreeFormView ? axf.Scale : bxf.Scale; + const scale = docView?.screenToViewTransform().Scale ?? 1; const at = a.getBounds?.transition; // these force re-render when a or b change size and at the end of an animated transition const bt = b.getBounds?.transition; // inquring getBounds() also causes text anchors to update whether or not they reflow (any size change triggers an invalidation) // if there's an element in the DOM with a classname containing a link anchor's id (eg a hypertext ), // then that DOM element is a hyperlink source for the current anchor and we want to place our link box at it's top right // otherwise, we just use the computed nearest point on the document boundary to the target Document - const targetAhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement(); - const targetBhyperlink = Array.from(window.document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement(); + const targetAhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_1)[Id])).lastElement(); + const targetBhyperlink = Array.from(document.getElementsByClassName(DocCast(this.dataDoc.link_anchor_2)[Id])).lastElement(); const aid = targetAhyperlink?.id || a.Document[Id]; const bid = targetBhyperlink?.id || b.Document[Id]; -- cgit v1.2.3-70-g09d2 From 536cd22988407ab86099595daa4ffe4f054b2914 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 7 Feb 2024 22:43:00 -0500 Subject: fixed icon placement in linkMenuItem. --- src/client/views/PropertiesView.tsx | 49 ++++++++++++++++-------------- src/client/views/linking/LinkMenuItem.scss | 32 +++++++++---------- src/client/views/linking/LinkMenuItem.tsx | 14 ++++----- src/client/views/nodes/LinkBox.tsx | 16 +++++----- 4 files changed, 58 insertions(+), 53 deletions(-) (limited to 'src/client/views/linking') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 3ae2362a1..e4e7bec32 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -41,6 +41,7 @@ import { DocumentView, OpenWhere } from './nodes/DocumentView'; import { StyleProviderFuncType } from './nodes/FieldView'; import { KeyValueBox } from './nodes/KeyValueBox'; import { PresBox, PresEffect, PresEffectDirection } from './nodes/trails'; +import { LinkBox } from './nodes/LinkBox'; const _global = (window /* browser */ || global) /* node */ as any; interface PropertiesViewProps { @@ -69,6 +70,10 @@ export class PropertiesView extends ObservableReactComponent LinkManager.Instance.currentLink, + () => this.selectedLink, link => { link && this.CloseAll(); link && (this.openLinks = true); @@ -583,11 +588,11 @@ export class PropertiesView extends ObservableReactComponent

) : null} - {LinkManager.Instance.currentLink?.title ? ( + {this.selectedLink?.title ? (

<> Link: - {LinkManager.Instance.currentLink.title} + {this.selectedLink.title}

) : null} @@ -1190,10 +1195,10 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.Instance.currentLink && this.selectedDoc) { + if (this.selectedLink) { this.setDescripValue(value); } }), @@ -1212,7 +1217,7 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.Instance.currentLink && this.selectedDoc) { + if (this.selectedLink) { this.setlinkRelationshipValue(value); } }), @@ -1221,17 +1226,17 @@ export class PropertiesView extends ObservableReactComponent { - if (LinkManager.Instance.currentLink) { - Doc.GetProto(LinkManager.Instance.currentLink).link_description = value; + if (this.selectedLink) { + this.selectedLink[DocData].link_description = value; } }); @undoBatch setlinkRelationshipValue = action((value: string) => { - if (LinkManager.Instance.currentLink) { - const prevRelationship = StrCast(LinkManager.Instance.currentLink.link_relationship); - LinkManager.Instance.currentLink.link_relationship = value; - Doc.GetProto(LinkManager.Instance.currentLink).link_relationship = value; + if (this.selectedLink) { + const prevRelationship = StrCast(this.selectedLink.link_relationship); + this.selectedLink.link_relationship = value; + Doc.GetProto(this.selectedLink).link_relationship = value; const linkRelationshipList = StrListCast(Doc.UserDoc().link_relationshipList); const linkRelationshipSizes = NumListCast(Doc.UserDoc().link_relationshipSizes); const linkColorList = StrListCast(Doc.UserDoc().link_ColorList); @@ -1315,11 +1320,11 @@ export class PropertiesView extends ObservableReactComponent { - setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => LinkManager.Instance.currentLink && (LinkManager.Instance.currentLink[prop] = !LinkManager.Instance.currentLink[prop])))); + setupMoveUpEvents(this, e, returnFalse, emptyFunction, undoBatch(action(() => this.selectedLink && (this.selectedLink[prop] = !this.selectedLink[prop])))); }; @computed get destinationAnchor() { - const ldoc = LinkManager.Instance.currentLink; + const ldoc = this.selectedLink; const lanch = this.selectedDocumentView?.anchorViewDoc ?? LinkManager.Instance.currentLinkAnchor; if (ldoc && lanch) return LinkManager.getOppositeAnchor(ldoc, lanch) ?? lanch; return ldoc ? DocCast(ldoc.link_anchor_2) : ldoc; @@ -1328,7 +1333,7 @@ export class PropertiesView extends ObservableReactComponent any = val => val) => { @@ -1354,7 +1359,7 @@ export class PropertiesView extends ObservableReactComponent this.handlelinkRelationshipChange(e.currentTarget.value)} @@ -1371,7 +1376,7 @@ export class PropertiesView extends ObservableReactComponent this.handleDescriptionChange(e.currentTarget.value)} @@ -1393,7 +1398,7 @@ export class PropertiesView extends ObservableReactComponent @@ -1424,7 +1429,7 @@ export class PropertiesView extends ObservableReactComponentOpening in new tab - {LinkManager.Instance.currentLink?.linksToAnnotation ? : null} + {this.selectedLink?.linksToAnnotation ? : null}
@@ -1609,7 +1614,7 @@ export class PropertiesView extends ObservableReactComponent @@ -1643,7 +1648,7 @@ export class PropertiesView extends ObservableReactComponent { onPointerDown={this.onLinkButtonDown}>
Edit Link
}> -
e.stopPropagation()}> - +
e.stopPropagation()}> + +
+ + Show/Hide Link
}> +
+
@@ -206,11 +211,6 @@ export class LinkMenuItem extends ObservableReactComponent {

) : null}
- Show/Hide Link
}> -
- -
- Follow Link
}>

{this._props.linkDoc.linksToAnnotation && Cast(this._props.destinationDoc.data, WebField)?.url.href === this._props.linkDoc.annotationUri ? 'Annotation in' : ''} {StrCast(title)} diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index 998f4f7aa..decdbb240 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -23,7 +23,7 @@ export class LinkBox extends ViewBoxBaseComponent() { return FieldView.LayoutString(LinkBox, fieldKey); } disposer: IReactionDisposer | undefined; - @observable _forceAnimate = 0; // forces xArrow to animate when a transition is detected on something that affects an anchor + @observable _forceAnimate = 0; // forces xArrow to animate when a transition animation is detected on something that affects an anchor @observable _hide = false; // don't render if anchor is not visible since that breaks xAnchor constructor(props: FieldViewProps) { @@ -38,22 +38,26 @@ export class LinkBox extends ViewBoxBaseComponent() { const anchor = anch?.layout_unrendered ? DocCast(anch.annotationOn) : anch; return DocumentManager.Instance.getDocumentView(anchor, this.DocumentView?.().containerViewPath?.().lastElement()); }; - componentWillUnmount(): void { + componentWillUnmount() { this.disposer?.(); } componentDidMount() { this._props.setContentViewBox?.(this); this.disposer = reaction( - () => ({ drag: SnappingManager.IsDragging, a: this.anchor1, b: this.anchor2 }), - ({ drag, a, b }) => { + () => ({ drag: SnappingManager.IsDragging }), + ({ drag }) => { !LightboxView.Contains(this.DocumentView?.()) && setTimeout( - // need to wait for drag manager to set 'hidden' flag on dragged elements + // need to wait for drag manager to set 'hidden' flag on dragged DOM elements action(() => { + const a = this.anchor1, + b = this.anchor2; let a1 = a && document.getElementById(a.Guid); let a2 = b && document.getElementById(b.Guid); + // test whether the anchors themselves are hidden,... if (!a1 || !a2 || (a?.ContentDiv as any)?.hidden || (b?.ContentDiv as any)?.hidden) this._hide = true; else { + // .. or whether and of their DOM parents are hidden for (; a1 && !a1.hidden; a1 = a1.parentElement); for (; a2 && !a2.hidden; a2 = a2.parentElement); this._hide = a1 || a2 ? true : false; @@ -65,8 +69,6 @@ export class LinkBox extends ViewBoxBaseComponent() { ); } - select = (ctrlKey: boolean, shiftKey: boolean) => (LinkManager.Instance.currentLink = this.Document); - render() { if (this._hide) return null; const a = this.anchor1; -- cgit v1.2.3-70-g09d2