diff options
Diffstat (limited to 'src')
31 files changed, 1571 insertions, 1410 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 7dff1ac55..cc7ee9537 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -518,7 +518,8 @@ export function clearStyleSheetRules(sheet: any) { return false; } -export function simulateMouseClick(element: Element, x: number, y: number, sx: number, sy: number, rightClick = true) { +export function simulateMouseClick(element: Element | null | undefined, x: number, y: number, sx: number, sy: number, rightClick = true) { + if (!element) return; ["pointerdown", "pointerup"].map(event => element.dispatchEvent( new PointerEvent(event, { view: window, diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ecb1342f4..f9e3add84 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -598,7 +598,7 @@ export namespace Docs { viewDoc.author = Doc.CurrentUserEmail; viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc); - if (Doc.UserDoc()?.defaultAclPrivate) viewDoc["ACL-Public"] = dataDoc["ACL-Public"] = "Not Shared"; + if (Doc.UserDoc()?.defaultAclPrivate) viewDoc["acl-Public"] = dataDoc["acl-Public"] = "Not Shared"; return Doc.assign(viewDoc, delegateProps, true); } diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 87c620d6d..7991022d2 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -152,13 +152,13 @@ export class SharingManager extends React.Component<{}> { const target = targetDoc || this.targetDoc!; const key = StrCast(group.groupName).replace(".", "_"); - const ACL = `ACL-${key}`; + const acl = `acl-${key}`; const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); docs.forEach(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); - GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc); + doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`acl-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); + GetEffectiveAcl(doc) === AclAdmin && distributeAcls(acl, permission as SharingPermissions, doc); if (key !== "Public") { const members: string[] = JSON.parse(StrCast(group.members)); @@ -198,7 +198,7 @@ export class SharingManager extends React.Component<{}> { } else { docs.forEach(doc => { - if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls("ACL-Public", permission, doc); + if (GetEffectiveAcl(doc) === AclAdmin) distributeAcls("acl-Public", permission, doc); }); } } @@ -225,9 +225,9 @@ export class SharingManager extends React.Component<{}> { removeGroup = (group: Doc) => { if (group.docsShared) { DocListCast(group.docsShared).forEach(doc => { - const ACL = `ACL-${StrCast(group.groupName)}`; + const acl = `acl-${StrCast(group.groupName)}`; - distributeAcls(ACL, SharingPermissions.None, doc); + distributeAcls(acl, SharingPermissions.None, doc); const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); @@ -244,14 +244,14 @@ export class SharingManager extends React.Component<{}> { const { user, notificationDoc } = recipient; const target = targetDoc || this.targetDoc!; const key = user.email.replace('.', '_'); - const ACL = `ACL-${key}`; + const acl = `acl-${key}`; const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); docs.forEach(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); - GetEffectiveAcl(doc) === AclAdmin && distributeAcls(ACL, permission as SharingPermissions, doc); + doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`] && distributeAcls(`acl-${Doc.CurrentUserEmail.replace(".", "_")}`, SharingPermissions.Admin, doc); + GetEffectiveAcl(doc) === AclAdmin && distributeAcls(acl, permission as SharingPermissions, doc); if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(notificationDoc, storage, (doc.aliasOf as Doc || doc)); @@ -445,8 +445,8 @@ export class SharingManager extends React.Component<{}> { const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym]?.[AclSym] && Object.keys(doc[DataSym][AclSym]))); // the list of users shared with - const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`ACL-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc, userColor }) => { - const userKey = `ACL-${user.email.replace('.', '_')}`; + const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${user.email.replace('.', '_')}`) : true).map(({ user, notificationDoc, userColor }) => { + const userKey = `acl-${user.email.replace('.', '_')}`; const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[userKey] === docs[0]?.[AclSym]?.[userKey] : doc?.[DataSym]?.[AclSym]?.[userKey] === docs[0]?.[DataSym]?.[AclSym]?.[userKey]); const permissions = uniform ? StrCast(targetDoc?.[userKey]) : "-multiple-"; @@ -503,7 +503,7 @@ export class SharingManager extends React.Component<{}> { <span className={"padding"}>Me</span> <div className="edit-actions"> <div className={"permissions-dropdown"}> - {targetDoc?.[`ACL-${Doc.CurrentUserEmail.replace(".", "_")}`]} + {targetDoc?.[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`]} </div> </div> </div> @@ -514,12 +514,12 @@ export class SharingManager extends React.Component<{}> { const publicDoc = new Doc; publicDoc.groupName = "Public"; // the list of groups shared with - const groupListMap = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`ACL-${StrCast(groupName).replace('.', '_')}`) : true); + const groupListMap = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${StrCast(groupName).replace('.', '_')}`) : true); groupListMap.unshift(publicDoc); const groupListContents = groupListMap.map(group => { - const groupKey = `ACL-${StrCast(group.groupName)}`; + const groupKey = `acl-${StrCast(group.groupName)}`; const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[groupKey] === docs[0]?.[AclSym]?.[groupKey] : doc?.[DataSym]?.[AclSym]?.[groupKey] === docs[0]?.[DataSym]?.[AclSym]?.[groupKey]); - const permissions = uniform ? StrCast(targetDoc?.[`ACL-${StrCast(group.groupName)}`]) : "-multiple-"; + const permissions = uniform ? StrCast(targetDoc?.[`acl-${StrCast(group.groupName)}`]) : "-multiple-"; return !permissions ? (null) : ( <div diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index b18b2302d..c5fd3c777 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -24,6 +24,7 @@ import { DocumentView } from './nodes/DocumentView'; import { GoogleRef } from "./nodes/formattedText/FormattedTextBox"; import { TemplateMenu } from "./TemplateMenu"; import React = require("react"); +import { PresBox } from './nodes/PresBox'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -183,24 +184,40 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV @computed get pinButton() { const targetDoc = this.view0?.props.Document; - let isPinned = targetDoc && Doc.isDocPinned(targetDoc); - // More than 1 document selected then all must be in presentation for isPinned to be true (then it will unpin all) - if (SelectionManager.SelectedDocuments().length > 1) { - SelectionManager.SelectedDocuments().forEach((docView: DocumentView) => { - if (Doc.isDocPinned(docView.props.Document)) isPinned = true; - else isPinned = false; - }); - } - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div></>}> + const isPinned = targetDoc && Doc.isDocPinned(targetDoc); + return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin to presentation"}</div></>}> <div className="documentButtonBar-linker" - style={{ backgroundColor: isPinned ? "white" : "", color: isPinned ? "black" : "white", border: isPinned ? "black 1px solid " : "" }} - onClick={e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, isPinned))}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" - /> + style={{ color: "white" }} + onClick={e => this.props.views().map(view => view && TabDocView.PinDoc(view.props.Document, false))}> + <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="map-pin" /> </div></Tooltip>; } @computed + get pinWithViewButton() { + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 17, transform: 'translate(0, 1px)' }} />; + const targetDoc = this.view0?.props.Document; + return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin with current view"}</div></>}> + <div className="documentButtonBar-linker" + onClick={e => { + if (targetDoc) { + TabDocView.PinDoc(targetDoc, false); + const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } + }}> + {presPinWithViewIcon} + </div> + </Tooltip>; + } + + @computed get shareButton() { const targetDoc = this.view0?.props.Document; return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Open Sharing Manager"}</div></>}> @@ -236,7 +253,8 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{`${CurrentUserUtils.propertiesWidth > 0 ? "Close" : "Open"} Properties Panel`}</div></>}> <div className="documentButtonBar-linker" style={{ color: "white", cursor: "e-resize" }} onClick={action(e => CurrentUserUtils.propertiesWidth = CurrentUserUtils.propertiesWidth > 0 ? 0 : 250)}> - <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="ellipsis-h" /> + <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="ellipsis-h" + /> </div></Tooltip >; } @@ -336,6 +354,9 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV <div className="documentButtonBar-button"> {this.pinButton} </div> + {/* <div className="documentButtonBar-button"> + {this.pinWithViewButton} + </div> */} {!Doc.UserDoc()["documentLinksButton-fullMenu"] ? (null) : <div className="documentButtonBar-button"> {this.shareButton} </div>} @@ -356,4 +377,4 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV </div>} */} </div>; } -} +}
\ No newline at end of file diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index cbbd78a20..d35271ffd 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -204,7 +204,7 @@ export class EditableView extends React.Component<EditableProps> { setTimeout(() => this.props.autosuggestProps?.resetValue(), 0); return this.props.contents instanceof ObjectField ? (null) : <div className={`editableView-container-editing${this.props.oneLine ? "-oneLine" : ""}`} ref={this._ref} - style={{ display: this.props.display, minHeight: "17px", whiteSpace: "nowrap", height: `${this.props.height ? this.props.height : "auto"}`, maxHeight: `${this.props.maxHeight}` }} + style={{ display: this.props.display, minHeight: "17px", whiteSpace: "nowrap", height: this.props.height || "auto", maxHeight: this.props.maxHeight }} onClick={this.onClick} placeholder={this.props.placeholder}> <span style={{ fontStyle: this.props.fontStyle, fontSize: this.props.fontSize }}>{ this.props.contents ? this.props.contents?.valueOf() : this.props.placeholder?.valueOf()} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index c2290dd09..595e07326 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -213,7 +213,7 @@ export class MainView extends React.Component { })); } const pres = Docs.Create.PresDocument(new List<Doc>(), - { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0", system: true }); + { title: "Untitled Presentation", _viewType: CollectionViewType.Stacking, _width: 400, _height: 500, targetDropAction: "alias", _chromeStatus: "replaced", boxShadow: "0 0" }); CollectionDockingView.AddSplit(pres, "right"); this.userDoc.activePresentation = pres; Doc.AddDocToList(this.userDoc.myPresentations as Doc, "data", pres); diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 53261d8de..0dd749b07 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -43,10 +43,10 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { @computed get MAX_EMBED_HEIGHT() { return 200; } - @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } + @computed get selectedDoc() { console.log(this.selectedDocumentView?.rootDoc.title); return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } @computed get selectedDocumentView() { - if (PresBox.Instance?._selectedArray.length) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; + if (PresBox.Instance && PresBox.Instance._selectedArray) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); return undefined; } @computed get isPres(): boolean { @@ -168,7 +168,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { const rows: JSX.Element[] = []; const noviceReqFields = ["author", "creationDate", "tags"]; const noviceLayoutFields = ["_curPage"]; - const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("ACL"))), + const noviceKeys = [...Array.from(Object.keys(ids)).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("acl"))), ...noviceReqFields, ...noviceLayoutFields]; for (const key of noviceKeys.sort()) { const docvals = new Set<any>(); @@ -416,13 +416,13 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { // if (Doc.UserDoc().sidebarUsersDisplayed) { // for (const [name, value] of Object.entries(sidebarUsersDisplayed!)) { - // if (value === true && !this.selectedDoc![`ACL-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None)); + // if (value === true && !this.selectedDoc![`acl-${name.substring(8).replace(".", "_")}`]) tableEntries.push(this.sharingItem(name.substring(8), effectiveAcl, SharingPermissions.None)); // } // } // }) // shifts the current user, owner, public to the top of the doc. - tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.every(doc => doc["ACL-Public"] === docs[0]["ACL-Public"]) ? (AclMap.get(target[AclSym]?.["ACL-Public"]) || SharingPermissions.None) : "-multiple-")); + tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.every(doc => doc["acl-Public"] === docs[0]["acl-Public"]) ? (AclMap.get(target[AclSym]?.["acl-Public"]) || SharingPermissions.None) : "-multiple-")); tableEntries.unshift(this.sharingItem("Me", showAdmin, docs.every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-")); if (Doc.CurrentUserEmail !== target.author && docs.every(doc => doc.author === docs[0].author)) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); @@ -1012,6 +1012,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { } if (this.isPres) { const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0; + const type = PresBox.Instance.activeItem?.type; return <div className="propertiesView" style={{ width: this.props.width }}> <div className="propertiesView-title" style={{ width: this.props.width }}> Presentation @@ -1038,7 +1039,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {PresBox.Instance.transitionDropdown} </div> : null} </div>} - {!selectedItem ? (null) : <div className="propertiesView-presTrails"> + {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) : <div className="propertiesView-presTrails"> <div className="propertiesView-presTrails-title" onPointerDown={action(() => this.openPresProgressivize = !this.openPresProgressivize)} style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> @@ -1051,7 +1052,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {PresBox.Instance.progressivizeDropdown} </div> : null} </div>} - {!selectedItem ? (null) : <div className="propertiesView-presTrails"> + {!selectedItem || (type !== DocumentType.COL && type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) : <div className="propertiesView-presTrails"> <div className="propertiesView-presTrails-title" onPointerDown={action(() => { this.openSlideOptions = !this.openSlideOptions; })} style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 5ac0a8ff0..4c5a71ace 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -369,9 +369,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp } @computed get selectedDocumentView() { - if (SelectionManager.SelectedDocuments().length) { - return SelectionManager.SelectedDocuments()[0]; - } else { return undefined; } + return SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } @computed get notACollection() { @@ -387,13 +385,38 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp const targetDoc = this.selectedDoc; const isPinned = targetDoc && Doc.isDocPinned(targetDoc); return !targetDoc ? (null) : <Tooltip key="pin" title={<div className="dash-tooltip">{Doc.isDocPinned(targetDoc) ? "Unpin from presentation" : "Pin to presentation"}</div>} placement="top"> - <button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderRight: "1px solid gray" }} + <button className="antimodeMenu-button" style={{ backgroundColor: isPinned ? "121212" : undefined, borderLeft: "1px solid gray" }} onClick={e => TabDocView.PinDoc(targetDoc, isPinned)}> <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-pin" /> </button> </Tooltip>; } + @computed + get pinWithViewButton() { + const presPinWithViewIcon = <img src={`/assets/pinWithView.png`} style={{ margin: "auto", width: 19 }} />; + const targetDoc = this.selectedDoc; + return (!targetDoc || (targetDoc._viewType !== CollectionViewType.Freeform && targetDoc.type !== DocumentType.IMG)) ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin to presentation trail with current view"}</div></>} placement="top"> + <button className="antimodeMenu-button" style={{ borderRight: "1px solid gray", borderLeft: "1px solid gray", justifyContent: 'center' }} + onClick={e => { + if (targetDoc) { + TabDocView.PinDoc(targetDoc, false); + const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeDoc.presPinView = true; + activeDoc.presPinViewX = x; + activeDoc.presPinViewY = y; + activeDoc.presPinViewScale = scale; + } + }}> + {presPinWithViewIcon} + </button> + </Tooltip>; + } + + @undoBatch onAlias = () => { if (this.selectedDoc && this.selectedDocumentView) { @@ -449,33 +472,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp </Tooltip>; } - @computed - get pinWithViewButton() { - const targetDoc = this.selectedDoc; - if (targetDoc) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - } - return !targetDoc ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Pin to presentation with current view"}</div></>} placement="top"> - <button className="antidmodeMenu-button" style={{ borderRight: "1px solid gray" }} - onClick={e => { - if (targetDoc) { - TabDocView.PinDoc(targetDoc, false); - const activeDoc = PresBox.Instance.childDocs[PresBox.Instance.childDocs.length - 1]; - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeDoc.presPinView = true; - activeDoc.presPinViewX = x; - activeDoc.presPinViewY = y; - activeDoc.presPinViewScale = scale; - } - }}> - <FontAwesomeIcon className="documentdecorations-icon" size="lg" icon="map-marker" /> - </button> - </Tooltip>; - } render() { @@ -485,8 +481,6 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp <div className="collectionViewBaseChrome"> {this.notACollection || this.props.type === CollectionViewType.Invalid ? (null) : this.viewModes} {!this._buttonizableCommands ? (null) : this.templateChrome} - - {this.props.docView.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Freeform ? (null) : <Tooltip title={<div className="dash-tooltip">Toggle Overlay Layer</div>} placement="bottom"> <button className={"antimodeMenu-button"} key="float" @@ -497,7 +491,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionMenuProp </Tooltip>} {this.notACollection ? (null) : this.lightboxButton} {this.aliasButton} - {this.pinButton} + {/* {this.pinButton} */} {this.pinWithViewButton} </div> {this.subChrome} @@ -899,14 +893,14 @@ export class CollectionStackingViewChrome extends React.Component<CollectionMenu if (docs instanceof Doc) { const keys = Object.keys(docs).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || - (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key[0] !== "_")); + (key[0].toUpperCase() === key[0] && key.substring(0, 3) !== "acl" && key[0] !== "_")); return keys.filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set<string>(); docs.forEach(doc => Doc.allKeys(doc).forEach(key => keys.add(key))); const noviceKeys = Array.from(keys).filter(key => key.indexOf("title") >= 0 || key.indexOf("author") >= 0 || key.indexOf("creationDate") >= 0 || key.indexOf("lastModified") >= 0 || - (key[0]?.toUpperCase() === key[0] && key.substring(0, 3) !== "ACL" && key[0] !== "_")); + (key[0]?.toUpperCase() === key[0] && key.substring(0, 3) !== "acl" && key[0] !== "_")); return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } } @@ -1308,4 +1302,4 @@ Scripting.addGlobal(function gotoFrame(doc: any, newFrame: any) { } CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0); doc._currentFrame = Math.max(0, newFrame); -});
\ No newline at end of file +}); diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index d2a1234ed..c02e88829 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -299,7 +299,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { this.updateFilter(); let keyOptions = this._searchTerm === "" ? this.props.possibleKeys : this.props.possibleKeys.filter(key => key.toUpperCase().indexOf(this._searchTerm.toUpperCase()) > -1); const blockedkeys = ["system", "title-custom", "limitHeight", "proto", "x", "y", "zIndex", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"]; - keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("ACL")); + keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("acl")); if (keyOptions.length) { this.onSelect(keyOptions[0]); } else if (this._searchTerm !== "" && this.props.canAddNew) { @@ -333,7 +333,7 @@ export class KeysDropdown extends React.Component<KeysDropdownProps> { this.props.existingKeys.findIndex(key => key.toUpperCase() === this._searchTerm.toUpperCase()) > -1; const blockedkeys = ["proto", "x", "y", "zIndex", "_timeStampOnEnter", "isPrototype", "text-annotations", "aliases", "text-lastModified", "text-noTemplate", "layoutKey", "baseProto", "layout", "layout_keyValue", "links"]; - keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("ACL")); + keyOptions = keyOptions.filter(n => !blockedkeys.includes(n) && !n.startsWith("_") && !n.startsWith("acl")); const options = keyOptions.map(key => { return <div key={key} className="key-option" style={{ diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index aaf17153a..20bfc0e9d 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -30,24 +30,6 @@ padding-left: 0; } - .outline-bullet { - position: relative; - width: 15px; - color: $intermediate-color; - transform: scale(0.5); - display: inline-block; - } - - .bullet { - position: relative; - width: 15px; - color: $intermediate-color; - margin-top: 3px; - transform: scale(1.3, 1.3); - border: #80808030 1px solid; - border-radius: 4px; - } - .editableView-container { font-weight: bold; } @@ -101,98 +83,4 @@ padding-left: 3px; padding-right: 3px; padding-bottom: 2px; -} - -.treeViewItem-container-active { - z-index: 100; - position: relative;; - .formattedTextbox-sidebar { - background-color: #ffff001f !important; - height: 500px !important; - } -} - -.treeViewItem-openRight { - display: none; - height: 17px; - width: 15px; -} - -.treeViewItem-openRight:hover { - background: #797777; - cursor: pointer; -} - -.treeViewItem-border-outline, -.treeViewItem-border { - display: flex; - overflow: hidden; -} -.treeViewItem-border{ - border-left: dashed 1px #00000042; -} - -.treeViewItem-header-editing, -.treeViewItem-header { - border: transparent 1px solid; - display: flex; - //align-items: center; - ::-webkit-scrollbar { - display: none; - } - .formattedTextBox-cont { - .formattedTextbox-sidebar { - overflow: visible !important; - border-left: unset; - } - overflow: visible !important; - } - - .editableView-container-editing-oneLine { - min-width: 15px; - } - - .documentView-node-topmost { - width: unset; - } - - >svg { - display: none; - } - -} - -.treeViewItem-header:hover { - .collectionTreeView-keyHeader { - display: inherit; - } - - >svg { - display: inherit; - } - - .treeViewItem-openRight { - display: inline-block; - height: 17px; - width: 15px; - - // display: inline; - svg { - display: block; - padding: 0px; - margin-left: 3px; - } - } -} - -.treeViewItem-header-above { - border-top: black 1px solid; -} - -.treeViewItem-header-below { - border-bottom: black 1px solid; -} - -.treeViewItem-header-inside { - border: black 1px solid; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 19b8400c8..46f18099a 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,791 +1,29 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable } from "mobx"; +import { action, computed } from "mobx"; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, Field, HeightSym, Opt, WidthSym, DocListCastOrNull } from '../../../fields/Doc'; +import { DataSym, Doc, DocListCast, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; -import { Document, listSpec } from '../../../fields/Schema'; -import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, emptyPath, returnFalse, returnOne, returnTrue, returnZero, simulateMouseClick, Utils, returnEmptyFilter, returnEmptyDoclist } from '../../../Utils'; -import { Docs, DocUtils } from '../../documents/Documents'; -import { DocumentType } from "../../documents/DocumentTypes"; -import { SnappingManager } from '../../util/SnappingManager'; +import { Document } from '../../../fields/Schema'; +import { ScriptField } from '../../../fields/ScriptField'; +import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; +import { TraceMobx } from '../../../fields/util'; +import { emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnTrue, Utils } from '../../../Utils'; +import { DocUtils } from '../../documents/Documents'; import { DragManager, dropActionType } from "../../util/DragManager"; -import { Scripting } from '../../util/Scripting'; import { SelectionManager } from '../../util/SelectionManager'; -import { Transform } from '../../util/Transform'; +import { SnappingManager } from '../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { EditableView } from "../EditableView"; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; -import { DocumentView } from '../nodes/DocumentView'; -import { KeyValueBox } from '../nodes/KeyValueBox'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; -import { CollectionViewType, CollectionView } from './CollectionView'; +import { TreeView } from "./TreeView"; import React = require("react"); -import { TraceMobx } from '../../../fields/util'; -import { CurrentUserUtils } from '../../util/CurrentUserUtils'; -import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; -import { RichTextField } from '../../../fields/RichTextField'; -import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; -import { DocumentManager } from '../../util/DocumentManager'; - -export interface TreeViewProps { - document: Doc; - dataDoc?: Doc; - containingCollection: Doc; - prevSibling?: Doc; - renderDepth: number; - removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; - moveDocument: DragManager.MoveFunction; - dropAction: dropActionType; - addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; - pinToPres: (document: Doc) => void; - panelWidth: () => number; - panelHeight: () => number; - ChromeHeight: undefined | (() => number); - addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; - indentDocument?: () => void; - outdentDocument?: () => void; - ScreenToLocalTransform: () => Transform; - backgroundColor?: (doc: Opt<Doc>, renderDepth: number) => string | undefined; - 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; - ignoreFields?: string[]; - firstLevel: boolean; - whenActiveChanged: (isActive: boolean) => void; -} - -@observer -/** - * Renders a treeView of a collection of documents - * - * 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 - */ -class TreeView extends React.Component<TreeViewProps> { - private _editTitleScript: (() => ScriptField) | undefined; - private _openScript: (() => ScriptField) | undefined; - private _header?: React.RefObject<HTMLDivElement> = React.createRef(); - private _treedropDisposer?: DragManager.DragDropDisposer; - private _dref = React.createRef<HTMLDivElement>(); - private _tref = React.createRef<HTMLDivElement>(); - private _docRef = React.createRef<DocumentView>(); - private _uniqueId = Utils.GenerateGuid(); - private _editMaxWidth: number | string = 0; - - get doc() { return this.props.document; } - get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); } - get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive - get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; } - get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode || this.outlineMode ? "layout" : "fields"); } - get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs ? this.fieldKey : this.defaultExpandedView); } - @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state - set treeViewOpen(c: boolean) { - if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; - else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; - } - @computed get outlineMode() { return this.props.treeView.doc.treeViewOutlineMode; } - @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; } - @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); } - @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } - @computed get dataDoc() { return this.doc[DataSym]; } - @computed get layoutDoc() { return Doc.Layout(this.doc); } - @computed get fieldKey() { const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; } - childDocList(field: string) { - const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined; - 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 - } - @computed get childDocs() { return this.childDocList(this.fieldKey); } - @computed get childLinks() { return this.childDocList("links"); } - @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } - @computed get boundsOfCollectionDocument() { - return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 || !DocListCast(this.props.document[this.fieldKey]).length ? undefined : - Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey])); - } - - @undoBatch openRight = () => this.props.addDocTab(this.doc, "add:right"); - @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { - return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc); - } - @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { - this.props.treeView.props.select(false); - return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); - } - @undoBatch @action removeDoc = (doc: Doc | Doc[]) => this.remove(doc, Doc.LayoutFieldKey(this.doc)); - - constructor(props: any) { - super(props); - const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); - const openScript = ScriptField.MakeScript(`openOnRight(self)`); - const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`); - this._editTitleScript = !Doc.IsSystem(this.props.document) ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript); - this._openScript = !Doc.IsSystem(this.props.document) ? openScript && (() => openScript) : undefined; - if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false); - } - - protected createTreeDropTarget = (ele: HTMLDivElement) => { - this._treedropDisposer?.(); - ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.doc); - } - - componentWillUnmount() { - document.removeEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointermove", this.onDragUp, true); - } - - onDragUp = (e: PointerEvent) => { - document.removeEventListener("pointerup", this.onDragUp, true); - document.removeEventListener("pointermove", this.onDragMove, true); - } - onPointerEnter = (e: React.PointerEvent): void => { - this.props.active(true) && Doc.BrushDoc(this.dataDoc); - if (e.buttons === 1 && SnappingManager.GetIsDragging()) { - this._header!.current!.className = "treeViewItem-header"; - document.removeEventListener("pointermove", this.onDragMove, true); - document.addEventListener("pointermove", this.onDragMove, true); - document.removeEventListener("pointerup", this.onDragUp, true); - document.addEventListener("pointerup", this.onDragUp, true); - } - } - onPointerLeave = (e: React.PointerEvent): void => { - Doc.UnBrushDoc(this.dataDoc); - if (this._header?.current?.className !== "treeViewItem-header-editing") { - this._header!.current!.className = "treeViewItem-header"; - } - document.removeEventListener("pointerup", this.onDragUp, true); - document.removeEventListener("pointermove", this.onDragMove, true); - } - onDragMove = (e: PointerEvent): void => { - Doc.UnBrushDoc(this.dataDoc); - const pt = [e.clientX, e.clientY]; - 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 = "treeViewItem-header"; - if (inside) this._header!.current!.className += " treeViewItem-header-inside"; - else if (before) this._header!.current!.className += " treeViewItem-header-above"; - else if (!before) this._header!.current!.className += " treeViewItem-header-below"; - e.stopPropagation(); - } - - public static makeTextBullet() { - const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 }); - Doc.GetProto(bullet).layout = CollectionView.LayoutString("data"); - Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); - Doc.GetProto(bullet).data = new List<Doc>([]); - Doc.SetInPlace(bullet, "editTitle", "*", false); - FormattedTextBox.SelectOnLoad = bullet[Id]; - return bullet; - } - - makeTextCollection = () => { - Doc.SetInPlace(this.doc, "editTitle", undefined, false); - const bullet = TreeView.makeTextBullet(); - const added = this.props.addDocument(bullet); - bullet.context = this.props.treeView.Document; - return added; - } - - editableView = (key: string, style?: string) => (<EditableView - oneLine={true} - display={"inline-block"} - editing={true} - contents={StrCast(this.doc[key])} - height={12} - sizeToContent={true} - fontStyle={style} - fontSize={12} - GetValue={() => StrCast(this.doc[key])} - SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { - if (this.outlineMode && enterKey) { - Doc.SetInPlace(this.doc, key, value, false); - this.makeTextCollection(); - } else { - Doc.SetInPlace(this.doc, key, value, false) || true; - Doc.SetInPlace(this.doc, "editTitle", undefined, false); - } - })} - onClick={() => { - SelectionManager.DeselectAll(); - Doc.UserDoc().activeSelection = new List([this.doc]); - return false; - }} - OnEmpty={undoBatch(() => this.props.treeView.doc.treeViewOutlineMode && this.props.removeDoc?.(this.doc))} - OnTab={undoBatch((shift?: boolean) => { - shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); - setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", `${this.props.treeView._uniqueId}`, false), 0); - })} - />) - - preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { - const dragData = de.complete.docDragData; - dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction); - } - - @undoBatch - treeDrop = (e: Event, de: DragManager.DropEvent) => { - const pt = [de.x, de.y]; - 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); - const complete = de.complete; - if (complete.linkDragData) { - const sourceDoc = complete.linkDragData.linkSourceDocument; - const destDoc = this.doc; - DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", ""); - e.stopPropagation(); - } - const docDragData = complete.docDragData; - if (docDragData) { - e.stopPropagation(); - if (docDragData.draggedDocuments[0] === this.doc) return true; - const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); - let addDoc = parentAddDoc; - if (inside) { - const localAdd = (doc: Doc) => { - const added = Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - added && (doc.context = this.doc.context); - return added; - }; - addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce( - (flg: boolean, doc) => flg && localAdd(doc), true) || parentAddDoc(doc); - } - const move = (!docDragData.dropAction || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument; - return docDragData.droppedDocuments.reduce((added, d) => (move ? docDragData.moveDocument?.(d, undefined, addDoc) : addDoc(d)) || added, false); - } - return false; - } - - refTransform = (ref: HTMLDivElement) => { - const { scale, translateX, translateY } = Utils.GetScreenTransform(ref); - const outerXf = this.props.outerXf(); - const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); - return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); - } - docTransform = () => this.refTransform(this._dref.current!); - getTransform = () => this.refTransform(this._tref.current!); - docWidth = () => { - const layoutDoc = this.layoutDoc; - const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); - if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); - return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; - } - docHeight = () => { - const layoutDoc = this.layoutDoc; - const bounds = this.boundsOfCollectionDocument; - return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => { - const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); - if (aspect) return this.docWidth() * aspect; - if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); - return layoutDoc._fitWidth ? (!this.doc._nativeHeight ? NumCast(this.props.containingCollection._height) : - Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, - NumCast(this.props.containingCollection._height)))) : - NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; - })())); - } - - @computed get expandedField() { - const ids: { [key: string]: string } = {}; - const doc = this.doc; - doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); - - const rows: JSX.Element[] = []; - for (const key of Object.keys(ids).slice().sort()) { - if (this.props.ignoreFields?.includes(key) || key === "title" || key === "treeViewOpen") continue; - const contents = doc[key]; - let contentElement: (JSX.Element | null)[] | JSX.Element = []; - - if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) { - const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); - const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { - const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); - added && (doc.context = this.doc.context); - return added; - }; - 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.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, - this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, - [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged); - } else { - contentElement = <EditableView key="editableView" - contents={contents !== undefined ? Field.toString(contents as Field) : "null"} - height={13} - fontSize={12} - GetValue={() => Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; - } - rows.push(<div style={{ display: "flex" }} key={key}> - <span style={{ fontWeight: "bold" }}>{key + ":"}</span> - - {contentElement} - </div>); - } - rows.push(<div style={{ display: "flex" }} key={"newKeyValue"}> - <EditableView - key="editableView" - contents={"+key:value"} - height={13} - fontSize={12} - GetValue={() => ""} - SetValue={(value: string) => { - value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true); - return true; - }} /> - </div>); - return rows; - } - - rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.panelWidth() - 20); - rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), 20); - - @computed get renderContent() { - TraceMobx(); - const expandKey = this.treeViewExpandedView; - if (["links", "annotations", this.fieldKey].includes(expandKey)) { - const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey); - const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { - const added = Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true); - added && (doc.context = this.doc.context); - return added; - }; - 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 === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; - const sortKey = `${this.fieldKey}-sortAscending`; - return <ul key={expandKey + "more"} className={this.doc.treeViewHideTitle ? "no-indent" : ""} onClick={(e) => { - !this.outlineMode && (this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true))); - 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.props.backgroundColor, this.props.ScreenToLocalTransform, - this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, - [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged)} - </ul >; - } else if (this.treeViewExpandedView === "fields") { - return <ul key={this.doc[Id] + this.doc.title}><div ref={this._dref} style={{ display: "inline-block" }} > - {this.expandedField} - </div></ul>; - } else { - const layoutDoc = this.layoutDoc; - const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight; - const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth; - return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.doc[Id]}> - <ContentFittingDocumentView - Document={this.doc} - DataDoc={undefined} - LibraryPath={emptyPath} - renderDepth={this.props.renderDepth + 1} - rootSelected={returnTrue} - treeViewDoc={undefined} - backgroundColor={this.props.backgroundColor} - fitToBox={this.boundsOfCollectionDocument !== undefined} - FreezeDimensions={true} - NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined} - NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined} - PanelWidth={panelWidth} - PanelHeight={panelHeight} - focus={returnFalse} - ScreenToLocalTransform={this.docTransform} - docFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containingCollection} - ContainingCollectionView={undefined} - addDocument={returnFalse} - moveDocument={this.move} - removeDocument={this.props.removeDoc} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - bringToFront={returnFalse} - ContentScaling={returnOne} - /> - </div>; - } - } - - get onCheckedClick() { return this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } - - @action - bulletClick = (e: React.MouseEvent) => { - if (this.onCheckedClick && this.doc.type !== DocumentType.COL) { - // this.props.document.treeViewChecked = this.props.document.treeViewChecked === "check" ? "x" : this.props.document.treeViewChecked === "x" ? undefined : "check"; - this.onCheckedClick?.script.run({ - this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, - heading: this.props.containingCollection.title, - checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check", - containingTreeView: this.props.treeView.props.Document, - }, console.log); - } else { - this.treeViewOpen = !this.treeViewOpen; - } - e.stopPropagation(); - } - - @computed get renderOutlineBullet() { - TraceMobx(); - return <div className="outline-bullet" - title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} - onClick={this.bulletClick} - style={{ opacity: NumCast(this.doc.opacity, 1) }}> - {(this.doc.text as RichTextField)?.Text ? <FontAwesomeIcon icon={this.childDocs?.length && !this.treeViewOpen ? ["fas", "circle"] : ["far", "circle"]} /> : (null)} - </div>; - } - @computed get renderBullet() { - TraceMobx(); - const checked = this.doc.type === DocumentType.COL ? undefined : this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined; - return <div className="bullet" - title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} - onClick={this.bulletClick} - style={{ - color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), - opacity: checked === "unchecked" ? undefined : 0.4 - }}> - {<FontAwesomeIcon icon={checked === "check" ? "check" : (checked === "x" ? "times" : checked === "unchecked" ? "square" : !this.treeViewOpen ? (this.childDocs?.length ? "caret-square-right" : "caret-right") : (this.childDocs?.length ? "caret-square-down" : "caret-down"))} />} - </div>; - } - - showContextMenu = (e: React.MouseEvent) => { - this._docRef.current?.ContentDiv && simulateMouseClick(this._docRef.current.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); - } - contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; - truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, 0); - @computed get showTitleEdit() { - return ["*", this._uniqueId, this.props.treeView._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || ""); - } - onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); - onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); - /** - * Renders the EditableView title element for placement into the tree. - */ - @computed - get renderTitle() { - TraceMobx(); - const headerElements = this.props.treeViewHideHeaderFields() ? (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.treeViewOpen) { - this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView : - this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.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.outlineMode ? "layout" : "fields"); - } - this.treeViewOpen = true; - })}> - {this.treeViewExpandedView} - </span> - </>; - const view = this.showTitleEdit ? this.editableView("title") : - <DocumentView - ref={this._docRef} - Document={this.doc} - DataDoc={undefined} - treeViewDoc={this.props.treeView.props.Document} - LibraryPath={emptyPath} - addDocument={undefined} - addDocTab={this.props.addDocTab} - rootSelected={returnTrue} - pinToPres={emptyFunction} - onClick={this.onChildClick} - onDoubleClick={this.onChildDoubleClick} - dropAction={this.props.dropAction} - moveDocument={this.move} - removeDocument={this.props.removeDoc} - ScreenToLocalTransform={this.getTransform} - ContentScaling={returnOne} - PanelWidth={this.truncateTitleWidth} - PanelHeight={returnZero} - contextMenuItems={this.contextMenuItems} - opacity={this.outlineMode ? undefined : returnOne} - renderDepth={1} - focus={returnTrue} - parentActive={returnTrue} - whenActiveChanged={this.props.whenActiveChanged} - bringToFront={emptyFunction} - dontRegisterView={BoolCast(this.props.treeView.props.Document.dontRegisterChildViews)} - docFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionView={undefined} - ContainingCollectionDoc={this.props.containingCollection} - />; - return <> - <div className={`docContainer${Doc.IsSystem(this.props.document) ? "-system" : ""}`} ref={this._tref} title="click to edit title. Double Click or Drag to Open" - style={{ - 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 - }} > - {view} - </div > - {Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode ? (null) : headerElements} - </>; - } - - refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); - - render() { - TraceMobx(); - if (this.props.renderedIds.indexOf(this.doc[Id]) !== -1) return null; - const sorting = this.doc[`${this.fieldKey}-sortAscending`]; - if (this.showTitleEdit) { // find containing CollectionTreeView and set our maximum width so the containing tree view won't have to scroll - let par: any = this._header?.current; - if (par) { - while (par && par.className !== "collectionTreeView-dropTarget") par = par.parentNode; - if (par) { - const par_rect = (par as HTMLElement).getBoundingClientRect(); - const my_recct = this._docRef.current?.ContentDiv?.getBoundingClientRect(); - this._editMaxWidth = Math.max(100, par_rect.right - (my_recct?.left || 0)); - } - } - } else this._editMaxWidth = ""; - const selected = SelectionManager.IsSelected(DocumentManager.Instance.getFirstDocumentView(this.doc)); - return this.doc.treeViewHideHeader || this.outlineMode ? - !StrCast(Doc.LayoutField(this.doc)).includes("CollectionView") ? this.renderContent : - <div className={`treeViewItem-container${selected ? "-active" : ""}`} ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()} - onKeyDown={e => { - e.stopPropagation(); - e.key === "Backspace" && this.doc.text && !(this.doc.text as RichTextField)?.Text && UndoManager.RunInBatch(() => this.props.removeDoc?.(this.doc), "delete"); - e.key === "Tab" && UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.() : this.props.indentDocument?.(), "tab"); - e.key === "Enter" && UndoManager.RunInBatch(() => this.makeTextCollection(), "bullet"); - e.key === "Tab" && setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); - }} - > - <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ alignItems: this.outlineMode ? "center" : undefined, maxWidth: this._editMaxWidth }} - onClick={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} - onPointerDown={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - {this.outlineMode ? this.renderOutlineBullet : this.renderBullet} - <div ref={this._dref} style={{ display: "inline-block", height: this.rtfOutlineHeight() }} key={this.doc[Id]}> - <ContentFittingDocumentView - Document={this.doc} - DataDoc={undefined} - LayoutTemplateString={FormattedTextBox.LayoutString("text")} - LibraryPath={emptyPath} - renderDepth={this.props.renderDepth + 1} - rootSelected={returnTrue} - treeViewDoc={undefined} - backgroundColor={this.props.backgroundColor} - fitToBox={this.boundsOfCollectionDocument !== undefined} - PanelWidth={this.rtfWidth} - PanelHeight={this.rtfOutlineHeight} - focus={this.refocus} - ScreenToLocalTransform={this.docTransform} - docFilters={returnEmptyFilter} - searchFilterDocs={returnEmptyDoclist} - ContainingCollectionDoc={this.props.containingCollection} - ContainingCollectionView={undefined} - addDocument={this.props.addDocument} - moveDocument={this.move} - removeDocument={this.props.removeDoc} - parentActive={this.props.active} - whenActiveChanged={this.props.whenActiveChanged} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} - bringToFront={returnFalse} - ContentScaling={returnOne} - /> - </div> - </div> - - <div className={`treeViewItem-border${this.outlineMode ? "outline" : ""}`} style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}> - {!this.treeViewOpen ? (null) : this.renderContent} - </div> - </div> : - <div className="treeViewItem-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}> - <li className="collection-child"> - <div className={`treeViewItem-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => { - if (this.props.active(true)) { - e.stopPropagation(); - e.preventDefault(); - SelectionManager.DeselectAll(); - } - }} - onPointerDown={e => { - if (this.props.active(true)) { - e.stopPropagation(); - e.preventDefault(); - } - }} - onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - {this.outlineMode ? this.renderOutlineBullet : this.renderBullet} - {this.renderTitle} - </div> - <div className={`treeViewItem-border${this.outlineMode ? "outline" : ""}`} style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}> - {!this.treeViewOpen ? (null) : this.renderContent} - </div> - </li> - </div>; - } - public static GetChildElements( - childDocs: Doc[], - treeView: CollectionTreeView, - containingCollection: Doc, - dataDoc: Doc | undefined, - key: string, - parentCollectionDoc: Doc | undefined, - parentPrevSibling: Doc | undefined, - add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean, - remove: ((doc: Doc | Doc[]) => boolean), - move: DragManager.MoveFunction, - dropAction: dropActionType, - addDocTab: (doc: Doc, where: string) => boolean, - pinToPres: (document: Doc) => void, - backgroundColor: undefined | ((document: Opt<Doc>, renderDepth: number) => string | undefined), - screenToLocalXf: () => Transform, - outerXf: () => { translateX: number, translateY: number }, - active: (outsideReaction?: boolean) => boolean, - panelWidth: () => number, - ChromeHeight: undefined | (() => number), - renderDepth: number, - treeViewHideHeaderFields: () => boolean, - treeViewPreventOpen: boolean, - renderedIds: string[], - onCheckedClick: undefined | (() => ScriptField), - onChildClick: undefined | (() => ScriptField), - ignoreFields: string[] | undefined, - firstLevel: boolean, - whenActiveChanged: (isActive: boolean) => void - ) { - const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); - if (viewSpecScript) { - childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); - } - - const docs = childDocs.slice(); - const ascending = containingCollection?.[key + "-sortAscending"]; - if (ascending !== undefined) { - const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => { - const reN = /[0-9]*$/; - const aA = a.replace(reN, ""); // get rid of trailing numbers - const bA = b.replace(reN, ""); - if (aA === bA) { // if header string matches, then compare numbers numerically - const aN = parseInt(a.match(reN)![0], 10); - const bN = parseInt(b.match(reN)![0], 10); - return aN === bN ? 0 : aN > bN ? 1 : -1; - } else { - return aA > bA ? 1 : -1; - } - }; - docs.sort(function (a, b): 0 | 1 | -1 { - const descA = ascending ? b : a; - const descB = ascending ? a : b; - const first = descA.title; - const second = descB.title; - // TODO find better way to sort how to sort.................. - if (typeof first === 'number' && typeof second === 'number') { - return (first - second) > 0 ? 1 : -1; - } - if (typeof first === 'string' && typeof second === 'string') { - return sortAlphaNum(first, second); - } - if (typeof first === 'boolean' && typeof second === 'boolean') { - // if (first === second) { // bugfixing?: otherwise, the list "flickers" because the list is resorted during every load - // return Number(descA.x) > Number(descB.x) ? 1 : -1; - // } - return first > second ? 1 : -1; - } - return ascending ? 1 : -1; - }); - } - - const rowWidth = () => panelWidth() - 20; - return docs.map((child, i) => { - const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, child); - if (!pair.layout || pair.data instanceof Promise) { - return (null); - } - - const indent = i === 0 ? undefined : () => { - if (remove && StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) { - const fieldKeysub = StrCast(docs[i - 1].layout).split('fieldKey')[1]; - const fieldKey = fieldKeysub.split("\'")[1]; - if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) { - remove(child); - FormattedTextBox.SelectOnLoad = child[Id]; - Doc.AddDocToList(docs[i - 1], fieldKey, child); - docs[i - 1].treeViewOpen = true; - child.context = treeView.Document; - } - } - }; - const outdent = !parentCollectionDoc ? undefined : () => { - if (remove && StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) { - const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1]; - const fieldKey = fieldKeysub.split("\'")[1]; - remove(child); - FormattedTextBox.SelectOnLoad = child[Id]; - Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false); - parentCollectionDoc.treeViewOpen = true; - child.context = treeView.Document; - } - }; - const addDocument = (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => { - return add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); - }; - const childLayout = Doc.Layout(pair.layout); - const rowHeight = () => { - const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0); - return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); - }; - return !(child instanceof Doc) ? (null) : <TreeView - document={pair.layout} - dataDoc={pair.data} - containingCollection={containingCollection} - prevSibling={docs[i]} - treeView={treeView} - key={child[Id]} - indentDocument={indent} - outdentDocument={outdent} - onCheckedClick={onCheckedClick} - onChildClick={onChildClick} - renderDepth={renderDepth} - removeDoc={StrCast(containingCollection.freezeChildren).includes("remove") ? undefined : remove} - addDocument={addDocument} - backgroundColor={backgroundColor} - panelWidth={rowWidth} - panelHeight={rowHeight} - ChromeHeight={ChromeHeight} - moveDocument={move} - dropAction={dropAction} - addDocTab={addDocTab} - pinToPres={pinToPres} - ScreenToLocalTransform={screenToLocalXf} - outerXf={outerXf} - parentKey={key} - active={active} - treeViewHideHeaderFields={treeViewHideHeaderFields} - treeViewPreventOpen={treeViewPreventOpen} - renderedIds={renderedIds} - ignoreFields={ignoreFields} - firstLevel={firstLevel} - whenActiveChanged={whenActiveChanged} />; - }); - } -} export type collectionTreeViewProps = { treeViewHideTitle?: boolean; @@ -797,18 +35,16 @@ export type collectionTreeViewProps = { @observer export class CollectionTreeView extends CollectionSubView<Document, Partial<collectionTreeViewProps>>(Document) { private treedropDisposer?: DragManager.DragDropDisposer; + private _isChildActive = false; private _mainEle?: HTMLDivElement; - public _uniqueId = Utils.GenerateGuid(); - _isChildActive = false; + @computed get doc() { return this.props.Document; } @computed get dataDoc() { return this.props.DataDoc || this.doc; } protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer?.(); - if (this._mainEle = ele) { - this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); - } + if (this._mainEle = ele) this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.doc, this.onInternalPreDrop.bind(this)); } protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { @@ -873,8 +109,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll @computed get renderClearButton() { return <div key="toolbar"> - <button className="toolbar-button round-button" title="Empty" - onClick={undoBatch(action(() => Doc.GetProto(this.doc)[this.props.fieldKey] = undefined))}> + <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 >; @@ -984,15 +219,4 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll </div> ); } -} - -Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { - const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []); - for (let i = 0; i < docFilters.length; i += 3) { - const [header, value, state] = docFilters.slice(i, i + 3); - if (header === facetHeader && value === facetValue) { - return state; - } - } - return undefined; -});
\ No newline at end of file +}
\ No newline at end of file diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index f89285923..1be85cfc1 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -22,7 +22,7 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; -import { PresBox } from '../nodes/PresBox'; +import { PresBox, PresMovement } from '../nodes/PresBox'; import { CollectionDockingView } from './CollectionDockingView'; import { CollectionDockingViewMenu } from './CollectionDockingViewMenu'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; @@ -122,27 +122,27 @@ export class TabDocView extends React.Component<TabDocViewProps> { **/ @undoBatch @action - public static PinDoc(doc: Doc, unpin = false) { - if (unpin) TabDocView.UnpinDoc(doc); - else { - //add this new doc to props.Document - const curPres = CurrentUserUtils.ActivePresentation; - if (curPres) { - const pinDoc = Doc.MakeAlias(doc); - pinDoc.presentationTargetDoc = doc; - pinDoc.presZoomButton = true; - pinDoc.context = curPres; - Doc.AddDocToList(curPres, "data", pinDoc); - if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; - if (!DocumentManager.Instance.getDocumentView(curPres)) { - CollectionDockingView.AddSplit(curPres, "right"); - } - DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null)); - setTimeout(() => { - curPres._itemIndex = DocListCast(curPres.data).length - 1; - doc.treeViewOutlineMode && PresBox.Instance.progressivizeChild(null as any); - }, 100); + public static PinDoc(doc: Doc, unpin = false, audioRange?: boolean) { + if (unpin) console.log('remove unpin'); + //add this new doc to props.Document + const curPres = CurrentUserUtils.ActivePresentation; + if (curPres) { + const pinDoc = Doc.MakeAlias(doc); + pinDoc.presentationTargetDoc = doc; + pinDoc.title = doc.title; + pinDoc.presMovement = PresMovement.Zoom; + pinDoc.context = curPres; + Doc.AddDocToList(curPres, "data", pinDoc); + if (pinDoc.type === "audio" && !audioRange) { + pinDoc.presStartTime = 0; + pinDoc.presEndTime = doc.duration; } + if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; + const curPresDocView = DocumentManager.Instance.getDocumentView(curPres); + if (!curPresDocView) { + CollectionDockingView.AddSplit(curPres, "right"); + } + DocumentManager.Instance.jumpToDocument(doc, false, undefined, Cast(doc.context, Doc, null)); } } @@ -169,7 +169,7 @@ export class TabDocView extends React.Component<TabDocViewProps> { })).observe(this.props.glContainer._element[0]); this.props.glContainer.layoutManager.on("activeContentItemChanged", this.onActiveContentItemChanged); this.props.glContainer.tab?.isActive && this.onActiveContentItemChanged(); - this._tabReaction = reaction(() => ({ selected: selected(), color: this.tabColor, title: this.tab.titleElement[0] }), + this._tabReaction = reaction(() => ({ selected: selected(), color: this.tabColor, title: this.tab?.titleElement[0] }), ({ selected, color, title }) => title && (title.style.backgroundColor = selected ? color : ""), { fireImmediately: true }); } @@ -318,7 +318,9 @@ export class TabDocView extends React.Component<TabDocViewProps> { </>; } focusFunc = (doc: Doc, willZoom: boolean, scale?: number, afterFocus?: () => void) => { - this.tab.header.parent.setActiveContentItem(this.tab.contentItem); + if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { + this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) + } afterFocus?.(); } setView = action((view: DocumentView) => this._view = view); @@ -371,4 +373,4 @@ export class TabDocView extends React.Component<TabDocViewProps> { {this.docView} </div >); } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss new file mode 100644 index 000000000..17c6b0750 --- /dev/null +++ b/src/client/views/collections/TreeView.scss @@ -0,0 +1,115 @@ +@import "../globalCssVariables"; + +.treeView-container, +.treeView-container-active { + .bullet-outline { + position: relative; + width: 15px; + color: $intermediate-color; + transform: scale(0.5); + display: inline-block; + } + + .bullet { + position: relative; + width: 15px; + color: $intermediate-color; + margin-top: 3px; + transform: scale(1.3, 1.3); + border: #80808030 1px solid; + border-radius: 4px; + } +} +.treeView-container-active { + z-index: 100; + position: relative;; + .formattedTextbox-sidebar { + background-color: #ffff001f !important; + height: 500px !important; + } +} + +.treeView-openRight { + display: none; + height: 17px; + width: 15px; +} + +.treeView-openRight:hover { + background: #797777; + cursor: pointer; +} + +.treeView-border-outline, +.treeView-border { + display: flex; + overflow: hidden; +} +.treeView-border{ + border-left: dashed 1px #00000042; +} + +.treeView-header-editing, +.treeView-header { + border: transparent 1px solid; + display: flex; + //align-items: center; + ::-webkit-scrollbar { + display: none; + } + .formattedTextBox-cont { + .formattedTextbox-sidebar { + overflow: visible !important; + border-left: unset; + } + overflow: visible !important; + } + + .editableView-container-editing-oneLine { + min-width: 15px; + } + + .documentView-node-topmost { + width: unset; + } + + >svg { + display: none; + } + +} + +.treeView-header:hover { + .collectionTreeView-keyHeader { + display: inherit; + } + + >svg { + display: inherit; + } + + .treeView-openRight { + display: inline-block; + height: 17px; + width: 15px; + + // display: inline; + svg { + display: block; + padding: 0px; + margin-left: 3px; + } + } +} + +.treeView-header-above { + border-top: black 1px solid; +} + +.treeView-header-below { + border-bottom: black 1px solid; +} + +.treeView-header-inside { + border: black 1px solid; +}
\ No newline at end of file diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx new file mode 100644 index 000000000..e509eb78d --- /dev/null +++ b/src/client/views/collections/TreeView.tsx @@ -0,0 +1,760 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, observable } from "mobx"; +import { observer } from "mobx-react"; +import { DataSym, Doc, DocListCast, DocListCastOrNull, Field, HeightSym, Opt, WidthSym } from '../../../fields/Doc'; +import { Id } from '../../../fields/FieldSymbols'; +import { List } from '../../../fields/List'; +import { RichTextField } from '../../../fields/RichTextField'; +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, returnOne, returnTrue, returnZero, simulateMouseClick, Utils } from '../../../Utils'; +import { Docs, DocUtils } from '../../documents/Documents'; +import { DocumentType } from "../../documents/DocumentTypes"; +import { CurrentUserUtils } from '../../util/CurrentUserUtils'; +import { DocumentManager } from '../../util/DocumentManager'; +import { DragManager, dropActionType } from "../../util/DragManager"; +import { SelectionManager } from '../../util/SelectionManager'; +import { SnappingManager } from '../../util/SnappingManager'; +import { Transform } from '../../util/Transform'; +import { undoBatch, UndoManager } from '../../util/UndoManager'; +import { EditableView } from "../EditableView"; +import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; +import { DocumentView } from '../nodes/DocumentView'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { RichTextMenu } from '../nodes/formattedText/RichTextMenu'; +import { KeyValueBox } from '../nodes/KeyValueBox'; +import { CollectionTreeView } from './CollectionTreeView'; +import { CollectionView, CollectionViewType } from './CollectionView'; +import "./TreeView.scss"; +import React = require("react"); + +export interface TreeViewProps { + document: Doc; + dataDoc?: Doc; + containingCollection: Doc; + prevSibling?: Doc; + renderDepth: number; + removeDoc: ((doc: Doc | Doc[]) => boolean) | undefined; + moveDocument: DragManager.MoveFunction; + dropAction: dropActionType; + addDocTab: (doc: Doc, where: string, libraryPath?: Doc[]) => boolean; + pinToPres: (document: Doc) => void; + panelWidth: () => number; + panelHeight: () => number; + ChromeHeight: undefined | (() => number); + addDocument: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean; + indentDocument?: () => void; + outdentDocument?: () => void; + ScreenToLocalTransform: () => Transform; + backgroundColor?: (doc: Opt<Doc>, renderDepth: number) => string | undefined; + 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; + ignoreFields?: string[]; + firstLevel: boolean; + whenActiveChanged: (isActive: boolean) => void; +} + +@observer +/** + * Renders a treeView of a collection of documents + * + * 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> { + private _editTitleScript: (() => ScriptField) | undefined; + private _openScript: (() => ScriptField) | undefined; + private _header?: React.RefObject<HTMLDivElement> = React.createRef(); + private _treedropDisposer?: DragManager.DragDropDisposer; + private _dref = React.createRef<HTMLDivElement>(); + private _tref = React.createRef<HTMLDivElement>(); + private _docRef = React.createRef<DocumentView>(); + private _uniqueId = Utils.GenerateGuid(); + private _editMaxWidth: number | string = 0; + + get doc() { return this.props.document; } + get noviceMode() { return BoolCast(Doc.UserDoc().noviceMode, false); } + get displayName() { return "TreeView(" + this.doc.title + ")"; } // this makes mobx trace() statements more descriptive + get treeViewLockExpandedView() { return this.doc.treeViewLockExpandedView; } + get defaultExpandedView() { return StrCast(this.doc.treeViewDefaultExpandedView, this.noviceMode || this.outlineMode ? "layout" : "fields"); } + get treeViewDefaultExpandedView() { return this.treeViewLockExpandedView ? this.defaultExpandedView : (this.childDocs ? this.fieldKey : this.defaultExpandedView); } + @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state + set treeViewOpen(c: boolean) { + if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; + else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; + } + @computed get outlineMode() { return this.props.treeView.doc.treeViewOutlineMode; } + @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; } + @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); } + @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } + @computed get dataDoc() { return this.doc[DataSym]; } + @computed get layoutDoc() { return Doc.Layout(this.doc); } + @computed get fieldKey() { const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; } + childDocList(field: string) { + const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined; + 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 + } + @computed get childDocs() { return this.childDocList(this.fieldKey); } + @computed get childLinks() { return this.childDocList("links"); } + @computed get childAnnos() { return this.childDocList(this.fieldKey + "-annotations"); } + @computed get boundsOfCollectionDocument() { + return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 || !DocListCast(this.props.document[this.fieldKey]).length ? undefined : + Doc.ComputeContentBounds(DocListCast(this.props.document[this.fieldKey])); + } + + @undoBatch openRight = () => this.props.addDocTab(this.doc, "add:right"); + @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { + return this.doc !== target && this.props.removeDoc?.(doc) === true && addDoc(doc); + } + @undoBatch @action remove = (doc: Doc | Doc[], key: string) => { + this.props.treeView.props.select(false); + return (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && Doc.RemoveDocFromList(this.dataDoc, key, doc), true); + } + @undoBatch @action removeDoc = (doc: Doc | Doc[]) => this.remove(doc, Doc.LayoutFieldKey(this.doc)); + + constructor(props: any) { + super(props); + const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); + const openScript = ScriptField.MakeScript(`openOnRight(self)`); + const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`); + this._editTitleScript = !Doc.IsSystem(this.props.document) ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript); + this._openScript = !Doc.IsSystem(this.props.document) ? openScript && (() => openScript) : undefined; + if (Doc.GetT(this.doc, "editTitle", "string", true) === "*") Doc.SetInPlace(this.doc, "editTitle", this._uniqueId, false); + } + + protected createTreeDropTarget = (ele: HTMLDivElement) => { + this._treedropDisposer?.(); + ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this), undefined, this.preTreeDrop.bind(this)), this.doc); + } + + componentWillUnmount() { + document.removeEventListener("pointermove", this.onDragMove, true); + document.removeEventListener("pointermove", this.onDragUp, true); + } + + onDragUp = (e: PointerEvent) => { + document.removeEventListener("pointerup", this.onDragUp, true); + document.removeEventListener("pointermove", this.onDragMove, true); + } + onPointerEnter = (e: React.PointerEvent): void => { + this.props.active(true) && Doc.BrushDoc(this.dataDoc); + if (e.buttons === 1 && SnappingManager.GetIsDragging()) { + this._header!.current!.className = "treeView-header"; + document.removeEventListener("pointermove", this.onDragMove, true); + document.addEventListener("pointermove", this.onDragMove, true); + document.removeEventListener("pointerup", this.onDragUp, true); + document.addEventListener("pointerup", this.onDragUp, true); + } + } + onPointerLeave = (e: React.PointerEvent): void => { + Doc.UnBrushDoc(this.dataDoc); + 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); + } + onDragMove = (e: PointerEvent): void => { + Doc.UnBrushDoc(this.dataDoc); + const pt = [e.clientX, e.clientY]; + 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"; + e.stopPropagation(); + } + + public static makeTextBullet() { + const bullet = Docs.Create.TextDocument("-text-", { title: "-title-", _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 }); + Doc.GetProto(bullet).layout = CollectionView.LayoutString("data"); + Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); + Doc.GetProto(bullet).data = new List<Doc>([]); + Doc.SetInPlace(bullet, "editTitle", "*", false); + FormattedTextBox.SelectOnLoad = bullet[Id]; + return bullet; + } + + makeTextCollection = () => { + Doc.SetInPlace(this.doc, "editTitle", undefined, false); + const bullet = TreeView.makeTextBullet(); + const added = this.props.addDocument(bullet); + bullet.context = this.props.treeView.Document; + return added; + } + + editableView = (key: string, style?: string) => (<EditableView + oneLine={true} + display={"inline-block"} + editing={true} + contents={StrCast(this.doc[key])} + height={12} + sizeToContent={true} + fontStyle={style} + fontSize={12} + GetValue={() => StrCast(this.doc[key])} + SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { + Doc.SetInPlace(this.doc, key, value, false); + if (this.outlineMode && enterKey) { + this.makeTextCollection(); + } else { + Doc.SetInPlace(this.doc, "editTitle", undefined, false); + } + })} + onClick={() => { + SelectionManager.DeselectAll(); + Doc.UserDoc().activeSelection = new List([this.doc]); + return false; + }} + OnEmpty={undoBatch(() => this.props.treeView.doc.treeViewOutlineMode && this.props.removeDoc?.(this.doc))} + OnTab={undoBatch((shift?: boolean) => { + shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); + setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", `${this.props.treeView._uniqueId}`, false), 0); + })} + />) + + preTreeDrop = (e: Event, de: DragManager.DropEvent, targetAction: dropActionType) => { + const dragData = de.complete.docDragData; + dragData && (dragData.dropAction = this.props.treeView.props.Document === dragData.treeViewDoc ? "same" : dragData.dropAction); + } + + @undoBatch + treeDrop = (e: Event, de: DragManager.DropEvent) => { + const pt = [de.x, de.y]; + 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); + if (de.complete.linkDragData) { + const sourceDoc = de.complete.linkDragData.linkSourceDocument; + const destDoc = this.doc; + DocUtils.MakeLink({ doc: sourceDoc }, { doc: destDoc }, "tree link", ""); + e.stopPropagation(); + } + const docDragData = de.complete.docDragData; + if (docDragData) { + e.stopPropagation(); + if (docDragData.draggedDocuments[0] === this.doc) return true; + const parentAddDoc = (doc: Doc | Doc[]) => this.props.addDocument(doc, undefined, before); + let addDoc = parentAddDoc; + if (inside) { + const localAdd = (doc: Doc) => { + const added = Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); + added && (doc.context = this.doc.context); + return added; + }; + addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce( + (flg: boolean, doc) => flg && localAdd(doc), true) || parentAddDoc(doc); + } + const move = (!docDragData.dropAction || docDragData.dropAction === "move" || docDragData.dropAction === "same") && docDragData.moveDocument; + return docDragData.droppedDocuments.reduce((added, d) => (move ? docDragData.moveDocument?.(d, undefined, addDoc) : addDoc(d)) || added, false); + } + return false; + } + + refTransform = (ref: HTMLDivElement) => { + const { scale, translateX, translateY } = Utils.GetScreenTransform(ref); + const outerXf = this.props.outerXf(); + const offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); + return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); + } + docTransform = () => this.refTransform(this._dref.current!); + getTransform = () => this.refTransform(this._tref.current!); + docWidth = () => { + const layoutDoc = this.layoutDoc; + const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); + if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); + return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; + } + docHeight = () => { + const layoutDoc = this.layoutDoc; + const bounds = this.boundsOfCollectionDocument; + return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => { + const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); + if (aspect) return this.docWidth() * aspect; + if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); + return layoutDoc._fitWidth ? (!this.doc._nativeHeight ? NumCast(this.props.containingCollection._height) : + Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, + NumCast(this.props.containingCollection._height)))) : + NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; + })())); + } + + @computed get expandedField() { + const ids: { [key: string]: string } = {}; + const rows: JSX.Element[] = []; + const doc = this.doc; + doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); + + for (const key of Object.keys(ids).slice().sort()) { + if (this.props.ignoreFields?.includes(key) || key === "title" || key === "treeViewOpen") continue; + const contents = doc[key]; + let contentElement: (JSX.Element | null)[] | JSX.Element = []; + + if (contents instanceof Doc || (Cast(contents, listSpec(Doc)) && (Cast(contents, listSpec(Doc))!.length && Cast(contents, listSpec(Doc))![0] instanceof Doc))) { + const remDoc = (doc: Doc | Doc[]) => this.remove(doc, key); + const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { + const added = Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false, true); + added && (doc.context = this.doc.context); + return added; + }; + 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.props.backgroundColor, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, + this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, + [...this.props.renderedIds, doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged); + } else { + contentElement = <EditableView key="editableView" + contents={contents !== undefined ? Field.toString(contents as Field) : "null"} + height={13} + fontSize={12} + GetValue={() => Field.toKeyValueString(doc, key)} + SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} />; + } + rows.push(<div style={{ display: "flex" }} key={key}> + <span style={{ fontWeight: "bold" }}>{key + ":"}</span> + + {contentElement} + </div>); + } + rows.push(<div style={{ display: "flex" }} key={"newKeyValue"}> + <EditableView + key="editableView" + contents={"+key:value"} + height={13} + fontSize={12} + GetValue={returnEmptyString} + SetValue={value => value.indexOf(":") !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(":")), value.substring(value.indexOf(":") + 1, value.length), true)} /> + </div>); + return rows; + } + + rtfWidth = () => Math.min(this.layoutDoc?.[WidthSym](), this.props.panelWidth() - 20); + rtfHeight = () => this.rtfWidth() <= this.layoutDoc?.[WidthSym]() ? Math.min(this.layoutDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; + rtfOutlineHeight = () => Math.max(this.layoutDoc?.[HeightSym](), 20); + + @computed get renderContent() { + TraceMobx(); + const expandKey = this.treeViewExpandedView; + if (["links", "annotations", this.fieldKey].includes(expandKey)) { + const remDoc = (doc: Doc | Doc[]) => this.remove(doc, expandKey); + const localAdd = (doc: Doc, addBefore?: Doc, before?: boolean) => { + const added = Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, false, true); + added && (doc.context = this.doc.context); + return added; + }; + 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 === "links" ? this.childLinks : expandKey === "annotations" ? this.childAnnos : this.childDocs; + const sortKey = `${this.fieldKey}-sortAscending`; + return <ul key={expandKey + "more"} className={this.doc.treeViewHideTitle ? "no-indent" : ""} onClick={(e) => { + !this.outlineMode && (this.doc[sortKey] = (this.doc[sortKey] ? false : (this.doc[sortKey] === false ? undefined : true))); + 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.props.backgroundColor, this.props.ScreenToLocalTransform, + this.props.outerXf, this.props.active, this.props.panelWidth, this.props.ChromeHeight, this.props.renderDepth, this.props.treeViewHideHeaderFields, this.props.treeViewPreventOpen, + [...this.props.renderedIds, this.doc[Id]], this.props.onCheckedClick, this.props.onChildClick, this.props.ignoreFields, false, this.props.whenActiveChanged)} + </ul >; + } else if (this.treeViewExpandedView === "fields") { + return <ul key={this.doc[Id] + this.doc.title}><div ref={this._dref} style={{ display: "inline-block" }} > + {this.expandedField} + </div></ul>; + } else { + const layoutDoc = this.layoutDoc; + const panelHeight = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfHeight : this.docHeight; + const panelWidth = StrCast(Doc.LayoutField(layoutDoc)).includes("FormattedTextBox") ? this.rtfWidth : this.docWidth; + return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.doc[Id]}> + <ContentFittingDocumentView + Document={this.doc} + DataDoc={undefined} + LibraryPath={emptyPath} + renderDepth={this.props.renderDepth + 1} + rootSelected={returnTrue} + treeViewDoc={undefined} + backgroundColor={this.props.backgroundColor} + fitToBox={this.boundsOfCollectionDocument !== undefined} + FreezeDimensions={true} + NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined} + NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined} + PanelWidth={panelWidth} + PanelHeight={panelHeight} + focus={returnFalse} + ScreenToLocalTransform={this.docTransform} + docFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.containingCollection} + ContainingCollectionView={undefined} + addDocument={returnFalse} + moveDocument={this.move} + removeDocument={this.props.removeDoc} + parentActive={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + ContentScaling={returnOne} + /> + </div>; + } + } + + get onCheckedClick() { return this.doc.type === DocumentType.COL ? undefined : this.props.onCheckedClick?.() ?? ScriptCast(this.doc.onCheckedClick); } + + @action + bulletClick = (e: React.MouseEvent) => { + if (this.onCheckedClick) { + this.onCheckedClick?.script.run({ + this: this.doc.isTemplateForField && this.props.dataDoc ? this.props.dataDoc : this.doc, + heading: this.props.containingCollection.title, + checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check", + containingTreeView: this.props.treeView.props.Document, + }, console.log); + } else { + this.treeViewOpen = !this.treeViewOpen; + } + e.stopPropagation(); + } + + @computed get renderBullet() { + TraceMobx(); + const checked = this.onCheckedClick ? (this.doc.treeViewChecked ?? "unchecked") : undefined; + return <div className={`bullet${this.outlineMode ? "-outline" : ""}`} title={this.childDocs?.length ? `click to see ${this.childDocs?.length} items` : "view fields"} + onClick={this.bulletClick} + style={this.outlineMode ? { opacity: NumCast(this.doc.opacity, 1) } : { + color: StrCast(this.doc.color, checked === "unchecked" ? "white" : "inherit"), + opacity: checked === "unchecked" ? undefined : 0.4 + }}> + {this.outlineMode && !(this.doc.text as RichTextField)?.Text ? (null) : + <FontAwesomeIcon icon={this.outlineMode ? [this.childDocs?.length && !this.treeViewOpen ? "fas" : "far", "circle"] : + checked === "check" ? "check" : + (checked === "x" ? "times" : checked === "unchecked" ? "square" : + !this.treeViewOpen ? (this.childDocs?.length ? "caret-square-right" : "caret-right") : + (this.childDocs?.length ? "caret-square-down" : "caret-down"))} />} + </div>; + } + @computed get showTitleEditorControl() { return ["*", this._uniqueId, this.props.treeView._uniqueId].includes(Doc.GetT(this.doc, "editTitle", "string", true) || ""); } + @computed get headerElements() { + return (Doc.IsSystem(this.doc) && Doc.UserDoc().noviceMode) || this.props.treeViewHideHeaderFields() ? (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.treeViewOpen) { + this.doc.treeViewExpandedView = this.treeViewLockExpandedView ? this.doc.treeViewExpandedView : + this.treeViewExpandedView === this.fieldKey ? (Doc.UserDoc().noviceMode || this.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.outlineMode ? "layout" : "fields"); + } + this.treeViewOpen = true; + })}> + {this.treeViewExpandedView} + </span> + </>; + } + + showContextMenu = (e: React.MouseEvent) => simulateMouseClick(this._docRef.current?.ContentDiv, e.clientX, e.clientY + 30, e.screenX, e.screenY + 30); + contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; + truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, 0); + onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); + onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + /** + * Renders the EditableView title element for placement into the tree. + */ + @computed + get renderTitle() { + TraceMobx(); + const view = this.showTitleEditorControl ? this.editableView("title") : + <DocumentView + ref={this._docRef} + Document={this.doc} + DataDoc={undefined} + treeViewDoc={this.props.treeView.props.Document} + LibraryPath={emptyPath} + addDocument={undefined} + addDocTab={this.props.addDocTab} + rootSelected={returnTrue} + pinToPres={emptyFunction} + onClick={this.onChildClick} + onDoubleClick={this.onChildDoubleClick} + dropAction={this.props.dropAction} + moveDocument={this.move} + removeDocument={this.props.removeDoc} + ScreenToLocalTransform={this.getTransform} + ContentScaling={returnOne} + PanelWidth={this.truncateTitleWidth} + PanelHeight={returnZero} + contextMenuItems={this.contextMenuItems} + opacity={this.outlineMode ? undefined : returnOne} + renderDepth={1} + focus={returnTrue} + parentActive={returnTrue} + whenActiveChanged={this.props.whenActiveChanged} + bringToFront={emptyFunction} + dontRegisterView={BoolCast(this.props.treeView.props.Document.dontRegisterChildViews)} + docFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionView={undefined} + ContainingCollectionDoc={this.props.containingCollection} + />; + return <> + <div className={`docContainer${Doc.IsSystem(this.props.document) ? "-system" : ""}`} ref={this._tref} title="click to edit title. Double Click or Drag to Open" + style={{ + 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 + }} > + {view} + </div > + {this.headerElements} + </>; + } + + refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); + + render() { + TraceMobx(); + if (this.props.renderedIds.indexOf(this.doc[Id]) !== -1) return null; + const sorting = this.doc[`${this.fieldKey}-sortAscending`]; + if (this.showTitleEditorControl) { // 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.current?.ContentDiv?.getBoundingClientRect(); + this._editMaxWidth = Math.max(100, par_rect.right - (my_recct?.left || 0)); + } + } + else this._editMaxWidth = ""; + const selected = SelectionManager.IsSelected(DocumentManager.Instance.getFirstDocumentView(this.doc)); + return this.doc.treeViewHideHeader || this.outlineMode ? + !StrCast(Doc.LayoutField(this.doc)).includes("CollectionView") ? + this.renderContent + : <div className={`treeView-container${selected ? "-active" : ""}`} ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()} + onKeyDown={e => { + e.stopPropagation(); + switch (e.key) { + case "Backspace": return this.doc.text && !(this.doc.text as RichTextField)?.Text && UndoManager.RunInBatch(() => this.props.removeDoc?.(this.doc), "delete"); + case "Enter": return UndoManager.RunInBatch(() => this.makeTextCollection(), "bullet"); + case "Tab": setTimeout(() => RichTextMenu.Instance.TextView?.EditorView?.focus(), 150); + return UndoManager.RunInBatch(() => e.shiftKey ? this.props.outdentDocument?.() : this.props.indentDocument?.(), "tab"); + } + }} > + <div className={`treeView-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ alignItems: this.outlineMode ? "center" : undefined, maxWidth: this._editMaxWidth }} + onClick={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} + onPointerDown={e => { if (this.props.active(true)) { e.stopPropagation(); e.preventDefault(); } }} + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + {this.renderBullet} + <div ref={this._dref} style={{ display: "inline-block", height: this.rtfOutlineHeight() }} key={this.doc[Id]}> + <ContentFittingDocumentView + Document={this.doc} + DataDoc={undefined} + LayoutTemplateString={FormattedTextBox.LayoutString("text")} + LibraryPath={emptyPath} + renderDepth={this.props.renderDepth + 1} + rootSelected={returnTrue} + treeViewDoc={undefined} + backgroundColor={this.props.backgroundColor} + fitToBox={this.boundsOfCollectionDocument !== undefined} + PanelWidth={this.rtfWidth} + PanelHeight={this.rtfOutlineHeight} + focus={this.refocus} + ScreenToLocalTransform={this.docTransform} + docFilters={returnEmptyFilter} + searchFilterDocs={returnEmptyDoclist} + ContainingCollectionDoc={this.props.containingCollection} + ContainingCollectionView={undefined} + addDocument={this.props.addDocument} + moveDocument={this.move} + removeDocument={this.props.removeDoc} + parentActive={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + addDocTab={this.props.addDocTab} + pinToPres={this.props.pinToPres} + bringToFront={returnFalse} + ContentScaling={returnOne} + /> + </div> + </div> + + <div className={`treeView-border${this.outlineMode ? "outline" : ""}`} style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}> + {!this.treeViewOpen ? (null) : this.renderContent} + </div> + </div> : + <div className="treeView-container" ref={this.createTreeDropTarget} onPointerDown={e => this.props.active(true) && SelectionManager.DeselectAll()}> + <li className="collection-child"> + <div className={`treeView-header` + (this._editMaxWidth ? "-editing" : "")} ref={this._header} style={{ maxWidth: this._editMaxWidth }} onClick={e => { + if (this.props.active(true)) { + e.stopPropagation(); + e.preventDefault(); + SelectionManager.DeselectAll(); + } + }} + onPointerDown={e => { + if (this.props.active(true)) { + e.stopPropagation(); + e.preventDefault(); + } + }} + onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> + {this.renderBullet} + {this.renderTitle} + </div> + <div className={`treeView-border${this.outlineMode ? "outline" : ""}`} style={{ borderColor: sorting === undefined ? undefined : sorting ? "crimson" : "blue" }}> + {!this.treeViewOpen ? (null) : this.renderContent} + </div> + </li> + </div>; + } + + + public static GetChildElements( + childDocs: Doc[], + treeView: CollectionTreeView, + containingCollection: Doc, + dataDoc: Doc | undefined, + key: string, + parentCollectionDoc: Doc | undefined, + parentPrevSibling: Doc | undefined, + add: (doc: Doc | Doc[], relativeTo?: Doc, before?: boolean) => boolean, + remove: ((doc: Doc | Doc[]) => boolean), + move: DragManager.MoveFunction, + dropAction: dropActionType, + addDocTab: (doc: Doc, where: string) => boolean, + pinToPres: (document: Doc) => void, + backgroundColor: undefined | ((document: Opt<Doc>, renderDepth: number) => string | undefined), + screenToLocalXf: () => Transform, + outerXf: () => { translateX: number, translateY: number }, + active: (outsideReaction?: boolean) => boolean, + panelWidth: () => number, + ChromeHeight: undefined | (() => number), + renderDepth: number, + treeViewHideHeaderFields: () => boolean, + treeViewPreventOpen: boolean, + renderedIds: string[], + onCheckedClick: undefined | (() => ScriptField), + onChildClick: undefined | (() => ScriptField), + ignoreFields: string[] | undefined, + firstLevel: boolean, + whenActiveChanged: (isActive: boolean) => void + ) { + const viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField); + if (viewSpecScript) { + childDocs = childDocs.filter(d => viewSpecScript.script.run({ doc: d }, console.log).result); + } + + const docs = childDocs.slice(); + const ascending = containingCollection?.[key + "-sortAscending"]; + if (ascending !== undefined) { + const sortAlphaNum = (a: string, b: string): 0 | 1 | -1 => { + const reN = /[0-9]*$/; + const aA = a.replace(reN, ""); // get rid of trailing numbers + const bA = b.replace(reN, ""); + if (aA === bA) { // if header string matches, then compare numbers numerically + const aN = parseInt(a.match(reN)![0], 10); + const bN = parseInt(b.match(reN)![0], 10); + return aN === bN ? 0 : aN > bN ? 1 : -1; + } else { + return aA > bA ? 1 : -1; + } + }; + docs.sort(function (a, b): 0 | 1 | -1 { + const first = (ascending ? b : a).title; + const second = (ascending ? a : b).title; + if (typeof first === 'number' && typeof second === 'number') return (first - second) > 0 ? 1 : -1; + if (typeof first === 'string' && typeof second === 'string') return sortAlphaNum(first, second); + return ascending ? 1 : -1; + }); + } + + const rowWidth = () => panelWidth() - 20; + return docs.map((child, i) => { + const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, child); + if (!pair.layout || pair.data instanceof Promise) { + return (null); + } + + const indent = i === 0 ? undefined : () => { + if (remove && StrCast(docs[i - 1].layout).indexOf('fieldKey') !== -1) { + const fieldKeysub = StrCast(docs[i - 1].layout).split('fieldKey')[1]; + const fieldKey = fieldKeysub.split("\'")[1]; + if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) { + remove(child); + FormattedTextBox.SelectOnLoad = child[Id]; + Doc.AddDocToList(docs[i - 1], fieldKey, child); + docs[i - 1].treeViewOpen = true; + child.context = treeView.Document; + } + } + }; + const outdent = !parentCollectionDoc ? undefined : () => { + if (remove && StrCast(parentCollectionDoc.layout).indexOf('fieldKey') !== -1) { + const fieldKeysub = StrCast(parentCollectionDoc.layout).split('fieldKey')[1]; + const fieldKey = fieldKeysub.split("\'")[1]; + remove(child); + FormattedTextBox.SelectOnLoad = child[Id]; + Doc.AddDocToList(parentCollectionDoc, fieldKey, child, parentPrevSibling, false); + parentCollectionDoc.treeViewOpen = true; + child.context = treeView.Document; + } + }; + 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 = () => { + const aspect = NumCast(childLayout._nativeWidth, 0) / NumCast(childLayout._nativeHeight, 0); + return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); + }; + return !(child instanceof Doc) ? (null) : <TreeView + document={pair.layout} + dataDoc={pair.data} + containingCollection={containingCollection} + prevSibling={docs[i]} + treeView={treeView} + key={child[Id]} + indentDocument={indent} + outdentDocument={!parentCollectionDoc ? undefined : outdent} + onCheckedClick={onCheckedClick} + onChildClick={onChildClick} + renderDepth={renderDepth} + removeDoc={StrCast(containingCollection.freezeChildren).includes("remove") ? undefined : remove} + addDocument={addDocument} + backgroundColor={backgroundColor} + panelWidth={rowWidth} + panelHeight={rowHeight} + ChromeHeight={ChromeHeight} + moveDocument={move} + dropAction={dropAction} + addDocTab={addDocTab} + pinToPres={pinToPres} + ScreenToLocalTransform={screenToLocalXf} + outerXf={outerXf} + parentKey={key} + active={active} + treeViewHideHeaderFields={treeViewHideHeaderFields} + treeViewPreventOpen={treeViewPreventOpen} + renderedIds={renderedIds} + ignoreFields={ignoreFields} + firstLevel={firstLevel} + whenActiveChanged={whenActiveChanged} />; + }); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 2b07c4efb..75cbc20ca 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -52,12 +52,16 @@ .pathOrder-frame { position: absolute; - width: 40; + width: 100%; + height: 100%; + min-height: 15px; text-align: center; - font-size: 30; background-color: #69a6db; + border-radius: 5px; + box-shadow: 0px 4px 4px rgba(0, 0, 0, 0.25); font-family: Roboto; - font-weight: 300; + font-weight: 500; + color: white; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7b0aaef3c..8af048d67 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -209,7 +209,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const layoutDoc = Doc.Layout(d); if (this.Document._currentFrame !== undefined) { const vals = CollectionFreeFormDocumentView.getValues(d, NumCast(d.activeFrame, 1000)); - CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, vals.scroll, vals.opacity); + CollectionFreeFormDocumentView.setValues(this.Document._currentFrame, d, x + vals.x - dropPos[0], y + vals.y - dropPos[1], vals.h, vals.w, this.Document.editScrollProgressivize ? vals.scroll : undefined, vals.opacity); } else { d.x = x + NumCast(d.x) - dropPos[0]; d.y = y + NumCast(d.y) - dropPos[1]; @@ -906,7 +906,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) { // glr: freeform transform speed can be set by adjusting presTransition field - needs a way of knowing when presentation is not active... - if (!doc.z) this.setPan(newPanX, newPanY, doc.presTransition || doc.presTransition === 0 ? `transform ${doc.presTransition}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow + if (!doc.z) this.setPan(newPanX, newPanY, doc.focusSpeed || doc.focusSpeed === 0 ? `transform ${doc.focusSpeed}ms` : "transform 500ms", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow } Doc.BrushDoc(this.props.Document); this.props.focus(this.props.Document); @@ -1595,21 +1595,17 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF <div key="presorder">{PresBox.Instance.order}</div> <svg key="svg" className={presPaths}> <defs> - <marker id="arrow" markerWidth="3" overflow="visible" markerHeight="3" refX="5" refY="5" orient="auto" markerUnits="strokeWidth"> - <path d="M0,0 L0,6 L9,3 z" fill="#69a6db" /> - </marker> - <marker id="square" markerWidth="3" markerHeight="3" overflow="visible" - refX="5" refY="5" orient="auto" markerUnits="strokeWidth"> - <path d="M 5,1 L 9,5 5,9 1,5 z" fill="#69a6db" /> + <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" + orient="auto" overflow="visible"> + <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" /> </marker> - <marker id="markerSquare" markerWidth="7" markerHeight="7" refX="4" refY="4" + <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible"> - <rect x="1" y="1" width="5" height="5" fill="#69a6db" /> + <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" /> </marker> - - <marker id="markerArrow" markerWidth="5" markerHeight="5" refX="2" refY="7" + <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible"> - <path d="M2,2 L2,13 L8,7 L2,2" fill="#69a6db" /> + <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" /> </marker> </defs> {PresBox.Instance.paths} @@ -1637,4 +1633,4 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF {this.zoomProgressivize} </div>; } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index 46298ec6f..fd4fa0c7e 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -24,6 +24,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { } render() { + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 19, transform: 'translate(-2px, -2px)' }} />; const buttons = [ <Tooltip key="group" title={<><div className="dash-tooltip">Create a Collection</div></>} placement="bottom"> <button @@ -53,12 +54,11 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { <FontAwesomeIcon icon="font" size="lg" /> </button> </Tooltip>, - <Tooltip key="pinWithView" title={<><div className="dash-tooltip">Pin to presentation with selected view</div></>} placement="bottom"> + <Tooltip key="pinWithView" title={<><div className="dash-tooltip">Pin with selected view</div></>} placement="bottom"> <button className="antimodeMenu-button" onPointerDown={this.pinWithView}> - <FontAwesomeIcon icon="map-pin" size="lg" /> - <div style={{ position: 'relative', fontSize: 25, fontWeight: 700, transform: 'translate(-4px, -22px)', color: 'rgba(250,250,250,0.55)' }}>V</div> + <>{presPinWithViewIcon}</> </button> </Tooltip>, ]; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 4f2399962..d04ef4ff9 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -25,6 +25,7 @@ import "./MarqueeView.scss"; import React = require("react"); import { Id } from "../../../../fields/FieldSymbols"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; +import { PresMovement } from "../../nodes/PresBox"; interface MarqueeViewProps { getContainerTransform: () => Transform; @@ -394,7 +395,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (curPres) { const pinDoc = Doc.MakeAlias(doc); pinDoc.presentationTargetDoc = doc; - pinDoc.presZoomButton = true; + pinDoc.presMovement = PresMovement.Zoom; pinDoc.context = curPres; Doc.AddDocToList(curPres, "data", pinDoc); if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 57d461bf8..d6f8ce19c 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -65,7 +65,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF w: Cast(doc["w-indexed"], listSpec("number"), [NumCast(doc._width)]).reduce((p, w, i) => (i <= timecode && w !== undefined) || p === undefined ? w : p, undefined as any as number), x: Cast(doc["x-indexed"], listSpec("number"), [NumCast(doc.x)]).reduce((p, x, i) => (i <= timecode && x !== undefined) || p === undefined ? x : p, undefined as any as number), y: Cast(doc["y-indexed"], listSpec("number"), [NumCast(doc.y)]).reduce((p, y, i) => (i <= timecode && y !== undefined) || p === undefined ? y : p, undefined as any as number), - scroll: Cast(doc["scroll-indexed"], listSpec("number"), [NumCast(doc._scrollTop, 0)]).reduce((p, s, i) => (i <= timecode && s !== undefined) || p === undefined ? s : p, undefined as any as number), + scroll: Cast(doc["scroll-indexed"], listSpec("number"), [NumCast(doc._scrollY, 0)]).reduce((p, s, i) => (i <= timecode && s !== undefined) || p === undefined ? s : p, undefined as any as number), opacity: Cast(doc["opacity-indexed"], listSpec("number"), [NumCast(doc.opacity, 1)]).reduce((p, o, i) => i <= timecode || p === undefined ? o : p, undefined as any as number), }); } @@ -83,7 +83,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF hindexed[timecode] = h as any as number; windexed[timecode] = w as any as number; oindexed[timecode] = opacity as any as number; - scrollIndexed[timecode] = scroll as any as number; + if (scroll) scrollIndexed[timecode] = scroll as any as number; d["x-indexed"] = new List<number>(xindexed); d["y-indexed"] = new List<number>(yindexed); d["h-indexed"] = new List<number>(hindexed); @@ -110,10 +110,10 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF public static setupScroll(doc: Doc, timecode: number) { const scrollList = new List<number>(); - scrollList[timecode] = NumCast(doc._scrollTop); + scrollList[timecode] = NumCast(doc._scrollY); doc["scroll-indexed"] = scrollList; doc.activeFrame = ComputedField.MakeFunction("self._currentFrame"); - doc._scrollTop = ComputedField.MakeInterpolated("scroll", "activeFrame"); + doc._scrollY = ComputedField.MakeInterpolated("scroll", "activeFrame"); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7ff8b635c..68328ea4b 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1045,10 +1045,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined, }}> <EditableView ref={this._titleRef} - contents={this.ShowTitle.split(";").map(field => field + ":" + (this.props.DataDoc || this.props.Document)[field]?.toString()).join(" ")} + contents={this.ShowTitle.split(";").map(field => field + ":" + (this.dataDoc || this.props.Document)[field]?.toString()).join(" ")} display={"block"} fontSize={10} GetValue={() => ""} - SetValue={undoBatch((value: string) => (Doc.GetProto(this.props.DataDoc || this.props.Document)[this.ShowTitle] = value) ? true : true)} + SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc || this.props.Document)[this.ShowTitle] = value) ? true : true)} /> </div>); return !this.ShowTitle && !showCaption ? diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 7a010532f..748af89ef 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -47,7 +47,7 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc const keys = new Set<string>(noviceFields); this.allDocs.forEach(doc => SearchBox.documentKeys(doc).filter(key => keys.add(key))); - return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("ACL")) || noviceFields.includes(key)).sort(); + return Array.from(keys.keys()).filter(key => key[0] === "#" || key.indexOf("lastModified") !== -1 || (key[0] === key[0].toUpperCase() && !key.startsWith("_") && !key.startsWith("acl")) || noviceFields.includes(key)).sort(); } /** * Responds to clicking the check box in the flyout menu @@ -193,6 +193,16 @@ export class FilterBox extends ViewBoxBaseComponent<FieldViewProps, FilterBoxDoc } } +Scripting.addGlobal(function determineCheckedState(layoutDoc: Doc, facetHeader: string, facetValue: string) { + const docFilters = Cast(layoutDoc._docFilters, listSpec("string"), []); + for (let i = 0; i < docFilters.length; i += 3) { + const [header, value, state] = docFilters.slice(i, i + 3); + if (header === facetHeader && value === facetValue) { + return state; + } + } + return undefined; +}); Scripting.addGlobal(function readFacetData(layoutDoc: Doc, facetHeader: string) { const allCollectionDocs = DocListCast(CollectionDockingView.Instance?.props.Document.allDocuments); const set = new Set<string>(); diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index 08160a2f4..7bc6c1dfd 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -144,6 +144,21 @@ $light-background: #ececec; grid-template-columns: repeat(auto-fit, 70px); } + .ribbon-colorBox { + cursor: pointer; + border: solid 1px black; + display: flex; + margin-left: 5px; + margin-top: 5px; + margin-bottom: 5px; + justify-content: center; + align-items: center; + height: 15px; + width: 15px; + padding: 0px; + transform: translate(2px, 3px); + } + .ribbon-property { font-size: 11; font-weight: 200; @@ -415,10 +430,38 @@ $light-background: #ececec; .ribbon-button { cursor: pointer; font-size: 10.5; + font-weight: 300; + height: 20; + background-color: #979797; + color: white; + display: flex; + margin-top: 5px; + margin-bottom: 5px; + border-radius: 5px; + margin-right: 5px; + width: max-content; + justify-content: center; + align-items: center; + padding-right: 10px; + padding-left: 10px; + } + + .ribbon-button:hover { + transform: scale(1.03); + transition: all 0.4s; + font-weight: 400; + opacity: 1; + color: white; + background-color: black; + } + + .ribbon-toggle { + cursor: pointer; + font-size: 10.5; font-weight: 200; height: 20; background-color: $light-background; - border: solid 1px black; + border: solid 1px rgba(0, 0, 0, 0.5); display: flex; margin-top: 5px; margin-bottom: 5px; @@ -431,12 +474,14 @@ $light-background: #ececec; padding-left: 10px; } - .ribbon-button.active { + .ribbon-toggle.active { background-color: #aedef8; } - .ribbon-button:hover { - background-color: lightgrey; + .ribbon-toggle:hover { + transform: scale(1.03); + transition: all 0.4s; + border: solid 1px rgba(0, 0, 0, 0.75); } svg.svg-inline--fa.fa-thumbtack.fa-w-12.toolbar-thumbtack { @@ -801,82 +846,6 @@ $light-background: #ececec; } - .presPanelOverlay { - background-color: #323232; - color: white; - border-radius: 5px; - grid-template-rows: 100%; - height: 25; - width: max-content; - min-width: max-content; - justify-content: space-evenly; - align-items: center; - display: flex; - position: absolute; - right: 10px; - transition: all 0.2s; - - .presPanel-button-text { - display: flex; - height: 20; - width: max-content; - font-family: Roboto; - font-weight: 400; - margin-left: 3px; - margin-right: 3px; - padding-right: 3px; - padding-left: 3px; - letter-spacing: normal; - border-radius: 5px; - align-items: center; - justify-content: center; - transition: all 0.3s; - } - - .presPanel-divider { - width: 0.5px; - height: 80%; - border-right: solid 1px #5a5a5a; - } - - .presPanel-button-frame { - justify-self: center; - align-self: center; - align-items: center; - display: grid; - grid-template-columns: auto auto auto; - justify-content: space-around; - font-size: 11; - margin-left: 7; - width: 30; - height: 85%; - background-color: rgba(91, 157, 221, 0.4); - border-radius: 5px; - } - - .presPanel-button { - cursor: pointer; - display: flex; - height: 20; - min-width: 20; - margin-left: 3px; - margin-right: 3px; - border-radius: 100%; - align-items: center; - justify-content: center; - transition: all 0.3s; - } - - .presPanel-button:hover { - background-color: #5a5a5a; - } - - .presPanel-button-text:hover { - background-color: #5a5a5a; - } - } - - .collectionViewBaseChrome-viewPicker { min-width: 50; @@ -952,80 +921,165 @@ $light-background: #ececec; } } -.miniPres:hover { - opacity: 1; -} - .miniPres { cursor: grab; position: absolute; - overflow: hidden; right: 10; top: 10; opacity: 0.1; transition: all 0.4s; - /* border: solid 1px; */ color: white; - /* box-shadow: black 0.4vw 0.4vw 0.8vw; */ +} - .miniPresOverlay { - display: grid; - grid-template-columns: auto auto auto auto auto auto auto auto auto auto; - grid-template-rows: 100%; - height: 100%; - justify-items: center; - align-items: center; +.miniPres:hover { + opacity: 1; +} - .miniPres-button-text { - cursor: pointer; - display: flex; - height: 20; - font-weight: 400; - min-width: 100%; - border-radius: 5px; - align-items: center; - justify-content: center; - transition: all 0.3s; - } +.presPanelOverlay { + background-color: #323232; + color: white; + border-radius: 5px; + grid-template-rows: 100%; + height: 25; + width: max-content; + min-width: max-content; + justify-content: space-evenly; + align-items: center; + display: flex; + position: absolute; + right: 10px; + transition: all 0.2s; - .miniPres-button-frame { - justify-self: center; - align-self: center; - align-items: center; - display: grid; - grid-template-columns: auto auto auto; - justify-content: space-around; - font-size: 11; - margin-left: 7; - width: 30; - height: 85%; - background-color: rgba(91, 157, 221, 0.4); - border-radius: 5px; - } + .presPanel-button-text { + display: flex; + height: 20; + width: max-content; + font-family: Roboto; + font-weight: 400; + margin-left: 3px; + margin-right: 3px; + padding-right: 3px; + padding-left: 3px; + letter-spacing: normal; + border-radius: 5px; + align-items: center; + justify-content: center; + transition: all 0.3s; + } - .miniPres-divider { - width: 0.5px; - height: 80%; - border-right: solid 2px #5a5a5a; - } + .presPanel-divider { + width: 0.5px; + height: 80%; + border-right: solid 1px #5a5a5a; + } - .miniPres-button { - cursor: pointer; - display: flex; - height: 20; - min-width: 20; - border-radius: 100%; - align-items: center; - justify-content: center; - transition: all 0.3s; - } + .presPanel-button-frame { + justify-self: center; + align-self: center; + align-items: center; + display: grid; + grid-template-columns: auto auto auto; + justify-content: space-around; + font-size: 11; + margin-left: 7; + width: 30; + height: 85%; + background-color: rgba(91, 157, 221, 0.4); + border-radius: 5px; + } - .miniPres-button:hover { - background-color: #5a5a5a; - } + .presPanel-button { + cursor: pointer; + display: flex; + height: 20; + min-width: 20; + margin-left: 3px; + margin-right: 3px; + border-radius: 100%; + align-items: center; + justify-content: center; + transition: all 0.3s; + } - .miniPres-button-text:hover { - background-color: #5a5a5a; - } + .presPanel-button:hover { + background-color: #5a5a5a; } -}
\ No newline at end of file + + .presPanel-button-text:hover { + background-color: #5a5a5a; + } +} + +// .miniPres { +// cursor: grab; +// position: absolute; +// overflow: hidden; +// right: 10; +// top: 10; +// opacity: 0.1; +// transition: all 0.4s; +// /* border: solid 1px; */ +// color: white; +// /* box-shadow: black 0.4vw 0.4vw 0.8vw; */ + +// .miniPresOverlay { +// display: grid; +// grid-template-columns: auto auto auto auto auto auto auto auto auto auto; +// grid-template-rows: 100%; +// height: 100%; +// justify-items: center; +// align-items: center; + +// .miniPres-button-text { +// cursor: pointer; +// display: flex; +// height: 20; +// font-weight: 400; +// min-width: 100%; +// border-radius: 5px; +// align-items: center; +// justify-content: center; +// transition: all 0.3s; +// } + +// .miniPres-button-frame { +// justify-self: center; +// align-self: center; +// align-items: center; +// display: grid; +// grid-template-columns: auto auto auto; +// justify-content: space-around; +// font-size: 11; +// margin-left: 7; +// width: 30; +// height: 85%; +// background-color: rgba(91, 157, 221, 0.4); +// border-radius: 5px; +// } + +// .miniPres-divider { +// width: 0.5px; +// height: 80%; +// border-right: solid 2px #5a5a5a; +// } + +// .miniPres-button { +// cursor: pointer; +// display: flex; +// height: 20; +// min-width: 20; +// border-radius: 100%; +// align-items: center; +// justify-content: center; +// transition: all 0.3s; +// } + +// .miniPres-button:hover { +// background-color: #5a5a5a; +// } + +// .miniPres-button-text:hover { +// background-color: #5a5a5a; +// } +// } +// }
\ No newline at end of file diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index b0563c373..c5a64af3e 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -4,7 +4,7 @@ import { Tooltip } from "@material-ui/core"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { ColorState, SketchPicker } from "react-color"; -import { Doc, DocCastAsync, DocListCast } from "../../../fields/Doc"; +import { Doc, DocCastAsync, DocListCast, DocListCastAsync } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { List } from "../../../fields/List"; @@ -28,6 +28,23 @@ import { AudioBox } from "./AudioBox"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; +import { VideoBox } from "./VideoBox"; + +export enum PresMovement { + Zoom = "zoom", + Pan = "pan", + Jump = "jump", + None = "none", +} + +export enum PresEffect { + Fade = "fade", + Flip = "flip", + Rotate = "rotate", + Bounce = "bounce", + Roll = "roll", + None = "none", +} type PresBoxSchema = makeInterface<[typeof documentSchema]>; const PresBoxDocument = makeInterface(documentSchema); @@ -50,11 +67,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @observable private transitionTools: boolean = false; @observable private newDocumentTools: boolean = false; @observable private progressivizeTools: boolean = false; - @observable private moreInfoTools: boolean = false; - @observable private playTools: boolean = false; @observable private presentTools: boolean = false; @observable private pathBoolean: boolean = false; - @observable private expandBoolean: boolean = false; @observable private openMovementDropdown: boolean = false; @observable private openEffectDropdown: boolean = false; @computed get childDocs() { return DocListCast(this.dataDoc[this.fieldKey]); } @@ -79,11 +93,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox } @computed get selectedDocumentView() { - if (SelectionManager.SelectedDocuments().length) { - return SelectionManager.SelectedDocuments()[0]; - } else if (PresBox.Instance._selectedArray.length) { - return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); - } else { return undefined; } + if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; + if (PresBox.Instance && PresBox.Instance._selectedArray) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); + return undefined; } @computed get isPres(): boolean { document.removeEventListener("keydown", this.keyEvents, true); @@ -97,18 +109,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> componentWillUnmount() { document.removeEventListener("keydown", this.keyEvents, true); - this.turnOffEdit(); + // Turn of progressivize editors + this.turnOffEdit(true); } - componentDidMount() { + componentDidMount = async () => { this.rootDoc.presBox = this.rootDoc; this.rootDoc._forceRenderEngine = "timeline"; this.rootDoc._replacedChrome = "replaced"; this.layoutDoc.presStatus = "edit"; this.layoutDoc._gridGap = 5; - if (!DocListCast((Doc.UserDoc().myPresentations as Doc).data).includes(this.rootDoc)) { - Doc.AddDocToList(Doc.UserDoc().myPresentations as Doc, "data", this.rootDoc); - } + this.turnOffEdit(true); + DocListCastAsync((Doc.UserDoc().myPresentations as Doc).data).then(async pres => { + await Promise.all(pres!); + if (!DocListCast((Doc.UserDoc().myPresentations as Doc).data).includes(this.rootDoc)) Doc.AddDocToList(Doc.UserDoc().myPresentations as Doc, "data", this.rootDoc); + }); } updateCurrentPresentation = () => { @@ -131,32 +146,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const lastFrame = Cast(targetDoc.lastFrame, "number", null); const curFrame = NumCast(targetDoc._currentFrame); let internalFrames: boolean = false; - if (targetDoc.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; + if (activeItem.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; // Case 1: There are still other frames and should go through all frames before going to next slide if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) { targetDoc._viewTransition = "all 1s"; setTimeout(() => targetDoc._viewTransition = undefined, 1010); - targetDoc._currentFrame = curFrame + 1; - if (targetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(targetDoc, currentFrame); - if (targetDoc.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); + // targetDoc._currentFrame = curFrame + 1; + this.nextKeyframe(targetDoc, activeItem); + // if (targetDoc.scrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(targetDoc, currentFrame); + if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); else targetDoc.editing = true; - if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(targetDoc); - // Case 2: Audio or video therefore wait to play the audio or video before moving on - } else if ((targetDoc.type === DocumentType.AUDIO) && !this._moveOnFromAudio && this.layoutDoc.presStatus !== 'auto') { - AudioBox.Instance.playFrom(0); - this._moveOnFromAudio = true; - // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide + // if (activeItem.zoomProgressivize) this.zoomProgressivizeNext(targetDoc); + // Case 2: 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played + } else if ((targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && !activeItem.playAuto && activeItem.playNow && this.layoutDoc.presStatus !== 'auto') { + if (targetDoc.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeItem.presStartTime)); + // if (targetDoc.type === DocumentType.VID) { VideoBox.Instance.Play() }; + activeItem.playNow = false; + // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide } else if (this.childDocs[this.itemIndex + 1] !== undefined) { + if (activeNext.presPinView) setTimeout(() => this.selectPres(), 0); + else this.selectPres(); const nextSelected = this.itemIndex + 1; - if (targetDoc.type === DocumentType.AUDIO) AudioBox.Instance.pause(); - this.gotoDocument(nextSelected, this.itemIndex); + if (targetDoc.type === DocumentType.AUDIO) { if (AudioBox.Instance._ele) AudioBox.Instance.pause(); } + // if (targetDoc.type === DocumentType.VID) { if (AudioBox.Instance._ele) VideoBox.Instance.Pause(); } const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); - if (activeNext && targetNext.type === DocumentType.AUDIO && activeNext.playAuto) { - AudioBox.Instance.playFrom(0); - this._moveOnFromAudio = true; - } else this._moveOnFromAudio = false; + // If next slide is audio / video 'Play automatically' then the next slide should be played + if (activeNext && (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) && activeNext.playAuto) { + console.log('play next automatically'); + if (targetNext.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeNext.presStartTime)); + // if (targetNext.type === DocumentType.VID) { VideoBox.Instance.Play() }; + } else if (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) { activeNext.playNow = true; console.log('play next after it is navigated to'); } + this.gotoDocument(nextSelected, this.itemIndex); } else if (this.childDocs[this.itemIndex + 1] === undefined && this.layoutDoc.presLoop) { - const nextSelected = 0; this.gotoDocument(0, this.itemIndex); } } @@ -176,6 +197,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); const lastFrame = Cast(targetDoc.lastFrame, "number", null); const curFrame = NumCast(targetDoc._currentFrame); + if (prevItem.presPinView) setTimeout(() => this.selectPres(), 0); + else this.selectPres(); if (lastFrame !== undefined && curFrame >= 1) { this.prevKeyframe(targetDoc, activeItem); } else if (activeItem) { @@ -193,9 +216,27 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> Doc.UnBrushAllDocs(); if (index >= 0 && index < this.childDocs.length) { this.rootDoc._itemIndex = index; + const activeItem: Doc = this.activeItem; const presTargetDoc = Cast(this.childDocs[this.itemIndex].presentationTargetDoc, Doc, null); + if (activeItem.presPinView) { + const bestTarget = DocumentManager.Instance.getFirstDocumentView(presTargetDoc)?.props.Document; + bestTarget && runInAction(() => { + if (activeItem.presMovement === PresMovement.Jump) { + bestTarget._viewTransition = '0s'; + } else { + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 1s'; + setTimeout(() => bestTarget._viewTransition = undefined, activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 1010); + } + }); + } else { + presTargetDoc && runInAction(() => { + if (activeItem.presMovement === PresMovement.Jump) presTargetDoc.focusSpeed = 0; + else presTargetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; + }); + setTimeout(() => presTargetDoc.focusSpeed = 500, this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); + } if (presTargetDoc?.lastFrame !== undefined) { - presTargetDoc.currentFrame = 0; + presTargetDoc._currentFrame = 0; } this._selectedArray = [this.childDocs[index]]; //Update selected array //Handles movement to element @@ -219,7 +260,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const collectionDocView = presCollection ? await DocumentManager.Instance.getDocumentView(presCollection) : undefined; this.turnOffEdit(); - if (this.itemIndex >= 0) { if (srcContext && targetDoc) { this.layoutDoc.presCollection = srcContext; @@ -244,9 +284,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.zoomProgressivizeNext(targetDoc); } else if (docToJump === curDoc) { //checking if curDoc has navigation open - if (curDoc.presNavButton && targetDoc) { + if (curDoc.presMovement === PresMovement.Pan && targetDoc) { await DocumentManager.Instance.jumpToDocument(targetDoc, false, undefined, srcContext); - } else if (curDoc.presZoomButton && targetDoc) { + } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(targetDoc, true, undefined, srcContext); } @@ -261,7 +301,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; bestTarget && runInAction(() => { - bestTarget._viewTransition = "all 1s"; + bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; bestTarget._panX = activeItem.presPinViewX; bestTarget._panY = activeItem.presPinViewY; bestTarget._viewScale = activeItem.presPinViewScale; @@ -358,13 +398,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.updateCurrentPresentation(); let activeItem: Doc = this.activeItem; let targetDoc: Doc = this.targetDoc; - let duration = NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition); + let duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); const timer = (ms: number) => new Promise(res => this._presTimer = setTimeout(res, ms)); const load = async () => { // Wrap the loop into an async function for this to work for (var i = startSlide; i < this.childDocs.length; i++) { activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); - duration = NumCast(targetDoc.presDuration) + NumCast(targetDoc.presTransition); + duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); if (duration <= 100) { duration = 2500; } if (NumCast(targetDoc.lastFrame) > 0) { for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { @@ -404,7 +444,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.rootDoc._itemIndex = 0; } - @action togglePath = () => this.pathBoolean = !this.pathBoolean; + @action togglePath = (srcContext: Doc, off?: boolean) => { + if (off) { + this.pathBoolean = false; + srcContext.presPathView = false; + } else { + this.pathBoolean = !this.pathBoolean; + srcContext.presPathView = this.pathBoolean; + } + } @action toggleExpandMode = () => { this.rootDoc.expandBoolean = !this.rootDoc.expandBoolean; this.childDocs.forEach((doc) => { @@ -445,7 +493,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> CollectionDockingView.AddSplit(this.rootDoc, "right"); this.layoutDoc.inOverlay = false; } else if (this.layoutDoc.context && docView) { - this.layoutDoc.presStatus = 'manual'; + this.layoutDoc.presStatus = 'edit'; + clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); this.rootDoc.y = pt[1] + 10; @@ -454,7 +503,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> docView.props.removeDocument?.(this.layoutDoc); Doc.AddDocToList((Doc.UserDoc().myOverlayDocs as Doc), undefined, this.rootDoc); } else { - this.layoutDoc.presStatus = 'manual'; + this.layoutDoc.presStatus = 'edit'; + clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); this.rootDoc.y = pt[1] + 10; @@ -485,33 +535,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @undoBatch updateMovement = action((movement: any, activeItem: Doc, targetDoc: Doc) => { switch (movement) { - case 'zoom': //Pan and zoom - activeItem.presNavButton = false; - activeItem.presZoomButton = !activeItem.presZoomButton; - targetDoc.presTransition = activeItem.presTransition; - if (activeItem.presZoomButton) activeItem.presMovement = 'zoom'; - else activeItem.presMovement = 'none'; + case PresMovement.Zoom: //Pan and zoom + activeItem.presMovement = PresMovement.Zoom; break; - case 'pan': //Pan - activeItem.presZoomButton = false; - activeItem.presNavButton = !activeItem.presNavButton; - targetDoc.presTransition = activeItem.presTransition; - if (activeItem.presNavButton) activeItem.presMovement = 'pan'; - else activeItem.presMovement = 'none'; + case PresMovement.Pan: //Pan + activeItem.presMovement = PresMovement.Pan; break; - case 'jump': //Jump Cut - activeItem.presTransition = targetDoc.presTransition; - targetDoc.presTransition = 0; - activeItem.presZoomButton = true; - activeItem.presSwitchButton = !activeItem.presSwitchButton; - if (activeItem.presSwitchButton) activeItem.presMovement = 'jump'; - else activeItem.presMovement = 'none'; + case PresMovement.Jump: //Jump Cut + activeItem.presJump = true; + activeItem.presMovement = PresMovement.Jump; break; - case 'none': default: - activeItem.presMovement = 'none'; - activeItem.presZoomButton = false; - activeItem.presNavButton = false; - activeItem.presSwitchButton = false; + case PresMovement.None: default: + activeItem.presMovement = PresMovement.None; break; } }); @@ -519,10 +554,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> setMovementName = action((movement: any, activeItem: Doc): string => { let output: string = 'none'; switch (movement) { - case 'zoom': output = 'Zoom'; break; //Pan and zoom - case 'pan': output = 'Pan'; break; //Pan - case 'jump': output = 'Jump cut'; break; //Jump Cut - case 'none': output = 'None'; break; //None + case PresMovement.Zoom: output = 'Zoom'; break; //Pan and zoom + case PresMovement.Pan: output = 'Pan'; break; //Pan + case PresMovement.Jump: output = 'Jump cut'; break; //Jump Cut + case PresMovement.None: output = 'None'; break; //None default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom } return output; @@ -535,9 +570,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> docs.forEach((doc, i) => { if (this.childDocs.includes(doc)) { if (docs.length === i + 1) return false; + } else if (doc.type === DocumentType.LABEL) { + const audio = Cast(doc.annotationOn, Doc, null); + if (audio) { + audio.aliasOf instanceof Doc; + audio.presStartTime = NumCast(doc.audioStart); + audio.presEndTime = NumCast(doc.audioEnd); + audio.presDuration = NumCast(doc.audioEnd) - NumCast(doc.audioStart); + TabDocView.PinDoc(audio, false, true); + setTimeout(() => this.removeDocument(doc), 1); + return false; + } } else { doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); - !this.childDocs.includes(doc) && (doc.presZoomButton = true); + !this.childDocs.includes(doc) && (doc.presMovement = PresMovement.Zoom); if (this.rootDoc.expandBoolean) doc.presExpandInlineButton = true; } }); @@ -571,16 +617,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const list = this._selectedArray.map((doc: Doc, index: any) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc!, Doc, null); - return ( - <div className="selectedList-items">{index + 1}. {tagDoc.title}</div> - ); + if (tagDoc) return <div className="selectedList-items">{index + 1}. {curDoc.title}</div>; + else if (curDoc) return <div className="selectedList-items">{index + 1}. {curDoc.title}</div>; }); return list; } @action selectPres = () => { - const presDocView = DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc)!; + const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc)!; SelectionManager.SelectDoc(presDocView, false); } @@ -588,7 +633,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action selectElement = (doc: Doc) => { this.gotoDocument(this.childDocs.indexOf(doc), NumCast(this.itemIndex)); - this.selectPres(); + if (doc.presPinView) setTimeout(() => this.selectPres(), 0); + else this.selectPres(); } //Command click @@ -645,7 +691,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> handled = true; } if (e.keyCode === 32) { // spacebar to 'present' or autoplay if (this.layoutDoc.presStatus !== "edit") this.startAutoPres(0); - else this.layoutDoc.presStatus = "manual"; if (this._presTimer) clearTimeout(this._presTimer); + else this.next(); handled = true; } if (e.keyCode === 8) { // delete selected items if (this.layoutDoc.presStatus === "edit") { @@ -680,14 +726,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> */ @undoBatch @action - viewPaths = async () => { + viewPaths = () => { const srcContext = Cast(this.rootDoc.presCollection, Doc, null); - if (this.pathBoolean && srcContext) { - this.togglePath(); - srcContext.presPathView = false; - } else if (srcContext) { - this.togglePath(); - srcContext.presPathView = true; + if (srcContext) { + this.togglePath(srcContext); } } @@ -697,10 +739,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.childDocs.forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); const srcContext = Cast(tagDoc?.context, Doc, null); + const width = NumCast(tagDoc._width) / 10; + const height = Math.max(NumCast(tagDoc._height) / 10, 15); + const edge = Math.max(width, height); + const fontSize = edge * 0.8; // Case A: Document is contained within the colleciton if (this.rootDoc.presCollection === srcContext) { order.push( - <div className="pathOrder" style={{ top: NumCast(tagDoc.y), left: NumCast(tagDoc.x) }}> + <div className="pathOrder" style={{ top: NumCast(tagDoc.y) - (edge / 2), left: NumCast(tagDoc.x) - (edge / 2), width: edge, height: edge, fontSize: fontSize }}> <div className="pathOrder-frame">{index + 1}</div> </div>); // Case B: Document is not inside of the collection @@ -744,11 +790,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> stroke: "#69a6db", strokeWidth: 5, strokeDasharray: '10 5', + boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)', }} fill="none" - markerStart="url(#markerSquare)" + markerStart="url(#markerArrow)" markerMid="url(#markerSquare)" - markerEnd="url(#markerArrow)" + markerEnd="url(#markerSquareFilled)" />); } @@ -781,7 +828,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; - if (this.targetDoc) this.targetDoc.presTransition = timeInMS; + if (this.activeItem) this.activeItem.presTransition = timeInMS; } // Converts seconds to ms and updates presDuration @@ -790,7 +837,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - if (this.targetDoc) this.targetDoc.presDuration = timeInMS; + if (this.activeItem) this.activeItem.presDuration = timeInMS; } @@ -798,9 +845,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { - const transitionSpeed = targetDoc.presTransition ? NumCast(targetDoc.presTransition) / 1000 : 0.5; - let duration = targetDoc.presDuration ? NumCast(targetDoc.presDuration) / 1000 : 2; - if (targetDoc.type === DocumentType.AUDIO) duration = NumCast(targetDoc.duration); + const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; + let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; + if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None'; activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return ( @@ -811,18 +858,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> {this.setMovementName(activeItem.presMovement, activeItem)} <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? '#5B9FDD' : 'black' }} icon={"angle-down"} /> <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'none' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('none', activeItem, targetDoc)}>None</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'zoom' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('zoom', activeItem, targetDoc)}>Pan and Zoom</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'pan' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('pan', activeItem, targetDoc)}>Pan</div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === 'jump' ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement('jump', activeItem, targetDoc)}>Jump cut</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None, activeItem, targetDoc)}>None</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom, activeItem, targetDoc)}>Pan and Zoom</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan, activeItem, targetDoc)}>Pan</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump, activeItem, targetDoc)}>Jump cut</div> </div> </div> - <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "inline-flex" : "none" }}> + <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}> <div className="presBox-subheading">Transition Speed</div> <div className="ribbon-property"> <input className="presBox-input" type="number" value={transitionSpeed} - onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e) => this.setTransitionTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> @@ -834,8 +880,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> </div> </div> - <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} /> - <div className={`slider-headers ${activeItem.presMovement === 'pan' || activeItem.presMovement === 'zoom' ? "" : "none"}`}> + <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`} id="toolbar-slider" onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} /> + <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> <div className="slider-text">Fast</div> <div className="slider-text">Medium</div> <div className="slider-text">Slow</div> @@ -844,15 +890,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-box"> Visibility {"&"} Duration <div className="ribbon-doubleButton"> - <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-button ${activeItem.presHideTillShownButton ? "active" : ""}`} onClick={() => activeItem.presHideTillShownButton = !activeItem.presHideTillShownButton}>Hide before</div></Tooltip> - <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-button ${activeItem.presHideAfterButton ? "active" : ""}`} onClick={() => activeItem.presHideAfterButton = !activeItem.presHideAfterButton}>Hide after</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideTillShownButton ? "active" : ""}`} onClick={() => activeItem.presHideTillShownButton = !activeItem.presHideTillShownButton}>Hide before</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfterButton ? "active" : ""}`} onClick={() => activeItem.presHideAfterButton = !activeItem.presHideAfterButton}>Hide after</div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Open document in a new tab"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? "#aedef8" : "" }} onClick={() => activeItem.openDocument = !activeItem.openDocument}>Open</div></Tooltip> </div> <div className="ribbon-doubleButton" > <div className="presBox-subheading">Slide Duration</div> <div className="ribbon-property"> <input className="presBox-input" type="number" value={duration} - onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} + // onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e) => this.setDurationTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> @@ -929,14 +976,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> applyTo = (array: Doc[]) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; - array.forEach((doc, index) => { + array.forEach((doc) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (tagDoc && targetDoc) { - tagDoc.presTransition = targetDoc.presTransition; - tagDoc.presDuration = targetDoc.presDuration; + curDoc.presTransition = activeItem.presTransition; + curDoc.presDuration = activeItem.presDuration; tagDoc.presEffect = targetDoc.presEffect; tagDoc.presEffectDirection = targetDoc.presEffectDirection; + tagDoc.presMovement = targetDoc.presMovement; curDoc.presMovement = activeItem.presMovement; this.updateMovement(activeItem.presMovement, curDoc, tagDoc); curDoc.presHideTillShownButton = activeItem.presHideTillShownButton; @@ -947,20 +995,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get optionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 16, filter: 'invert(1)' }} />; if (activeItem && targetDoc) { return ( <div> <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.playAuto ? "#aedef8" : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div> - <div className="ribbon-button" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : "#aedef8" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div> - </div> - <div className="ribbon-doubleButton" style={{ display: "flex" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.openDocument ? "#aedef8" : "" }} onClick={() => activeItem.openDocument = !activeItem.openDocument}>Open document</div> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.playAuto ? "#aedef8" : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div> + <div className="ribbon-toggle" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : "#aedef8" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div> </div> + {targetDoc.type === DocumentType.VID ? <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presVidFullScreen ? "#aedef8" : "" }} onClick={() => activeItem.presVidFullScreen = !activeItem.presVidFullScreen}>Full screen</div> : (null)} + {targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Start time</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presStartTime)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })} /> + </div> + </div> : (null)} + {targetDoc.type === DocumentType.VID || targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">End time</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presEndTime)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presEndTime = Number(val); })} /> + </div> + </div> : (null)} + {targetDoc.type === DocumentType.COL ? 'Presentation Pin View' : (null)} <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.COL ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.presPinView ? "#aedef8" : "" }} + <div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? "#aedef8" : "" }} onClick={() => { activeItem.presPinView = !activeItem.presPinView; targetDoc.presPinView = activeItem.presPinView; @@ -972,7 +1038,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> activeItem.presPinViewY = y; activeItem.presPinViewScale = scale; } - }}>Presentation pin view</div> + }}>{presPinWithViewIcon}</div> + {activeItem.presPinView ? <div className="ribbon-button" + onClick={() => { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + }}>Update</div> : (null)}; </div> <div style={{ display: activeItem.presPinView ? "block" : "none" }}> <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> @@ -981,7 +1056,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <input className="presBox-input" style={{ textAlign: 'left', width: 50 }} type="number" value={NumCast(activeItem.presPinViewX)} - onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> </div> </div> @@ -991,7 +1065,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <input className="presBox-input" style={{ textAlign: 'left', width: 50 }} type="number" value={NumCast(activeItem.presPinViewY)} - onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> </div> </div> @@ -1001,13 +1074,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <input className="presBox-input" style={{ textAlign: 'left', width: 50 }} type="number" value={NumCast(activeItem.presPinViewScale)} - onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> </div> </div> </div> {/* <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}> - <div className="ribbon-button" onClick={this.progressivizeText}>Store original website</div> + <div className="ribbon-toggle" onClick={this.progressivizeText}>Store original website</div> </div> */} </div> </div> @@ -1051,18 +1123,18 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-box"> Slide Title: <br></br> <input className="ribbon-textInput" placeholder="..." type="text" name="fname" - onFocus={() => document.removeEventListener("keydown", this.keyEvents, true)} onChange={(e) => { e.stopPropagation(); e.preventDefault(); runInAction(() => this.title = e.target.value); - }}></input> + }}> + </input> </div> <div className="ribbon-box"> Choose type: <div className="ribbon-doubleButton"> - <div title="Text" className={'ribbon-button'} style={{ background: this.addFreeform ? "" : "#aedef8" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div> - <div title="Freeform" className={'ribbon-button'} style={{ background: this.addFreeform ? "#aedef8" : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div> + <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "" : "#aedef8" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Text</div> + <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? "#aedef8" : "" }} onClick={action(() => this.addFreeform = !this.addFreeform)}>Freeform</div> </div> </div> <div className="ribbon-box" style={{ display: this.addFreeform ? "grid" : "none" }}> @@ -1129,13 +1201,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20; } let doc = undefined; - const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24px", }); - const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16px" }); - const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20px" }); - const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24px" }); - const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14px" }); - const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14px" }); - const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14px" }); + const title = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 58, _fontSize: "24pt", }); + const subtitle = Docs.Create.TextDocument("Click to change subtitle", { title: "Slide subtitle", _width: 380, _height: 50, x: 10, y: 118, _fontSize: "16pt" }); + const header = Docs.Create.TextDocument("Click to change header", { title: "Slide header", _width: 380, _height: 65, x: 10, y: 80, _fontSize: "20pt" }); + const contentTitle = Docs.Create.TextDocument("Click to change title", { title: "Slide title", _width: 380, _height: 60, x: 10, y: 10, _fontSize: "24pt" }); + const content = Docs.Create.TextDocument("Click to change text", { title: "Slide text", _width: 380, _height: 145, x: 10, y: 70, _fontSize: "14pt" }); + const content1 = Docs.Create.TextDocument("Click to change text", { title: "Column 1", _width: 185, _height: 140, x: 10, y: 80, _fontSize: "14pt" }); + const content2 = Docs.Create.TextDocument("Click to change text", { title: "Column 2", _width: 185, _height: 140, x: 205, y: 80, _fontSize: "14pt" }); switch (layout) { case 'blank': doc = Docs.Create.FreeformDocument([], { title: input ? input : "Blank slide", _width: 400, _height: 225, x: x, y: y }); @@ -1162,10 +1234,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get presentDropdown() { return ( <div className={`dropdown-play ${this.presentTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div className="dropdown-play-button" onClick={this.updateMinimize}> + <div className="dropdown-play-button" onClick={(action(() => { this.updateMinimize(); this.turnOffEdit(true); }))}> Minimize </div> - <div className="dropdown-play-button" onClick={(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(); }))}> + <div className="dropdown-play-button" onClick={(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(true); }))}> Sidebar view </div> </div> @@ -1179,7 +1251,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); const currentFrame = Cast(tagDoc._currentFrame, "number", null); if (currentFrame === undefined) { - tagDoc.currentFrame = 0; + tagDoc._currentFrame = 0; CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } @@ -1187,15 +1259,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); - if (curDoc.zoomProgressivize) { - const resize = document.getElementById('resizable'); - if (resize) { - resize.style.width = this.checkList(tagDoc, curDoc["viewfinder-width-indexed"]) + 'px'; - resize.style.height = this.checkList(tagDoc, curDoc["viewfinder-height-indexed"]) + 'px'; - resize.style.top = this.checkList(tagDoc, curDoc["viewfinder-top-indexed"]) + 'px'; - resize.style.left = this.checkList(tagDoc, curDoc["viewfinder-left-indexed"]) + 'px'; - } - } + // if (curDoc.zoomProgressivize) { + // const resize = document.getElementById('resizable'); + // if (resize) { + // resize.style.width = this.checkList(tagDoc, curDoc["viewfinder-width-indexed"]) + 'px'; + // resize.style.height = this.checkList(tagDoc, curDoc["viewfinder-height-indexed"]) + 'px'; + // resize.style.top = this.checkList(tagDoc, curDoc["viewfinder-top-indexed"]) + 'px'; + // resize.style.left = this.checkList(tagDoc, curDoc["viewfinder-left-indexed"]) + 'px'; + // } + // } } @undoBatch @@ -1209,15 +1281,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); - if (actItem.zoomProgressivize) { - const resize = document.getElementById('resizable'); - if (resize) { - resize.style.width = this.checkList(tagDoc, actItem["viewfinder-width-indexed"]) + 'px'; - resize.style.height = this.checkList(tagDoc, actItem["viewfinder-height-indexed"]) + 'px'; - resize.style.top = this.checkList(tagDoc, actItem["viewfinder-top-indexed"]) + 'px'; - resize.style.left = this.checkList(tagDoc, actItem["viewfinder-left-indexed"]) + 'px'; - } - } + // if (actItem.zoomProgressivize) { + // const resize = document.getElementById('resizable'); + // if (resize) { + // resize.style.width = this.checkList(tagDoc, actItem["viewfinder-width-indexed"]) + 'px'; + // resize.style.height = this.checkList(tagDoc, actItem["viewfinder-height-indexed"]) + 'px'; + // resize.style.top = this.checkList(tagDoc, actItem["viewfinder-top-indexed"]) + 'px'; + // resize.style.left = this.checkList(tagDoc, actItem["viewfinder-left-indexed"]) + 'px'; + // } + // } } /** @@ -1227,9 +1299,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; let type: string = ''; - const effectiveType = targetDoc.treeViewOutlineMode ? DocumentType.COL : targetDoc.type;// bcz: Argh .. .need a better way to identify a slide doc if (activeItem) { - switch (effectiveType) { + switch (targetDoc.type) { case DocumentType.PDF: type = "PDF"; break; case DocumentType.RTF: type = "Text node"; break; case DocumentType.COL: type = "Collection"; break; @@ -1249,9 +1320,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get progressivizeDropdown() { - const activeItem = this.activeItem; - const targetDoc = this.targetDoc; - const effectiveType = targetDoc.treeViewOutlineMode ? DocumentType.COL : targetDoc.type; + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { const activeFontColor = targetDoc["pres-text-color"] ? StrCast(targetDoc["pres-text-color"]) : "Black"; const viewedFontColor = targetDoc["pres-text-viewed-color"] ? StrCast(targetDoc["pres-text-viewed-color"]) : "Black"; @@ -1260,29 +1330,29 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> {this.stringType} selected - <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: effectiveType === DocumentType.COL ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Contents</div> - <div className="ribbon-button" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div> + <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeChild}>Contents</div> + <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? "#aedef8" : "" }} onClick={this.editProgressivize}>Edit</div> </div> <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> <div className="presBox-subheading">Active text color</div> - <div className="ribbon-property" style={{ backgroundColor: activeFontColor }} onClick={action(() => { this.openActiveColorPicker = !this.openActiveColorPicker; })}> + <div className="ribbon-colorBox" style={{ backgroundColor: activeFontColor, height: 15, width: 15 }} onClick={action(() => { this.openActiveColorPicker = !this.openActiveColorPicker; })}> </div> </div> {this.activeColorPicker} <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? "inline-flex" : "none" }}> <div className="presBox-subheading">Viewed font color</div> - <div className="ribbon-property" style={{ backgroundColor: viewedFontColor }} onClick={action(() => this.openViewedColorPicker = !this.openViewedColorPicker)}> + <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => this.openViewedColorPicker = !this.openViewedColorPicker)}> </div> </div> {this.viewedColorPicker} - {/* <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (effectiveType === DocumentType.COL && targetDoc._viewType === 'freeform') || effectiveType === DocumentType.IMG ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Zoom</div> - <div className="ribbon-button" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Edit</div> + {/* <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? "inline-flex" : "none" }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeZoom}>Zoom</div> + <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? "#aedef8" : "" }} onClick={this.editZoomProgressivize}>Edit</div> </div> */} - <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || effectiveType === DocumentType.PDF || effectiveType === DocumentType.WEB || effectiveType === DocumentType.RTF ? "inline-flex" : "none" }}> - <div className="ribbon-button" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll</div> - <div className="ribbon-button" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div> + <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: targetDoc._viewType === "stacking" || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? "#aedef8" : "" }} onClick={this.progressivizeScroll}>Scroll</div> + <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? "#aedef8" : "" }} onClick={this.editScrollProgressivize}>Edit</div> </div> </div> <div className="ribbon-final-box" style={{ display: activeItem.zoomProgressivize || activeItem.scrollProgressivize || activeItem.presProgressivize || activeItem.textProgressivize ? "grid" : "none" }}> @@ -1302,7 +1372,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> <Tooltip title={<><div className="dash-tooltip">{"Last frame"}</div></>}><div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div></Tooltip> </div> - <div className="ribbon-button" style={{ height: 20, backgroundColor: "#AEDDF8" }} onClick={() => console.log(" TODO: play frames")}>Play</div> + <div className="ribbon-toggle" style={{ height: 20, backgroundColor: "#AEDDF8" }} onClick={() => console.log(" TODO: play frames")}>Play</div> </div> </div> </div> @@ -1349,15 +1419,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> color={StrCast(targetDoc["pres-text-viewed-color"])} />; } - turnOffEdit = () => { + @action + turnOffEdit = (paths?: boolean) => { + if (paths) { + // Turn off paths + const srcContext = Cast(this.rootDoc.presCollection, Doc, null); + if (srcContext) this.togglePath(srcContext, true); + } + // Turn off the progressivize editors for each this.childDocs.forEach((doc) => { doc.editSnapZoomProgressivize = false; doc.editZoomProgressivize = false; - doc.editScrollProgressivize = false; const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); if (targetDoc) { targetDoc.editZoomProgressivize = false; - targetDoc.editScrollProgressivize = false; + // targetDoc.editScrollProgressivize = false; } }); } @@ -1437,28 +1513,23 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } @action - progressivizeChild = (e?: React.MouseEvent) => { - e?.stopPropagation(); + progressivizeChild = (e: React.MouseEvent) => { + e.stopPropagation(); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; + const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); if (!activeItem.presProgressivize) { targetDoc.editing = false; activeItem.presProgressivize = true; targetDoc.presProgressivize = true; targetDoc._currentFrame = 0; - let count = 0; - const setupProgressivize = (doc: Doc) => { - CollectionFreeFormDocumentView.setupKeyframes([doc], count++, true); - targetDoc.treeViewOutlineMode && DocListCast(doc[Doc.LayoutFieldKey(doc)]).forEach(setupProgressivize); - }; - setupProgressivize(targetDoc); - targetDoc.lastFrame = count; + docs.forEach((doc, i) => CollectionFreeFormDocumentView.setupKeyframes([doc], i, true)); + targetDoc.lastFrame = targetDoc.lastFrame ? NumCast(targetDoc.lastFrame) : docs.length - 1; } else { targetDoc.editProgressivize = false; activeItem.presProgressivize = false; targetDoc.presProgressivize = false; targetDoc._currentFrame = 0; - targetDoc.lastFrame = 0; targetDoc.editing = true; } } @@ -1667,9 +1738,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const targetDoc: Doc = this.targetDoc; return ( <> - {this.targetDoc ? <div className="miniPres-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}> + {this.targetDoc ? <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? "inline-flex" : "none" }}> <div>{targetDoc._currentFrame}</div> - <div className="miniPres-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div> + <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div> <div>{targetDoc.lastFrame}</div> </div> : null} </> @@ -1690,7 +1761,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> {this.playButtonFrames} </div> <div className="presPanel-divider"></div> - {this.props.PanelWidth() > 250 ? <div className="presPanel-button-text" onClick={() => this.layoutDoc.presStatus = "edit"}>EXIT</div> + {this.props.PanelWidth() > 250 ? <div className="presPanel-button-text" onClick={() => { this.layoutDoc.presStatus = "edit"; clearTimeout(this._presTimer); }}>EXIT</div> : <div className="presPanel-button" onClick={() => this.layoutDoc.presStatus = "edit"}> <FontAwesomeIcon icon={"times"} /> </div>} @@ -1699,7 +1770,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @action startOrPause = () => { - if (this.layoutDoc.presStatus === "manual") this.startAutoPres(this.itemIndex); + if (this.layoutDoc.presStatus === "manual" || this.layoutDoc.presStatus === "edit") this.startAutoPres(this.itemIndex); else this.pauseAutoPres(); } @@ -1710,21 +1781,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; return this.layoutDoc.inOverlay ? - <div className="miniPres" style={{ width: 250, height: 35, background: '#323232', top: 0, zIndex: 3000000 }}> - {<div className="miniPresOverlay"> - <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="miniPres-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> - <div className="miniPres-divider"></div> - <div className="miniPres-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> - <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="miniPres-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> - <div className="miniPres-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text"> + <div className="miniPres"> + <div className="presPanelOverlay" style={{ display: "inline-flex", height: 35, background: '#323232', top: 0, zIndex: 3000000 }}> + <Tooltip title={<><div className="dash-tooltip">{"Loop"}</div></>}><div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? '#AEDDF8' : undefined }} onClick={() => this.layoutDoc.presLoop = !this.layoutDoc.presLoop}><FontAwesomeIcon icon={"redo-alt"} /></div></Tooltip> + <div className="presPanel-divider"></div> + <div className="presPanel-button" onClick={this.back}><FontAwesomeIcon icon={"arrow-left"} /></div> + <Tooltip title={<><div className="dash-tooltip">{this.layoutDoc.presStatus === "auto" ? "Pause" : "Autoplay"}</div></>}><div className="presPanel-button" onClick={this.startOrPause}><FontAwesomeIcon icon={this.layoutDoc.presStatus === "auto" ? "pause" : "play"} /></div></Tooltip> + <div className="presPanel-button" onClick={this.next}><FontAwesomeIcon icon={"arrow-right"} /></div> + <div className="presPanel-divider"></div> + <div className="presPanel-button-text"> Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames} </div> - <div className="miniPres-divider"></div> - <div className="miniPres-button-text" onClick={this.updateMinimize}>EXIT</div> - </div>} + <div className="presPanel-divider"></div> + <div className="presPanel-button-text" onClick={this.updateMinimize}>EXIT</div> + </div> </div> : <div className="presBox-cont" style={{ minWidth: this.layoutDoc.inOverlay ? 240 : undefined }} > diff --git a/src/client/views/nodes/formattedText/DashFieldView.tsx b/src/client/views/nodes/formattedText/DashFieldView.tsx index a4114ed2c..b39a845db 100644 --- a/src/client/views/nodes/formattedText/DashFieldView.tsx +++ b/src/client/views/nodes/formattedText/DashFieldView.tsx @@ -1,5 +1,5 @@ import { IReactionDisposer, observable, computed, action } from "mobx"; -import { Doc, DocListCast, Field } from "../../../../fields/Doc"; +import { Doc, DocListCast, Field, DataSym } from "../../../../fields/Doc"; import { List } from "../../../../fields/List"; import { listSpec } from "../../../../fields/Schema"; import { SchemaHeaderField } from "../../../../fields/SchemaHeaderField"; @@ -82,7 +82,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna // set the display of the field's value (checkbox for booleans, span of text for strings) @computed get fieldValueContent() { if (this._dashDoc) { - const dashVal = this._dashDoc[this._fieldKey] || (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : ""); + const dashVal = this._dashDoc[DataSym][this._fieldKey] ?? this._dashDoc[this._fieldKey] ?? (this._fieldKey === "PARAMS" ? this._textBoxDoc[this._fieldKey] : ""); const fval = dashVal instanceof List ? dashVal.join(this.multiValueDelimeter) : StrCast(dashVal).startsWith(":=") || dashVal === "" ? Doc.Layout(this._textBoxDoc)[this._fieldKey] : dashVal; const boolVal = Cast(fval, "boolean", null); const strVal = Field.toString(fval as Field) || ""; @@ -94,7 +94,7 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna type="checkbox" checked={boolVal} onChange={e => { if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = e.target.checked; - this._dashDoc![this._fieldKey] = e.target.checked; + Doc.SetInPlace(this._dashDoc!, this._fieldKey, e.target.checked, true); }} />; } @@ -155,22 +155,22 @@ export class DashFieldViewInternal extends React.Component<IDashFieldViewInterna if (modText) { // elementfieldSpan.innerHTML = this._dashDoc![this._fieldKey as string] = modText; DocUtils.addFieldEnumerations(this._textBoxDoc, this._fieldKey, []); - this._dashDoc![this._fieldKey] = modText; + Doc.SetInPlace(this._dashDoc!, this._fieldKey, modText, true); } // if the text starts with a ':=' then treat it as an expression by making a computed field from its value storing it in the key else if (nodeText.startsWith(":=")) { - this._dashDoc![this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); + this._dashDoc![DataSym][this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(2)); } else if (nodeText.startsWith("=:=")) { Doc.Layout(this._textBoxDoc)[this._fieldKey] = ComputedField.MakeFunction(nodeText.substring(3)); } else { if (Number(newText).toString() === newText) { if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = Number(newText); - this._dashDoc![this._fieldKey] = Number(newText); + Doc.SetInPlace(this._dashDoc!, this._fieldKey, newText, true); } else { const splits = newText.split(this.multiValueDelimeter); if (this._fieldKey !== "PARAMS" || !this._textBoxDoc[this._fieldKey] || this._dashDoc?.PARAMS) { const strVal = splits.length > 1 ? new List<string>(splits) : newText; if (this._fieldKey.startsWith("_")) Doc.Layout(this._textBoxDoc)[this._fieldKey] = strVal; - this._dashDoc![this._fieldKey] = strVal; + Doc.SetInPlace(this._dashDoc!, this._fieldKey, strVal, true); } } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 83012bab5..dc0fafd24 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -22,7 +22,7 @@ import { RichTextField } from "../../../../fields/RichTextField"; import { RichTextUtils } from '../../../../fields/RichTextUtils'; import { makeInterface } from "../../../../fields/Schema"; import { Cast, DateCast, NumCast, StrCast, ScriptCast, BoolCast } from "../../../../fields/Types"; -import { TraceMobx, OVERRIDE_ACL, GetEffectiveAcl } from '../../../../fields/util'; +import { TraceMobx, OVERRIDE_acl, GetEffectiveAcl } from '../../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, emptyFunction, numberRange, returnOne, returnZero, Utils, setupMoveUpEvents, OmitKeys } from '../../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../../DocServer"; @@ -794,9 +794,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp tr = tr.addMark(pos, pos + node.nodeSize, link); } }); - OVERRIDE_ACL(true); + OVERRIDE_acl(true); this._editorView!.dispatch(tr.removeMark(sel.from, sel.to, splitter)); - OVERRIDE_ACL(false); + OVERRIDE_acl(false); } } componentDidMount() { diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss index 6ee190b82..4642caeb2 100644 --- a/src/client/views/presentationview/PresElementBox.scss +++ b/src/client/views/presentationview/PresElementBox.scss @@ -114,6 +114,7 @@ $light-background: #ececec; } .presElementBox-name { + z-index: 300; align-self: center; font-size: 13px; font-family: Roboto; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 048d3a3d0..e9ab5911d 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, reaction } from "mobx"; +import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DataSym, DocListCast } from "../../../fields/Doc"; import { documentSchema } from '../../../fields/documentSchemas'; @@ -15,12 +15,13 @@ import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { PresBox } from "../nodes/PresBox"; +import { PresBox, PresMovement } from "../nodes/PresBox"; import { DocumentType } from "../../documents/DocumentTypes"; import { Tooltip } from "@material-ui/core"; import { DragManager } from "../../util/DragManager"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; import { undoBatch } from "../../util/UndoManager"; +import { EditableView } from "../EditableView"; export const presSchema = createSchema({ presentationTargetDoc: Doc, @@ -114,20 +115,23 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc @computed get duration() { let durationInS: number; - if (this.targetDoc.presDuration) durationInS = NumCast(this.targetDoc.presDuration) / 1000; + if (this.rootDoc.type === DocumentType.AUDIO) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; } + else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000; else durationInS = 2; - return "D: " + durationInS + "s"; + return this.rootDoc.presMovement === PresMovement.Jump ? (null) : "D: " + durationInS + "s"; } @computed get transition() { let transitionInS: number; - if (this.targetDoc.presTransition) transitionInS = NumCast(this.targetDoc.presTransition) / 1000; + if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000; else transitionInS = 0.5; return "M: " + transitionInS + "s"; } private _itemRef: React.RefObject<HTMLDivElement> = React.createRef(); private _dragRef: React.RefObject<HTMLDivElement> = React.createRef(); + private _titleRef: React.RefObject<EditableView> = React.createRef(); + @action headerDown = (e: React.PointerEvent<HTMLDivElement>) => { @@ -180,19 +184,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc onPointerTop = (e: React.PointerEvent<HTMLDivElement>) => { - if (DragManager.docsBeingDragged.length > 0) { + if (DragManager.docsBeingDragged.length > 1) { this._highlightTopRef.current!.style.borderTop = "solid 2px #5B9FDD"; } } onPointerBottom = (e: React.PointerEvent<HTMLDivElement>) => { - if (DragManager.docsBeingDragged.length > 0) { + if (DragManager.docsBeingDragged.length > 1) { this._highlightBottomRef.current!.style.borderBottom = "solid 2px #5B9FDD"; } } onPointerLeave = (e: React.PointerEvent<HTMLDivElement>) => { - if (DragManager.docsBeingDragged.length > 0) { + if (DragManager.docsBeingDragged.length > 1) { this._highlightBottomRef.current!.style.borderBottom = "0px"; this._highlightTopRef.current!.style.borderTop = "0px"; } @@ -200,8 +204,8 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc @action toggleProperties = () => { - if (CurrentUserUtils.propertiesWidth === 0) { - CurrentUserUtils.propertiesWidth = 250; + if (CurrentUserUtils.propertiesWidth < 5) { + action(() => (CurrentUserUtils.propertiesWidth = 250)); } } @@ -214,9 +218,14 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc e.stopPropagation(); }); + @action + onSetValue = (value: string) => { + this.rootDoc.title = value; + return true; + } + render() { const className = "presElementBox-item" + (PresBox.Instance._selectedArray.includes(this.rootDoc) ? " presElementBox-active" : ""); - const pbi = "presElementBox-interaction"; return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : ( <div className={className} key={this.props.Document[Id] + this.indexInPres} ref={this._itemRef} @@ -240,6 +249,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc } }} onDoubleClick={e => { + console.log('double click to open'); this.toggleProperties(); this.props.focus(this.rootDoc); PresBox.Instance._eleArray = []; @@ -255,7 +265,15 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc {`${this.indexInPres + 1}.`} </div> <div ref={this._dragRef} className="presElementBox-name" style={{ maxWidth: (PresBox.Instance.toolbarWidth - 70) }}> - {`${this.targetDoc?.title}`} + <EditableView + ref={this._titleRef} + contents={this.rootDoc.title} + GetValue={() => StrCast(this.rootDoc.title)} + SetValue={action((value: string) => { + this.onSetValue(value); + return true; + })} + /> </div> <Tooltip title={<><div className="dash-tooltip">{"Movement speed"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.transition}</div></Tooltip> <Tooltip title={<><div className="dash-tooltip">{"Duration"}</div></>}><div className="presElementBox-time" style={{ display: PresBox.Instance.toolbarWidth > 300 ? "block" : "none" }}>{this.duration}</div></Tooltip> diff --git a/src/client/views/search/SearchBox.tsx b/src/client/views/search/SearchBox.tsx index b37ae02c3..3238e4dc6 100644 --- a/src/client/views/search/SearchBox.tsx +++ b/src/client/views/search/SearchBox.tsx @@ -530,10 +530,10 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc style={{ cursor: "hand", color: "black", padding: 1, position: "relative" }} /></div> </Tooltip> </div> - <div style={{ position: "absolute", left: 200, width: 30, zIndex: 9000, color: "grey", background: "white", }}> + <div style={{ position: "absolute", left: 220, width: 30, zIndex: 9000, color: "grey", background: "white", }}> {`${this._results.length}` + " of " + `${this.realTotalResults}`} </div> - <div style={{ cursor: "default", left: 235, position: "absolute", }}> + {/* <div style={{ cursor: "default", left: 235, position: "absolute", }}> <Tooltip title={<div className="dash-tooltip" >only display documents matching search</div>} > <div> <FontAwesomeIcon icon={"filter"} size="lg" @@ -542,7 +542,7 @@ export class SearchBox extends ViewBoxBaseComponent<FieldViewProps, SearchBoxDoc onClick={action(() => this.setSearchFilter(this.currentSelectedCollection, this.filter ? undefined : this.docsforfilter))} /> </div> </Tooltip> - </div> + </div> */} {this.scopeButtons} </div> </div > diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index a8950f1ee..ba7c9c7da 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -121,7 +121,7 @@ const AclMap = new Map<string, symbol>([ export function fetchProto(doc: Doc) { const permissions: { [key: string]: symbol } = {}; - Object.keys(doc).filter(key => key.startsWith("ACL")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!); + Object.keys(doc).filter(key => key.startsWith("acl")).forEach(key => permissions[key] = AclMap.get(StrCast(doc[key]))!); if (Object.keys(permissions).length) doc[AclSym] = permissions; @@ -253,7 +253,7 @@ export class Doc extends RefField { const prev = GetEffectiveAcl(this); this[UpdatingFromServer] = true; this[fKey] = value; - if (fKey.startsWith("ACL")) { + if (fKey.startsWith("acl")) { fetchProto(this); } this[UpdatingFromServer] = false; @@ -261,7 +261,7 @@ export class Doc extends RefField { DocServer.GetRefField(this[Id], true); } }; - if (sameAuthor || fKey.startsWith("ACL") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { + if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; await fn(); } else { @@ -775,7 +775,7 @@ export namespace Doc { } }); copy.author = Doc.CurrentUserEmail; - Doc.UserDoc().defaultAclPrivate && (copy["ACL-Public"] = "Not Shared"); + Doc.UserDoc().defaultAclPrivate && (copy["acl-Public"] = "Not Shared"); return copy; } @@ -803,7 +803,7 @@ export namespace Doc { const applied = ApplyTemplateTo(templateDoc, target, targetKey, templateDoc.title + "(..." + _applyCount++ + ")"); target.layoutKey = targetKey; applied && (Doc.GetProto(applied).type = templateDoc.type); - Doc.UserDoc().defaultAclPrivate && (applied["ACL-Public"] = "Not Shared"); + Doc.UserDoc().defaultAclPrivate && (applied["acl-Public"] = "Not Shared"); return applied; } return undefined; diff --git a/src/fields/Schema.ts b/src/fields/Schema.ts index 23ac50f74..4607e0fd5 100644 --- a/src/fields/Schema.ts +++ b/src/fields/Schema.ts @@ -52,7 +52,7 @@ export function makeInterface<T extends Interface[]>(...schemas: T): InterfaceFu return field; }, set(target: any, prop, value, receiver) { - receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in ACLs + receiver.doc && (receiver.doc[prop] = value); // receiver.doc may be undefined as the result of a change in acls return true; } }); diff --git a/src/fields/util.ts b/src/fields/util.ts index 82525f92b..00e1683bd 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -111,7 +111,7 @@ export function makeEditable() { _setter = _setterImpl; } var _overrideAcl = false; -export function OVERRIDE_ACL(val: boolean) { +export function OVERRIDE_acl(val: boolean) { _overrideAcl = val; } @@ -168,7 +168,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, if (currentUserGroups.includes("Admin")) return AclAdmin; - // if the ACL is being overriden or the property being modified is one of the playground fields (which can be freely modified) + // if the acl is being overriden or the property being modified is one of the playground fields (which can be freely modified) if (_overrideAcl || (in_prop && DocServer.PlaygroundFields?.includes(in_prop.toString()))) return AclEdit; let effectiveAcl = AclPrivate; @@ -198,11 +198,11 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, } /** * Recursively distributes the access right for a user across the children of a document and its annotations. - * @param key the key storing the access right (e.g. ACL-groupname) + * @param key the key storing the access right (e.g. acl-groupname) * @param acl the access right being stored (e.g. "Can Edit") * @param target the document on which this access right is being set - * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the ACLs from the collection) - * inheritingFromCollection is not currently being used but could be used if ACL assignment defaults change + * @param inheritingFromCollection whether the target is being assigned rights after being dragged into a collection (and so is inheriting the acls from the collection) + * inheritingFromCollection is not currently being used but could be used if acl assignment defaults change */ export function distributeAcls(key: string, acl: SharingPermissions, target: Doc, inheritingFromCollection?: boolean) { @@ -271,8 +271,8 @@ export function setter(target: any, in_prop: string | symbol | number, value: an if (effectiveAcl !== AclEdit && effectiveAcl !== AclAdmin) return true; // if you're trying to change an acl but don't have Admin access / you're trying to change it to something that isn't an acceptable acl, you can't - if (typeof prop === "string" && prop.startsWith("ACL") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; - // if (typeof prop === "string" && prop.startsWith("ACL") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true; + if (typeof prop === "string" && prop.startsWith("acl") && (effectiveAcl !== AclAdmin || ![...Object.values(SharingPermissions), undefined].includes(value))) return true; + // if (typeof prop === "string" && prop.startsWith("acl") && !["Can Edit", "Can Add", "Can View", "Not Shared", undefined].includes(value)) return true; if (typeof prop === "string" && prop !== "__id" && prop !== "__fields" && (prop.startsWith("_") || layoutProps.includes(prop))) { if (!prop.startsWith("_")) { |