diff options
author | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-04-04 13:38:55 -0400 |
---|---|---|
committer | usodhi <61431818+usodhi@users.noreply.github.com> | 2021-04-04 13:38:55 -0400 |
commit | 15c1c952c50bb2de972c614e46c5f33c2b2952cc (patch) | |
tree | e8d7330336b80939e82749290c22ef5a05b01224 | |
parent | 479dff344ff2cf92ace9c68c3ce6d03e6e6dce22 (diff) | |
parent | ee17752109ba1238d645a4df7cee1cf60855f8df (diff) |
merging
71 files changed, 818 insertions, 950 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9bf3e7fc0..12d8445b4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -175,6 +175,7 @@ export class DocumentOptions { childLayoutTemplate?: Doc; // template for collection to use to render its children (see PresBox or Buxton layout in tree view) childLayoutString?: string; // template string for collection to use to render its children childDontRegisterViews?: boolean; + childHideLinkButton?: boolean; // hide link buttons on all children hideLinkButton?: boolean; // whether the blue link counter button should be hidden hideAllLinks?: boolean; // whether all individual blue anchor dots should be hidden isTemplateForField?: string; // the field key for which the containing document is a rendering template @@ -242,17 +243,17 @@ export class DocumentOptions { searchFileTypes?: List<string>; // file types allowed in a search query strokeWidth?: number; freezeChildren?: string; // whether children are now allowed to be added and or removed from a collection - treeViewPreventOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expand/collapse state to be independent of other views of the same document in the tree view treeViewHideTitle?: boolean; // whether to hide the top document title of a tree view treeViewHideHeader?: boolean; // whether to hide the header for a document in a tree view treeViewHideHeaderFields?: boolean; // whether to hide the drop down options for tree view items. + treeViewShowClearButton?: boolean; // whether a clear button should be displayed + treeViewOpenIsTransient?: boolean; // ignores the treeViewOpen Doc flag, allowing a treeViewItem's expand/collapse state to be independent of other views of the same document in the same or any other tree view treeViewOpen?: boolean; // whether this document is expanded in a tree view treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view + treeViewExpandedViewLock?: boolean; // whether the expanded view can be changed treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked treeViewTruncateTitleWidth?: number; treeViewType?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy - treeViewLockExpandedView?: boolean; // whether the expanded view can be changed - treeViewDefaultExpandedView?: string; // default expanded view sidebarColor?: string; // background color of text sidebar sidebarViewType?: string; // collection type of text sidebar docMaxAutoHeight?: number; // maximum height for newly created (eg, from pasting) text documents @@ -366,13 +367,10 @@ export namespace Docs { [DocumentType.LINK, { layout: { view: LinkBox, dataField: defaultDataKey }, options: { - childDontRegisterViews: true, _isLinkButton: true, treeViewHideTitle: true, - treeViewOpen: true, _height: 150, description: "", + childDontRegisterViews: true, _isLinkButton: true, _height: 150, description: "", backgroundColor: "lightblue", // lightblue is default color for linking dot and link documents text comment area - treeViewExpandedView: "fields", _removeDropProperties: new List(["_layerTags", "isLinkButton"]), links: ComputedField.MakeFunction("links(self)") as any, - linkBoxExcludedKeys: new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "_removeDropProperties", - "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]) + _removeDropProperties: new List(["_layerTags", "isLinkButton"]), } }], [DocumentType.LINKDB, { @@ -839,7 +837,7 @@ export namespace Docs { } export function TreeDocument(documents: Array<Doc>, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { childDontRegisterViews: true, ...options, _viewType: CollectionViewType.Tree }, id); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { ...options, _viewType: CollectionViewType.Tree }, id); } export function StackingDocument(documents: Array<Doc>, options: DocumentOptions, id?: string, protoId?: string) { @@ -889,9 +887,9 @@ export namespace Docs { } export function DockDocument(documents: Array<Doc>, config: string, options: DocumentOptions, id?: string) { - const tabs = TreeDocument(documents, { title: "On-Screen Tabs", freezeChildren: "remove|add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", _fitWidth: true, system: true }); - const all = TreeDocument([], { title: "Off-Screen Tabs", freezeChildren: "add", treeViewLockExpandedView: true, treeViewDefaultExpandedView: "data", system: true }); - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewDefaultExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); + const tabs = TreeDocument(documents, { title: "On-Screen Tabs", childDontRegisterViews: true, freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", _fitWidth: true, system: true }); + const all = TreeDocument([], { title: "Off-Screen Tabs", childDontRegisterViews: true, freezeChildren: "add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", system: true }); + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List([tabs, all]), { freezeChildren: "remove|add", treeViewExpandedViewLock: true, treeViewExpandedView: "data", ...options, _viewType: CollectionViewType.Docking, dockingConfig: config }, id); } export function DirectoryImportDocument(options: DocumentOptions = {}) { @@ -1344,7 +1342,10 @@ export namespace DocUtils { export function LeavePushpin(doc: Doc) { if (doc.isPushpin) return undefined; const context = Cast(doc.context, Doc, null) ?? Cast(doc.annotationOn, Doc, null); - const hasContextAnchor = DocListCast(doc.links).some(l => (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); + const hasContextAnchor = DocListCast(doc.links). + some(l => + (l.anchor2 === doc && Cast(l.anchor1, Doc, null)?.annotationOn === context) || + (l.anchor1 === doc && Cast(l.anchor2, Doc, null)?.annotationOn === context)); if (context && !hasContextAnchor && (context.type === DocumentType.VID || context.type === DocumentType.WEB || context.type === DocumentType.PDF || context.type === DocumentType.IMG)) { const pushpin = Docs.Create.FontIconDocument({ title: "pushpin", label: "", annotationOn: Cast(doc.annotationOn, Doc, null), isPushpin: true, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index ea27b7327..81627fb03 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -743,9 +743,9 @@ export class CurrentUserUtils { await doc.myDashboards; if (doc.myDashboards === undefined) { doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "My Dashboards", _height: 400, + title: "My Dashboards", _height: 400, childHideLinkButton: true, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })); const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); @@ -761,7 +761,7 @@ export class CurrentUserUtils { doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Presentations", _height: 100, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })); const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); @@ -780,8 +780,8 @@ export class CurrentUserUtils { doc.myFilesystem = new PrefetchProxy(Docs.Create.TreeDocument([doc.myFileRoot as Doc, doc.myFileOrphans as Doc], { title: "My Documents", _height: 100, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, - isFolder: true, treeViewType: "fileSystem", + treeViewTruncateTitleWidth: 150, ignoreClick: true, + isFolder: true, treeViewType: "fileSystem", childHideLinkButton: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "proto", system: true })); } @@ -792,9 +792,9 @@ export class CurrentUserUtils { // setup Recently Closed library item if (doc.myRecentlyClosedDocs === undefined) { doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "Recently Closed", + title: "Recently Closed", treeViewShowClearButton: true, childHideLinkButton: true, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })); const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); @@ -809,7 +809,7 @@ export class CurrentUserUtils { doc.currentFilter = Docs.Create.FilterDocument({ title: "FilterDoc", _height: 150, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, childDropAction: "none", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true }); const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); @@ -825,7 +825,7 @@ export class CurrentUserUtils { doc.treeViewExpandedView = "fields"; doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, _forceActive: true, title: "My UserDoc", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, + treeViewTruncateTitleWidth: 150, ignoreClick: true, _lockedPosition: true, boxShadow: "0 0", childDontRegisterViews: true, targetDropAction: "same", system: true })) as any as Doc; } @@ -855,7 +855,7 @@ export class CurrentUserUtils { static blist = (opts: DocumentOptions, docs: Doc[]) => new PrefetchProxy(Docs.Create.LinearDocument(docs, { ...opts, _gridGap: 5, _xMargin: 5, _yMargin: 5, _height: 42, _width: 100, boxShadow: "0 0", _forceActive: true, dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - backgroundColor: "black", treeViewPreventOpen: true, _lockedPosition: true, linearViewIsExpanded: true, system: true + backgroundColor: "black", _lockedPosition: true, linearViewIsExpanded: true, system: true })) as any as Doc static ficon = (opts: DocumentOptions) => new PrefetchProxy(Docs.Create.FontIconDocument({ @@ -911,6 +911,7 @@ export class CurrentUserUtils { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, _lockedPosition: true, "acl-Public": SharingPermissions.Add, "_acl-Public": SharingPermissions.Add, _chromeHidden: true, }, sharingDocumentId + "outer", sharingDocumentId); + (sharedDocs as Doc)["acl-Public"] = (sharedDocs as Doc)[DataSym]["acl-Public"] = SharingPermissions.Add; } if (sharedDocs instanceof Doc) { sharedDocs.userColor = sharedDocs.userColor || "rgb(202, 202, 202)"; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 8b37c9a6e..304215a8f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -208,7 +208,6 @@ export class DocumentManager { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); if (targetDoc.layout) { // there will no layout for a TEXTANCHOR type document - // Doc.SetInPlace(targetDoc, "annotationOn", undefined, false); createViewFunc(Doc.BrushDoc(targetDoc), finished); // create a new view of the target } } else { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 38d0ecaa6..c1235163b 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -379,7 +379,7 @@ export namespace DragManager { if (docsBeingDragged.length) { const pdfBox = dragElement.getElementsByTagName("canvas"); const pdfBoxSrc = ele.getElementsByTagName("canvas"); - Array.from(pdfBox).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); + Array.from(pdfBox).filter(pb => pb.width && pb.height).map((pb, i) => pb.getContext('2d')!.drawImage(pdfBoxSrc[i], 0, 0)); } [dragElement, ...Array.from(dragElement.getElementsByTagName('*'))].forEach(ele => ele.hasAttribute("style") && ((ele as any).style.pointerEvents = "none")); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index b132af035..ca5ef75d2 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -28,9 +28,9 @@ export namespace SelectionManager { } manager.SelectedViews.set(docView, true); - docView.props.whenActiveChanged(true); + docView.props.whenChildContentsActiveChanged(true); } else if (!ctrlPressed && Array.from(manager.SelectedViews.entries()).length > 1) { - Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenActiveChanged(false)); + Array.from(manager.SelectedViews.keys()).map(dv => dv !== docView && dv.props.whenChildContentsActiveChanged(false)); manager.SelectedSchemaDocument = undefined; manager.SelectedSchemaCollection = undefined; manager.SelectedViews.clear(); @@ -42,14 +42,14 @@ export namespace SelectionManager { if (manager.SelectedViews.get(docView)) { manager.SelectedViews.delete(docView); - docView.props.whenActiveChanged(false); + docView.props.whenChildContentsActiveChanged(false); } } @action DeselectAll(): void { manager.SelectedSchemaCollection = undefined; manager.SelectedSchemaDocument = undefined; - Array.from(manager.SelectedViews.keys()).map(dv => dv.props.whenActiveChanged(false)); + Array.from(manager.SelectedViews.keys()).map(dv => dv.props.whenChildContentsActiveChanged(false)); manager.SelectedViews.clear(); } } diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 08dfb5066..b7a39a963 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -174,7 +174,7 @@ export class SharingManager extends React.Component<{}> { const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - docs.forEach(doc => { + return !docs.map(doc => { doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc); if (permission === SharingPermissions.None) { @@ -186,9 +186,9 @@ export class SharingManager extends React.Component<{}> { distributeAcls(acl, permission as SharingPermissions, doc); - if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); - else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); - }); + if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); + else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); + }).some(success => !success); } /** @@ -204,7 +204,8 @@ export class SharingManager extends React.Component<{}> { const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - docs.forEach(doc => { + // ! ensures it returns true if document has been shared successfully, false otherwise + return !docs.map(doc => { doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc); if (permission === SharingPermissions.None) { @@ -223,12 +224,12 @@ export class SharingManager extends React.Component<{}> { // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List<Doc>).push(doc) : group.docsShared = new List<Doc>([doc]); - users.forEach(({ user, sharingDoc }) => { - if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added - else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists - }); + return users.map(({ user, sharingDoc }) => { + if (permission !== SharingPermissions.None) return Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else return GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists + }).some(success => !success); } - }); + }).some(success => success); } /** @@ -306,24 +307,6 @@ export class SharingManager extends React.Component<{}> { } } - /** - * Shares the document with a user. - */ - // setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { - // const { user, sharingDoc } = recipient; - // const target = targetDoc || this.targetDoc!; - // const acl = `acl-${normalizeEmail(user.email)}`; - // const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; - - // const docs = SelectionManager.Views().length < 2 ? [target] : SelectionManager.Views().map(docView => docView.props.Document); - // docs.forEach(doc => { - // doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc); - // distributeAcls(acl, permission as SharingPermissions, doc); - - // if (permission !== SharingPermissions.None) Doc.AddDocToList(sharingDoc, storage, doc); - // else GetEffectiveAcl(doc, user.email) === AclPrivate && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); - // }); - // } // private setExternalSharing = (permission: string) => { @@ -535,10 +518,10 @@ export class SharingManager extends React.Component<{}> { {this.sharingOptions(uniform)} </select> ) : ( - <div className={"permissions-dropdown"}> - {permissions === SharingPermissions.Add ? "Can Augment" : permissions} - </div> - )} + <div className={"permissions-dropdown"}> + {permissions === SharingPermissions.Add ? "Can Augment" : permissions} + </div> + )} </div> </div> ); @@ -609,10 +592,10 @@ export class SharingManager extends React.Component<{}> { {this.sharingOptions(uniform, group.title === "Override")} </select> ) : ( - <div className={"permissions-dropdown"}> - {permissions} - </div> - )} + <div className={"permissions-dropdown"}> + {permissions} + </div> + )} </div> </div> ); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index be1eab86b..5ff96ac8d 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -10,6 +10,8 @@ import { ScriptField } from '../../fields/ScriptField'; import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail } from '../../fields/util'; import { CurrentUserUtils } from '../util/CurrentUserUtils'; import { DocUtils } from '../documents/Documents'; +import { returnFalse } from '../../Utils'; +import { UndoManager } from '../util/UndoManager'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) @@ -34,7 +36,7 @@ export function DocComponent<P extends DocComponentProps, T>(schemaCtor: (doc: D return Component; } -/// FieldViewBoxProps - a generic base class for field views that are not annotatable (e.g. AudioBox, FormattedTextBox) +/// FieldViewBoxProps - a generic base class for field views that are not annotatable (e.g. InkingStroke, ColorBox) interface ViewBoxBaseProps { Document: Doc; DataDoc?: Doc; @@ -42,6 +44,7 @@ interface ViewBoxBaseProps { fieldKey: string; layerProvider?: (doc: Doc, assign?: boolean) => boolean; isSelected: (outsideReaction?: boolean) => boolean; + isContentActive: () => boolean; renderDepth: number; rootSelected: (outsideReaction?: boolean) => boolean; } @@ -62,7 +65,10 @@ export function ViewBoxBaseComponent<P extends ViewBoxBaseProps, T>(schemaCtor: lookupField = (field: string) => ScriptCast(this.layoutDoc.lookupField)?.script.run({ self: this.layoutDoc, data: this.rootDoc, field: field, container: this.props.ContainingCollectionDoc }).result; - active = (outsideReaction?: boolean) => this.props.layerProvider?.(this.props.Document) !== false && (this.props.rootSelected(outsideReaction) || this.props.isSelected(outsideReaction) || this.props.renderDepth === 0 || this.layoutDoc.forceActive);// && !Doc.SelectedTool(); // bcz: inking state shouldn't affect static tools + isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || + (this.props.isContentActive?.() || this.props.Document.forceActive || + this.props.isSelected(outsideReaction) || + this.props.rootSelected(outsideReaction)) ? true : false) protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; } return Component; @@ -74,22 +80,23 @@ export interface ViewBoxAnnotatableProps { Document: Doc; DataDoc?: Doc; fieldKey: string; - layerProvider?: (doc: Doc) => boolean; - active: () => boolean; + filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) + layerProvider?: (doc: Doc, assign?: boolean) => boolean; + isContentActive: () => boolean; select: (isCtrlPressed: boolean) => void; - whenActiveChanged: (isActive: boolean) => void; + whenChildContentsActiveChanged: (isActive: boolean) => void; isSelected: (outsideReaction?: boolean) => boolean; rootSelected: (outsideReaction?: boolean) => boolean; renderDepth: number; + isAnnotationOverlay?: boolean; } -export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T>(schemaCtor: (doc: Doc) => T) { +export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T>(schemaCtor: (doc: Doc) => T, _annotationKey: string = "annotations") { class Component extends Touchable<P> { - @observable _annotationKey: string = "annotations"; + @observable _annotationKey: string = _annotationKey; - @observable _isChildActive = false; + @observable _isAnyChildContentActive = false; //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then @computed get Document(): T { return schemaCtor(this.props.Document); } - // This is the "The Document" -- it encapsulates, data, layout, and any templates @computed get rootDoc() { return Cast(this.props.Document.rootDocument, Doc, null) || this.props.Document; } // This is the rendering data of a document -- it may be "The Document", or it may be some template document that holds the rendering info @@ -125,7 +132,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; - @computed public get annotationKey() { return this.fieldKey + "-" + this._annotationKey; } + @computed public get annotationKey() { return this.fieldKey + (this._annotationKey ? "-" + this._annotationKey : ""); } @action.bound removeDocument(doc: Doc | Doc[], annotationKey?: string, leavePushpin?: boolean): boolean { @@ -156,16 +163,28 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T return false; } - // if the moved document is already in this overlay collection nothing needs to be done. - // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. @action.bound - moveDocument(doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string): boolean { - return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc, annotationKey, true) ? addDocument(doc) : false; + moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean, annotationKey?: string): boolean => { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { + return true; + } + const first = doc instanceof Doc ? doc : doc[0]; + if (!first?._stayInCollection && addDocument !== returnFalse) { + return UndoManager.RunInTempBatch(() => this.removeDocument(doc, annotationKey, true) && addDocument(doc)); + } + return false; } @action.bound addDocument(doc: Doc | Doc[], annotationKey?: string): boolean { const docs = doc instanceof Doc ? [doc] : doc; - docs.map(doc => doc.context = Doc.GetProto(doc).annotationOn = this.props.Document); + if (this.props.filterAddDocument?.(docs) === false || + docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document))) { + return false; + } const targetDataDoc = this.props.Document[DataSym]; const docList = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); const added = docs.filter(d => !docList.includes(d)); @@ -187,10 +206,21 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T } if (effectiveAcl === AclAddonly) { - added.map(doc => Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc)); + added.map(doc => { + doc.context = this.props.Document; + if (annotationKey ?? this._annotationKey) Doc.GetProto(doc).annotationOn = this.props.Document; + this.props.layerProvider?.(doc, true); + Doc.AddDocToList(targetDataDoc, annotationKey ?? this.annotationKey, doc); + }); } else { - added.map(doc => doc.context = this.props.Document); + added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document + this.props.layerProvider?.(doc, true); + DocUtils.LeavePushpin(doc); + doc._stayInCollection = undefined; + doc.context = this.props.Document; + if (annotationKey ?? this._annotationKey) Doc.GetProto(doc).annotationOn = this.props.Document; + }); const annoDocs = targetDataDoc[annotationKey ?? this.annotationKey] as List<Doc>; if (annoDocs) annoDocs.push(...added); else targetDataDoc[annotationKey ?? this.annotationKey] = new List<Doc>(added); @@ -201,13 +231,11 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps, T return true; } - whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); - active = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool === InkTool.None && - (this.props.rootSelected(outsideReaction) || - this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) - annotationsActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || - (this.props.layerProvider?.(this.props.Document) === false && this.props.active()) || - (this.props.Document.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) + whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); + isContentActive = (outsideReaction?: boolean) => (CurrentUserUtils.SelectedTool !== InkTool.None || + (this.props.isContentActive?.() || this.props.Document.forceActive || + this.props.isSelected(outsideReaction) || this._isAnyChildContentActive || + this.props.rootSelected(outsideReaction)) ? true : false) } return Component; }
\ No newline at end of file diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index a92891ee5..ce7ff1bbd 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -191,7 +191,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b X: Math.cos(angle) * (ink.X - pair.X) - Math.sin(angle) * (ink.Y - pair.Y) + pair.X, Y: Math.sin(angle) * (ink.X - pair.X) + Math.cos(angle) * (ink.Y - pair.Y) + pair.Y })) || []; - Doc.SetInPlace(pair.doc, "data", new InkField(newPoints), true); + Doc.GetProto(pair.doc).data = new InkField(newPoints); pair.doc._width = ((xs) => (Math.max(...xs) - Math.min(...xs)))(newPoints.map(p => p.X) || [0]); pair.doc._height = ((ys) => (Math.max(...ys) - Math.min(...ys)))(newPoints.map(p => p.Y) || [0]); @@ -375,11 +375,11 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b //need to change points for resize, or else rotation/control points will fail. this._inkDragDocs.map(oldbds => ({ oldbds, inkPts: Cast(oldbds.doc.data, InkField)?.inkData || [] })) .forEach(({ oldbds: { doc, x, y, width, height }, inkPts }) => { - Doc.SetInPlace(doc, "data", new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth) + Doc.GetProto(doc).data = new InkField(inkPts.map(ipt => // (new x — oldx) + newWidth * (oldxpoint /oldWidth) ({ X: (NumCast(doc.x) - x) + NumCast(doc.width) * ipt.X / width, Y: (NumCast(doc.y) - y) + NumCast(doc.height) * ipt.Y / height - }))), true); + }))); Doc.SetNativeWidth(doc, undefined); Doc.SetNativeHeight(doc, undefined); }); @@ -475,10 +475,10 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b {seldoc.props.renderDepth <= 1 || !seldoc.props.ContainingCollectionView ? (null) : topBtn("selector", "arrow-alt-circle-up", this.onSelectorClick, "tap to select containing document")} + <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`} + onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>{useRotation && "⟲"}</div> </> } - <div key="rot" className={`documentDecorations-${useRotation ? "rotation" : "borderRadius"}`} - onPointerDown={useRotation ? this.onRotateDown : this.onRadiusDown} onContextMenu={(e) => e.preventDefault()}>{useRotation && "⟲"}</div> </div > {seldoc?.Document.type === DocumentType.FONTICON ? (null) : <div className="link-button-container" key="links" style={{ left: bounds.x - this._resizeBorderWidth / 2 + 10, top: bounds.b + this._resizeBorderWidth / 2 }}> <DocumentButtonBar views={SelectionManager.Views} /> diff --git a/src/client/views/EditableView.scss b/src/client/views/EditableView.scss index 5953baec1..5dc0c1962 100644 --- a/src/client/views/EditableView.scss +++ b/src/client/views/EditableView.scss @@ -25,4 +25,5 @@ .editableView-input { width: 100%; background: inherit; + pointer-events: all; }
\ No newline at end of file diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index f1cecd272..5daac77ee 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -174,7 +174,7 @@ export class EditableView extends React.Component<EditableProps> { }} /> : <input className="editableView-input" ref={this._inputref} - style={{ display: this.props.display, fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }} + style={{ display: this.props.display, overflow: "auto", fontSize: this.props.fontSize, minWidth: 20, background: this.props.background }} placeholder={this.props.placeholder} onBlur={e => this.finalizeEdit(e.currentTarget.value, false, true, false)} defaultValue={this.props.GetValue()} diff --git a/src/client/views/GestureOverlay.tsx b/src/client/views/GestureOverlay.tsx index f5fac17a9..491bf18b2 100644 --- a/src/client/views/GestureOverlay.tsx +++ b/src/client/views/GestureOverlay.tsx @@ -870,13 +870,14 @@ export class GestureOverlay extends Touchable { ScreenToLocalTransform={this.screenToLocalTransform} PanelWidth={this.return300} PanelHeight={this.return300} + isDocumentActive={returnFalse} + isContentActive={returnFalse} renderDepth={0} styleProvider={returnEmptyString} layerProvider={undefined} docViewPath={returnEmptyDoclist} focus={DocUtils.DefaultFocus} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docRangeFilters={returnEmptyFilter} docFilters={returnEmptyFilter} diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index 07ebe5fa4..e33b3b35e 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -5,7 +5,7 @@ import "normalize.css"; import * as React from 'react'; import { Doc, DocListCast, Opt } from '../../fields/Doc'; import { Cast, NumCast, StrCast } from '../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue } from '../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnTrue, returnFalse } from '../../Utils'; import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { LinkManager } from '../util/LinkManager'; @@ -231,6 +231,8 @@ export class LightboxView extends React.Component<LightboxViewProps> { DataDoc={undefined} addDocument={undefined} fitContentsToDoc={this.fitToBox} + isDocumentActive={returnFalse} + isContentActive={returnTrue} addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} rootSelected={returnTrue} @@ -243,8 +245,7 @@ export class LightboxView extends React.Component<LightboxViewProps> { PanelWidth={this.lightboxWidth} PanelHeight={this.lightboxHeight} focus={DocUtils.DefaultFocus} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 2f871c13d..19a2eb1b4 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -230,7 +230,7 @@ export class MainView extends React.Component { createNewPresentation = async () => { if (!await this.userDoc.myPresentations) { this.userDoc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { - title: "PRESENTATION TRAILS", _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true + title: "PRESENTATION TRAILS", childDontRegisterViews: true, _height: 100, _forceActive: true, boxShadow: "0 0", _lockedPosition: true, treeViewOpen: true, system: true })); } const pres = Docs.Create.PresDocument(new List<Doc>(), @@ -255,13 +255,13 @@ export class MainView extends React.Component { layerProvider={undefined} styleProvider={undefined} rootSelected={returnTrue} + isContentActive={returnTrue} removeDocument={undefined} ScreenToLocalTransform={Transform.Identity} PanelWidth={this.getPWidth} PanelHeight={this.getPHeight} focus={DocUtils.DefaultFocus} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} @@ -351,10 +351,10 @@ export class MainView extends React.Component { PanelWidth={this.flyoutWidthFunc} PanelHeight={this.getContentsHeight} renderDepth={0} + isContentActive={returnTrue} scriptContext={CollectionDockingView.Instance.props.Document} focus={DocUtils.DefaultFocus} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} @@ -385,8 +385,8 @@ export class MainView extends React.Component { focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} layerProvider={undefined} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + isContentActive={returnTrue} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} @@ -485,13 +485,12 @@ export class MainView extends React.Component { fieldKey={"data"} dropAction={"alias"} setHeight={returnFalse} - parentActive={returnFalse} styleProvider={DefaultStyleProvider} layerProvider={undefined} rootSelected={returnTrue} bringToFront={emptyFunction} select={emptyFunction} - active={returnFalse} + isContentActive={returnFalse} isSelected={returnFalse} docViewPath={returnEmptyDoclist} moveDocument={this.moveButtonDoc} @@ -505,7 +504,7 @@ export class MainView extends React.Component { PanelHeight={this.getContentsHeight} renderDepth={0} focus={DocUtils.DefaultFocus} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} @@ -553,7 +552,7 @@ export class MainView extends React.Component { fieldKey="data" dropAction="move" isSelected={returnTrue} - active={returnTrue} + isContentActive={returnTrue} select={returnTrue} setHeight={returnFalse} addDocument={undefined} @@ -569,8 +568,7 @@ export class MainView extends React.Component { renderDepth={0} focus={DocUtils.DefaultFocus} docViewPath={returnEmptyDoclist} - parentActive={returnFalse} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} @@ -596,13 +594,12 @@ export class MainView extends React.Component { setHeight={returnFalse} rootSelected={returnFalse} renderDepth={0} - parentActive={returnFalse} addDocTab={returnFalse} pinToPres={returnFalse} ScreenToLocalTransform={Transform.Identity} bringToFront={returnFalse} - active={returnFalse} - whenActiveChanged={returnFalse} + isContentActive={returnFalse} + whenChildContentsActiveChanged={returnFalse} focus={returnFalse} docViewPath={returnEmptyDoclist} PanelWidth={() => 500} @@ -673,9 +670,8 @@ export class MainView extends React.Component { pinToPres={returnFalse} ScreenToLocalTransform={Transform.Identity} bringToFront={returnFalse} - active={returnFalse} - parentActive={returnFalse} - whenActiveChanged={returnFalse} + isContentActive={returnFalse} + whenChildContentsActiveChanged={returnFalse} focus={returnFalse} PanelWidth={() => 500} PanelHeight={() => 800} diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index 5696b16e9..af04b967a 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -190,8 +190,9 @@ export class OverlayView extends React.Component { PanelHeight={returnOne} ScreenToLocalTransform={Transform.Identity} renderDepth={1} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + isDocumentActive={returnTrue} + isContentActive={returnFalse} + whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={DefaultStyleProvider} layerProvider={undefined} diff --git a/src/client/views/Palette.tsx b/src/client/views/Palette.tsx index fbf67f0a0..86ab881bb 100644 --- a/src/client/views/Palette.tsx +++ b/src/client/views/Palette.tsx @@ -49,12 +49,13 @@ export default class Palette extends React.Component<PaletteProps> { PanelWidth={() => window.screen.width} PanelHeight={() => window.screen.height} renderDepth={0} + isDocumentActive={returnTrue} + isContentActive={returnFalse} focus={emptyFunction} docViewPath={returnEmptyDoclist} styleProvider={returnEmptyString} layerProvider={undefined} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 5c8b5f985..e1c0b96c0 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -30,8 +30,8 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || SelectionManager.Views().lastElement()?.rootDoc; } - propertyToggleBtn = (label: string, property: string, tooltip: (on?: any) => string, icon: (on: boolean) => string, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void) => { - const targetDoc = this.selectedDoc; + propertyToggleBtn = (label: string, property: string, tooltip: (on?: any) => string, icon: (on: boolean) => string, onClick?: (dv: Opt<DocumentView>, doc: Doc, property: string) => void, useUserDoc?: boolean) => { + const targetDoc = useUserDoc ? Doc.UserDoc() : this.selectedDoc; const onPropToggle = (dv: Opt<DocumentView>, doc: Doc, prop: string) => (dv?.layoutDoc || doc)[prop] = (dv?.layoutDoc || doc)[prop] ? false : true; return !targetDoc ? (null) : <Tooltip title={<div className={`dash-tooltip`}>{tooltip(targetDoc?.[property])} </div>} placement="top"> @@ -39,7 +39,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { <div className={`propertiesButtons-linkButton-empty toggle-${StrCast(targetDoc[property]).includes(":hover") ? "hover" : targetDoc[property] ? "on" : "off"}`} onPointerDown={e => e.stopPropagation()} onClick={undoBatch(() => { - if (SelectionManager.Views().length) { + if (SelectionManager.Views().length > 1) { SelectionManager.Views().forEach(dv => (onClick ?? onPropToggle)(dv, dv.rootDoc, property)); } else if (targetDoc) (onClick ?? onPropToggle)(undefined, targetDoc, property); })} > @@ -85,6 +85,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { @computed get gridButton() { return this.propertyToggleBtn("Grid", "_backgroundGrid-show", on => `Display background grid in collection`, on => "border-all"); } + @computed get snapButton() { + return this.propertyToggleBtn("Snap\xA0Lines", "showSnapLines", on => `Display snapping lines when objects are dragged`, on => "border-all", undefined, true); + } @computed get onClickButton() { @@ -200,6 +203,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { {toggle(this.maskButton, { display: !isInk ? "none" : "" })} {toggle(this.chromeButton, { display: isCollection ? "" : "none" })} {toggle(this.gridButton, { display: isCollection ? "" : "none" })} + {toggle(this.snapButton, { display: isCollection ? "" : "none" })} {toggle(this.clustersButton, { display: !isFreeForm ? "none" : "" })} {toggle(this.panButton, { display: !isFreeForm ? "none" : "" })} {toggle(this.perspectiveButton, { display: !isCollection ? "none" : "" })} diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 470e51835..9fc19253e 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -291,12 +291,15 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { Document={layoutDoc} DataDoc={this.dataDoc} renderDepth={1} + fitContentsToDoc={returnTrue} rootSelected={returnFalse} styleProvider={DefaultStyleProvider} layerProvider={undefined} docViewPath={returnEmptyDoclist} freezeDimensions={true} dontCenter={"y"} + isDocumentActive={returnFalse} + isContentActive={returnFalse} NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined} NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined} PanelWidth={panelWidth} @@ -311,8 +314,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { addDocument={returnFalse} moveDocument={undefined} removeDocument={returnFalse} - parentActive={returnFalse} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} addDocTab={returnFalse} pinToPres={emptyFunction} bringToFront={returnFalse} diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx index 6b0b928b3..b5bdf4ca8 100644 --- a/src/client/views/SidebarAnnos.tsx +++ b/src/client/views/SidebarAnnos.tsx @@ -22,8 +22,7 @@ interface ExtraProps { layoutDoc: Doc; rootDoc: Doc; dataDoc: Doc; - annotationsActive: (outsideReaction: boolean) => boolean; - whenActiveChanged: (isActive: boolean) => void; + whenChildContentsActiveChanged: (isActive: boolean) => void; ScreenToLocalTransform: () => Transform; sidebarAddDocument: (doc: (Doc | Doc[]), suffix: string) => boolean; removeDocument: (doc: (Doc | Doc[]), suffix: string) => boolean; @@ -47,7 +46,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { anchorMenuClick = (anchor: Doc) => { const startup = StrListCast(this.props.rootDoc.docFilters).map(filter => filter.split(":")[0]).join(" "); const target = Docs.Create.TextDocument(startup, { - title: "anno", + title: "-note-", annotationOn: this.props.rootDoc, _width: 200, _height: 50, _fitWidth: true, _autoHeight: true, _fontSize: StrCast(Doc.UserDoc().fontSize), _fontFamily: StrCast(Doc.UserDoc().fontFamily) }); @@ -98,7 +97,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { }; return !this.props.layoutDoc._showSidebar ? (null) : <div style={{ - position: "absolute", pointerEvents: this.props.active() ? "all" : undefined, top: 0, right: 0, + position: "absolute", pointerEvents: this.props.isContentActive() ? "all" : undefined, top: 0, right: 0, background: this.props.styleProvider?.(this.props.rootDoc, this.props, StyleProp.WidgetColor), width: `${this.panelWidth()}px`, height: "100%" @@ -120,9 +119,8 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> { scaleField={this.sidebarKey() + "-scale"} isAnnotationOverlay={false} select={emptyFunction} - active={this.props.annotationsActive} scaling={returnOne} - whenActiveChanged={this.props.whenActiveChanged} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} childHideDecorationTitle={returnTrue} removeDocument={this.removeDocument} moveDocument={this.moveDocument} diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 0460addf3..d1e2fefcb 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import 'golden-layout/src/css/goldenlayout-base.css'; import 'golden-layout/src/css/goldenlayout-dark-theme.css'; import { runInAction } from 'mobx'; -import { Doc, Opt, StrListCast, LayoutSym } from "../../fields/Doc"; +import { Doc, Opt, StrListCast } from "../../fields/Doc"; import { List } from '../../fields/List'; import { listSpec } from '../../fields/Schema'; import { BoolCast, Cast, NumCast, StrCast } from "../../fields/Types"; @@ -12,8 +12,7 @@ import { SnappingManager } from '../util/SnappingManager'; import { UndoManager } from '../util/UndoManager'; import { CollectionViewType } from './collections/CollectionView'; import { MainView } from './MainView'; -import { DocumentViewProps, DocumentView } from "./nodes/DocumentView"; -import { FieldViewProps } from './nodes/FieldView'; +import { DocumentViewProps } from "./nodes/DocumentView"; import "./StyleProvider.scss"; import "./collections/TreeView.scss"; import "./nodes/FilterBox.scss"; @@ -57,13 +56,13 @@ function toggleBackground(doc: Doc) { } export function testDocProps(toBeDetermined: any): toBeDetermined is DocumentViewProps { - return (toBeDetermined?.active) ? undefined : toBeDetermined; + return (toBeDetermined?.isContentActive) ? toBeDetermined : undefined; } // // a preliminary implementation of a dash style sheet for setting rendering properties of documents nested within a Tab // -export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string): any { +export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any { const docProps = testDocProps(props) ? props : undefined; const selected = property.includes(":selected"); const isCaption = property.includes(":caption"); @@ -79,7 +78,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | case StyleProp.DocContents: return undefined; case StyleProp.WidgetColor: return isAnnotated ? "lightBlue" : darkScheme() ? "lightgrey" : "dimgrey"; case StyleProp.Opacity: return Cast(doc?._opacity, "number", Cast(doc?.opacity, "number", null)); - case StyleProp.HideLinkButton: return props?.dontRegisterView || (!selected && (doc?.isLinkButton || doc?.hideLinkButton)); + case StyleProp.HideLinkButton: return props?.hideLinkButton || (!selected && (doc?.isLinkButton || doc?.hideLinkButton)); case StyleProp.ShowTitle: return doc && !doc.presentationTargetDoc && StrCast(doc._showTitle, !Doc.IsSystem(doc) && doc.type === DocumentType.RTF ? (doc.author === Doc.CurrentUserEmail ? StrCast(Doc.UserDoc().showTitle) : "author;creationDate") : "") || ""; @@ -96,22 +95,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | case StyleProp.HeaderMargin: return ([CollectionViewType.Stacking, CollectionViewType.Masonry].includes(doc?._viewType as any) || doc?.type === DocumentType.RTF) && showTitle() && !StrCast(doc?.showTitle).includes(":hover") ? 15 : 0; case StyleProp.BackgroundColor: { - if (isAnchor && docProps) return "transparent"; if (isCaption) return "rgba(0,0,0 ,0.4)"; if (Doc.UserDoc().renderStyle === "comic") return "transparent"; let docColor: Opt<string> = StrCast(doc?._backgroundColor); - if (!docProps) { - if (MainView.Instance.LastButton === doc) return darkScheme() ? "dimgrey" : "lightgrey"; - switch (doc?.type) { - case DocumentType.FONTICON: return docColor || "black"; - case DocumentType.LINK: return docColor || "lightblue"; - default: undefined; - } - } + if (MainView.Instance.LastButton === doc) return darkScheme() ? "dimgrey" : "lightgrey"; switch (doc?.type) { case DocumentType.PRESELEMENT: docColor = docColor || (darkScheme() ? "" : ""); break; case DocumentType.PRES: docColor = docColor || (darkScheme() ? "#3e3e3e" : "white"); break; - case DocumentType.FONTICON: docColor = undefined; break; + case DocumentType.FONTICON: docColor = docColor || "black"; break; case DocumentType.RTF: docColor = docColor || (darkScheme() ? "#2d2d2d" : "#f1efeb"); break; case DocumentType.FILTER: docColor = docColor || (darkScheme() ? "#2d2d2d" : "rgba(105, 105, 105, 0.432)"); break; case DocumentType.INK: docColor = doc?.isInkMask ? "rgba(0,0,0,0.7)" : undefined; break; @@ -119,7 +110,8 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | case DocumentType.EQUATION: docColor = docColor || "transparent"; break; case DocumentType.LABEL: docColor = docColor || (doc.annotationOn !== undefined ? "rgba(128, 128, 128, 0.18)" : undefined); break; case DocumentType.BUTTON: docColor = docColor || (darkScheme() ? "#2d2d2d" : "lightgray"); break; - case DocumentType.LINK: return "transparent"; + case DocumentType.LINKANCHOR: docColor = isAnchor ? "lightblue" : "transparent"; break; + case DocumentType.LINK: docColor = docColor || "transparent"; break; case DocumentType.WEB: case DocumentType.PDF: case DocumentType.SCREENSHOT: @@ -163,7 +155,6 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps | } } case StyleProp.PointerEvents: - if (isAnchor && docProps) return "none"; if (props?.pointerEvents === "none") return "none"; const layer = doc && props?.layerProvider?.(doc); if (opacity() === 0 || (doc?.type === DocumentType.INK && !docProps?.treeViewDoc) || doc?.isInkMask) return "none"; diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index dc6e2fe65..5491a81e6 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -141,11 +141,10 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> { onCheckedClick={this.scriptField} onChildClick={this.scriptField} dropAction={undefined} - active={returnTrue} - parentActive={returnFalse} + isContentActive={returnTrue} bringToFront={emptyFunction} focus={emptyFunction} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} ScreenToLocalTransform={Transform.Identity} isSelected={returnFalse} pinToPres={emptyFunction} diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index f0b9b5240..b2ae441d6 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -54,7 +54,6 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume PanelHeight={this.panelHeight} ScreenToLocalTransform={this.props.ScreenToLocalTransform} bringToFront={returnFalse} - parentActive={this.props.active} />; }; @@ -127,7 +126,7 @@ export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocume } @computed get buttons() { - if (!this.props.active()) return null; + if (!this.props.isContentActive()) return null; return <div className="arrow-buttons" > <div key="back" className="carousel3DView-back" style={{ background: `${StrCast(this.props.Document.backgroundColor)}` }} onClick={(e) => this.onArrowClick(e, -1)} diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index f400ac5a2..cc90b9134 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -58,7 +58,6 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) PanelHeight={this.panelHeight} ScreenToLocalTransform={this.props.ScreenToLocalTransform} bringToFront={returnFalse} - parentActive={this.props.active} /> </div> <div className="collectionCarouselView-caption" key="caption" diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 7e89cf55d..388f9a909 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -353,7 +353,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { if (clone) { const cloned = (await Doc.MakeClone(doc)); Array.from(cloned.map.entries()).map(entry => json = json.replace(entry[0], entry[1][Id])); - Doc.SetInPlace(cloned.clone, "dockingConfig", json, true); + Doc.GetProto(cloned.clone).dockingConfig = json; return cloned.clone; } const matches = json.match(/\"documentId\":\"[a-z0-9-]+\"/g); @@ -363,7 +363,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { const origtabdocs = DocListCast(origtab.data); const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true) : Doc.MakeAlias(origtab); const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeAlias(origtabdoc)); - newtabdocs.length && Doc.SetInPlace(newtab, "data", new List<Doc>(newtabdocs), true); + newtabdocs.length && (Doc.GetProto(newtab).data = new List<Doc>(newtabdocs)); json = json.replace(origtab[Id], newtab[Id]); return newtab; }); @@ -437,9 +437,7 @@ export class CollectionDockingView extends CollectionSubView(doc => doc) { } render() { - return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef}> - {this.props.renderDepth > 0 ? "Nested dashboards can't be rendered" : (null)} - </div>; + return <div className="collectiondockingview-container" onPointerDown={this.onPointerDown} ref={this._containerRef} />; } } diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index ead0ce6c4..e0b90304b 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -7,12 +7,13 @@ import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { makeInterface } from '../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnTrue, Utils, emptyPath, returnEmptyDoclist } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, Utils } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { Transform } from '../../util/Transform'; import { DocumentLinksButton } from '../nodes/DocumentLinksButton'; import { DocumentView } from '../nodes/DocumentView'; import { LinkDescriptionPopup } from '../nodes/LinkDescriptionPopup'; +import { StyleProp } from '../StyleProvider'; import "./CollectionLinearView.scss"; import { CollectionSubView } from './CollectionSubView'; import { CollectionViewType } from './CollectionView'; @@ -37,14 +38,13 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { } componentDidMount() { - // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). - this._widthDisposer = reaction(() => 5 + (this.props.Document.linearViewIsExpanded ? this.childDocs.length * (this.props.Document[HeightSym]()) : 10), - width => this.childDocs.length && (this.props.Document._width = width), + this._widthDisposer = reaction(() => 5 + (this.layoutDoc.linearViewIsExpanded ? this.childDocs.length * (this.rootDoc[HeightSym]()) : 10), + width => this.childDocs.length && (this.layoutDoc._width = width), { fireImmediately: true } ); this._selectedDisposer = reaction( - () => NumCast(this.props.Document.selectedIndex), + () => NumCast(this.layoutDoc.selectedIndex), (i) => runInAction(() => { this._selectedIndex = i; let selected: any = undefined; @@ -71,7 +71,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { } } - dimension = () => NumCast(this.props.Document._height); // 2 * the padding + dimension = () => NumCast(this.rootDoc._height); // 2 * the padding getTransform = (ele: React.RefObject<HTMLDivElement>) => () => { if (!ele.current) return Transform.Identity(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ele.current); @@ -109,21 +109,21 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { render() { const guid = Utils.GenerateGuid(); const flexDir: any = StrCast(this.Document.flexDirection); - const backgroundColor = StrCast(this.props.Document.backgroundColor, "black"); - const color = StrCast(this.props.Document.color, "white"); + const backgroundColor = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor); + const color = this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.Color); const menuOpener = <label htmlFor={`${guid}`} style={{ pointerEvents: "all", cursor: "pointer", background: backgroundColor === color ? "black" : backgroundColor, }} onPointerDown={e => e.stopPropagation()} > - <p>{BoolCast(this.props.Document.linearViewIsExpanded) ? "–" : "+"}</p> + <p>{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "–" : "+"}</p> </label>; return <div className="collectionLinearView-outer"> <div className="collectionLinearView" ref={this.createDashEventsTarget} > - <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.props.Document.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top"> + <Tooltip title={<><div className="dash-tooltip">{BoolCast(this.layoutDoc.linearViewIsExpanded) ? "Close menu" : "Open menu"}</div></>} placement="top"> {menuOpener} </Tooltip> - <input id={`${guid}`} type="checkbox" checked={BoolCast(this.props.Document.linearViewIsExpanded)} ref={this.addMenuToggle} - onChange={action(() => this.props.Document.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> + <input id={`${guid}`} type="checkbox" checked={BoolCast(this.layoutDoc.linearViewIsExpanded)} ref={this.addMenuToggle} + onChange={action(() => this.layoutDoc.linearViewIsExpanded = this.addMenuToggle.current!.checked)} /> <div className="collectionLinearView-content" style={{ height: this.dimension(), flexDirection: flexDir }}> {this.childLayoutPairs.map((pair, ind) => { @@ -140,6 +140,8 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { <DocumentView Document={pair.layout} DataDoc={pair.data} + isContentActive={returnFalse} + isDocumentActive={returnTrue} addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} addDocTab={this.props.addDocTab} @@ -154,8 +156,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { styleProvider={this.props.styleProvider} layerProvider={this.props.layerProvider} docViewPath={returnEmptyDoclist} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx index 92288c1f2..2d7569d45 100644 --- a/src/client/views/collections/CollectionMapView.tsx +++ b/src/client/views/collections/CollectionMapView.tsx @@ -208,7 +208,7 @@ export class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMap render() { const { childLayoutPairs } = this; - const { Document, fieldKey, active, google } = this.props; + const { Document, fieldKey, isContentActive: active, google } = this.props; const mapLoc = this.getLocation(this.rootDoc, `${fieldKey}-mapCenter`, false); let center = mapLoc; if (center === undefined) { diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index ba701b2a4..a94e706eb 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -281,7 +281,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr gridTemplateColumns: numberRange(rows).reduce((list: string, i: any) => list + ` ${this.props.parent.columnWidth}px`, ""), }}> {this.props.parent.children(this.props.docList)} - {this.props.showHandle && this.props.parent.props.active() ? this.props.parent.columnDragger : (null)} + {this.props.showHandle && this.props.parent.props.isContentActive() ? this.props.parent.columnDragger : (null)} </div> </div>; } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index aaf243567..623e05b33 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -341,8 +341,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp } @computed get viewModes() { - const excludedViewTypes = Doc.UserDoc().noviceMode ? [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Stacking, CollectionViewType.Map, CollectionViewType.Linear] : - [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; + const excludedViewTypes = [CollectionViewType.Invalid, CollectionViewType.Docking, CollectionViewType.Pile, CollectionViewType.StackedTimeline, CollectionViewType.Linear]; const isPres: boolean = (this.document && this.document.type === DocumentType.PRES); return isPres ? (null) : (<div className="collectionViewBaseChrome-viewModes" > <Tooltip title={<div className="dash-tooltip">drop document to apply or drag to create button</div>} placement="bottom"> diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index a265045b8..b33c437a9 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -328,7 +328,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { @action onWheel(e: React.WheelEvent) { const scale = this.props.ScreenToLocalTransform().Scale; - this.props.active(true) && e.stopPropagation(); + this.props.isContentActive(true) && e.stopPropagation(); } @computed get renderMenuContent() { @@ -410,6 +410,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { rootSelected={this.rootSelected} PanelWidth={this.previewWidth} PanelHeight={this.previewHeight} + isContentActive={returnTrue} + isDocumentActive={returnFalse} ScreenToLocalTransform={this.getPreviewTransform} docFilters={this.docFilters} docRangeFilters={this.docRangeFilters} @@ -422,8 +424,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { moveDocument={this.props.moveDocument} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} @@ -445,7 +446,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { renderDepth={this.props.renderDepth} moveDocument={this.props.moveDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} - active={this.props.active} + active={this.props.isContentActive} onDrop={this.onExternalDrop} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} @@ -534,11 +535,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { this.columns = columns; } - onZoomMenu = (e: React.WheelEvent) => this.props.active(true) && e.stopPropagation(); + onZoomMenu = (e: React.WheelEvent) => this.props.isContentActive(true) && e.stopPropagation(); render() { TraceMobx(); - if (!this.props.active()) setTimeout(() => this.closeHeader(), 0); + if (!this.props.isContentActive()) setTimeout(() => this.closeHeader(), 0); const menuContent = this.renderMenuContent; const menu = <div className="collectionSchema-header-menu" onWheel={e => this.onZoomMenu(e)} @@ -554,21 +555,21 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { return <div className={"collectionSchemaView" + (this.props.Document._searchDoc ? "-searchContainer" : "-container")} style={{ overflow: this.props.scrollOverflow === true ? "scroll" : undefined, backgroundColor: "white", - pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined, + pointerEvents: this.props.Document._searchDoc !== undefined && !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined, width: name === "collectionSchemaView-searchContainer" ? "auto" : this.props.PanelWidth() || "100%", height: this.props.PanelHeight() || "100%", position: "relative", }} > <div className="collectionSchemaView-tableContainer" style={{ width: `calc(100% - ${this.previewWidth()}px)` }} onContextMenu={this.onSpecificMenu} onPointerDown={this.onPointerDown} - onWheel={e => this.props.active(true) && e.stopPropagation()} + onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} onDrop={e => this.onExternalDrop(e, {})} ref={this.createTarget}> {this.schemaTable} </div> {this.dividerDragger} {!this.previewWidth() ? (null) : this.previewPanel} - {this._headerOpen && this.props.active() ? menu : null} + {this._headerOpen && this.props.isContentActive() ? menu : null} </div>; } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index c0cebf021..6a1242f20 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -32,10 +32,10 @@ export type CollectionStackedTimelineProps = { playFrom: (seekTimeInSeconds: number, endTime?: number) => void; playing: () => boolean; setTime: (time: number) => void; - isChildActive: () => boolean; startTag: string; endTag: string; mediaPath: string; + dictationKey: string; }; @observer @@ -114,7 +114,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument onPointerDownTimeline = (e: React.PointerEvent): void => { const rect = this._timeline?.getBoundingClientRect(); const clientX = e.clientX; - if (rect && this.props.active()) { + if (rect && this.props.isContentActive()) { const wasPlaying = this.props.playing(); if (wasPlaying) this.props.Pause(); const wasSelecting = CollectionStackedTimeline.SelectingRegion === this; @@ -145,7 +145,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument e.shiftKey && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime); !wasPlaying && doubleTap && this.props.Play(); }, - this.props.isSelected(true) || this.props.isChildActive(), undefined, + this.props.isSelected(true) || this.props.isContentActive(), undefined, () => !wasPlaying && this.props.setTime((clientX - rect.x) / rect.width * this.duration)); } } @@ -238,18 +238,19 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument timelineContentHeight = () => this.props.PanelHeight() * 2 / 3; dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight()); @computed get renderDictation() { - const dictation = Cast(this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")], Doc, null); + const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); return !dictation ? (null) : <div style={{ position: "absolute", height: this.dictationHeight(), top: this.timelineContentHeight(), background: "tan" }}> <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} Document={dictation} PanelHeight={this.dictationHeight} isAnnotationOverlay={true} + isDocumentActive={returnFalse} select={emptyFunction} scaling={returnOne} xMargin={25} yMargin={10} ScreenToLocalTransform={this.dictationScreenToLocalTransform} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} removeDocument={returnFalse} moveDocument={returnFalse} addDocument={returnFalse} @@ -274,7 +275,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = []; const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; - const isActive = this.props.isChildActive() || this.props.isSelected(false); + const isActive = this.props.isContentActive() || this.props.isSelected(false); return <div className="collectionStackedTimeline" ref={(timeline: HTMLDivElement | null) => this._timeline = timeline} onClick={e => isActive && StopEvent(e)} onPointerDown={e => isActive && this.onPointerDownTimeline(e)}> {drawAnchors.map(d => { @@ -323,7 +324,6 @@ interface StackedTimelineAnchorProps { toTimeline: (screen_delta: number, width: number) => number; playLink: (linkDoc: Doc) => void; setTime: (time: number) => void; - isChildActive: () => boolean; startTag: string; endTag: string; renderDepth: number; @@ -395,11 +395,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} LayoutTemplateString={LabelBox.LayoutString("data")} + isDocumentActive={returnFalse} PanelWidth={() => width} PanelHeight={() => height} ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x, -y)} focus={focusFunc} - parentActive={out => this.props.isSelected(out) || this.props.isChildActive()} rootSelected={returnFalse} onClick={script} onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 23c63561c..cc5a41c72 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -196,7 +196,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, }); } - styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => { + styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { if (property === StyleProp.Opacity && doc) { if (this.props.childOpacity) { return this.props.childOpacity(); @@ -207,6 +207,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, } return this.props.styleProvider?.(doc, props, property); } + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); getDisplayDoc(doc: Doc, width: () => number) { const dataDoc = (!doc.isTemplateDoc && !doc.isTemplateForField && !doc.PARAMS) ? undefined : this.props.DataDoc; const height = () => this.getDocHeight(doc); @@ -224,6 +225,8 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, layerProvider={this.props.layerProvider} docViewPath={this.props.docViewPath} fitWidth={this.props.childFitWidth} + isContentActive={returnFalse} + isDocumentActive={this.isContentActive} LayoutTemplate={this.props.childLayoutTemplate} LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} @@ -248,8 +251,7 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} contentPointerEvents={StrCast(this.layoutDoc.contentPointerEvents)} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.addDocTab} bringToFront={returnFalse} scriptContext={this.props.scriptContext} @@ -534,14 +536,14 @@ export class CollectionStackingView extends CollectionSubView<StackingDocument, <div className={this.isStackingView ? "collectionStackingView" : "collectionMasonryView"} ref={this.createRef} style={{ - overflowY: this.props.active() ? "auto" : "hidden", + overflowY: this.props.isContentActive() ? "auto" : "hidden", background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.BackgroundColor), pointerEvents: this.backgroundEvents ? "all" : undefined }} onScroll={action(e => this._scroll = e.currentTarget.scrollTop)} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} - onWheel={e => this.props.active(true) && e.stopPropagation()} > + onWheel={e => this.props.isContentActive(true) && e.stopPropagation()} > {this.renderedSections} {!this.showAddAGroup ? (null) : <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 28f4f76be..79fab8b62 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -442,25 +442,23 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: }); } } - this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument); - batch.end(); + this.slowLoadDocuments(files, options, generatedDocuments, text, completed, e.clientX, e.clientY, addDocument).then(batch.end); } slowLoadDocuments = async (files: File[], options: DocumentOptions, generatedDocuments: Doc[], text: string, completed: (() => void) | undefined, clientX: number, clientY: number, addDocument: (doc: Doc | Doc[]) => boolean) => { - runInAction(() => CollectionSubViewLoader.Waiting = "block"); const disposer = OverlayView.Instance.addElement( <ReactLoading type={"spinningBubbles"} color={"green"} height={250} width={250} />, { x: clientX - 125, y: clientY - 125 }); generatedDocuments.push(...await DocUtils.uploadFilesToDocs(files, options)); if (generatedDocuments.length) { const set = generatedDocuments.length > 1 && generatedDocuments.map(d => DocUtils.iconify(d)); if (set) { - UndoManager.RunInBatch(() => addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!), "drop"); + addDocument(DocUtils.pileup(generatedDocuments, options.x!, options.y!)!); } else { - UndoManager.RunInBatch(() => generatedDocuments.forEach(addDocument), "drop"); + generatedDocuments.forEach(addDocument); } completed?.(); } else { if (text && !text.includes("https://")) { - UndoManager.RunInBatch(() => addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })), "drop"); + addDocument(Docs.Create.TextDocument(text, { ...options, title: text.substring(0, 20), _width: 400, _height: 315 })); } else { alert("Document upload failed - possibly an unsupported file type."); } @@ -472,10 +470,6 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: return CollectionSubView; } -export class CollectionSubViewLoader { - @observable public static Waiting = "none"; -} - import { DragManager, dropActionType } from "../../util/DragManager"; import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 0702febae..f41043179 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -136,7 +136,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { } @computed get contents() { - return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.active() ? undefined : "none" }} + return <div className="collectionTimeView-innards" key="timeline" style={{ pointerEvents: this.props.isContentActive() ? undefined : "none" }} onClick={this.contentsDown}> <CollectionFreeFormView {...this.props} engineProps={{ pivotField: this.pivotField, docFilters: this.docFilters, docRangeFilters: this.docRangeFilters }} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index f5eb534aa..37600fa4d 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -27,6 +27,8 @@ import { TreeView } from "./TreeView"; import React = require("react"); export type collectionTreeViewProps = { + treeViewExpandedView?: "fields" | "layout" | "links" | "data"; + treeViewOpen?: boolean; treeViewHideTitle?: boolean; treeViewHideHeaderFields?: boolean; treeViewSkipFields?: string[]; // prevents specific fields from being displayed (see LinkBox) @@ -38,12 +40,19 @@ export type collectionTreeViewProps = { export class CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) { private treedropDisposer?: DragManager.DragDropDisposer; - private _isChildActive = false; private _mainEle?: HTMLDivElement; - public _uniqueId = Utils.GenerateGuid(); - - @computed get doc() { TraceMobx(); return this.props.Document; } + MainEle = () => this._mainEle; + @computed get doc() { return this.props.Document; } @computed get dataDoc() { return this.props.DataDoc || this.doc; } + @computed get treeViewtruncateTitleWidth() { return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth()); } + @computed get treeChildren() { return this.props.childDocuments || this.childDocs; } + @computed get outlineMode() { return this.doc.treeViewType === "outline"; } + @computed get fileSysMode() { return this.doc.treeViewType === "fileSystem"; } + + componentWillUnmount() { + super.componentWillUnmount(); + this.treedropDisposer?.(); + } protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer?.(); @@ -53,19 +62,13 @@ export class protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { const dragData = de.complete.docDragData; if (dragData) { - if (targetAction && !dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d))) { - dragData.dropAction = targetAction; - } else dragData.dropAction = this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction; + const isInTree = () => dragData.draggedDocuments.some(d => d.context === this.doc && this.childDocs.includes(d)); + dragData.dropAction = targetAction && !isInTree() ? targetAction : this.doc === dragData?.treeViewDoc ? "same" : dragData.dropAction; } } - componentWillUnmount() { - super.componentWillUnmount(); - this.treedropDisposer?.(); - } - - @undoBatch - remove = action((doc: Doc | Doc[]): boolean => { + @action + remove = (doc: Doc | Doc[]): boolean => { const docs = doc instanceof Doc ? [doc] : doc; const targetDataDoc = this.doc[DataSym]; const value = DocListCast(targetDataDoc[this.props.fieldKey]); @@ -83,27 +86,24 @@ export class return true; } return false; - }); + } + @action - addDoc = (doc: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { + addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { const doAddDoc = (doc: Doc | Doc[]) => - (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => - flg && Doc.AddDocToList(this.doc[DataSym], this.props.fieldKey, doc, relativeTo, before, false, false, false), true); - if (this.doc.resolvedDataDoc instanceof Promise) { - this.doc.resolvedDataDoc.then((resolved: any) => doAddDoc(doc)); - } else if (relativeTo === undefined) { - this.props.addDocument?.(doc); - } else { - doAddDoc(doc); - (doc instanceof Doc ? [doc] : doc).forEach(d => d.context = this.props.Document); - } - return true; + (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => { + const res = flg && Doc.AddDocToList(this.doc[DataSym], this.props.fieldKey, doc, relativeTo, before); + res && (doc.context = this.props.Document); + return res; + }, true); + if (this.doc.resolvedDataDoc instanceof Promise) return false; + return relativeTo === undefined ? this.props.addDocument?.(docs) || false : doAddDoc(docs); } onContextMenu = (e: React.MouseEvent): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout if (!Doc.UserDoc().noviceMode) { const layoutItems: ContextMenuProps[] = []; - layoutItems.push({ description: (this.doc.treeViewPreventOpen ? "Persist" : "Abandon") + "Treeview State", event: () => this.doc.treeViewPreventOpen = !this.doc.treeViewPreventOpen, icon: "paint-brush" }); + layoutItems.push({ description: "Make tree state " + (this.doc.treeViewOpenIsTransient ? "persistent" : "transient"), event: () => this.doc.treeViewOpenIsTransient = !this.doc.treeViewOpenIsTransient, icon: "paint-brush" }); layoutItems.push({ description: (this.doc.treeViewHideHeaderFields ? "Show" : "Hide") + " Header Fields", event: () => this.doc.treeViewHideHeaderFields = !this.doc.treeViewHideHeaderFields, icon: "paint-brush" }); layoutItems.push({ description: (this.doc.treeViewHideTitle ? "Show" : "Hide") + " Title", event: () => this.doc.treeViewHideTitle = !this.doc.treeViewHideTitle, icon: "paint-brush" }); ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" }); @@ -113,50 +113,30 @@ export class !existingOnClick && ContextMenu.Instance.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); } } - outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onExternalDrop(e, {}); - @computed get renderClearButton() { - return !this.doc.allowClear ? (null) : <div key="toolbar"> - <button className="toolbar-button round-button" title="Empty" onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}> - <FontAwesomeIcon icon={"trash"} size="sm" /> - </button> - </div >; - } - @undoBatch - makeTextCollection = action((childDocs: Doc[]) => { - Doc.SetInPlace(this.doc, "editTitle", undefined, false); - const bullet = TreeView.makeTextBullet(); - bullet.context = this.doc; - this.addDoc(bullet, childDocs.length ? childDocs[0] : undefined, true); - }); + makeTextCollection = (childDocs: Doc[]) => { + this.addDoc(TreeView.makeTextBullet(), childDocs.length ? childDocs[0] : undefined, true); + } editableTitle = (childDocs: Doc[]) => { - return !this.dataDoc ? (null) : <EditableView - contents={this.dataDoc.title} - editing={false} - display={"block"} - maxHeight={72} - height={"auto"} - GetValue={() => StrCast(this.dataDoc.title)} - SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (enter) { - switch (this.props.Document.treeViewType) { - case "outline": this.makeTextCollection(childDocs); break; - case "fileSystem": break; - } - } - return Doc.SetInPlace(this.dataDoc, "title", value, false); - })} />; + return !this.dataDoc ? (null) : + <EditableView + contents={this.dataDoc.title} + display={"block"} + maxHeight={72} + height={"auto"} + GetValue={() => StrCast(this.dataDoc.title)} + SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { + if (enter && this.props.Document.treeViewType === "outline") this.makeTextCollection(childDocs); + this.dataDoc.title = value; + return true; + })} />; } - - rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.PanelWidth() - 20); - rtfOutlineHeight = () => Math.min(this.layoutDoc?.[HeightSym](), (StrCast(this.layoutDoc?._fontSize) ? Number(StrCast(this.layoutDoc?._fontSize, "32px").replace("px", "")) : NumCast(this.layoutDoc?._fontSize)) * 2); - titleTransform = () => this.props.ScreenToLocalTransform().translate(-NumCast(this.doc._xPadding, 10), -NumCast(this.doc._yPadding, 20)); documentTitle = (childDocs: Doc[]) => { - return <div style={{ display: "inline-block", width: "100%", height: this.rtfOutlineHeight() }} key={this.doc[Id]} + return <div style={{ display: "inline-block", width: "100%", height: this.documentTitleHeight() }} key={this.doc[Id]} onKeyDown={e => { e.stopPropagation(); e.key === "Enter" && this.makeTextCollection(childDocs); @@ -166,15 +146,15 @@ export class DataDoc={undefined} LayoutTemplateString={FormattedTextBox.LayoutString("text")} renderDepth={this.props.renderDepth + 1} + isContentActive={this.isContentActive} rootSelected={returnTrue} - //dontRegisterView={true} docViewPath={this.props.docViewPath} styleProvider={this.props.styleProvider} layerProvider={this.props.layerProvider} - PanelWidth={this.rtfWidth} - PanelHeight={this.rtfOutlineHeight} - NativeWidth={this.rtfWidth} - NativeHeight={this.rtfOutlineHeight} + PanelWidth={this.documentTitleWidth} + PanelHeight={this.documentTitleHeight} + NativeWidth={this.documentTitleWidth} + NativeHeight={this.documentTitleHeight} focus={this.props.focus} ScreenToLocalTransform={this.titleTransform} docFilters={returnEmptyFilter} @@ -185,54 +165,77 @@ export class addDocument={this.props.addDocument} moveDocument={returnFalse} removeDocument={returnFalse} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} /> </div>; } - @computed get treeViewtruncateTitleWidth() { return NumCast(this.doc.treeViewTruncateTitleWidth, this.panelWidth()); } - truncateTitleWidth = () => this.treeViewtruncateTitleWidth; - @computed get outlineMode() { return this.doc.treeViewType === "outline"; } - @computed get fileSysMode() { return this.doc.treeViewType === "fileSystem"; } - onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); - whenActiveChanged = (isActive: boolean) => { this.props.whenActiveChanged(this._isChildActive = isActive); }; - active = (outsideReaction: boolean | undefined) => this.props.active(outsideReaction) || this._isChildActive; - panelWidth = () => this.props.PanelWidth() - 20; // bcz: 20 is the 10 + 10 for the left and right padding. - @computed get treeChildren() { - TraceMobx(); - return this.props.childDocuments || this.childDocs; - } @computed get treeViewElements() { TraceMobx(); const dropAction = StrCast(this.doc.childDropAction) as dropActionType; const addDoc = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => this.addDoc(doc, relativeTo, before); const moveDoc = (d: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.props.moveDocument?.(d, target, addDoc) || false; - return TreeView.GetChildElements(this.treeChildren, this, this.doc, this.props.DataDoc, this.props.fieldKey, this.props.ContainingCollectionDoc, undefined, addDoc, this.remove, - moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.styleProvider, returnTrue, this.props.ScreenToLocalTransform, - this.outerXf, this.active, this.panelWidth, this.props.renderDepth, () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields), - BoolCast(this.doc.treeViewPreventOpen), [], this.props.onCheckedClick, - this.onChildClick, this.props.treeViewSkipFields, true, this.whenActiveChanged, this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null), this); + return TreeView.GetChildElements( + this.treeChildren, + this, + this, + this.doc, + this.props.DataDoc, + this.props.ContainingCollectionDoc, + undefined, + addDoc, + this.remove, + moveDoc, + dropAction, + this.props.addDocTab, + this.props.styleProvider, + this.props.ScreenToLocalTransform, + this.props.isContentActive, + this.panelWidth, + this.props.renderDepth, + () => this.props.treeViewHideHeaderFields || BoolCast(this.doc.treeViewHideHeaderFields), + [], + this.props.onCheckedClick, + this.onChildClick, + this.props.treeViewSkipFields, + true, + this.props.whenChildContentsActiveChanged, + this.props.dontRegisterView || Cast(this.props.Document.childDontRegisterViews, "boolean", null)); } @computed get titleBar() { const hideTitle = this.props.treeViewHideTitle || this.doc.treeViewHideTitle; return hideTitle ? (null) : (this.doc.treeViewType === "outline" ? this.documentTitle : this.editableTitle)(this.treeChildren); } + + @computed get renderClearButton() { + return !this.doc.treeViewShowClearButton ? (null) : <div key="toolbar"> + <button className="toolbar-button round-button" title="Empty" onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}> + <FontAwesomeIcon icon={"trash"} size="sm" /> + </button> + </div >; + } + + paddingX = () => NumCast(this.doc._xPadding, 15); + documentTitleWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.panelWidth()); + documentTitleHeight = () => Math.min(this.layoutDoc?.[HeightSym](), (StrCast(this.layoutDoc?._fontSize) ? Number(StrCast(this.layoutDoc?._fontSize, "32px").replace("px", "")) : NumCast(this.layoutDoc?._fontSize)) * 2); + titleTransform = () => this.props.ScreenToLocalTransform().translate(-NumCast(this.doc._xPadding, 10), -NumCast(this.doc._yPadding, 20)); + truncateTitleWidth = () => this.treeViewtruncateTitleWidth; + onChildClick = () => this.props.onChildClick?.() || ScriptCast(this.doc.onChildClick); + panelWidth = () => this.props.PanelWidth() - 2 * this.paddingX(); + isContentActive = () => this.props.isContentActive() || this.props.isSelected(); render() { TraceMobx(); - if (!(this.doc instanceof Doc)) return (null); - const background = this.props.treeViewHideTitle && this.props.treeViewHideHeaderFields ? "#9F9F9F" : this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); - const paddingX = `${NumCast(this.doc._xPadding, 15)}px`; - const paddingTop = `${NumCast(this.doc._yPadding, 20)}px`; - const pointerEvents = !this.props.active() && !SnappingManager.GetIsDragging() && !this._isChildActive ? "none" : undefined; + const background = () => this.props.styleProvider?.(this.doc, this.props, StyleProp.BackgroundColor); + const paddingTop = () => `${NumCast(this.doc._yPadding, 20)}px`; + const pointerEvents = () => !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined; - return !this.treeChildren ? (null) : ( + return !(this.doc instanceof Doc) || !this.treeChildren ? (null) : <div className="collectionTreeView-container" onContextMenu={this.onContextMenu}> <div className="collectionTreeView-dropTarget" - style={{ background, paddingLeft: paddingX, paddingRight: paddingX, paddingTop, pointerEvents }} - onWheel={(e) => this._mainEle && this._mainEle.scrollHeight > this._mainEle.clientHeight && e.stopPropagation()} + style={{ background: background(), paddingLeft: `${this.paddingX()}px`, paddingRight: `${this.paddingX()}px`, paddingTop: paddingTop(), pointerEvents: pointerEvents() }} + onWheel={e => e.stopPropagation()} onDrop={this.onTreeDrop} ref={this.createTreeDropTarget}> {this.titleBar} @@ -241,7 +244,6 @@ export class {this.treeViewElements} </ul> </div > - </div> - ); + </div>; } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 1e693f594..91f159d8e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -2,26 +2,23 @@ import { action, computed, observable } from 'mobx'; import { observer } from "mobx-react"; import * as React from 'react'; import 'react-image-lightbox-with-rotate/style.css'; // This only needs to be imported once in your app -import { DateField } from '../../../fields/DateField'; -import { AclAddonly, AclAdmin, AclEdit, AclPrivate, AclReadonly, AclSym, DataSym, Doc, DocListCast, DocListCastAsync, Field } from '../../../fields/Doc'; +import { Doc, DocListCast } from '../../../fields/Doc'; +import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; -import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; +import { makeInterface } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { Cast, ScriptCast, StrCast, DateCast } from '../../../fields/Types'; -import { denormalizeEmail, distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; -import { returnFalse } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; -import { BranchTask, BranchCreate } from '../../documents/Gitlike'; -import { DocumentType } from '../../documents/DocumentTypes'; +import { Cast, ScriptCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { DocUtils } from '../../documents/Documents'; +import { BranchCreate, BranchTask } from '../../documents/Gitlike'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { InteractionUtils } from '../../util/InteractionUtils'; -import { UndoManager } from '../../util/UndoManager'; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; -import { Touchable } from '../Touchable'; import { CollectionCarousel3DView } from './CollectionCarousel3DView'; import { CollectionCarouselView } from './CollectionCarouselView'; import { CollectionDockingView } from "./CollectionDockingView"; @@ -66,8 +63,6 @@ export enum CollectionViewType { export interface CollectionViewProps extends FieldViewProps { isAnnotationOverlay?: boolean; // is the collection an annotation overlay (eg an overlay on an image/video/etc) layoutEngine?: () => string; - parentActive: (outsideReaction: boolean) => boolean; - filterAddDocument?: (doc: Doc | Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) setPreviewCursor?: (func: (x: number, y: number, drag: boolean) => void) => void; // property overrides for child documents @@ -84,19 +79,20 @@ export interface CollectionViewProps extends FieldViewProps { childClickScript?: ScriptField; childDoubleClickScript?: ScriptField; } + +type CollectionDocument = makeInterface<[typeof documentSchema]>; +const CollectionDocument = makeInterface(documentSchema); @observer -export class CollectionView extends Touchable<CollectionViewProps> { +export class CollectionView extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & CollectionViewProps, CollectionDocument>(CollectionDocument, "") { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } - _isChildActive = false; //TODO should this be observable? - @observable private _curLightboxImg = 0; @observable private static _safeMode = false; public static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; } protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; get collectionViewType(): CollectionViewType | undefined { - const viewField = StrCast(this.props.Document._viewType); + const viewField = StrCast(this.layoutDoc._viewType); if (CollectionView._safeMode) { switch (viewField) { case CollectionViewType.Freeform: @@ -107,133 +103,16 @@ export class CollectionView extends Touchable<CollectionViewProps> { return viewField as any as CollectionViewType; } - active = (outsideReaction?: boolean) => { - return this.props.renderDepth === 0 || - this.props.isSelected(outsideReaction) || - this.props.rootSelected(outsideReaction) || - (this.props.layerProvider?.(this.props.Document) !== false && (this.props.Document.forceActive || this.props.Document._isGroup)) || - this._isChildActive ? - true : - false; - } - - whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); - - /** - * Applies the collection/dashboard's current filter attributes to the doc being added - */ - addFilterAttributes = (doc: Doc) => { - Cast((FilterBox._filterScope === "Current Collection" ? this.props.Document : CurrentUserUtils.ActiveDashboard)._docFilters, listSpec("string"))?.forEach(attribute => { - if (attribute.charAt(0).toUpperCase() === attribute.charAt(0)) { - const fields = attribute.split(':'); - if (fields[2] === "check") doc[DataSym][fields[0]] = fields[1]; - else if (fields[2] === "x" && doc[DataSym][fields[0]] === fields[1]) doc[DataSym][fields[0]] = undefined; - } - }); - } - - @action.bound - addDocument = (doc: Doc | Doc[]): boolean => { - if (this.props.filterAddDocument?.(doc) === false) { - return false; - } - - const docs = doc instanceof Doc ? [doc] : doc; - - if (docs.find(doc => Doc.AreProtosEqual(doc, this.props.Document))) return false; - const targetDataDoc = this.props.Document[DataSym]; - const docList = DocListCast(targetDataDoc[this.props.fieldKey]); - const added = docs.filter(d => !docList.includes(d)); - const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); - - if (added.length) { - if (effectiveAcl === AclPrivate || effectiveAcl === AclReadonly) { - return false; - } - else { - if (this.props.Document[AclSym] && Object.keys(this.props.Document[AclSym])) { - added.forEach(d => { - for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); - //else if (this.props.Document[key] === SharingPermissions.Admin) distributeAcls(key, SharingPermissions.Add, d, true); - //else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); - } - }); - } - - if (effectiveAcl === AclAddonly) { - added.map(doc => { - this.props.layerProvider?.(doc, true);// assigns layer values to the newly added document... testing the utility of this - Doc.AddDocToList(targetDataDoc, this.props.fieldKey, doc); - doc.context = this.props.Document; - }); - } - else { - added.filter(doc => [AclAdmin, AclEdit].includes(GetEffectiveAcl(doc))).map(doc => { // only make a pushpin if we have acl's to edit the document - DocUtils.LeavePushpin(doc); - doc._stayInCollection = undefined; - doc.context = this.props.Document; - this.addFilterAttributes(doc); // - }); - added.map(doc => this.props.layerProvider?.(doc, true));// assigns layer values to the newly added document... testing the utility of this - (targetDataDoc[this.props.fieldKey] as List<Doc>).push(...added); - targetDataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now())); - } - } - } - return true; - } - - @action.bound - removeDocument = (doc: any): boolean => { - const effectiveAcl = GetEffectiveAcl(this.props.Document[DataSym]); - const indocs = doc instanceof Doc ? [doc] : doc as Doc[]; - const docs = indocs.filter(doc => effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin); - if (docs.length) { - const targetDataDoc = this.props.Document[DataSym]; - const value = DocListCast(targetDataDoc[this.props.fieldKey]); - const toRemove = value.filter(v => docs.includes(v)); - if (toRemove.length !== 0) { - const recent = Cast(Doc.UserDoc().myRecentlyClosedDocs, Doc) as Doc; - toRemove.forEach(doc => { - const ind = (targetDataDoc[this.props.fieldKey] as List<Doc>).indexOf(doc); - if (ind !== -1) { - Doc.RemoveDocFromList(targetDataDoc, this.props.fieldKey, doc); - doc.context = undefined; - recent && Doc.AddDocToList(recent, "data", doc, undefined, true, true); - } - }); - return true; - } - } - return false; - } - - // this is called with the document that was dragged and the collection to move it into. - // if the target collection is the same as this collection, then the move will be allowed. - // otherwise, the document being moved must be able to be removed from its container before - // moving it into the target. - @action.bound - moveDocument = (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (doc: Doc | Doc[]) => boolean): boolean => { - if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { - return true; - } - const first = doc instanceof Doc ? doc : doc[0]; - if (!first?._stayInCollection && addDocument !== returnFalse) { - return UndoManager.RunInTempBatch(() => this.removeDocument(doc) && addDocument(doc)); - } - return false; - } - showIsTagged = () => { return (null); // this section would display an icon in the bototm right of a collection to indicate that all // photos had been processed through Google's content analysis API and Google's tags had been // assigned to the documents googlePhotosTags field. - // const children = DocListCast(this.props.Document[this.props.fieldKey]); + // const children = DocListCast(this.rootDoc[this.props.fieldKey]); // const imageProtos = children.filter(doc => Cast(doc.data, ImageField)).map(Doc.GetProto); // const allTagged = imageProtos.length > 0 && imageProtos.every(image => image.googlePhotosTags); // return !allTagged ? (null) : <img id={"google-tags"} src={"/assets/google_tags.png"} />; + this.isContentActive(); } screenToLocalTransform = () => this.props.renderDepth ? this.props.ScreenToLocalTransform() : this.props.ScreenToLocalTransform().scale(this.props.PanelWidth() / this.bodyPanelWidth()); @@ -268,7 +147,6 @@ export class CollectionView extends Touchable<CollectionViewProps> { } subItems.push({ description: "Schema", event: () => func(CollectionViewType.Schema), icon: "th-list" }); subItems.push({ description: "Tree", event: () => func(CollectionViewType.Tree), icon: "tree" }); - !Doc.UserDoc().noviceMode && subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking), icon: "ellipsis-v" }); subItems.push({ description: "Stacking", event: () => func(CollectionViewType.Stacking)._autoHeight = true, icon: "ellipsis-v" }); subItems.push({ description: "Multicolumn", event: () => func(CollectionViewType.Multicolumn), icon: "columns" }); subItems.push({ description: "Multirow", event: () => func(CollectionViewType.Multirow), icon: "columns" }); @@ -279,7 +157,7 @@ export class CollectionView extends Touchable<CollectionViewProps> { !Doc.UserDoc().noviceMode && subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "th-list" }); - if (!Doc.IsSystem(this.props.Document) && !this.props.Document.annotationOn) { + if (!Doc.IsSystem(this.rootDoc) && !this.rootDoc.annotationOn) { const existingVm = ContextMenu.Instance.findByDescription(category); const catItems = existingVm && "subitems" in existingVm ? existingVm.subitems : []; catItems.push({ description: "Add a Perspective...", addDivider: true, noexpand: true, subitems: subItems, icon: "eye" }); @@ -289,9 +167,9 @@ export class CollectionView extends Touchable<CollectionViewProps> { onContextMenu = (e: React.MouseEvent): void => { const cm = ContextMenu.Instance; - if (cm && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + if (cm && !e.isPropagationStopped() && this.rootDoc[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 this.setupViewTypes("UI Controls...", vtype => { - const newRendition = Doc.MakeAlias(this.props.Document); + const newRendition = Doc.MakeAlias(this.rootDoc); newRendition._viewType = vtype; this.props.addDocTab(newRendition, "add:right"); return newRendition; @@ -299,34 +177,34 @@ export class CollectionView extends Touchable<CollectionViewProps> { const options = cm.findByDescription("Options..."); const optionItems = options && "subitems" in options ? options.subitems : []; - !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.props.Document.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.props.Document.forceActive = !this.props.Document.forceActive, icon: "project-diagram" }) : null; - if (this.props.Document.childLayout instanceof Doc) { - optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.props.Document.childLayout as Doc, "add:right"), icon: "project-diagram" }); + !Doc.UserDoc().noviceMode ? optionItems.splice(0, 0, { description: `${this.rootDoc.forceActive ? "Select" : "Force"} Contents Active`, event: () => this.rootDoc.forceActive = !this.rootDoc.forceActive, icon: "project-diagram" }) : null; + if (this.rootDoc.childLayout instanceof Doc) { + optionItems.push({ description: "View Child Layout", event: () => this.props.addDocTab(this.rootDoc.childLayout as Doc, "add:right"), icon: "project-diagram" }); } - if (this.props.Document.childClickedOpenTemplateView instanceof Doc) { - optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.props.Document.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" }); + if (this.rootDoc.childClickedOpenTemplateView instanceof Doc) { + optionItems.push({ description: "View Child Detailed Layout", event: () => this.props.addDocTab(this.rootDoc.childClickedOpenTemplateView as Doc, "add:right"), icon: "project-diagram" }); } - !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.props.Document.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.props.Document.isInPlaceContainer = !this.props.Document.isInPlaceContainer, icon: "project-diagram" }); + !Doc.UserDoc().noviceMode && optionItems.push({ description: `${this.rootDoc.isInPlaceContainer ? "Unset" : "Set"} inPlace Container`, event: () => this.rootDoc.isInPlaceContainer = !this.rootDoc.isInPlaceContainer, icon: "project-diagram" }); optionItems.push({ - description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.props.Document), "add:right"), icon: "project-diagram" + description: "Create Branch", event: async () => this.props.addDocTab(await BranchCreate(this.rootDoc), "add:right"), icon: "project-diagram" }); optionItems.push({ - description: "Pull Master", event: () => BranchTask(this.props.Document, "pull"), icon: "project-diagram" + description: "Pull Master", event: () => BranchTask(this.rootDoc, "pull"), icon: "project-diagram" }); optionItems.push({ - description: "Merge Branches", event: () => BranchTask(this.props.Document, "merge"), icon: "project-diagram" + description: "Merge Branches", event: () => BranchTask(this.rootDoc, "merge"), icon: "project-diagram" }); !options && cm.addItem({ description: "Options...", subitems: optionItems, icon: "hand-point-right" }); - if (!Doc.UserDoc().noviceMode && !this.props.Document.annotationOn) { + if (!Doc.UserDoc().noviceMode && !this.rootDoc.annotationOn) { const existingOnClick = cm.findByDescription("OnClick..."); const onClicks = existingOnClick && "subitems" in existingOnClick ? existingOnClick.subitems : []; const funcs = [{ key: "onChildClick", name: "On Child Clicked" }, { key: "onChildDoubleClick", name: "On Child Double Clicked" }]; funcs.map(func => onClicks.push({ description: `Edit ${func.name} script`, icon: "edit", event: (obj: any) => { - const alias = Doc.MakeAlias(this.props.Document); + const alias = Doc.MakeAlias(this.rootDoc); DocUtils.makeCustomViewClicked(alias, undefined, func.key); this.props.addDocTab(alias, "add:right"); } @@ -335,15 +213,15 @@ export class CollectionView extends Touchable<CollectionViewProps> { onClicks.push({ description: `Set child ${childClick.title}`, icon: "edit", - event: () => Doc.GetProto(this.props.Document)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), + event: () => Doc.GetProto(this.rootDoc)[StrCast(childClick.targetScriptKey)] = ObjectField.MakeCopy(ScriptCast(childClick.data)), })); - !Doc.IsSystem(this.props.Document) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); + !Doc.IsSystem(this.rootDoc) && !existingOnClick && cm.addItem({ description: "OnClick...", noexpand: true, subitems: onClicks, icon: "mouse-pointer" }); } if (!Doc.UserDoc().noviceMode) { const more = cm.findByDescription("More..."); const moreItems = more && "subitems" in more ? more.subitems : []; - moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.props.Document) }); + moreItems.push({ description: "Export Image Hierarchy", icon: "columns", event: () => ImageUtils.ExportHierarchyToFileSystem(this.rootDoc) }); !more && cm.addItem({ description: "More...", subitems: moreItems, icon: "hand-point-right" }); } } @@ -351,28 +229,26 @@ export class CollectionView extends Touchable<CollectionViewProps> { bodyPanelWidth = () => this.props.PanelWidth(); - childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.props.Document.childLayoutTemplate, Doc, null); - @computed get childLayoutString() { return StrCast(this.props.Document.childLayoutString); } + childLayoutTemplate = () => this.props.childLayoutTemplate?.() || Cast(this.rootDoc.childLayoutTemplate, Doc, null); + @computed get childLayoutString() { return StrCast(this.rootDoc.childLayoutString); } render() { TraceMobx(); const props: SubCollectionViewProps = { ...this.props, addDocument: this.addDocument, - removeDocument: this.removeDocument, moveDocument: this.moveDocument, - active: this.active, - whenActiveChanged: this.whenActiveChanged, - parentActive: this.props.parentActive, + removeDocument: this.removeDocument, + isContentActive: this.isContentActive, PanelWidth: this.bodyPanelWidth, PanelHeight: this.props.PanelHeight, + ScreenToLocalTransform: this.screenToLocalTransform, childLayoutTemplate: this.childLayoutTemplate, childLayoutString: this.childLayoutString, - ScreenToLocalTransform: this.screenToLocalTransform, CollectionView: this, }; return (<div className={"collectionView"} onContextMenu={this.onContextMenu} - style={{ pointerEvents: this.props.layerProvider?.(this.props.Document) === false ? "none" : undefined }}> + style={{ pointerEvents: this.props.layerProvider?.(this.rootDoc) === false ? "none" : undefined }}> {this.showIsTagged()} {this.collectionViewType !== undefined ? this.SubView(this.collectionViewType, props) : (null)} </div>); diff --git a/src/client/views/collections/SchemaTable.tsx b/src/client/views/collections/SchemaTable.tsx index 4005c751e..0c69ee030 100644 --- a/src/client/views/collections/SchemaTable.tsx +++ b/src/client/views/collections/SchemaTable.tsx @@ -15,7 +15,7 @@ import { ComputedField } from "../../../fields/ScriptField"; import { Cast, FieldValue, NumCast, StrCast } from "../../../fields/Types"; import { ImageField } from "../../../fields/URLField"; import { GetEffectiveAcl } from "../../../fields/util"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse } from "../../../Utils"; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue } from "../../../Utils"; import { Docs, DocumentOptions, DocUtils } from "../../documents/Documents"; import { DocumentType } from "../../documents/DocumentTypes"; import { CompileScript, Transformer, ts } from "../../util/Scripting"; @@ -559,15 +559,15 @@ export class SchemaTable extends React.Component<SchemaTableProps> { onPointerDown={this.props.onPointerDown} onClick={this.props.onClick} onWheel={e => this.props.active(true) && e.stopPropagation()} onDrop={e => this.props.onDrop(e, {})} onContextMenu={this.onContextMenu} > {this.reactTable} - {this.props.Document._chromeHidden ? undefined : <div className="collectionSchemaView-addRow" onClick={() => this.createRow()}>+ new</div>} + {this.props.Document._chromeHidden ? undefined : <div className="collectionSchemaView-addRow" onClick={this.createRow}>+ new</div>} {!this._showDoc ? (null) : - <div className="collectionSchemaView-documentPreview" + <div className="collectionSchemaView-documentPreview" ref="overlay" style={{ position: "absolute", width: 150, height: 150, background: "dimGray", display: "block", top: 0, left: 0, transform: `translate(${this._showDocPos[0]}px, ${this._showDocPos[1] - 180}px)` - }} - ref="overlay"><DocumentView + }} > + <DocumentView Document={this._showDoc} DataDoc={this._showDataDoc} styleProvider={DefaultStyleProvider} @@ -576,7 +576,9 @@ export class SchemaTable extends React.Component<SchemaTableProps> { freezeDimensions={true} focus={DocUtils.DefaultFocus} renderDepth={this.props.renderDepth} - rootSelected={() => false} + rootSelected={returnFalse} + isContentActive={returnTrue} + isDocumentActive={returnFalse} PanelWidth={() => 150} PanelHeight={() => 150} ScreenToLocalTransform={this.getPreviewTransform} @@ -586,8 +588,7 @@ export class SchemaTable extends React.Component<SchemaTableProps> { ContainingCollectionDoc={this.props.CollectionView?.props.Document} ContainingCollectionView={this.props.CollectionView} moveDocument={this.props.moveDocument} - parentActive={this.props.active} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} addDocTab={this.props.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse}> diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index f333c4077..f6aecbb14 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -309,6 +309,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { DataDoc={!Doc.AreProtosEqual(this._document[DataSym], this._document) ? this._document[DataSym] : undefined} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} + isContentActive={returnTrue} PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight} layerProvider={this.layerProvider} @@ -322,8 +323,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { ScreenToLocalTransform={this.ScreenToLocalTransform} dontCenter={"y"} rootSelected={returnTrue} - parentActive={this.active} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} focus={this.focusFunc} docViewPath={returnEmptyDoclist} bringToFront={emptyFunction} @@ -368,7 +368,7 @@ interface TabMinimapViewProps { } @observer export class TabMinimapView extends React.Component<TabMinimapViewProps> { - static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => { + static miniStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => { if (doc) { switch (property.split(":")[0]) { default: return DefaultStyleProvider(doc, props, property); @@ -417,12 +417,11 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { CollectionView={undefined} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} - parentActive={returnFalse} docViewPath={returnEmptyDoclist} childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. noOverlay={true} // don't render overlay Docs since they won't scale setHeight={returnFalse} - active={returnTrue} + isContentActive={returnTrue} select={emptyFunction} dropAction={undefined} isSelected={returnFalse} @@ -437,7 +436,7 @@ export class TabMinimapView extends React.Component<TabMinimapViewProps> { PanelHeight={this.returnMiniSize} ScreenToLocalTransform={Transform.Identity} renderDepth={0} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} focus={DocUtils.DefaultFocus} styleProvider={TabMinimapView.miniStyleProvider} layerProvider={undefined} diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 2f74a49bb..5b0c04f33 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -13,14 +13,15 @@ .treeView-container-active { .bullet-outline { position: relative; - width: 15px; + width: $TREE_BULLET_WIDTH; color: $intermediate-color; transform: scale(0.5); - display: inline-block; + display: inline-flex; + align-items: center; } .treeView-bulletIcons { - width: 15px; + width: $TREE_BULLET_WIDTH; .treeView-expandIcon { display: none; diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 1d14c63c7..94a19a673 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, trace, reaction, IReactionDisposer } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; @@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, simulateMouseClick, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from "../../documents/DocumentTypes"; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; @@ -22,7 +22,6 @@ import { undoBatch, UndoManager } from '../../util/UndoManager'; import { EditableView } from "../EditableView"; import { TREE_BULLET_WIDTH } from '../globalCssVariables.scss'; import { DocumentView, DocumentViewProps, StyleProviderFunc } from '../nodes/DocumentView'; -import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { KeyValueBox } from '../nodes/KeyValueBox'; @@ -32,43 +31,35 @@ import { CollectionTreeView } from './CollectionTreeView'; import { CollectionView, CollectionViewType } from './CollectionView'; import "./TreeView.scss"; import React = require("react"); -import { ContextMenu } from '../ContextMenu'; -import { ContextMenuProps } from '../ContextMenuItem'; -import { SharingManager } from '../../util/SharingManager'; export interface TreeViewProps { + treeView: CollectionTreeView; + parentTreeView: TreeView | CollectionTreeView | undefined; + prevSibling?: Doc; document: Doc; dataDoc?: Doc; - containingCollection: Doc; - prevSibling?: Doc; + containerCollection: Doc; renderDepth: number; dropAction: dropActionType; addDocTab: (doc: Doc, where: string) => boolean; - pinToPres: (document: Doc) => void; panelWidth: () => number; panelHeight: () => number; addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; moveDocument: DragManager.MoveFunction; + isContentActive: (outsideReaction?: boolean) => boolean; + whenChildContentsActiveChanged: (isActive: boolean) => void; indentDocument?: (editTitle: boolean) => void; outdentDocument?: (editTitle: boolean) => void; ScreenToLocalTransform: () => Transform; dontRegisterView?: boolean; styleProvider?: StyleProviderFunc | undefined; - layerProvider?: undefined | ((doc: Doc, assign?: boolean) => boolean); - outerXf: () => { translateX: number, translateY: number }; - treeView: CollectionTreeView; - parentKey: string; - active: (outsideReaction?: boolean) => boolean; treeViewHideHeaderFields: () => boolean; - treeViewPreventOpen: boolean; renderedIds: string[]; // list of document ids rendered used to avoid unending expansion of items in a cycle onCheckedClick?: () => ScriptField; onChildClick?: () => ScriptField; skipFields?: string[]; firstLevel: boolean; - whenActiveChanged: (isActive: boolean) => void; - parentTreeView: TreeView | CollectionTreeView | undefined; } const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("px", "")); }; @@ -79,49 +70,52 @@ const treeBulletWidth = function () { return Number(TREE_BULLET_WIDTH.replace("p * * special fields: * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden - * treeViewPreventOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree */ export class TreeView extends React.Component<TreeViewProps> { static _editTitleOnLoad: Opt<{ id: string, parent: TreeView | CollectionTreeView | undefined }>; static _openTitleScript: Opt<ScriptField | undefined>; static _openLevelScript: Opt<ScriptField | undefined>; - private _editTitleScript: (() => ScriptField) | undefined; - private _openScript: (() => ScriptField) | undefined; - private _header?: React.RefObject<HTMLDivElement> = React.createRef(); - private _treedropDisposer?: DragManager.DragDropDisposer; + private _header: React.RefObject<HTMLDivElement> = React.createRef(); private _tref = React.createRef<HTMLDivElement>(); private _docRef: Opt<DocumentView>; - private _editMaxWidth: number | string = 0; private _selDisposer: Opt<IReactionDisposer>; + private _editTitleScript: (() => ScriptField) | undefined; + private _openScript: (() => ScriptField) | undefined; + private _treedropDisposer?: DragManager.DragDropDisposer; + get treeViewOpenIsTransient() { return this.props.treeView.doc.treeViewOpenIsTransient || Doc.IsPrototype(this.doc); } set treeViewOpen(c: boolean) { - if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; - else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; + if (this.treeViewOpenIsTransient) this._transientOpenState = c; + else { + this.doc.treeViewOpen = c; + this._transientOpenState = false; + } } - @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state + @observable _transientOpenState = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state @observable _editTitle: boolean = false; @observable _dref: DocumentView | undefined | null; get displayName() { return "TreeView(" + this.props.document.title + ")"; } // this makes mobx trace() statements more descriptive - get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; } - get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.props.treeView.fileSysMode ? (this.doc.isFolder ? "data" : "aliases") : Doc.UserDoc().noviceMode || this.props.treeView.outlineMode ? "layout" : "fields"); } - get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs && !this.props.treeView.fileSysMode ? this.fieldKey : this.defaultExpandedView); } - - @computed get doc() { TraceMobx(); return this.props.document; } - @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._overrideTreeViewOpen; } - @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); } - @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } + get defaultExpandedView() { + return this.props.treeView.fileSysMode ? (this.doc.isFolder ? this.fieldKey : "aliases") : + this.props.treeView.outlineMode || this.childDocs ? this.fieldKey : Doc.UserDoc().noviceMode ? "layout" : StrCast(this.props.treeView.doc.treeViewExpandedView, "fields"); + } + + @computed get doc() { return this.props.document; } + @computed get treeViewOpen() { return (!this.treeViewOpenIsTransient && Doc.GetT(this.doc, "treeViewOpen", "boolean", true)) || this._transientOpenState; } + @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.defaultExpandedView); } + @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containerCollection.maxEmbedHeight, 200); } @computed get dataDoc() { return this.doc[DataSym]; } @computed get layoutDoc() { return Doc.Layout(this.doc); } - @computed get fieldKey() { TraceMobx(); const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; } - @computed get childDocs() { TraceMobx(); return this.childDocList(this.fieldKey); } + @computed get fieldKey() { return Doc.LayoutFieldKey(this.doc); } + @computed get childDocs() { return this.childDocList(this.fieldKey); } @computed get childLinks() { return this.childDocList("links"); } @computed get childAliases() { return this.childDocList("aliases"); } @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } - @computed get selected() { return SelectionManager.Views().length && SelectionManager.Views()[0].props.Document === this.props.document; } + @computed get selected() { return SelectionManager.Views().lastElement()?.props.Document === this.props.document; } childDocList(field: string) { - const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined; + const layout = Cast(Doc.LayoutField(this.doc), Doc, null); return (this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields DocListCastOrNull(this.doc[field]); // otherwise use the document's data field @@ -184,9 +178,9 @@ export class TreeView extends React.Component<TreeViewProps> { document.removeEventListener("pointermove", this.onDragMove, true); } onPointerEnter = (e: React.PointerEvent): void => { - this.props.active(true) && Doc.BrushDoc(this.dataDoc); + this.props.isContentActive(true) && Doc.BrushDoc(this.dataDoc); if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = "treeView-header"; + this._header.current!.className = "treeView-header"; document.removeEventListener("pointermove", this.onDragMove, true); document.removeEventListener("pointerup", this.onDragUp, true); document.addEventListener("pointermove", this.onDragMove, true); @@ -195,8 +189,8 @@ export class TreeView extends React.Component<TreeViewProps> { } onPointerLeave = (e: React.PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); - if (this._header?.current?.className !== "treeView-header-editing") { - this._header!.current!.className = "treeView-header"; + if (this._header.current?.className !== "treeView-header-editing") { + this._header.current!.className = "treeView-header"; } document.removeEventListener("pointerup", this.onDragUp, true); document.removeEventListener("pointermove", this.onDragMove, true); @@ -204,13 +198,13 @@ export class TreeView extends React.Component<TreeViewProps> { onDragMove = (e: PointerEvent): void => { Doc.UnBrushDoc(this.dataDoc); const pt = [e.clientX, e.clientY]; - const rect = this._header!.current!.getBoundingClientRect(); + const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); - this._header!.current!.className = "treeView-header"; - if (inside) this._header!.current!.className += " treeView-header-inside"; - else if (before) this._header!.current!.className += " treeView-header-above"; - else if (!before) this._header!.current!.className += " treeView-header-below"; + this._header.current!.className = "treeView-header"; + if (inside) this._header.current!.className += " treeView-header-inside"; + else if (before) this._header.current!.className += " treeView-header-above"; + else if (!before) this._header.current!.className += " treeView-header-below"; e.stopPropagation(); } @@ -218,6 +212,7 @@ export class TreeView extends React.Component<TreeViewProps> { const bullet = Docs.Create.TextDocument("-text-", { layout: CollectionView.LayoutString("data"), title: "-title-", "sidebarColor": "transparent", "sidebarViewType": CollectionViewType.Freeform, + treeViewExpandedViewLock: true, treeViewExpandedView: "data", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline", x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, backgroundColor: "transparent", _width: 1000, _height: 10 }); @@ -229,18 +224,14 @@ export class TreeView extends React.Component<TreeViewProps> { makeTextCollection = () => { const bullet = TreeView.makeTextBullet(); - const added = this.props.addDocument(bullet); TreeView._editTitleOnLoad = { id: bullet[Id], parent: this }; - bullet.context = this.props.treeView.Document; - return added; + return this.props.addDocument(bullet); } makeFolder = () => { const folder = Docs.Create.TreeDocument([], { title: "-folder-", _stayInCollection: true, isFolder: true }); - const added = this.props.addDocument(folder); - folder.context = this.props.treeView.Document; TreeView._editTitleOnLoad = { id: folder[Id], parent: this.props.parentTreeView }; - return added; + return this.props.addDocument(folder); } preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { @@ -251,7 +242,7 @@ export class TreeView extends React.Component<TreeViewProps> { @undoBatch treeDrop = (e: Event, de: DragManager.DropEvent) => { const pt = [de.x, de.y]; - const rect = this._header!.current!.getBoundingClientRect(); + const rect = this._header.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; const inside = this.props.treeView.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); if (de.complete.linkDragData) { @@ -265,7 +256,7 @@ export class TreeView extends React.Component<TreeViewProps> { e.stopPropagation(); if (docDragData.draggedDocuments[0] === this.doc) return true; const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); - const canAdd = !StrCast((inside ? this.props.document : this.props.containingCollection)?.freezeChildren).includes("add") || docDragData.treeViewDoc === this.props.treeView.props.Document; + const canAdd = !StrCast((inside ? this.props.document : this.props.containerCollection)?.freezeChildren).includes("add") || docDragData.treeViewDoc === this.props.treeView.props.Document; const localAdd = (doc: Doc) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc) && ((doc.context = this.doc.context) || true) ? true : false; const addDoc = !inside ? parentAddDoc : (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc), true as boolean); @@ -279,7 +270,7 @@ export class TreeView extends React.Component<TreeViewProps> { refTransform = (ref: HTMLDivElement | undefined | null) => { if (!ref) return this.props.ScreenToLocalTransform(); const { scale, translateX, translateY } = Utils.GetScreenTransform(ref); - const outerXf = this.props.outerXf(); + const outerXf = Utils.GetScreenTransform(this.props.treeView.MainEle()); const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); } @@ -299,9 +290,9 @@ export class TreeView extends React.Component<TreeViewProps> { if (aspect) return this.docWidth() / (aspect || 1); return layoutDoc._fitWidth ? (!Doc.NativeHeight(this.doc) ? - NumCast(this.props.containingCollection._height) + NumCast(this.props.containerCollection._height) : - Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc)) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containingCollection._height)) + Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, Doc.NativeHeight(layoutDoc)) / (Doc.NativeWidth(layoutDoc) || NumCast(this.props.containerCollection._height)) )) : (layoutDoc[HeightSym]() || 50); @@ -328,10 +319,10 @@ export class TreeView extends React.Component<TreeViewProps> { }; const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] : DocListCast(contents), - this.props.treeView, doc, undefined, key, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.titleStyleProvider, this.props.layerProvider, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, - this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, - [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenActiveChanged, this.props.dontRegisterView, this); + this.props.treeView, this, doc, undefined, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, + this.props.dropAction, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, this.props.isContentActive, + this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, + [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, this.props.dontRegisterView); } else { contentElement = <EditableView key="editableView" contents={contents !== undefined ? Field.toString(contents as Field) : "null"} @@ -378,18 +369,17 @@ export class TreeView extends React.Component<TreeViewProps> { TraceMobx(); const expandKey = this.treeViewExpandedView; if (["links", "annotations", "aliases", this.fieldKey].includes(expandKey)) { - const key = expandKey === "annotations" ? this.fieldKey + "-annotations" : expandKey; + const key = (expandKey === "annotations" ? `${this.fieldKey}-` : "") + expandKey; const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { // if there's a sort ordering specified that can be modified on drop (eg, zorder can be modified, alphabetical can't), // then the modification would be done here - const ordering = StrCast(this.doc[this.fieldKey + "-sortCriteria"]); + const ordering = StrCast(this.doc.treeViewSortCriterion); if (ordering === "Z") { const docs = TreeView.sortDocs(this.childDocs || ([] as Doc[]), ordering); - doc.zIndex = addBefore ? (before ? NumCast(addBefore.zIndex) - 0.5 : NumCast(addBefore.zIndex) + 0.5) : 1000; + doc.zIndex = addBefore ? NumCast(addBefore.zIndex) + (before ? -0.5 : 0.5) : 1000; docs.push(doc); - docs.sort((a, b) => NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1); - docs.forEach((d, i) => d.zIndex = i); + docs.sort((a, b) => NumCast(a.zIndex) > NumCast(b.zIndex) ? 1 : -1).forEach((d, i) => d.zIndex = i); } const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); added && (doc.context = this.doc.context); @@ -397,24 +387,23 @@ export class TreeView extends React.Component<TreeViewProps> { }; const addDoc = (doc: Doc | Doc[], addBefore?: Doc, before?: boolean) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && localAdd(doc, addBefore, before), true); const docs = expandKey === "aliases" ? this.childAliases : expandKey === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; - const sortKey = `${this.fieldKey}-sortCriteria`; let downX = 0, downY = 0; const sortings = ["up", "down", "Z", undefined]; - const curSort = Math.max(0, sortings.indexOf(Cast(this.doc[sortKey], "string", null))); + const curSort = Math.max(0, sortings.indexOf(Cast(this.doc.treeViewSortCriterion, "string", null))); return <ul key={expandKey + "more"} title={"sort: " + sortings[curSort]} className={this.doc.treeViewHideTitle ? "no-indent" : ""} onPointerDown={e => { downX = e.clientX; downY = e.clientY; e.stopPropagation(); }} onClick={(e) => { - if (this.props.active() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { - !this.props.treeView.outlineMode && (this.doc[sortKey] = sortings[(curSort + 1) % sortings.length]); + if (this.props.isContentActive() && Math.abs(e.clientX - downX) < 3 && Math.abs(e.clientY - downY) < 3) { + !this.props.treeView.outlineMode && (this.doc.treeViewSortCriterion = sortings[(curSort + 1) % sortings.length]); e.stopPropagation(); } }}> {!docs ? (null) : - TreeView.GetChildElements(docs, this.props.treeView, this.layoutDoc, - this.dataDoc, expandKey, this.props.containingCollection, this.props.prevSibling, addDoc, remDoc, this.move, - StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.props.pinToPres, this.titleStyleProvider, this.props.layerProvider, this.props.ScreenToLocalTransform, - this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, - [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenActiveChanged, this.props.dontRegisterView, this)} + TreeView.GetChildElements(docs, this.props.treeView, this, this.layoutDoc, + this.dataDoc, this.props.containerCollection, this.props.prevSibling, addDoc, remDoc, this.move, + StrCast(this.doc.childDropAction, this.props.dropAction) as dropActionType, this.props.addDocTab, this.titleStyleProvider, this.props.ScreenToLocalTransform, + this.props.isContentActive, this.props.panelWidth, this.props.renderDepth, this.props.treeViewHideHeaderFields, + [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.skipFields, false, this.props.whenChildContentsActiveChanged, this.props.dontRegisterView)} </ul >; } else if (this.treeViewExpandedView === "fields") { return <ul key={this.doc[Id] + this.doc.title}> @@ -433,7 +422,7 @@ export class TreeView extends React.Component<TreeViewProps> { if (this.onCheckedClick) { this.onCheckedClick?.script.run({ this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, - heading: this.props.containingCollection.title, + heading: this.props.containerCollection.title, checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? "remove" : "check", containingTreeView: this.props.treeView.props.Document, }, console.log); @@ -470,47 +459,49 @@ export class TreeView extends React.Component<TreeViewProps> { } </div>; } + + @action + expandNextviewType = () => { + if (this.treeViewOpen && !this.doc.isFolder && !this.props.treeView.outlineMode && !this.doc.treeViewExpandedViewLock) { + const next = (modes: any[]) => modes[(modes.indexOf(StrCast(this.doc.treeViewExpandedView)) + 1) % modes.length]; + const annos = () => DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : ""; + const links = () => DocListCast(this.doc.links).length ? "links" : ""; + const children = () => this.childDocs ? this.fieldKey : ""; + this.doc.treeViewExpandedView = next(this.props.treeView.fileSysMode ? + (Doc.UserDoc().noviceMode ? ["layout", "aliases"] : ["layout", "aliases", "fields"]) : + (Doc.UserDoc().noviceMode ? [children(), "layout"] : [children(), "fields", "layout", links(), annos()]).filter(mode => mode)); + } + this.treeViewOpen = true; + } + @computed get headerElements() { - return (Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode) || this.props.treeViewHideHeaderFields() ? (null) : - <> + return this.props.treeViewHideHeaderFields() || Doc.IsSystem(this.doc) ? (null) + : <> <FontAwesomeIcon key="bars" icon="bars" size="sm" onClick={e => { this.showContextMenu(e); e.stopPropagation(); }} /> - <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} - onPointerDown={action(() => { - if (this.props.treeView.fileSysMode) { - this.doc.treeViewExpandedView = this.doc.isFolder ? this.fieldKey : this.treeViewExpandedView === "layout" ? "aliases" : - this.treeViewExpandedView === "aliases" && !Doc.UserDoc().noviceMode ? "fields" : "layout"; - } else if (this.treeViewOpen) { - this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView : - this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.props.treeView.outlineMode ? "layout" : "fields") : - this.treeViewExpandedView === "fields" && this.layoutDoc ? "layout" : - this.treeViewExpandedView === "layout" && DocListCast(this.doc.links).length ? "links" : - (this.treeViewExpandedView === "links" || this.treeViewExpandedView === "layout") && DocListCast(this.doc[this.fieldKey + "-annotations"]).length ? "annotations" : - this.childDocs ? this.fieldKey : (Doc.UserDoc().noviceMode || this.props.treeView.outlineMode ? "layout" : "fields"); - } - this.treeViewOpen = true; - })}> - {this.treeViewExpandedView} - </span> + {this.doc.treeViewExpandedViewLock ? (null) : + <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={this.expandNextviewType}> + {this.treeViewExpandedView} + </span>} </>; } showContextMenu = (e: React.MouseEvent) => simulateMouseClick(this._docRef?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); - contextMenuItems = () => this.doc.isFolder ? - [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] : Doc.IsSystem(this.doc) ? [] : - this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ? - [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }] : - [{ script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }] + contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : this.doc.isFolder ? + [{ script: ScriptField.MakeFunction(`scriptContext.makeFolder()`, { scriptContext: "any" })!, label: "New Folder" }] : + this.props.treeView.fileSysMode && this.doc === Doc.GetProto(this.doc) ? + [{ script: ScriptField.MakeFunction(`openOnRight(getAlias(self))`)!, label: "Open Alias" }] : + [{ script: ScriptField.MakeFunction(`DocFocusOrOpen(self)`)!, label: "Focus or Open" }] onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); onChildDoubleClick = () => (!this.props.treeView.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { - if (this.props.active(true)) { + if (this.props.isContentActive(true)) { e.stopPropagation(); e.preventDefault(); } } - titleStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => { + titleStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => { if (!doc || doc !== this.doc) return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView switch (property.split(":")[0]) { @@ -524,9 +515,10 @@ export class TreeView extends React.Component<TreeViewProps> { {StrCast(doc?.title)} </div>; case StyleProp.Decorations: return (null); + default: return this.props?.treeView?.props.styleProvider?.(doc, props, property); } } - embeddedStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => { + embeddedStyleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => { if (property.startsWith(StyleProp.Decorations)) return (null); return this.props?.treeView?.props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView } @@ -542,6 +534,7 @@ export class TreeView extends React.Component<TreeViewProps> { } } } + titleWidth = () => Math.max(20, Math.min(this.props.treeView.truncateTitleWidth(), this.props.panelWidth() - treeBulletWidth())); /** * Renders the EditableView title element for placement into the tree. @@ -582,8 +575,10 @@ export class TreeView extends React.Component<TreeViewProps> { Document={this.doc} DataDoc={undefined} scriptContext={this} + hideDecorationTitle={this.props.treeView.outlineMode} + hideResizeHandles={this.props.treeView.outlineMode} styleProvider={this.titleStyleProvider} - layerProvider={this.props.layerProvider} + layerProvider={returnTrue} docViewPath={returnEmptyDoclist} treeViewDoc={this.props.treeView.props.Document} addDocument={undefined} @@ -597,17 +592,19 @@ export class TreeView extends React.Component<TreeViewProps> { removeDocument={this.props.removeDoc} ScreenToLocalTransform={this.getTransform} NativeHeight={() => 18} - NativeWidth={this.props.treeView.truncateTitleWidth} - PanelWidth={this.props.treeView.truncateTitleWidth} + NativeWidth={this.titleWidth} + PanelWidth={this.titleWidth} PanelHeight={() => 18} contextMenuItems={this.contextMenuItems} renderDepth={1} - focus={returnTrue} - parentActive={returnTrue} - whenActiveChanged={this.props.whenActiveChanged} + isContentActive={this.props.isContentActive} + isDocumentActive={this.props.isContentActive} + focus={this.refocus} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} bringToFront={emptyFunction} - cantBrush={this.props.treeView.props.cantBrush} - dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews)} + disableDocBrushing={this.props.treeView.props.disableDocBrushing} + hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} @@ -621,22 +618,21 @@ export class TreeView extends React.Component<TreeViewProps> { fontWeight: Doc.IsSearchMatch(this.doc) !== undefined ? "bold" : undefined, textDecoration: Doc.GetT(this.doc, "title", "string", true) ? "underline" : undefined, outline: this.doc === CurrentUserUtils.ActiveDashboard ? "dashed 1px #06123232" : undefined, - pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined + pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined }} > {view} </div > <div className={"right-buttons-container"}> - {this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations)} {/* hide and lock buttons */} + {this.props.styleProvider?.(this.doc, this.props.treeView.props, StyleProp.Decorations + (Doc.IsSystem(this.props.containerCollection) ? ":afterHeader" : ""))} {/* hide and lock buttons */} {this.headerElements} </div> </>; } - renderBulletHeader = (contents: JSX.Element) => { + renderBulletHeader = (contents: JSX.Element, editing: boolean) => { return <> - <div className={`treeView-header` + (this._editMaxWidth ? "-editing" : "")} key="titleheader" + <div className={`treeView-header` + (editing ? "-editing" : "")} key="titleheader" ref={this._header} - style={{ maxWidth: this._editMaxWidth }} onClick={this.ignoreEvent} onPointerDown={this.ignoreEvent} onPointerEnter={this.onPointerEnter} @@ -647,13 +643,6 @@ export class TreeView extends React.Component<TreeViewProps> { </>; } - // renders the text version of a document as the header (e.g., useful for Slide views where the "") - @computed get renderTitleAsHeader() { - return <> - {this.renderBullet} - {this.renderTitle} - </>; - } renderEmbeddedDocument = (asText: boolean) => { const layout = StrCast(Doc.LayoutField(this.layoutDoc)); @@ -667,34 +656,47 @@ export class TreeView extends React.Component<TreeViewProps> { PanelHeight={panelHeight} NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} - fitContentsToDoc={returnTrue} - hideTitle={asText} LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined} - focus={asText ? this.refocus : returnFalse} - dontRegisterView={asText ? undefined : this.props.dontRegisterView} + isContentActive={asText ? this.props.isContentActive : returnFalse} + isDocumentActive={asText ? this.props.isContentActive : returnFalse} + styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} + hideTitle={asText} + fitContentsToDoc={returnTrue} + hideDecorationTitle={this.props.treeView.outlineMode} + hideResizeHandles={this.props.treeView.outlineMode} + focus={this.refocus} + hideLinkButton={BoolCast(this.props.treeView.props.Document.childHideLinkButton)} + dontRegisterView={BoolCast(this.props.treeView.props.Document.childDontRegisterViews, this.props.dontRegisterView)} ScreenToLocalTransform={this.docTransform} renderDepth={this.props.renderDepth + 1} rootSelected={returnTrue} - styleProvider={asText ? this.titleStyleProvider : this.embeddedStyleProvider} - layerProvider={this.props.layerProvider} + layerProvider={returnTrue} docViewPath={this.props.treeView.props.docViewPath} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containingCollection} + ContainingCollectionDoc={this.props.containerCollection} ContainingCollectionView={undefined} addDocument={this.props.addDocument} moveDocument={this.move} removeDocument={this.props.removeDoc} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - cantBrush={this.props.treeView.props.cantBrush} + pinToPres={this.props.treeView.props.pinToPres} + disableDocBrushing={this.props.treeView.props.disableDocBrushing} bringToFront={returnFalse} />; } + // renders the text version of a document as the header. This is used in the file system mode and in other vanilla tree views. + @computed get renderTitleAsHeader() { + return <> + {this.renderBullet} + {this.renderTitle} + </>; + } + + // renders the document in the header field instead of a text proxy. @computed get renderDocumentAsHeader() { return <> {this.renderBullet} @@ -712,29 +714,18 @@ export class TreeView extends React.Component<TreeViewProps> { render() { TraceMobx(); - if (this.props.renderedIds.indexOf(this.doc[Id]) !== -1) return "<" + this.doc.title + ">"; - if (this._editTitle) { // find containing CollectionTreeView and set our maximum width so the containing tree view won't have to scroll - let par: any = this._header?.current; - while (par && par.className !== "collectionTreeView-dropTarget") par = par.parentNode; - if (par) { - const par_rect = (par as HTMLElement).getBoundingClientRect(); - const my_recct = this._docRef?.ContentDiv?.getBoundingClientRect(); - this._editMaxWidth = Math.max(100, par_rect.right - (my_recct?.left || 0)); - } - } - else this._editMaxWidth = ""; - const hideTitle = this.doc.treeViewHideHeader || this.props.treeView.outlineMode; - return <div className={`treeView-container${this._dref?.contentsActive() ? "-active" : ""}`} - ref={this.createTreeDropTarget} - onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()} - onKeyDown={this.onKeyDown}> - <li className="collection-child"> - {hideTitle && this.doc.type !== DocumentType.RTF ? - this.renderEmbeddedDocument(false) : - this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader : this.renderTitleAsHeader)} - </li> - </div>; + return this.props.renderedIds.indexOf(this.doc[Id]) !== -1 ? "<" + this.doc.title + ">" : // just print the title of documents we've previously rendered in this hierarchical path to avoid cycles + <div className={`treeView-container${this.props.isContentActive() ? "-active" : ""}`} + ref={this.createTreeDropTarget} + onPointerDown={e => this.props.isContentActive(true) && SelectionManager.DeselectAll()} + onKeyDown={this.onKeyDown}> + <li className="collection-child"> + {hideTitle && this.doc.type !== DocumentType.RTF ? + this.renderEmbeddedDocument(false) : + this.renderBulletHeader(hideTitle ? this.renderDocumentAsHeader : this.renderTitleAsHeader, this._editTitle)} + </li> + </div>; } public static sortDocs(childDocs: Doc[], criterion: string | undefined) { @@ -768,45 +759,40 @@ export class TreeView extends React.Component<TreeViewProps> { public static GetChildElements( childDocs: Doc[], treeView: CollectionTreeView, - containingCollection: Doc, + parentTreeView: CollectionTreeView | TreeView | undefined, + conainerCollection: Doc, dataDoc: Doc | undefined, - key: string, parentCollectionDoc: Doc | undefined, - parentPrevSibling: Doc | undefined, + containerPrevSibling: Doc | undefined, add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean, remove: undefined | ((doc: Doc | Doc[]) => boolean), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => boolean, - pinToPres: (document: Doc) => void, styleProvider: undefined | StyleProviderFunc, - layerProvider: undefined | ((doc: Doc, assign?: boolean) => boolean), screenToLocalXf: () => Transform, - outerXf: () => { translateX: number, translateY: number }, - active: (outsideReaction?: boolean) => boolean, + isContentActive: (outsideReaction?: boolean) => boolean, panelWidth: () => number, renderDepth: number, treeViewHideHeaderFields: () => boolean, - treeViewPreventOpen: boolean, renderedIds: string[], onCheckedClick: undefined | (() => ScriptField), onChildClick: undefined | (() => ScriptField), skipFields: string[] | undefined, firstLevel: boolean, - whenActiveChanged: (isActive: boolean) => void, + whenChildContentsActiveChanged: (isActive: boolean) => void, dontRegisterView: boolean | undefined, - parentTreeView: CollectionTreeView | TreeView | undefined ) { - const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); + const viewSpecScript = Cast(conainerCollection.viewSpecScript, ScriptField); if (viewSpecScript) { childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); } - const docs = TreeView.sortDocs(childDocs, StrCast(containingCollection?.[key + "-sortCriteria"])); + const docs = TreeView.sortDocs(childDocs, StrCast(conainerCollection.treeViewSortCriterion)); const rowWidth = () => panelWidth() - treeBulletWidth(); const treeViewRefs = new Map<Doc, TreeView | undefined>(); return docs.filter(child => child instanceof Doc).map((child, i) => { - const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, child); + const pair = Doc.GetLayoutDataDocPair(conainerCollection, dataDoc, child); if (!pair.layout || pair.data instanceof Promise) { return (null); } @@ -823,7 +809,7 @@ export class TreeView extends React.Component<TreeViewProps> { } }; const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); - const outdent = parentCollectionDoc?._viewType !== CollectionViewType.Tree ? undefined : ((editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, parentPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined)); + const outdent = parentCollectionDoc?._viewType !== CollectionViewType.Tree ? undefined : ((editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView.props.parentTreeView : undefined)); const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); const childLayout = Doc.Layout(pair.layout); const rowHeight = () => { @@ -833,7 +819,7 @@ export class TreeView extends React.Component<TreeViewProps> { return <TreeView key={child[Id]} ref={r => treeViewRefs.set(child, r ? r : undefined)} document={pair.layout} dataDoc={pair.data} - containingCollection={containingCollection} + containerCollection={conainerCollection} prevSibling={docs[i]} treeView={treeView} indentDocument={indent} @@ -841,27 +827,22 @@ export class TreeView extends React.Component<TreeViewProps> { onCheckedClick={onCheckedClick} onChildClick={onChildClick} renderDepth={renderDepth} - removeDoc={StrCast(containingCollection.freezeChildren).includes("remove") ? undefined : remove} + removeDoc={StrCast(conainerCollection.freezeChildren).includes("remove") ? undefined : remove} addDocument={addDocument} styleProvider={styleProvider} - layerProvider={layerProvider} panelWidth={rowWidth} panelHeight={rowHeight} dontRegisterView={dontRegisterView} moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} - pinToPres={pinToPres} ScreenToLocalTransform={screenToLocalXf} - outerXf={outerXf} - parentKey={key} - active={active} + isContentActive={isContentActive} treeViewHideHeaderFields={treeViewHideHeaderFields} - treeViewPreventOpen={treeViewPreventOpen} renderedIds={renderedIds} skipFields={skipFields} firstLevel={firstLevel} - whenActiveChanged={whenActiveChanged} + whenChildContentsActiveChanged={whenChildContentsActiveChanged} parentTreeView={parentTreeView} />; }); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f0d99611a..c623ce653 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -36,7 +36,6 @@ import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveIn import { LightboxView } from "../../LightboxView"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocFocusOptions, DocumentView, DocumentViewProps, ViewAdjustment, ViewSpecPrefix } from "../../nodes/DocumentView"; -import { FieldViewProps } from "../../nodes/FieldView"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import { PresBox } from "../../nodes/PresBox"; @@ -67,7 +66,6 @@ export const panZoomSchema = createSchema({ type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof collectionSchema, typeof documentSchema, typeof pageSchema]>; const PanZoomDocument = makeInterface(panZoomSchema, collectionSchema, documentSchema, pageSchema); export type collectionFreeformViewProps = { - parentActive: (outsideReaction: boolean) => boolean; annotationLayerHostsContent?: boolean; // whether to force scaling of content (needed by ImageBox) viewDefDivClick?: ScriptField; childPointerEvents?: boolean; @@ -117,7 +115,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @computed get views() { return this._layoutElements.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); } @computed get backgroundEvents() { return this.props.layerProvider?.(this.layoutDoc) === false && SnappingManager.GetIsDragging(); } - @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.active() || this.props.active()); } + @computed get backgroundActive() { return this.props.layerProvider?.(this.layoutDoc) === false && (this.props.ContainingCollectionView?.isContentActive() || this.props.isContentActive()); } @computed get fitToContentVals() { return { bounds: { ...this.contentBounds, cx: (this.contentBounds.x + this.contentBounds.r) / 2, cy: (this.contentBounds.y + this.contentBounds.b) / 2 }, @@ -152,7 +150,6 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P getKeyFrameEditing = () => this._keyframeEditing; onChildClickHandler = () => this.props.childClickScript || ScriptCast(this.Document.onChildClick); onChildDoubleClickHandler = () => this.props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); - parentActive = (outsideReaction: boolean) => this.props.active(outsideReaction) || this.props.parentActive?.(outsideReaction) || this.backgroundActive || this.layoutDoc._viewType === CollectionViewType.Pile ? true : false; elementFunc = () => this._layoutElements; shrinkWrap = () => { const vals = this.fitToContentVals; @@ -422,7 +419,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => { + getClusterColor = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => { let styleProp = this.props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 if (property !== StyleProp.BackgroundColor) return styleProp; const cluster = NumCast(doc?.cluster); @@ -459,7 +456,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return; } this._hitCluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); - if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.active(true)) { + if (e.button === 0 && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -484,7 +481,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const pt = me.changedTouches[0]; if (pt) { this._hitCluster = this.pickCluster(this.getTransform().transformPoint(pt.clientX, pt.clientY)); - if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.active(true)) { + if (!e.shiftKey && !e.altKey && !e.ctrlKey && this.props.isContentActive(true)) { this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -625,8 +622,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P onClick = (e: React.MouseEvent) => { if ((Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { - if (e.shiftKey && Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click - this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); + if (e.shiftKey) { + if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a shift + double click + this.zoomSmoothlyAboutPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); + } e.stopPropagation(); e.preventDefault(); } @@ -647,7 +646,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (this.props.Document._isGroup) return; // groups don't pan when dragged -- instead let the event go through to allow the group itself to drag if (InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) return; if (InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) { - if (this.props.active(true)) e.stopPropagation(); + if (this.props.isContentActive(true)) e.stopPropagation(); } else if (!e.cancelBubble) { if (CurrentUserUtils.SelectedTool === InkTool.None) { if (this.tryDragCluster(e, this._hitCluster)) { @@ -735,7 +734,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @action handle2PointersDown = (e: React.TouchEvent, me: InteractionUtils.MultiTouchEvent<React.TouchEvent>) => { - if (!e.nativeEvent.cancelBubble && this.props.active(true)) { + if (!e.nativeEvent.cancelBubble && this.props.isContentActive(true)) { // const pt1: React.Touch | null = e.targetTouches.item(0); // const pt2: React.Touch | null = e.targetTouches.item(1); // // if (!pt1 || !pt2) return; @@ -819,7 +818,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } - else if (this.props.active(true) && !this.Document._isGroup) { + else if (this.props.isContentActive(true) && !this.Document._isGroup) { e.stopPropagation(); e.preventDefault(); this.zoom(e.clientX, e.clientY, e.deltaY); // if (!this.props.isAnnotationOverlay) // bcz: do we want to zoom in on images/videos/etc? @@ -1007,6 +1006,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P }; } + isContentActive = () => this.props.isSelected() || this.props.isContentActive(); + getChildDocView(entry: PoolData) { const childLayout = entry.pair.layout; const childData = entry.pair.data; @@ -1030,14 +1031,15 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P docFilters={this.freeformDocFilters} docRangeFilters={this.freeformRangeDocFilters} searchFilterDocs={this.searchFilterDocs} + isContentActive={this.isAnnotationOverlay ? this.props.isContentActive : returnFalse} + isDocumentActive={this.isContentActive} focus={this.focusDocument} addDocTab={this.addDocTab} addDocument={this.props.addDocument} removeDocument={this.props.removeDocument} moveDocument={this.props.moveDocument} pinToPres={this.props.pinToPres} - whenActiveChanged={this.props.whenActiveChanged} - parentActive={this.parentActive} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} docViewPath={this.props.docViewPath} styleProvider={this.getClusterColor} layerProvider={this.props.layerProvider} @@ -1444,7 +1446,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> <div ref={this._marqueeRef}> - {this.layoutDoc["_backgroundGrid-show"] && (!SnappingManager.GetIsDragging() || !Doc.UserDoc().showSnapLines) ? this.backgroundGrid : (null)} + {this.layoutDoc["_backgroundGrid-show"] ? this.backgroundGrid : (null)} <CollectionFreeFormViewPannableContents isAnnotationOverlay={this.isAnnotationOverlay} transform={this.contentTransform} @@ -1488,7 +1490,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P width: `${100 / (this.contentScaling || 1)}%`, height: this.isAnnotationOverlay && this.Document.scrollHeight ? this.Document.scrollHeight : `${100 / (this.contentScaling || 1)}%`// : this.isAnnotationOverlay ? (this.Document.scrollHeight ? this.Document.scrollHeight : "100%") : this.props.PanelHeight() }}> - {this.Document._freeformLOD && !this.props.active() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ? + {this.Document._freeformLOD && !this.props.isContentActive() && !this.props.isAnnotationOverlay && this.props.renderDepth > 0 ? this.placeholder : this.marqueeView} {this.props.noOverlay ? (null) : <CollectionFreeFormOverlayView elements={this.elementFunc} />} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index af391a078..d14b68fa7 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -132,18 +132,6 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque }); })(); e.stopPropagation(); - } else if (e.key === "f" && e.ctrlKey) { - e.preventDefault(); - const root = Docs.Create.TreeDocument([], { title: "folder", _stayInCollection: true, isFolder: true }); - const folder = Docs.Create.TreeDocument([root], { title: "root", isFolder: true, treeViewType: "fileSystem", treeViewTruncateTitleWidth: 150 }); - Doc.GetProto(folder).isFolder = true; - folder.x = x; - folder.y = y; - folder._width = 200; - folder._height = 300; - this.props.addDocument?.(folder); - //setTimeout(() => SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(slide)!, false)); - e.stopPropagation(); } else if (e.key === "b" && e.ctrlKey) { // e.preventDefault(); // navigator.clipboard.readText().then(text => { @@ -167,7 +155,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } else if (!e.ctrlKey && !e.metaKey && SelectionManager.Views().length < 2) { FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this.props.childLayoutString ? e.key : ""; FormattedTextBox.LiveTextUndo = UndoManager.StartBatch("live text batch"); - this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0, this.props.isAnnotationOverlay ? this.props.Document : undefined)); + this.props.addLiveTextDocument(CurrentUserUtils.GetNewTextDoc("-typed text-", x, y, 200, 100, this.props.xMargin === 0)); e.stopPropagation(); } } @@ -366,6 +354,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque newCollection._width = this.Bounds.width; newCollection._height = this.Bounds.height; newCollection._isGroup = makeGroup; + newCollection.forceActive = makeGroup; newCollection.x = this.Bounds.left; newCollection.y = this.Bounds.top; selected.forEach(d => d.context = newCollection); diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index e2feff5ed..b0030471d 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -171,7 +171,6 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { ScreenToLocalTransform={dxf} onClick={this.onChildClickHandler} renderDepth={this.props.renderDepth + 1} - parentActive={this.props.active} dontCenter={"y"} />; } @@ -296,7 +295,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { * Handles text document creation on double click. */ onPointerDown = (e: React.PointerEvent) => { - if (this.props.active(true)) { + if (this.props.isContentActive(true)) { setupMoveUpEvents(this, e, returnFalse, returnFalse, (e: PointerEvent, doubleTap?: boolean) => { if (doubleTap) { @@ -316,7 +315,7 @@ export class CollectionGridView extends CollectionSubView(GridSchema) { render() { return ( <div className="collectionGridView-contents" ref={this.createDashEventsTarget} - style={{ pointerEvents: !this.props.active() && !SnappingManager.GetIsDragging() ? "none" : undefined }} + style={{ pointerEvents: !this.props.isContentActive() && !SnappingManager.GetIsDragging() ? "none" : undefined }} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onDrop={this.onExternalDrop} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 0c0dbef9f..8b5c02b75 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -223,6 +223,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu LayoutTemplateString={this.props.childLayoutString} freezeDimensions={this.props.childFreezeDimensions} renderDepth={this.props.renderDepth + 1} + isContentActive={returnFalse} PanelWidth={width} PanelHeight={height} rootSelected={this.rootSelected} @@ -239,8 +240,7 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 0a1000a20..2c5e40d02 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -232,6 +232,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) ScreenToLocalTransform={dxf} focus={this.props.focus} docFilters={this.docFilters} + isContentActive={returnFalse} docRangeFilters={this.docRangeFilters} searchFilterDocs={this.searchFilterDocs} ContainingCollectionDoc={this.props.CollectionView?.props.Document} @@ -239,8 +240,7 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) addDocument={this.props.addDocument} moveDocument={this.props.moveDocument} removeDocument={this.props.removeDocument} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} + whenChildContentsActiveChanged={this.props.whenChildContentsActiveChanged} addDocTab={this.addDocTab} pinToPres={this.props.pinToPres} bringToFront={returnFalse} diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 06a27c22a..85899578c 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -17,7 +17,7 @@ import { SnappingManager } from "../../util/SnappingManager"; import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import "./AudioBox.scss"; import { FieldView, FieldViewProps } from './FieldView'; import { LinkDocPreview } from "./LinkDocPreview"; @@ -29,7 +29,7 @@ type AudioDocument = makeInterface<[typeof documentSchema]>; const AudioDocument = makeInterface(documentSchema); @observer -export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) { +export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, AudioDocument>(AudioDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } public static Enabled = false; static playheadWidth = 30; // width of playhead @@ -63,7 +63,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD @computed get pauseTime() { return this._pauseEnd - this._pauseStart; } // total time paused to update the correct time @computed get heightPercent() { return AudioBox.heightPercent; } - constructor(props: Readonly<FieldViewProps>) { + constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) { super(props); AudioBox.Instance = this; @@ -267,7 +267,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD // returns the html audio element @computed get audio() { - return <audio ref={this.setRef} className={`audiobox-control${this.active() ? "-interactive" : ""}`}> + return <audio ref={this.setRef} className={`audiobox-control${this.isContentActive() ? "-interactive" : ""}`}> <source src={this.path} type="audio/mpeg" /> Not supported. </audio>; @@ -311,8 +311,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD } } - isActiveChild = () => this._isChildActive; - timelineWhenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(runInAction(() => this._isChildActive = isActive)); + isActiveChild = () => this._isAnyChildContentActive; + timelineWhenChildContentsActiveChanged = (isActive: boolean) => this.props.whenChildContentsActiveChanged(runInAction(() => this._isAnyChildContentActive = isActive)); timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-AudioBox.playheadWidth, -(100 - this.heightPercent) / 200 * this.props.PanelHeight()); setAnchorTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time; timelineHeight = () => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) @@ -320,6 +320,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD @computed get renderTimeline() { return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props} fieldKey={this.annotationKey} + dictationKey={this.fieldKey + "-dictation"} mediaPath={this.path} renderDepth={this.props.renderDepth + 1} startTag={"_timecodeToShow" /* audioStart */} @@ -331,13 +332,12 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD playFrom={this.playFrom} setTime={this.setAnchorTime} playing={this.playing} - whenActiveChanged={this.timelineWhenActiveChanged} + whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged} removeDocument={this.removeDocument} ScreenToLocalTransform={this.timelineScreenToLocal} - isChildActive={this.isActiveChild} Play={this.Play} Pause={this.Pause} - active={this.active} + isContentActive={this.isContentActive} playLink={this.playLink} PanelWidth={this.timelineWidth} PanelHeight={this.timelineHeight} @@ -345,7 +345,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD } render() { - const interactive = SnappingManager.GetIsDragging() || this.active() ? "-interactive" : ""; + const interactive = SnappingManager.GetIsDragging() || this.isContentActive() ? "-interactive" : ""; return <div className="audiobox-container" onContextMenu={this.specificContextMenu} onClick={!this.path && !this._recorder ? this.recordAudioAnnotation : undefined} @@ -370,7 +370,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD RECORD </button>} </div> : - <div className="audiobox-controls" style={{ pointerEvents: this._isChildActive || this.active() ? "all" : "none" }} > + <div className="audiobox-controls" style={{ pointerEvents: this._isAnyChildContentActive || this.isContentActive() ? "all" : "none" }} > <div className="audiobox-dictation" /> <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} > <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div> diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index e96fc088e..1fda4cc5e 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -49,7 +49,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF @computed get dataProvider() { return this.props.dataProvider?.(this.props.Document, this.props.replica); } @computed get sizeProvider() { return this.props.sizeProvider?.(this.props.Document, this.props.replica); } - styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => { + styleProvider = (doc: Doc | undefined, props: Opt<DocumentViewProps>, property: string) => { if (property === StyleProp.Opacity && doc === this.layoutDoc) return this.Opacity; // only change the opacity for this specific document, not its children return this.props.styleProvider?.(doc, props, property); } diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index 59a26f323..46c599abe 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -2,21 +2,20 @@ import React = require("react"); import { action } from "mobx"; import { observer } from "mobx-react"; import { ColorState, SketchPicker } from 'react-color'; -import { Doc, WidthSym, HeightSym } from '../../../fields/Doc'; -import { Utils } from "../../../Utils"; +import { Doc, HeightSym, WidthSym } from '../../../fields/Doc'; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { makeInterface } from "../../../fields/Schema"; import { StrCast } from "../../../fields/Types"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch } from "../../util/UndoManager"; import { ViewBoxBaseComponent } from "../DocComponent"; -import { ActiveInkPen, ActiveInkWidth, ActiveInkBezierApprox, SetActiveInkColor, SetActiveInkWidth, SetActiveBezierApprox, ActiveInkColor } from "../InkingStroke"; +import { ActiveInkColor, ActiveInkWidth, SetActiveInkColor, SetActiveInkWidth } from "../InkingStroke"; import "./ColorBox.scss"; import { FieldView, FieldViewProps } from './FieldView'; -import { DocumentType } from "../../documents/DocumentTypes"; import { RichTextMenu } from "./formattedText/RichTextMenu"; -import { CurrentUserUtils } from "../../util/CurrentUserUtils"; type ColorDocument = makeInterface<[typeof documentSchema]>; const ColorDocument = makeInterface(documentSchema); @@ -50,7 +49,7 @@ export class ColorBox extends ViewBoxBaseComponent<FieldViewProps, ColorDocument render() { const scaling = Math.min(this.layoutDoc.fitWidth ? 10000 : this.props.PanelHeight() / this.rootDoc[HeightSym](), this.props.PanelWidth() / this.rootDoc[WidthSym]()); - return <div className={`colorBox-container${this.active() ? "-interactive" : ""}`} + return <div className={`colorBox-container${this.isContentActive() ? "-interactive" : ""}`} onPointerDown={e => e.button === 0 && !e.ctrlKey && e.stopPropagation()} onClick={e => e.stopPropagation()} style={{ transform: `scale(${scaling})`, width: `${100 * scaling}%`, height: `${100 * scaling}%` }} > diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index b1bbc9506..153176afc 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -9,7 +9,7 @@ import { emptyFunction, OmitKeys, setupMoveUpEvents } from '../../../Utils'; import { DragManager } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch } from '../../util/UndoManager'; -import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import "./ComparisonBox.scss"; import { DocumentView } from './DocumentView'; import { FieldView, FieldViewProps } from './FieldView'; @@ -21,7 +21,7 @@ type ComparisonDocument = makeInterface<[typeof comparisonSchema, typeof documen const ComparisonDocument = makeInterface(comparisonSchema, documentSchema); @observer -export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, ComparisonDocument>(ComparisonDocument) { +export class ComparisonBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ComparisonDocument>(ComparisonDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ComparisonBox, fieldKey); } protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined; private _disposers: (DragManager.DragDropDisposer | undefined)[] = [undefined, undefined]; @@ -86,8 +86,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} Document={whichDoc} DataDoc={undefined} - pointerEvents={"none"} - parentActive={this.props.active} /> + pointerEvents={"none"} /> {clearButton(which)} </> : // placeholder image if doc is missing <div className="placeholder"> @@ -103,7 +102,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps, C }; return ( - <div className={`comparisonBox${this.active() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}> + <div className={`comparisonBox${this.isContentActive() || SnappingManager.GetIsDragging() ? "-interactive" : ""}` /* change className to easily disable/enable pointer events in CSS */}> {displayBox("after", 1, this.props.PanelWidth() - 3)} <div className="clip-div" style={{ width: clipWidth, transition: this._animating, background: StrCast(this.layoutDoc._backgroundColor, "gray") }}> {displayBox("before", 0, 0)} diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index 2f7923574..b53827371 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -40,6 +40,7 @@ import React = require("react"); import { TraceMobx, GetEffectiveAcl } from "../../../fields/util"; import { ScriptField } from "../../../fields/ScriptField"; import XRegExp = require("xregexp"); +import { DocumentType } from "../../documents/DocumentTypes"; const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this? @@ -158,7 +159,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & Fo "onPointerUp", ]; const list = { - ...OmitKeys(this.props, [...docOnlyProps], "", (obj: any) => obj.active = this.props.parentActive).omit, + ...OmitKeys(this.props, [...docOnlyProps], "").omit, RootDoc: Cast(this.layoutDoc?.rootDocument, Doc, null) || this.layoutDoc, Document: this.layoutDoc, DataDoc: this.dataDoc, diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 231c9ff38..1c4008a55 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,18 +1,19 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable, runInAction, reaction, IReactionDisposer } from "mobx"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from "../../../fields/List"; +import { ObjectField } from "../../../fields/ObjectField"; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { AudioField } from "../../../fields/URLField"; import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; -import { emptyFunction, hasDescendantTarget, OmitKeys, returnFalse, returnVal, Utils } from "../../../Utils"; +import { emptyFunction, hasDescendantTarget, OmitKeys, returnVal, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; @@ -34,19 +35,18 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { InkingStroke } from "../InkingStroke"; +import { LightboxView } from "../LightboxView"; import { StyleLayers, StyleProp } from "../StyleProvider"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; import { DocumentContentsView } from "./DocumentContentsView"; import { DocumentLinksButton } from './DocumentLinksButton'; import "./DocumentView.scss"; -import { FieldViewProps } from "./FieldView"; import { LinkAnchorBox } from './LinkAnchorBox'; import { LinkDocPreview } from "./LinkDocPreview"; import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); -import { ObjectField } from "../../../fields/ObjectField"; -import { LightboxView } from "../LightboxView"; +import { ScriptingBox } from "./ScriptingBox"; const { Howl } = require('howler'); interface Window { @@ -75,7 +75,7 @@ export interface DocFocusOptions { } export type DocAfterFocusFunc = (notFocused: boolean) => Promise<ViewAdjustment>; export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void; -export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string) => any; +export type StyleProviderFunc = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string) => any; export interface DocComponentView { getAnchor?: () => Doc; // returns an Anchor Doc that represents the current state of the doc's componentview (e.g., the current playhead location of a an audio/video box) scrollFocus?: (doc: Doc, smooth: boolean) => Opt<number>; // returns the duration of the focus @@ -86,6 +86,7 @@ export interface DocComponentView { 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) setKeyFrameEditing?: (set: boolean) => void; // 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) playFrom?: (time: number, endTime?: number) => void; + setFocus?: () => void; } export interface DocumentViewSharedProps { renderDepth: number; @@ -106,11 +107,10 @@ export interface DocumentViewSharedProps { docFilters: () => string[]; docRangeFilters: () => string[]; searchFilterDocs: () => Doc[]; - contentsActive?: (setActive: () => boolean) => void; - parentActive: (outsideReaction: boolean) => boolean; - whenActiveChanged: (isActive: boolean) => void; + whenChildContentsActiveChanged: (isActive: boolean) => void; rootSelected: (outsideReaction?: boolean) => boolean; // whether the root of a template has been selected addDocTab: (doc: Doc, where: string) => boolean; + filterAddDocument?: (doc: Doc[]) => boolean; // allows a document that renders a Collection view to filter or modify any documents added to the collection (see PresBox for an example) addDocument?: (doc: Doc | Doc[]) => boolean; removeDocument?: (doc: Doc | Doc[]) => boolean; moveDocument?: (doc: Doc | Doc[], targetCollection: Doc | undefined, addDocument: (document: Doc | Doc[]) => boolean) => boolean; @@ -119,8 +119,9 @@ export interface DocumentViewSharedProps { bringToFront: (doc: Doc, sendToBack?: boolean) => void; dropAction?: dropActionType; dontRegisterView?: boolean; + hideLinkButton?: boolean; ignoreAutoHeight?: boolean; - cantBrush?: boolean; // whether the document doesn't show brush highlighting + disableDocBrushing?: boolean; // should highlighting for this view be disabled when same document in another view is hovered over. pointerEvents?: string; scriptContext?: any; // can be assigned anything and will be passed as 'scriptContext' to any OnClick script that executes on this document createNewFilterDoc?: () => void; @@ -133,6 +134,8 @@ export interface DocumentViewProps extends DocumentViewSharedProps { hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings hideDecorationTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings treeViewDoc?: Doc; + isDocumentActive?: () => boolean | undefined; // whether a document should handle pointer events + isContentActive: () => boolean | undefined; // whether a document should handle pointer events contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents radialMenu?: String[]; LayoutTemplateString?: string; @@ -177,7 +180,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps _componentView: Opt<DocComponentView>; // needs to be accessed from DocumentView wrapper class private get topMost() { return this.props.renderDepth === 0; } - private get active() { return this.props.isSelected(true) || this.props.parentActive(true); } public get displayName() { return "DocumentView(" + this.props.Document.title + ")"; } // this makes mobx trace() statements more descriptive public get ContentDiv() { return this._mainCont.current; } public get LayoutFieldKey() { return Doc.LayoutFieldKey(this.layoutDoc); } @@ -281,7 +283,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps this._downX = touch.clientX; this._downY = touch.clientY; if (!e.nativeEvent.cancelBubble) { - if ((this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation(); + if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !e.ctrlKey && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) e.stopPropagation(); this.removeMoveListeners(); this.addMoveListeners(); this.removeEndListeners(); @@ -292,10 +294,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } handle1PointerMove = (e: TouchEvent, me: InteractionUtils.MultiTouchEvent<TouchEvent>) => { - if (e.cancelBubble && this.active) { + if (e.cancelBubble && this.props.isDocumentActive?.()) { this.removeMoveListeners(); } - else if (!e.cancelBubble && (this.active || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart || this.onClickHandler) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { const touch = me.touchEvent.changedTouches.item(0); if (touch && (Math.abs(this._downX - touch.clientX) > 3 || Math.abs(this._downY - touch.clientY) > 3)) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler)) { @@ -407,9 +409,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if (!this._titleRef.current) setTimeout(() => this._titleRef.current?.setIsFocused(true), 0); else if (!this._titleRef.current.setIsFocused(true)) { // if focus didn't change, focus on interior text... this._titleRef.current?.setIsFocused(false); - const any = (this._mainCont.current?.getElementsByClassName("ProseMirror")?.[0] as any); - any.keeplocation = true; - any?.focus(); + this._componentView?.setFocus?.(); } } } @@ -440,16 +440,15 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps clearTimeout(this._timeout); this._timeout = undefined; } - if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself + if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself + const { clientX, clientY, shiftKey } = e; const func = () => this.onDoubleClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.DocumentView(), - clientX: e.clientX, - clientY: e.clientY, - shiftKey: e.shiftKey + clientX, clientY, shiftKey }, console.log); UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click"); } else if (!Doc.IsSystem(this.rootDoc)) { @@ -459,28 +458,23 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps } Doc.UnBrushDoc(this.props.Document); } - } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself - const shiftKey = e.shiftKey; - const clientX = e.clientX; - const clientY = e.clientY; + } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes(ScriptingBox.name)) { // bcz: hack? don't execute script if you're clicking on a scripting box itself + const { clientX, clientY, shiftKey } = e; const func = () => this.onClickHandler.script.run({ this: this.layoutDoc, self: this.rootDoc, scriptContext: this.props.scriptContext, thisContainer: this.props.ContainingCollectionDoc, documentView: this.props.DocumentView(), - clientX: clientX, - clientY: clientY, - shiftKey - }, console.log); - const clickFunc = () => this.props.Document.dontUndo ? func() : - UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on click"); + clientX, clientY, shiftKey + }, console.log).result?.select === true ? this.props.select(false) : ""; + const clickFunc = () => this.props.Document.dontUndo ? func() : UndoManager.RunInBatch(func, "on click"); if (this.onDoubleClickHandler) { this._timeout = setTimeout(() => { this._timeout = undefined; clickFunc(); }, 350); } else clickFunc(); } else if (this.Document["onClick-rawScript"] && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) {// bcz: hack? don't edit a script if you're clicking on a scripting box itself this.props.addDocTab(DocUtils.makeCustomViewClicked(Doc.MakeAlias(this.props.Document), undefined, "onClick"), "add:right"); - } else if (this.allLinks && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { + } else if (this.allLinks && this.Document.type !== DocumentType.LINK && this.Document.isLinkButton && !e.shiftKey && !e.ctrlKey) { this.allLinks.length && LinkManager.FollowLink(undefined, this.props.Document, this.props, e.altKey); } else { if ((this.layoutDoc.onDragStart || this.props.Document.rootDocument) && !(e.ctrlKey || e.button > 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part @@ -512,7 +506,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps if ((!e.nativeEvent.cancelBubble || this.onClickHandler || this.layoutDoc.onDragStart) && // if this is part of a template, let the event go up to the tempalte root unless right/ctrl clicking !(this.props.Document.rootDocument && !(e.ctrlKey || e.button > 0))) { - if ((this.active || this.layoutDoc.onDragStart) && + if ((this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { @@ -529,10 +523,10 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps onPointerMove = (e: PointerEvent): void => { if ((InteractionUtils.IsType(e, InteractionUtils.PENTYPE) || [InkTool.Highlighter, InkTool.Pen].includes(CurrentUserUtils.SelectedTool))) return; - if (e.cancelBubble && this.active) { + if (e.cancelBubble && this.props.isDocumentActive?.()) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && (this.active || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { + else if (!e.cancelBubble && (this.props.isDocumentActive?.() || this.layoutDoc.onDragStart) && !this.layoutDoc._lockedPosition && !CurrentUserUtils.OverlayDocs.includes(this.layoutDoc)) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && (!this.topMost || this.layoutDoc.onDragStart || this.onClickHandler) && (e.buttons === 1 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE))) { document.removeEventListener("pointermove", this.onPointerMove); @@ -757,14 +751,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps rootSelected = (outsideReaction?: boolean) => this.props.isSelected(outsideReaction) || (this.props.Document.rootDocument && this.props.rootSelected?.(outsideReaction)) || false; panelHeight = () => this.props.PanelHeight() - this.headerMargin; - parentActive = (outsideReaction: boolean) => this.props.layerProvider?.(this.layoutDoc) === false ? this.props.parentActive(outsideReaction) : false; screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; setHeight = (height: number) => this.layoutDoc._height = height; setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; - @observable contentsActive: () => boolean = returnFalse; - @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive; + isContentActive = (outsideReaction?: boolean) => this.props.isContentActive() ? true : false; @computed get contents() { TraceMobx(); const audioView = !this.layoutDoc._showAudio ? (null) : @@ -788,8 +780,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps scaling={this.contentScaling} PanelHeight={this.panelHeight} setHeight={this.setHeight} - contentsActive={this.setContentsActive} - parentActive={this.parentActive} + isContentActive={this.isContentActive} ScreenToLocalTransform={this.screenToLocal} rootSelected={this.rootSelected} onClick={this.onClickFunc} @@ -813,8 +804,12 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true) anchorPanelWidth = () => this.props.PanelWidth() || 1; anchorPanelHeight = () => this.props.PanelHeight() || 1; - anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => { - return property !== StyleProp.LinkSource ? this.props.styleProvider?.(doc, props, property + ":anchor") : this.props.Document; // pass the LinkSource to the LinkAnchorBox + anchorStyleProvider = (doc: Opt<Doc>, props: Opt<DocumentViewProps>, property: string): any => { + switch (property) { + case StyleProp.PointerEvents: return "none"; + case StyleProp.LinkSource: return this.props.Document;// pass the LinkSource to the LinkAnchorBox + default: return this.props.styleProvider?.(doc, props, property); + } } @computed get directLinks() { TraceMobx(); return LinkManager.Instance.getAllDirectLinks(this.rootDoc); } @computed get allLinks() { TraceMobx(); return LinkManager.Instance.getAllRelatedLinks(this.rootDoc); } @@ -961,7 +956,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"])[highlightIndex]; const highlightStyle = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid"][highlightIndex]; const excludeTypes = !this.props.treeViewDoc ? [DocumentType.FONTICON, DocumentType.INK] : [DocumentType.FONTICON]; - let highlighting = !this.props.cantBrush && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; + let highlighting = !this.props.disableDocBrushing && highlightIndex && !excludeTypes.includes(this.layoutDoc.type as any) && this.layoutDoc._viewType !== CollectionViewType.Linear; highlighting = highlighting && this.props.focus !== emptyFunction && this.layoutDoc.title !== "[pres element template]"; // bcz: hack to turn off highlighting onsidebar panel documents. need to flag a document as not highlightable in a more direct way const boxShadow = highlighting && this.borderRounding && highlightStyle !== "dashed" ? `0 0 0 ${highlightIndex}px ${highlightColor}` : @@ -1040,7 +1035,6 @@ export class DocumentView extends React.Component<DocumentViewProps> { @computed get centeringY() { return this.fitWidth || this.props.dontCenter?.includes("y") ? 0 : this.Yshift; } toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); - contentsActive = () => this.docView?.contentsActive(); focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 97f53aac0..0fc7a752f 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -18,8 +18,8 @@ export interface FieldViewProps extends DocumentViewSharedProps { fieldKey: string; scrollOverflow?: boolean; // bcz: would like to think this can be avoided -- need to look at further - active: (outsideReaction?: boolean) => boolean; select: (isCtrlPressed: boolean) => void; + isContentActive: (outsideReaction?: boolean) => boolean; isSelected: (outsideReaction?: boolean) => boolean; scaling?: () => number; setHeight: (height: number) => void; diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index b9a981e77..1d3f6a7cc 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -20,6 +20,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import './FilterBox.scss'; import { Scripting } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; +import { CollectionView } from "../collections/CollectionView"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -258,9 +259,11 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc newFacet.onThumbChanged = ScriptField.MakeScript(scriptText, { this: Doc.name, range: "number" }); } else { newFacet = new Doc(); - newFacet.sytem = true; + newFacet.system = true; newFacet.title = facetHeader; newFacet.treeViewOpen = true; + newFacet.layout = CollectionView.LayoutString("data"); + newFacet.layoutKey = "layout"; newFacet.type = DocumentType.COL; // const capturedVariables = { layoutDoc: targetDoc, system: true, _stayInCollection: true, _hideContextMenu: true, dataDoc: (targetDoc.data as any)[0][DataSym] }; // newFacet.data = ComputedField.MakeFunction(`readFacetData(layoutDoc, "${facetHeader}")`, {}, capturedVariables); @@ -415,7 +418,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc DataDoc={Doc.GetProto(facetCollection)} fieldKey={this.props.fieldKey} CollectionView={undefined} - cantBrush={true} + disableDocBrushing={true} setHeight={returnFalse} // if the tree view can trigger the height of the filter box to change, then this needs to be filled in. onChildClick={this.suppressChildClick} docFilters={returnEmptyFilter} @@ -434,9 +437,8 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc isSelected={returnFalse} select={returnFalse} bringToFront={emptyFunction} - active={returnTrue} - parentActive={returnFalse} - whenActiveChanged={returnFalse} + isContentActive={returnTrue} + whenChildContentsActiveChanged={returnFalse} treeViewHideTitle={true} focus={returnFalse} treeViewHideHeaderFields={true} diff --git a/src/client/views/nodes/FunctionPlotBox.tsx b/src/client/views/nodes/FunctionPlotBox.tsx index 4be7d1c37..b00f97236 100644 --- a/src/client/views/nodes/FunctionPlotBox.tsx +++ b/src/client/views/nodes/FunctionPlotBox.tsx @@ -80,7 +80,7 @@ export class FunctionPlotBox extends ViewBoxBaseComponent<FieldViewProps, Equati TraceMobx(); return (<div style={{ - pointerEvents: !this.active() ? "all" : undefined, + pointerEvents: !this.isContentActive() ? "all" : undefined, width: this.props.PanelWidth(), height: this.props.PanelHeight() }} diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 9426f6afc..8a6946b78 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -20,7 +20,7 @@ import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent'; import { MarqueeAnnotator } from '../MarqueeAnnotator'; import { StyleProp } from '../StyleProvider'; import { FaceRectangles } from './FaceRectangles'; @@ -46,7 +46,7 @@ const uploadIcons = { }; @observer -export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageDocument>(ImageDocument) { +export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ImageDocument>(ImageDocument) { protected _multiTouchDisposer?: import("../../util/InteractionUtils").InteractionUtils.MultiTouchEventDisposer | undefined; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ImageBox, fieldKey); } private _imgRef: React.RefObject<HTMLImageElement> = React.createRef(); @@ -319,7 +319,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD } @action marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true)) this._marqueeing = [e.clientX, e.clientY]; } @action finishMarquee = () => { @@ -358,8 +358,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD focus={this.props.focus} isSelected={this.props.isSelected} select={emptyFunction} - active={this.annotationsActive} - whenActiveChanged={this.whenActiveChanged}> + isContentActive={this.isContentActive} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged}> {this.contentFunc} </CollectionFreeFormView> {this.annotationLayer} diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 83a49a393..881cbf2bb 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -72,9 +72,8 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { dropAction: "alias", bringToFront: emptyFunction, renderDepth: 1, - parentActive: returnFalse, - active: returnFalse, - whenActiveChanged: emptyFunction, + isContentActive: returnFalse, + whenChildContentsActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, focus: emptyFunction, PanelWidth: this.props.PanelWidth, diff --git a/src/client/views/nodes/LinkAnchorBox.tsx b/src/client/views/nodes/LinkAnchorBox.tsx index 3d72d047e..8f9959693 100644 --- a/src/client/views/nodes/LinkAnchorBox.tsx +++ b/src/client/views/nodes/LinkAnchorBox.tsx @@ -120,7 +120,7 @@ export class LinkAnchorBox extends ViewBoxBaseComponent<FieldViewProps, LinkAnch const x = NumCast(this.rootDoc[this.fieldKey + "_x"], 100); const y = NumCast(this.rootDoc[this.fieldKey + "_y"], 100); const linkSource = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.LinkSource); - const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor); + const background = this.props.styleProvider?.(this.dataDoc, this.props, StyleProp.BackgroundColor + ":anchor"); const anchor = this.fieldKey === "anchor1" ? "anchor2" : "anchor1"; const anchorScale = !this.dataDoc[this.fieldKey + "-useLinkSmallAnchor"] && (x === 0 || x === 100 || y === 0 || y === 100) ? 1 : .25; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index f542652d0..c65ba9c69 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,14 +1,13 @@ import React = require("react"); import { observer } from "mobx-react"; import { documentSchema } from "../../../fields/documentSchemas"; -import { makeInterface, listSpec } from "../../../fields/Schema"; -import { returnFalse, returnZero } from "../../../Utils"; +import { makeInterface } from "../../../fields/Schema"; +import { returnFalse } from "../../../Utils"; import { CollectionTreeView } from "../collections/CollectionTreeView"; import { ViewBoxBaseComponent } from "../DocComponent"; +import { StyleProp } from "../StyleProvider"; import { FieldView, FieldViewProps } from './FieldView'; import "./LinkBox.scss"; -import { Cast } from "../../../fields/Types"; -import { StyleProp } from "../StyleProvider"; type LinkDocument = makeInterface<[typeof documentSchema]>; const LinkDocument = makeInterface(documentSchema); @@ -16,16 +15,22 @@ const LinkDocument = makeInterface(documentSchema); @observer export class LinkBox extends ViewBoxBaseComponent<FieldViewProps, LinkDocument>(LinkDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(LinkBox, fieldKey); } + isContentActiveFunc = () => this.isContentActive() ? true : false; render() { - return <div className={`linkBox-container${this.active() ? "-interactive" : ""}`} - style={{ background: this.props.styleProvider?.(this.props.Document, this.props, StyleProp.BackgroundColor) }} > - + if (this.dataDoc.treeViewOpen === undefined) setTimeout(() => this.dataDoc.treeViewOpen = true); + return <div className={`linkBox-container${this.isContentActive() ? "-interactive" : ""}`} + style={{ background: this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BackgroundColor) }} > <CollectionTreeView {...this.props} childDocuments={[this.dataDoc]} - treeViewSkipFields={Cast(this.props.Document.linkBoxExcludedKeys, listSpec("string"), null)} + treeViewOpen={true} + treeViewExpandedView={"fields"} + treeViewHideTitle={true} + treeViewSkipFields={["treeViewExpandedView", "aliases", "_removeDropProperties", + "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]} dontRegisterView={true} renderDepth={this.props.renderDepth + 1} CollectionView={undefined} + isContentActive={this.isContentActiveFunc} addDocument={returnFalse} removeDocument={returnFalse} moveDocument={returnFalse}> diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 3fe164f8a..45e3c8382 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -164,7 +164,8 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { layerProvider={this.props.docProps?.layerProvider} docViewPath={returnEmptyDoclist} ScreenToLocalTransform={Transform.Identity} - parentActive={returnFalse} + isDocumentActive={returnFalse} + isContentActive={returnFalse} addDocument={returnFalse} removeDocument={returnFalse} addDocTab={returnFalse} @@ -179,7 +180,7 @@ export class LinkDocPreview extends React.Component<LinkDocPreviewProps> { PanelWidth={this.width} PanelHeight={this.height} focus={DocUtils.DefaultFocus} - whenActiveChanged={returnFalse} + whenChildContentsActiveChanged={returnFalse} bringToFront={returnFalse} NativeWidth={Doc.NativeWidth(this._targetDoc) ? () => Doc.NativeWidth(this._targetDoc) : undefined} NativeHeight={Doc.NativeHeight(this._targetDoc) ? () => Doc.NativeHeight(this._targetDoc) : undefined} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index f27a34e36..d55156057 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -9,14 +9,14 @@ import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from '../../../fields/util'; -import { Utils } from '../../../Utils'; +import { Utils, setupMoveUpEvents, emptyFunction } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { KeyCodes } from '../../util/KeyCodes'; import { undoBatch } from '../../util/UndoManager'; import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { PDFViewer } from "../pdf/PDFViewer"; import { SidebarAnnos } from '../SidebarAnnos'; import { FieldView, FieldViewProps } from './FieldView'; @@ -28,7 +28,7 @@ type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, t const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer -export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocument>(PdfDocument) { +export class PDFBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, PdfDocument>(PdfDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PDFBox, fieldKey); } private _searchString: string = ""; private _initialScrollTarget: Opt<Doc>; @@ -150,6 +150,20 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum if (!this.layoutDoc._showSidebar) this.toggleSidebar(); return this.addDocument(doc, sidebarKey); } + sidebarBtnDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, (e, down, delta) => { + const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); + const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); + const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; + if (ratio >= 1) { + this.layoutDoc.nativeWidth = nativeWidth * ratio; + this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; + this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; + } + return false; + }, emptyFunction, this.toggleSidebar); + } toggleSidebar = action(() => { const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth; @@ -171,9 +185,9 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum </>; const searchTitle = `${!this._searching ? "Open" : "Close"} Search Bar`; const curPage = this.Document._curPage || 1; - return !this.active() ? (null) : + return !this.isContentActive() ? (null) : <div className="pdfBox-ui" onKeyDown={e => [KeyCodes.BACKSPACE, KeyCodes.DELETE].includes(e.keyCode) ? e.stopPropagation() : true} - onPointerDown={e => e.stopPropagation()} style={{ display: this.active() ? "flex" : "none" }}> + onPointerDown={e => e.stopPropagation()} style={{ display: this.isContentActive() ? "flex" : "none" }}> <div className="pdfBox-overlayCont" onPointerDown={(e) => e.stopPropagation()} style={{ left: `${this._searching ? 0 : 100}%` }}> <button className="pdfBox-overlayButton" title={searchTitle} /> <input className="pdfBox-searchBar" placeholder="Search" ref={this._searchRef} onChange={this.searchStringChanged} @@ -203,8 +217,8 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum {this._pageControls ? pageBtns : (null)} </div> <button className="pdfBox-sidebarBtn" title="Toggle Sidebar" - style={{ right: this.sidebarWidth() + 7, display: !this.active() ? "none" : undefined }} - onPointerDown={e => e.stopPropagation()} onClick={e => this.toggleSidebar()} > + style={{ display: !this.isContentActive() ? "none" : undefined }} + onPointerDown={this.sidebarBtnDown} > <FontAwesomeIcon icon={"chevron-left"} size="sm" /> </button> </div>; @@ -221,7 +235,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum } @computed get renderTitleBox() { - const classname = "pdfBox" + (this.active() ? "-interactive" : ""); + const classname = "pdfBox" + (this.isContentActive() ? "-interactive" : ""); return <div className={classname} > <div className="pdfBox-title-outer"> <strong className="pdfBox-title" >{this.props.Document.title}</strong> @@ -238,28 +252,31 @@ export class PDFBox extends ViewBoxAnnotatableComponent<FieldViewProps, PdfDocum }}> <div className="pdfBox-background" /> <PDFViewer {...this.props} + rootDoc={this.rootDoc} + layoutDoc={this.layoutDoc} + dataDoc={this.dataDoc} pdf={this._pdf!} url={this.pdfUrl!.url.pathname} - active={this.active} + isContentActive={this.isContentActive} anchorMenuClick={this._sidebarRef.current?.anchorMenuClick} loaded={!Doc.NativeAspect(this.dataDoc) ? this.loaded : undefined} setPdfViewer={this.setPdfViewer} addDocument={this.addDocument} - whenActiveChanged={this.whenActiveChanged} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} startupLive={true} ContentScaling={this.props.scaling} sidebarWidth={this.sidebarWidth} /> <SidebarAnnos ref={this._sidebarRef} {...this.props} - annotationsActive={this.annotationsActive} rootDoc={this.rootDoc} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} sidebarAddDocument={this.sidebarAddDocument} moveDocument={this.moveDocument} removeDocument={this.removeDocument} - active={this.active} + isContentActive={this.isContentActive} /> {this.settingsPanel()} </div>; diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 2aba461e0..8c5f77550 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -711,10 +711,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return output; }); - whenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); + whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isChildActive = isActive)); // For dragging documents into the presentation trail - addDocumentFilter = (doc: Doc | Doc[]) => { - const docs = doc instanceof Doc ? [doc] : doc; + addDocumentFilter = (docs: Doc[]) => { docs.forEach((doc, i) => { if (doc.presentationTargetDoc) return true; if (doc.type === DocumentType.LABEL) { @@ -749,7 +748,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.dataDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65);// listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; - active = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && this.props.layerProvider?.(this.layoutDoc) !== false) && + isContentActive = (outsideReaction?: boolean) => ((CurrentUserUtils.SelectedTool === InkTool.None && this.props.layerProvider?.(this.layoutDoc) !== false) && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false) /** diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx index c00c79eb9..8163b652c 100644 --- a/src/client/views/nodes/ScreenshotBox.tsx +++ b/src/client/views/nodes/ScreenshotBox.tsx @@ -19,7 +19,7 @@ import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { FieldView, FieldViewProps } from './FieldView'; import "./ScreenshotBox.scss"; import { VideoBox } from "./VideoBox"; @@ -33,7 +33,7 @@ type ScreenshotDocument = makeInterface<[typeof documentSchema]>; const ScreenshotDocument = makeInterface(documentSchema); @observer -export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScreenshotDocument>(ScreenshotDocument) { +export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ScreenshotDocument>(ScreenshotDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(ScreenshotBox, fieldKey); } private _videoRef = React.createRef<HTMLVideoElement>(); private _audioRec: any; @@ -159,9 +159,9 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S isSelected={this.props.isSelected} isAnnotationOverlay={true} select={emptyFunction} - active={returnFalse} + isContentActive={returnFalse} scaling={returnOne} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} removeDocument={returnFalse} moveDocument={returnFalse} addDocument={returnFalse} @@ -181,11 +181,11 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S isSelected={this.props.isSelected} isAnnotationOverlay={true} select={emptyFunction} - active={returnFalse} + isContentActive={returnFalse} scaling={returnOne} xMargin={25} yMargin={10} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} removeDocument={returnFalse} moveDocument={returnFalse} addDocument={returnFalse} diff --git a/src/client/views/nodes/ScriptingBox.tsx b/src/client/views/nodes/ScriptingBox.tsx index f1f2cd7d3..183140cd7 100644 --- a/src/client/views/nodes/ScriptingBox.tsx +++ b/src/client/views/nodes/ScriptingBox.tsx @@ -15,7 +15,7 @@ import { InteractionUtils } from "../../util/InteractionUtils"; import { CompileScript, Scripting, ScriptParam } from "../../util/Scripting"; import { ScriptManager } from "../../util/ScriptManager"; import { ContextMenu } from "../ContextMenu"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { EditableView } from "../EditableView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import { OverlayView } from "../OverlayView"; @@ -29,7 +29,7 @@ type ScriptingDocument = makeInterface<[typeof ScriptingSchema, typeof documentS const ScriptingDocument = makeInterface(ScriptingSchema, documentSchema); @observer -export class ScriptingBox extends ViewBoxAnnotatableComponent<FieldViewProps, ScriptingDocument>(ScriptingDocument) { +export class ScriptingBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, ScriptingDocument>(ScriptingDocument) { private dropDisposer?: DragManager.DragDropDisposer; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer | undefined; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index efcddd7b3..fb58ddefc 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -19,7 +19,7 @@ import { CollectionFreeFormView } from "../collections/collectionFreeForm/Collec import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; import { StyleProp } from "../StyleProvider"; @@ -32,7 +32,7 @@ type VideoDocument = makeInterface<[typeof documentSchema]>; const VideoDocument = makeInterface(documentSchema); @observer -export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) { +export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, VideoDocument>(VideoDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } static _youtubeIframeCounter: number = 0; static Instance: VideoBox; @@ -63,7 +63,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; } public get player(): HTMLVideoElement | null { return this._videoRef; } - constructor(props: Readonly<FieldViewProps>) { + constructor(props: Readonly<ViewBoxAnnotatableProps & FieldViewProps>) { super(props); VideoBox.Instance = this; } @@ -309,7 +309,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD const interactive = CurrentUserUtils.SelectedTool !== InkTool.None || !this.props.isSelected() ? "" : "-interactive"; const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ? <div key="loading">Loading</div> : - <div className="container" key="container" style={{ pointerEvents: this._isChildActive || this.active() ? "all" : "none" }}> + <div className="container" key="container" style={{ pointerEvents: this._isAnyChildContentActive || this.isContentActive() ? "all" : "none" }}> <div className={`${style}`} style={{ width: "100%", height: "100%", left: "0px" }}> <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef} style={{ height: "100%", width: "auto", display: "flex", margin: "auto" }} @@ -323,7 +323,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD Not supported. </video> {!this.audiopath || this.audiopath === field.url.href ? (null) : - <audio ref={this.setAudioRef} className={`audiobox-control${this.active() ? "-interactive" : ""}`}> + <audio ref={this.setAudioRef} className={`audiobox-control${this.isContentActive() ? "-interactive" : ""}`}> <source src={this.audiopath} type="audio/mpeg" /> Not supported. </audio>} @@ -420,7 +420,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD setupMoveUpEvents(this, e, action((e: PointerEvent) => { this._clicking = false; - if (this.active()) { + if (this.isContentActive()) { const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY); this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100)); } @@ -429,7 +429,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD () => { this.layoutDoc._timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent; setTimeout(action(() => this._clicking = false), 500); - }, this.active(), this.active()); + }, this.isContentActive(), this.isContentActive()); }); onResetDown = (e: React.PointerEvent) => { @@ -501,8 +501,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD } playing = () => this._playing; - isActiveChild = () => this._isChildActive; - timelineWhenActiveChanged = action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive)); + timelineWhenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive)); timelineScreenToLocal = () => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight()); setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time; timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100; @@ -510,6 +509,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}> <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props} fieldKey={this.annotationKey} + dictationKey={this.fieldKey + "-dictation"} mediaPath={this.audiopath} renderDepth={this.props.renderDepth + 1} startTag={"_timecodeToShow" /* videoStart */} @@ -520,13 +520,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD playFrom={this.playFrom} setTime={this.setAnchorTime} playing={this.playing} - whenActiveChanged={this.timelineWhenActiveChanged} + whenChildContentsActiveChanged={this.timelineWhenChildContentsActiveChanged} removeDocument={this.removeDocument} ScreenToLocalTransform={this.timelineScreenToLocal} - isChildActive={this.isActiveChild} Play={this.Play} Pause={this.Pause} - active={this.active} + isContentActive={this.isContentActive} playLink={this.playLink} PanelHeight={this.timelineHeight} /> @@ -538,7 +537,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD } marqueeDown = action((e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.isContentActive(true)) this._marqueeing = [e.clientX, e.clientY]; }); finishMarquee = action(() => { @@ -572,13 +571,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD isAnnotationOverlay={true} annotationLayerHostsContent={true} select={emptyFunction} - active={this.annotationsActive} + isContentActive={this.isContentActive} scaling={returnOne} docFilters={this.timelineDocFilter} PanelWidth={this.panelWidth} PanelHeight={this.panelHeight} ScreenToLocalTransform={this.screenToLocalTransform} - whenActiveChanged={this.whenActiveChanged} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} addDocument={this.addDocWithTimecode} diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 3337865a5..ab178c60b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -12,7 +12,7 @@ import { makeInterface, listSpec } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { WebField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, getWordAtPoint, OmitKeys, returnOne, smoothScroll, Utils } from "../../../Utils"; +import { emptyFunction, getWordAtPoint, OmitKeys, returnOne, smoothScroll, Utils, setupMoveUpEvents } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; @@ -21,7 +21,7 @@ import { undoBatch } from "../../util/UndoManager"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; +import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { LightboxView } from "../LightboxView"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; @@ -39,7 +39,7 @@ type WebDocument = makeInterface<[typeof documentSchema]>; const WebDocument = makeInterface(documentSchema); @observer -export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocument>(WebDocument) { +export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps & FieldViewProps, WebDocument>(WebDocument) { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(WebBox, fieldKey); } private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); @@ -391,7 +391,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum @action onMarqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.active(true)) { + if (!e.altKey && e.button === 0 && this.isContentActive(true)) { this._marqueeing = [e.clientX, e.clientY]; this.props.select(false); } @@ -429,6 +429,20 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum if (!this.layoutDoc._showSidebar) this.toggleSidebar(); return this.addDocument(doc, sidebarKey); } + sidebarBtnDown = (e: React.PointerEvent) => { + setupMoveUpEvents(this, e, (e, down, delta) => { + const localDelta = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformDirection(delta[0], delta[1]); + const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); + const curNativeWidth = NumCast(this.layoutDoc.nativeWidth, nativeWidth); + const ratio = (curNativeWidth + localDelta[0] / (this.props.scaling?.() || 1)) / nativeWidth; + if (ratio >= 1) { + this.layoutDoc.nativeWidth = nativeWidth * ratio; + this.layoutDoc._width = this.layoutDoc[WidthSym]() + localDelta[0]; + this.layoutDoc._showSidebar = nativeWidth !== this.layoutDoc._nativeWidth; + } + return false; + }, emptyFunction, this.toggleSidebar); + } toggleSidebar = action(() => { const nativeWidth = NumCast(this.layoutDoc[this.fieldKey + "-nativeWidth"]); const ratio = ((!this.layoutDoc.nativeWidth || this.layoutDoc.nativeWidth === nativeWidth ? 250 : 0) + nativeWidth) / nativeWidth; @@ -440,7 +454,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum sidebarWidth = () => !this.layoutDoc._showSidebar ? 0 : (NumCast(this.layoutDoc.nativeWidth) - Doc.NativeWidth(this.dataDoc)) * this.props.PanelWidth() / NumCast(this.layoutDoc.nativeWidth); @computed get content() { - return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.active() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")} + return <div className={"webBox-cont" + (!this.props.docViewPath().lastElement()?.docView?._pendingDoubleClick && this.isContentActive() && CurrentUserUtils.SelectedTool === InkTool.None && !DocumentDecorations.Instance?.Interacting ? "-interactive" : "")} style={{ width: NumCast(this.layoutDoc[this.fieldKey + "-contentWidth"]) || `${100 / (this.props.scaling?.() || 1)}%`, }}> {this.urlContent} </div>; @@ -464,7 +478,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum const inactiveLayer = this.props.layerProvider?.(this.layoutDoc) === false; const scale = this.props.scaling?.() || 1; return ( - <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.annotationsActive() ? "all" : this.active() || SnappingManager.GetIsDragging() ? undefined : "none" }} > + <div className="webBox" ref={this._mainCont} style={{ pointerEvents: this.isContentActive() ? "all" : this.isContentActive() || SnappingManager.GetIsDragging() ? undefined : "none" }} > <div className={`webBox-container`} style={{ pointerEvents: inactiveLayer ? "none" : undefined }} onContextMenu={this.specificContextMenu}> @@ -493,10 +507,10 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum PanelHeight={this.panelHeight} dropAction={"alias"} select={emptyFunction} - active={this.active} + isContentActive={this.isContentActive} ContentScaling={returnOne} bringToFront={emptyFunction} - whenActiveChanged={this.whenActiveChanged} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} addDocument={this.sidebarAddDocument} @@ -523,23 +537,22 @@ export class WebBox extends ViewBoxAnnotatableComponent<FieldViewProps, WebDocum annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />} </div > - <button className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar" - style={{ right: this.sidebarWidth() + 7, display: !this.active() ? "none" : undefined }} - onPointerDown={e => e.stopPropagation()} onClick={e => this.toggleSidebar()} > - <FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" /> - </button> <SidebarAnnos ref={this._sidebarRef} {...this.props} fieldKey={this.annotationKey} - annotationsActive={this.annotationsActive} rootDoc={this.rootDoc} layoutDoc={this.layoutDoc} dataDoc={this.dataDoc} sidebarAddDocument={this.sidebarAddDocument} moveDocument={this.moveDocument} removeDocument={this.removeDocument} - active={this.active} + isContentActive={this.isContentActive} /> + <button className="webBox-overlayButton-sidebar" key="sidebar" title="Toggle Sidebar" + style={{ display: !this.isContentActive() ? "none" : undefined }} + onPointerDown={this.sidebarBtnDown} > + <FontAwesomeIcon style={{ color: "white" }} icon={"chevron-left"} size="sm" /> + </button> </div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/formattedText/DashDocView.tsx b/src/client/views/nodes/formattedText/DashDocView.tsx index 0eade44ac..8915d7c47 100644 --- a/src/client/views/nodes/formattedText/DashDocView.tsx +++ b/src/client/views/nodes/formattedText/DashDocView.tsx @@ -2,7 +2,7 @@ import { IReactionDisposer, reaction, observable, action } from "mobx"; import { NodeSelection } from "prosemirror-state"; import { Doc, HeightSym, WidthSym } from "../../../../fields/Doc"; import { Cast, StrCast } from "../../../../fields/Types"; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils } from "../../../../Utils"; +import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnEmptyString, returnFalse, Utils, returnTransparent } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; @@ -166,6 +166,8 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { addDocument={returnFalse} rootSelected={this._textBox.props.isSelected} removeDocument={this.removeDoc} + isDocumentActive={returnFalse} + isContentActive={this._textBox.props.isContentActive} layerProvider={this._textBox.props.layerProvider} styleProvider={this._textBox.props.styleProvider} docViewPath={this._textBox.props.docViewPath} @@ -176,8 +178,7 @@ export class DashDocViewInternal extends React.Component<IDashDocViewInternal> { PanelWidth={this._finalLayout[WidthSym]} PanelHeight={this._finalLayout[HeightSym]} focus={this.outerFocus} - parentActive={returnFalse} - whenActiveChanged={returnFalse} + whenChildContentsActiveChanged={returnFalse} bringToFront={emptyFunction} dontRegisterView={false} docFilters={this.props.tbox?.props.docFilters} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index abfc63b40..26e78cfbf 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -145,7 +145,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }; } - public static FocusedBox: FormattedTextBox | undefined; public static PasteOnLoad: ClipboardEvent | undefined; public static SelectOnLoad = ""; public static DontSelectInitialText = false; // whether initial text should be selected or not @@ -782,7 +781,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp componentDidMount() { this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link. - this.props.contentsActive?.(this.active); this._cachedLinks = DocListCast(this.Document.links); this._disposers.breakupDictation = reaction(() => DocumentManager.Instance.RecordingEvent, this.breakupDictation); this._disposers.autoHeight = reaction(() => this.autoHeight, autoHeight => autoHeight && this.tryUpdateScrollHeight()); @@ -1192,7 +1190,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if ((e.nativeEvent as any).formattedHandled) { console.log("handled"); } - if (!(e.nativeEvent as any).formattedHandled && this.active(true)) { + if (!(e.nativeEvent as any).formattedHandled && this.isContentActive(true)) { const editor = this._editorView!; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); @@ -1225,26 +1223,17 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp e.stopPropagation(); } } + setFocus = () => { + const pos = this._editorView?.state.selection.$from.pos || 1; + (this.ProseRef?.children?.[0] as any).focus(); + setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); + } @action onFocused = (e: React.FocusEvent): void => { - FormattedTextBox.FocusedBox = this; //applyDevTools.applyDevTools(this._editorView); - - // see if we need to preserve the insertion point - const prosediv = this.ProseRef?.children?.[0] as any; - const keeplocation = prosediv?.keeplocation; - prosediv && (prosediv.keeplocation = undefined); - const pos = this._editorView?.state.selection.$from.pos || 1; - keeplocation && setTimeout(() => this._editorView?.dispatch(this._editorView?.state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos)))); - this._editorView && RichTextMenu.Instance?.updateMenu(this._editorView, undefined, this.props); } - onPointerWheel = (e: React.WheelEvent): void => { - // if a text note is selected and scrollable, stop event to prevent, say, outer collection from zooming. - if ((this.props.rootSelected(true) || this.props.isSelected(true)) || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { - e.stopPropagation(); - } - } + onClick = (e: React.MouseEvent): void => { if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { this._forceDownNode = undefined; @@ -1447,7 +1436,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp @computed get sidebarHandle() { TraceMobx(); const annotated = DocListCast(this.dataDoc[this.SidebarKey]).filter(d => d?.author).length; - return (!annotated && !this.active()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} + return (!annotated && !this.isContentActive()) ? (null) : <div className="formattedTextBox-sidebar-handle" onPointerDown={this.sidebarDown} style={{ left: `max(0px, calc(100% - ${this.sidebarWidthPercent} ${this.sidebarWidth() ? "- 5px" : "- 10px"}))`, background: this.props.styleProvider?.(this.rootDoc, this.props, StyleProp.WidgetColor + (annotated ? ":annotated" : "")) @@ -1467,9 +1456,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp scaleField={this.SidebarKey + "-scale"} isAnnotationOverlay={false} select={emptyFunction} - active={this.annotationsActive} + isContentActive={this.isContentActive} scaling={this.sidebarContentScaling} - whenActiveChanged={this.whenActiveChanged} + whenChildContentsActiveChanged={this.whenChildContentsActiveChanged} removeDocument={this.sidebarRemDocument} moveDocument={this.sidebarMoveDocument} addDocument={this.sidebarAddDocument} @@ -1489,7 +1478,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp render() { TraceMobx(); const selected = this.props.isSelected(); - const active = this.active(); + const active = this.isContentActive(); const scale = this.props.hideOnLeave ? 1 : (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); const rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; const interactive = (CurrentUserUtils.SelectedTool === InkTool.None || SnappingManager.GetIsDragging()) && (this.layoutDoc.z || this.props.layerProvider?.(this.layoutDoc) !== false); @@ -1501,6 +1490,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const selPaddingClass = selected && !this.layoutDoc._singleLine && margins >= 10 ? "-selected" : ""; return ( <div className="formattedTextBox-cont" + onWheel={e => this.isContentActive() && e.stopPropagation()} style={{ transform: `scale(${scale})`, transformOrigin: "top left", @@ -1529,7 +1519,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} onMouseUp={this.onMouseUp} - onWheel={this.onPointerWheel} onDoubleClick={this.onDoubleClick} > <div className={`formattedTextBox-outer${selected ? "-selected" : ""}`} ref={this._scrollRef} diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 84006f722..4c181e49a 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -1,13 +1,11 @@ -import { action, computed, IReactionDisposer, observable, reaction, runInAction, ObservableMap } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; -import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; -import { documentSchema } from "../../../fields/documentSchemas"; +import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { InkTool } from "../../../fields/InkField"; -import { createSchema, makeInterface } from "../../../fields/Schema"; +import { createSchema } from "../../../fields/Schema"; import { Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; import { PdfField } from "../../../fields/URLField"; import { TraceMobx } from "../../../fields/util"; @@ -20,7 +18,6 @@ import { SelectionManager } from "../../util/SelectionManager"; import { SharingManager } from "../../util/SharingManager"; import { SnappingManager } from "../../util/SnappingManager"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; -import { ViewBoxAnnotatableComponent } from "../DocComponent"; import { MarqueeAnnotator } from "../MarqueeAnnotator"; import { FieldViewProps } from "../nodes/FieldView"; import { LinkDocPreview } from "../nodes/LinkDocPreview"; @@ -33,21 +30,16 @@ const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); const _global = (window /* browser */ || global /* node */) as any; -export const pageSchema = createSchema({ - _curPage: "number", - rotation: "number", - scrollHeight: "number", - serachMatch: "boolean", -}); - //pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; // The workerSrc property shall be specified. pdfjsLib.GlobalWorkerOptions.workerSrc = "https://unpkg.com/pdfjs-dist@2.4.456/build/pdf.worker.min.js"; -type PdfDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>; -const PdfDocument = makeInterface(documentSchema, pageSchema); - interface IViewerProps extends FieldViewProps { + Document: Doc; + rootDoc: Doc; + dataDoc: Doc; + layoutDoc: Doc; + fieldKey: string; pdf: Pdfjs.PDFDocumentProxy; url: string; startupLive: boolean; @@ -62,7 +54,7 @@ interface IViewerProps extends FieldViewProps { * Handles rendering and virtualization of the pdf */ @observer -export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocument>(PdfDocument) { +export class PDFViewer extends React.Component<IViewerProps> { static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _savedAnnotations = new ObservableMap<number, HTMLDivElement[]>(); @@ -93,22 +85,24 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu private _initialScroll: Opt<number>; private _forcedScroll = true; + + // key where data is stored @computed get allAnnotations() { - return DocUtils.FilterDocs(DocListCast(this.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined); + return DocUtils.FilterDocs(DocListCast(this.props.dataDoc[this.props.fieldKey + "-annotations"]), this.props.docFilters(), this.props.docRangeFilters(), undefined); } @computed get inlineTextAnnotations() { return this.allAnnotations.filter(a => a.textInlineAnnotations); } componentDidMount = async () => { // change the address to be the file address of the PNG version of each page // file address of the pdf - const { url: { href } } = Cast(this.dataDoc[this.props.fieldKey], PdfField)!; + const { url: { href } } = Cast(this.props.dataDoc[this.props.fieldKey], PdfField)!; const { url: relative } = this.props; if (relative.includes("/pdfs/")) { const pathComponents = relative.split("/pdfs/")[1].split("/"); const coreFilename = pathComponents.pop()!.split(".")[0]; const params: any = { coreFilename, - pageNum: Math.min(this.props.pdf.numPages, Math.max(1, this.Document._curPage || 1)), + pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))), }; if (pathComponents.length) { params.subtree = `${pathComponents.join("/")}/`; @@ -117,7 +111,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } else { const params: any = { coreFilename: relative.split("/")[relative.split("/").length - 1], - pageNum: Math.min(this.props.pdf.numPages, Math.max(1, this.Document._curPage || 1)), + pageNum: Math.min(this.props.pdf.numPages, Math.max(1, NumCast(this.props.Document._curPage, 1))), }; this._coverPath = "http://cs.brown.edu/~bcz/face.gif";//href.startsWith(window.location.origin) ? await Networking.PostToServer("/thumbnail", params) : { width: 100, height: 100, path: "" }; } @@ -125,13 +119,13 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu this.props.startupLive && this.setupPdfJsViewer(); this._mainCont.current?.addEventListener("scroll", e => (e.target as any).scrollLeft = 0); - this._disposers.autoHeight = reaction(() => this.layoutDoc._autoHeight, + this._disposers.autoHeight = reaction(() => this.props.layoutDoc._autoHeight, () => { - this.layoutDoc._nativeHeight = NumCast(this.props.Document[this.fieldKey + "-nativeHeight"]); - this.props.setHeight(NumCast(this.props.Document[this.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); + this.props.layoutDoc._nativeHeight = NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]); + this.props.setHeight(NumCast(this.props.Document[this.props.fieldKey + "-nativeHeight"]) * (this.props.scaling?.() || 1)); }); - this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.rootDoc), + this._disposers.searchMatch = reaction(() => Doc.IsSearchMatch(this.props.rootDoc), m => { if (m) (this._lastSearch = true) && this.search(Doc.SearchQuery(), m.searchMatch > 0); else !(this._lastSearch = false) && setTimeout(() => !this._lastSearch && this.search("", false, true), 200); @@ -146,7 +140,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu (SelectionManager.Views().length === 1) && this.setupPdfJsViewer(); }, { fireImmediately: true }); - this._disposers.curPage = reaction(() => this.Document._curPage, + this._disposers.curPage = reaction(() => Cast(this.props.Document._curPage, "number", null), (page) => page !== undefined && page !== this._pdfViewer?.currentPageNumber && this.gotoPage(page), { fireImmediately: true } ); @@ -158,7 +152,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } copy = (e: ClipboardEvent) => { - if (this.active() && e.clipboardData) { + if (this.props.isContentActive() && e.clipboardData) { e.clipboardData.setData("text/plain", this._selectionText); e.preventDefault(); } @@ -180,7 +174,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu page.view[page0or180 ? 3 : 2] - page.view[page0or180 ? 1 : 0], i); } })))); - this.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; + this.props.Document.scrollHeight = this._pageSizes.reduce((size, page) => size + page.height, 0) * 96 / 72; } } @@ -189,8 +183,8 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu scrollFocus = (doc: Doc, smooth: boolean) => { const mainCont = this._mainCont.current; let focusSpeed: Opt<number>; - if (doc !== this.rootDoc && mainCont && this._pdfViewer) { - const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); + if (doc !== this.props.rootDoc && mainCont && this._pdfViewer) { + const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.props.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); if (scrollTo !== undefined) { focusSpeed = 500; @@ -212,7 +206,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu await this.initialLoad(); this._disposers.filterScript = reaction( - () => ScriptCast(this.Document.filterScript), + () => ScriptCast(this.props.Document.filterScript), action(scriptField => { const oldScript = this._script.originalScript; this._script = scriptField?.script.compiled ? scriptField.script : CompileScript("return true") as CompiledScript; @@ -228,16 +222,16 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu pagesinit = () => { if (this._pdfViewer._setDocumentViewerElement.offsetParent) { runInAction(() => this._pdfViewer.currentScaleValue = this._zoomed = 1); - this.gotoPage(this.Document._curPage || 1); + this.gotoPage(NumCast(this.props.Document._curPage, 1)); } document.removeEventListener("pagesinit", this.pagesinit); var quickScroll: string | undefined = this._initialScroll ? this._initialScroll.toString() : ""; this._disposers.scroll = reaction( - () => Math.abs(NumCast(this.Document._scrollTop)), + () => Math.abs(NumCast(this.props.Document._scrollTop)), (pos) => { if (!this._ignoreScroll) { (this._showCover || this._showWaiting) && this.setupPdfJsViewer(); - const viewTrans = quickScroll ?? StrCast(this.Document._viewTransition); + const viewTrans = quickScroll ?? StrCast(this.props.Document._viewTransition); const durationMiliStr = viewTrans.match(/([0-9]*)ms/); const durationSecStr = viewTrans.match(/([0-9.]*)s/); const duration = durationMiliStr ? Number(durationMiliStr[1]) : durationSecStr ? Number(durationSecStr[1]) * 1000 : 0; @@ -324,7 +318,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu if (this._mainCont.current && !this._forcedScroll) { this._ignoreScroll = true; // the pdf scrolled, so we need to tell the Doc to scroll but we don't want the doc to then try to set the PDF scroll pos (which would interfere with the smooth scroll animation) if (!LinkDocPreview.LinkInfo) { - this.layoutDoc._scrollTop = this._mainCont.current.scrollTop; + this.props.layoutDoc._scrollTop = this._mainCont.current.scrollTop; } this._ignoreScroll = false; } @@ -374,11 +368,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu // if alt+left click, drag and annotate this._downX = e.clientX; this._downY = e.clientY; - if ((this.Document._viewScale || 1) !== 1) return; - if ((e.button !== 0 || e.altKey) && this.active(true)) { + if ((this.props.Document._viewScale || 1) !== 1) return; + if ((e.button !== 0 || e.altKey) && this.props.isContentActive(true)) { this._setPreviewCursor?.(e.clientX, e.clientY, true); } - if (!e.altKey && e.button === 0 && this.props.active(true)) { + if (!e.altKey && e.button === 0 && this.props.isContentActive(true)) { this.props.select(false); this._marqueeing = [e.clientX, e.clientY]; if (e.target && ((e.target as any).className.includes("endOfContent") || ((e.target as any).parentElement.className !== "textLayer"))) { @@ -452,7 +446,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } scrollXf = () => { - return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.layoutDoc._scrollTop || 0) : this.props.ScreenToLocalTransform(); + return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, NumCast(this.props.layoutDoc._scrollTop)) : this.props.ScreenToLocalTransform(); } onClick = (e: React.MouseEvent) => { @@ -469,12 +463,12 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu getCoverImage = () => { if (!this.props.Document[HeightSym]() || !Doc.NativeHeight(this.props.Document)) { setTimeout((() => { - this.Document._height = this.Document[WidthSym]() * this._coverPath.height / this._coverPath.width; - Doc.SetNativeWidth(this.Document, (Doc.NativeWidth(this.Document) || 0) * this._coverPath.height / this._coverPath.width); + this.props.Document._height = this.props.Document[WidthSym]() * this._coverPath.height / this._coverPath.width; + Doc.SetNativeWidth(this.props.Document, (Doc.NativeWidth(this.props.Document) || 0) * this._coverPath.height / this._coverPath.width); }).bind(this), 0); } - const nativeWidth = Doc.NativeWidth(this.Document); - const nativeHeight = Doc.NativeHeight(this.Document); + const nativeWidth = Doc.NativeWidth(this.props.Document); + const nativeHeight = Doc.NativeHeight(this.props.Document); const resolved = Utils.prepend(this._coverPath.path); return <img key={resolved} src={resolved} onError={action(() => this._coverPath.path = "http://www.cs.brown.edu/~bcz/face.gif")} onLoad={action(() => this._showWaiting = false)} style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />; @@ -482,7 +476,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu @action onZoomWheel = (e: React.WheelEvent) => { - if (this.active(true)) { + if (this.props.isContentActive(true)) { e.stopPropagation(); if (e.ctrlKey) { const curScale = Number(this._pdfViewer.currentScaleValue); @@ -493,9 +487,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu } @computed get annotationLayer() { - return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}> + return <div className="pdfViewerDash-annotationLayer" style={{ height: Doc.NativeHeight(this.props.Document), transform: `scale(${this._zoomed})` }} ref={this._annotationLayer}> {this.inlineTextAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map(anno => - <Annotation {...this.props} fieldKey={this.fieldKey + "-annotations"} showInfo={this.showInfo} dataDoc={this.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />) + <Annotation {...this.props} fieldKey={this.props.fieldKey + "-annotations"} showInfo={this.showInfo} dataDoc={this.props.dataDoc} anno={anno} key={`${anno[Id]}-annotation`} />) } </div>; } @@ -522,19 +516,14 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu }}> <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit} isAnnotationOverlay={true} - fieldKey={this.annotationKey} + fieldKey={this.props.fieldKey + "-annotations"} setPreviewCursor={this.setPreviewCursor} PanelHeight={this.panelHeight} PanelWidth={this.panelWidth} dropAction={"alias"} select={emptyFunction} - active={this.annotationsActive} ContentScaling={this.contentZoom} bringToFront={emptyFunction} - whenActiveChanged={this.whenActiveChanged} - removeDocument={this.removeDocument} - moveDocument={this.moveDocument} - addDocument={this.addDocument} CollectionView={undefined} ScreenToLocalTransform={this.overlayTransform} renderDepth={this.props.renderDepth + 1} @@ -542,7 +531,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu </div>; } @computed get pdfViewerDiv() { - return <div className={"pdfViewerDash-text" + (this._textSelecting && (this.props.isSelected() || this.props.active()) ? "-selected" : "")} ref={this._viewer} />; + return <div className={"pdfViewerDash-text" + (this._textSelecting && (this.props.isSelected() || this.props.isContentActive()) ? "-selected" : "")} ref={this._viewer} />; } @computed get contentScaling() { return this.props.ContentScaling?.() || 1; } @computed get standinViews() { @@ -555,7 +544,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu render() { TraceMobx(); return <div className="pdfViewer-content"> - <div className={`pdfViewerDash${this.annotationsActive() ? "-interactive" : ""}`} ref={this._mainCont} + <div className={`pdfViewerDash${this.props.isContentActive() ? "-interactive" : ""}`} ref={this._mainCont} onScroll={this.onScroll} onWheel={this.onZoomWheel} onPointerDown={this.onPointerDown} onClick={this.onClick} style={{ overflowX: this._zoomed !== 1 ? "scroll" : undefined, @@ -569,9 +558,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent<IViewerProps, PdfDocu {this.overlayInfo} {this.standinViews} {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) : - <MarqueeAnnotator rootDoc={this.rootDoc} scrollTop={0} down={this._marqueeing} + <MarqueeAnnotator rootDoc={this.props.rootDoc} scrollTop={0} down={this._marqueeing} anchorMenuClick={this.props.anchorMenuClick} - addDocument={this.addDocument} + addDocument={(doc: Doc | Doc[]) => this.props.addDocument!(doc)} finishMarquee={this.finishMarquee} docView={this.props.docViewPath().lastElement()} getPageFromScroll={this.getPageFromScroll} diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index c794a4132..a1fc77a92 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -78,7 +78,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc // embedWidth = () => this.props.PanelWidth(); // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); embedWidth = (): number => this.props.PanelWidth() - 35; - styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps | FieldViewProps>, property: string): any => { + styleProvider = (doc: (Doc | undefined), props: Opt<DocumentViewProps>, property: string): any => { if (property === StyleProp.Opacity) return 1; return this.props.styleProvider?.(doc, props, property); } @@ -98,16 +98,16 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc rootSelected={returnTrue} addDocument={returnFalse} removeDocument={returnFalse} + isContentActive={this.props.isContentActive} addDocTab={returnFalse} pinToPres={returnFalse} PanelWidth={this.embedWidth} PanelHeight={this.embedHeight} ScreenToLocalTransform={Transform.Identity} - parentActive={this.props.active} moveDocument={this.props.moveDocument!} renderDepth={this.props.renderDepth + 1} focus={DocUtils.DefaultFocus} - whenActiveChanged={returnFalse} + whenChildContentsActiveChanged={returnFalse} bringToFront={returnFalse} docFilters={this.props.docFilters} docRangeFilters={this.props.docRangeFilters} diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 31043f5be..c36bfb04b 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -428,6 +428,11 @@ export namespace Doc { index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1); return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); } + + /** + * Removes doc from the list of Docs at listDoc[fieldKey] + * @returns true if successful, false otherwise. + */ export function RemoveDocFromList(listDoc: Doc, fieldKey: string | undefined, doc: Doc) { const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); if (listDoc[key] === undefined) { @@ -443,6 +448,11 @@ export namespace Doc { } return false; } + + /** + * Adds doc to the list of Docs stored at listDoc[fieldKey]. + * @returns true if successful, false otherwise. + */ export function AddDocToList(listDoc: Doc, fieldKey: string | undefined, doc: Doc, relativeTo?: Doc, before?: boolean, first?: boolean, allowDuplicates?: boolean, reversed?: boolean) { const key = fieldKey ? fieldKey : Doc.LayoutFieldKey(listDoc); if (listDoc[key] === undefined) { @@ -476,9 +486,9 @@ export namespace Doc { return false; } - // - // Computes the bounds of the contents of a set of documents. - // + /** + * Computes the bounds of the contents of a set of documents. + */ export function ComputeContentBounds(docList: Doc[]) { const bounds = docList.reduce((bounds, doc) => { const [sptX, sptY] = [NumCast(doc.x), NumCast(doc.y)]; diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 275249840..f17a390a6 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -75,9 +75,8 @@ export const documentSchema = createSchema({ textTransform: "string", treeViewOpen: "boolean", // flag denoting whether the documents sub-tree (contents) is visible or hidden treeViewExpandedView: "string", // name of field whose contents are being displayed as the document's subtree - treeViewLockExpandedView: "boolean", // whether the expanded view can be changed - treeViewDefaultExpandedView: "string", // name of field whose contents are displayed by default - treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) + treeViewExpandedViewLock: "boolean", // whether the expanded view can be changed + treeViewOpenIsTransient: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) treeViewType: "string", // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off // interaction and linking properties diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx index cbae71270..71ddda866 100644 --- a/src/mobile/AudioUpload.tsx +++ b/src/mobile/AudioUpload.tsx @@ -93,12 +93,13 @@ export class AudioUpload extends React.Component { PanelWidth={() => 600} PanelHeight={() => 400} renderDepth={0} + isDocumentActive={returnTrue} + isContentActive={returnFalse} focus={emptyFunction} layerProvider={undefined} styleProvider={() => "rgba(0,0,0,0)"} docViewPath={returnEmptyDoclist} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 1001d9e19..404e828ea 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -210,12 +210,13 @@ export class MobileInterface extends React.Component { PanelWidth={this.returnWidth} PanelHeight={this.returnHeight} renderDepth={0} + isDocumentActive={returnTrue} + isContentActive={returnFalse} focus={DocUtils.DefaultFocus} styleProvider={this.whitebackground} layerProvider={undefined} docViewPath={returnEmptyDoclist} - parentActive={returnTrue} - whenActiveChanged={emptyFunction} + whenChildContentsActiveChanged={emptyFunction} bringToFront={emptyFunction} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} |