From 481e5586d3b84b874b965b6413ef751fefdc74bc Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Wed, 30 Sep 2020 22:14:14 -0600 Subject: add tooltip for re-distribute ACLs button --- src/client/views/PropertiesView.tsx | 1958 ++++++++++++++++++----------------- 1 file changed, 980 insertions(+), 978 deletions(-) (limited to 'src') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 5cab7726e..4ce9ef154 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -33,1033 +33,1035 @@ export const Flyout = higflyout.default; const _global = (window /* browser */ || global /* node */) as any; interface PropertiesViewProps { - width: number; - height: number; + width: number; + height: number; } @observer export class PropertiesView extends React.Component { - private _widthUndo?: UndoManager.Batch; - - @computed get MAX_EMBED_HEIGHT() { return 200; } - - @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } - @computed get selectedDocumentView() { - if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; - if (PresBox.Instance && PresBox.Instance._selectedArray) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); - return undefined; - } - @computed get isPres(): boolean { - if (this.selectedDoc?.type === DocumentType.PRES) return true; - return false; + private _widthUndo?: UndoManager.Batch; + + @computed get MAX_EMBED_HEIGHT() { return 200; } + + @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } + @computed get selectedDocumentView() { + if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; + if (PresBox.Instance && PresBox.Instance._selectedArray) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); + return undefined; + } + @computed get isPres(): boolean { + if (this.selectedDoc?.type === DocumentType.PRES) return true; + return false; + } + @computed get dataDoc() { return this.selectedDoc?.[DataSym]; } + + @observable layoutFields: boolean = false; + + @observable openOptions: boolean = true; + @observable openSharing: boolean = true; + @observable openFields: boolean = true; + @observable openLayout: boolean = true; + @observable openContexts: boolean = true; + @observable openAppearance: boolean = true; + @observable openTransform: boolean = true; + // @observable selectedUser: string = ""; + // @observable addButtonPressed: boolean = false; + @observable layoutDocAcls: boolean = false; + + //Pres Trails booleans: + @observable openPresTransitions: boolean = false; + @observable openPresProgressivize: boolean = false; + @observable openAddSlide: boolean = false; + @observable openSlideOptions: boolean = false; + + @observable inOptions: boolean = false; + @observable _controlBtn: boolean = false; + @observable _lock: boolean = false; + + @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; } + + rtfWidth = () => { + return !this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20); + } + rtfHeight = () => { + return !this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; + } + + @action + docWidth = () => { + if (this.selectedDoc) { + const layoutDoc = this.selectedDoc; + 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.width - 20)); + return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20; + } else { + return 0; } - @computed get dataDoc() { return this.selectedDoc?.[DataSym]; } - - @observable layoutFields: boolean = false; - - @observable openOptions: boolean = true; - @observable openSharing: boolean = true; - @observable openFields: boolean = true; - @observable openLayout: boolean = true; - @observable openContexts: boolean = true; - @observable openAppearance: boolean = true; - @observable openTransform: boolean = true; - // @observable selectedUser: string = ""; - // @observable addButtonPressed: boolean = false; - @observable layoutDocAcls: boolean = false; - - //Pres Trails booleans: - @observable openPresTransitions: boolean = false; - @observable openPresProgressivize: boolean = false; - @observable openAddSlide: boolean = false; - @observable openSlideOptions: boolean = false; - - @observable inOptions: boolean = false; - @observable _controlBtn: boolean = false; - @observable _lock: boolean = false; - - @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; } - - rtfWidth = () => { - return !this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20); + } + + @action + docHeight = () => { + if (this.selectedDoc && this.dataDoc) { + const layoutDoc = this.selectedDoc; + 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; + return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) : + Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, + NumCast(this.props.height)))) : + NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; + })())); + } else { + return 0; } - rtfHeight = () => { - return !this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - } - - @action - docWidth = () => { - if (this.selectedDoc) { - const layoutDoc = this.selectedDoc; - 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.width - 20)); - return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20; - } else { - return 0; - } - } - - @action - docHeight = () => { - if (this.selectedDoc && this.dataDoc) { - const layoutDoc = this.selectedDoc; - 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; - return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) : - Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, - NumCast(this.props.height)))) : - NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; - })())); - } else { - return 0; - } - } - - @computed get expandedField() { - if (this.dataDoc && this.selectedDoc) { - const ids: { [key: string]: string } = {}; - const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : - SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? Doc.Layout(dv.layoutDoc) : dv.dataDoc); - docs.forEach(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()) { - const docvals = new Set(); - docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; - if (key[0] === "#") { - rows.push(
- {key} + } + + @computed get expandedField() { + if (this.dataDoc && this.selectedDoc) { + const ids: { [key: string]: string } = {}; + const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : + SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? Doc.Layout(dv.layoutDoc) : dv.dataDoc); + docs.forEach(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()) { + const docvals = new Set(); + docs.forEach(doc => docvals.add(doc[key])); + const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; + if (key[0] === "#") { + rows.push(
+ {key}  
); - } else { - const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} - SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} - />; - rows.push(
- {key + ":"} + } else { + const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} + SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} + />; + rows.push(
+ {key + ":"}   {contentElement} -
); - } - } - rows.push(
- ""} - SetValue={this.setKeyValue} /> -
); - return rows; +
); } + } + rows.push(
+ ""} + SetValue={this.setKeyValue} /> +
); + return rows; } - - @computed get noviceFields() { - if (this.dataDoc) { - const ids: { [key: string]: string } = {}; - const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => dv.dataDoc); - docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); - 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"))), - ...noviceReqFields, ...noviceLayoutFields]; - for (const key of noviceKeys.sort()) { - const docvals = new Set(); - docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; - if (key[0] === "#") { - rows.push(
- {key} + } + + @computed get noviceFields() { + if (this.dataDoc) { + const ids: { [key: string]: string } = {}; + const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => dv.dataDoc); + docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); + 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"))), + ...noviceReqFields, ...noviceLayoutFields]; + for (const key of noviceKeys.sort()) { + const docvals = new Set(); + docs.forEach(doc => docvals.add(doc[key])); + const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; + if (key[0] === "#") { + rows.push(
+ {key}  
); - } else if (contents !== undefined) { - const value = Field.toString(contents as Field); - if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) { - rows.push(
- {key + ": "} -
{value}
-
); - } else { - const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} - SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} - />; - - rows.push(
- {key + ":"} + } else if (contents !== undefined) { + const value = Field.toString(contents as Field); + if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) { + rows.push(
+ {key + ": "} +
{value}
+
); + } else { + const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} + SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} + />; + + rows.push(
+ {key + ":"}   {contentElement} -
); - } - } - } - rows.push(
- ""} - SetValue={this.setKeyValue} />
); - return rows; + } } + } + rows.push(
+ ""} + SetValue={this.setKeyValue} /> +
); + return rows; } - - @undoBatch - setKeyValue = (value: string) => { - const docs = SelectionManager.SelectedDocuments().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc); - docs.forEach(doc => { - if (value.indexOf(":") !== -1) { - const newVal = value[0].toUpperCase() + value.substring(1, value.length); - const splits = newVal.split(":"); - KeyValueBox.SetField(doc, splits[0], splits[1], true); - const tags = StrCast(doc.tags, ":"); - if (tags.includes(`${splits[0]}:`) && splits[1] === "undefined") { - KeyValueBox.SetField(doc, "tags", `"${tags.replace(splits[0] + ":", "")}"`, true); - } - return true; - } else if (value[0] === "#") { - const newVal = value + `:'${value}'`; - doc[DataSym][value] = value; - const tags = StrCast(doc.tags, ":"); - if (!tags.includes(`${value}:`)) { - doc[DataSym].tags = `${tags + value + ':'}`; - } - return true; - } - }); - return false; - } - - @observable transform: Transform = Transform.Identity(); - getTransform = () => this.transform; - propertiesDocViewRef = (ref: HTMLDivElement) => { - const observer = new _global.ResizeObserver(action((entries: any) => { - const cliRect = ref.getBoundingClientRect(); - this.transform = new Transform(-cliRect.x, -cliRect.y, 1); - })); - ref && observer.observe(ref); - } - - @computed get contexts() { - return !this.selectedDoc ? (null) : CollectionDockingView.AddSplit(doc, "right")} />; - } - - previewBackground = () => "lightgrey"; - @computed get layoutPreview() { - if (SelectionManager.SelectedDocuments().length > 1) { - return "-- multiple selected --"; - } - if (this.selectedDoc) { - const layoutDoc = Doc.Layout(this.selectedDoc); - 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
- false} - whenActiveChanged={emptyFunction} - addDocTab={returnFalse} - pinToPres={emptyFunction} - bringToFront={returnFalse} - ContentScaling={returnOne} - dontRegisterView={true} - dropAction={undefined} - /> -
; - } else { - return null; + } + + @undoBatch + setKeyValue = (value: string) => { + const docs = SelectionManager.SelectedDocuments().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc); + docs.forEach(doc => { + if (value.indexOf(":") !== -1) { + const newVal = value[0].toUpperCase() + value.substring(1, value.length); + const splits = newVal.split(":"); + KeyValueBox.SetField(doc, splits[0], splits[1], true); + const tags = StrCast(doc.tags, ":"); + if (tags.includes(`${splits[0]}:`) && splits[1] === "undefined") { + KeyValueBox.SetField(doc, "tags", `"${tags.replace(splits[0] + ":", "")}"`, true); } - } - - /** - * Handles the changing of a user's permissions from the permissions panel. - */ - @undoBatch - changePermissions = (e: any, user: string) => { - const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.selectedDoc!] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); - SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); - } - - /** - * @returns the options for the permissions dropdown. - */ - getPermissionsSelect(user: string, permission: string) { - const dropdownValues: string[] = Object.values(SharingPermissions); - if (permission === "-multiple-") dropdownValues.unshift(permission); - if (user === "Override") dropdownValues.unshift("None"); - return ; - } - - /** - * @returns the notification icon. On clicking, it should notify someone of a document been shared with them. - */ - @computed get notifyIcon() { - return Notify with message
}> -
- -
- ; - } - - /** - * ... next to the owner that opens the main SharingManager interface on click. - */ - @computed get expansionIcon() { - return {"Show more permissions"}
}> -
{ - if (this.selectedDocumentView || this.selectedDoc) { - SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc); - } - }}> - -
- ; - } - - /** - * @returns a row of the permissions panel - */ - sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { - return
this.selectedUser = this.selectedUser === name ? "" : name)} - > -
{name}
- {/* {name !== "Me" ? this.notifyIcon : null} */} -
- {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission} - {permission === "Owner" || showExpansionIcon ? this.expansionIcon : null} -
-
; - } - - /** - * @returns the sharing and permissions panel. - */ - @computed get sharingTable() { - const AclMap = new Map([ - [AclUnset, "None"], - [AclPrivate, SharingPermissions.None], - [AclReadonly, SharingPermissions.View], - [AclAddonly, SharingPermissions.Add], - [AclEdit, SharingPermissions.Edit], - [AclAdmin, SharingPermissions.Admin] - ]); - - // all selected docs - const docs = SelectionManager.SelectedDocuments().length < 2 ? - [this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym]] - : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]); - - const target = docs[0]; - - // tslint:disable-next-line: no-unnecessary-callback-wrapper - const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); - const showAdmin = effectiveAcls.every(acl => acl === AclAdmin); - - // users in common between all docs - const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym]))); - - const tableEntries = []; - - // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { - if (commonKeys.length) { - for (const key of commonKeys) { - const name = key.substring(4).replace("_", "."); - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]); - if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) { - tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-")); - } - } + return true; + } else if (value[0] === "#") { + const newVal = value + `:'${value}'`; + doc[DataSym][value] = value; + const tags = StrCast(doc.tags, ":"); + if (!tags.includes(`${value}:`)) { + doc[DataSym].tags = `${tags + value + ':'}`; } - - // 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)); - // } - // } - // }) - - const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); - // shifts the current user, owner, public to the top of the doc. - tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-")); - tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).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.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame)); - if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); - - return
- {tableEntries} -
; - } - - @computed get fieldsCheckbox() { - return ; - } - - @action - toggleCheckbox = () => { - this.layoutFields = !this.layoutFields; + return true; + } + }); + return false; + } + + @observable transform: Transform = Transform.Identity(); + getTransform = () => this.transform; + propertiesDocViewRef = (ref: HTMLDivElement) => { + const observer = new _global.ResizeObserver(action((entries: any) => { + const cliRect = ref.getBoundingClientRect(); + this.transform = new Transform(-cliRect.x, -cliRect.y, 1); + })); + ref && observer.observe(ref); + } + + @computed get contexts() { + return !this.selectedDoc ? (null) : CollectionDockingView.AddSplit(doc, "right")} />; + } + + previewBackground = () => "lightgrey"; + @computed get layoutPreview() { + if (SelectionManager.SelectedDocuments().length > 1) { + return "-- multiple selected --"; } - - @computed get editableTitle() { - const titles = new Set(); - SelectionManager.SelectedDocuments().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); - const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title); - return
title} - SetValue={this.setTitle} />
; + if (this.selectedDoc) { + const layoutDoc = Doc.Layout(this.selectedDoc); + 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
+ false} + whenActiveChanged={emptyFunction} + addDocTab={returnFalse} + pinToPres={emptyFunction} + bringToFront={returnFalse} + ContentScaling={returnOne} + dontRegisterView={true} + dropAction={undefined} + /> +
; + } else { + return null; } - - @undoBatch - @action - setTitle = (value: string) => { - if (SelectionManager.SelectedDocuments().length > 1) { - SelectionManager.SelectedDocuments().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true)); - return true; - } else if (this.dataDoc) { - if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true); - else KeyValueBox.SetField(this.dataDoc, "title", value, true); - return true; + } + + /** + * Handles the changing of a user's permissions from the permissions panel. + */ + @undoBatch + changePermissions = (e: any, user: string) => { + const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.selectedDoc!] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); + SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); + } + + /** + * @returns the options for the permissions dropdown. + */ + getPermissionsSelect(user: string, permission: string) { + const dropdownValues: string[] = Object.values(SharingPermissions); + if (permission === "-multiple-") dropdownValues.unshift(permission); + if (user === "Override") dropdownValues.unshift("None"); + return ; + } + + /** + * @returns the notification icon. On clicking, it should notify someone of a document been shared with them. + */ + @computed get notifyIcon() { + return Notify with message
}> +
+ +
+ ; + } + + /** + * ... next to the owner that opens the main SharingManager interface on click. + */ + @computed get expansionIcon() { + return {"Show more permissions"}}> +
{ + if (this.selectedDocumentView || this.selectedDoc) { + SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc); } - return false; - } - - - @undoBatch - @action - rotate = (angle: number) => { - const _centerPoints: { X: number, Y: number }[] = []; - if (this.selectedDoc) { - const doc = this.selectedDoc; - if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - const xs = ink.map(p => p.X); - const ys = ink.map(p => p.Y); - const left = Math.min(...xs); - const top = Math.min(...ys); - const right = Math.max(...xs); - const bottom = Math.max(...ys); - _centerPoints.push({ X: left, Y: top }); - } - } - - var index = 0; - if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { - doc.rotation = Number(doc.rotation) + Number(angle); - const inks = Cast(doc.data, InkField)?.inkData; - if (inks) { - const newPoints: { X: number, Y: number }[] = []; - inks.forEach(ink => { - const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; - const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; - newPoints.push({ X: newX, Y: newY }); - }); - doc.data = new InkField(newPoints); - const xs = newPoints.map(p => p.X); - const ys = newPoints.map(p => p.Y); - const left = Math.min(...xs); - const top = Math.min(...ys); - const right = Math.max(...xs); - const bottom = Math.max(...ys); - - doc._height = (bottom - top); - doc._width = (right - left); - } - index++; - } + }}> + +
+
; + } + + /** + * @returns a row of the permissions panel + */ + sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { + return
this.selectedUser = this.selectedUser === name ? "" : name)} + > +
{name}
+ {/* {name !== "Me" ? this.notifyIcon : null} */} +
+ {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission} + {permission === "Owner" || showExpansionIcon ? this.expansionIcon : null} +
+
; + } + + /** + * @returns the sharing and permissions panel. + */ + @computed get sharingTable() { + const AclMap = new Map([ + [AclUnset, "None"], + [AclPrivate, SharingPermissions.None], + [AclReadonly, SharingPermissions.View], + [AclAddonly, SharingPermissions.Add], + [AclEdit, SharingPermissions.Edit], + [AclAdmin, SharingPermissions.Admin] + ]); + + // all selected docs + const docs = SelectionManager.SelectedDocuments().length < 2 ? + [this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym]] + : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]); + + const target = docs[0]; + + // tslint:disable-next-line: no-unnecessary-callback-wrapper + const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); + const showAdmin = effectiveAcls.every(acl => acl === AclAdmin); + + // users in common between all docs + const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym]))); + + const tableEntries = []; + + // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { + if (commonKeys.length) { + for (const key of commonKeys) { + const name = key.substring(4).replace("_", "."); + const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]); + if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) { + tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-")); } + } } - - - @computed - get controlPointsButton() { - const formatInstance = InkStrokeProperties.Instance; - return !formatInstance ? (null) :
- {"Edit points"}
}> -
formatInstance._controlBtn = !formatInstance._controlBtn)} style={{ backgroundColor: formatInstance._controlBtn ? "black" : "" }}> - -
- - {formatInstance._lock ? "Unlock ratio" : "Lock ratio"}}> -
formatInstance._lock = !formatInstance._lock)} > - -
-
- {"Rotate 90˚"}}> -
this.rotate(Math.PI / 2))}> - -
-
- ; - } - - inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => { - return
-
{title}
- { - setter(e.target.value); - }} - onKeyPress={e => { - e.stopPropagation(); - }} /> -
-
this.upDownButtons("up", key)))} > - -
-
this.upDownButtons("down", key)))} > - -
-
-
; - } - - inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => { - return
- {this.inputBox(key, value, setter, title1)} - {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)} -
; + // 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)); + // } + // } + // }) + + const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); + // shifts the current user, owner, public to the top of the doc. + tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-")); + tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).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.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame)); + if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); + + return
+ {tableEntries} +
; + } + + @computed get fieldsCheckbox() { + return ; + } + + @action + toggleCheckbox = () => { + this.layoutFields = !this.layoutFields; + } + + @computed get editableTitle() { + const titles = new Set(); + SelectionManager.SelectedDocuments().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); + const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title); + return
title} + SetValue={this.setTitle} />
; + } + + @undoBatch + @action + setTitle = (value: string) => { + if (SelectionManager.SelectedDocuments().length > 1) { + SelectionManager.SelectedDocuments().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true)); + return true; + } else if (this.dataDoc) { + if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true); + else KeyValueBox.SetField(this.dataDoc, "title", value, true); + return true; } - - @action - upDownButtons = (dirs: string, field: string) => { - switch (field) { - case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break; - // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break; - case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break; - case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break; - case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break; - case "wid": - const oldWidth = NumCast(this.selectedDoc?._width); - const oldHeight = NumCast(this.selectedDoc?._height); - const oldX = NumCast(this.selectedDoc?.x); - const oldY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height))); - const doc = this.selectedDoc; - if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - const newPoints: { X: number, Y: number }[] = []; - for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth; - const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight; - newPoints.push({ X: newX, Y: newY }); - } - doc.data = new InkField(newPoints); - } - } - break; - case "hgt": - const oWidth = NumCast(this.selectedDoc?._width); - const oHeight = NumCast(this.selectedDoc?._height); - const oX = NumCast(this.selectedDoc?.x); - const oY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width))); - const docu = this.selectedDoc; - if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) { - const ink = Cast(docu.data, InkField)?.inkData; - if (ink) { - const newPoints: { X: number, Y: number }[] = []; - for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth; - const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight; - newPoints.push({ X: newX, Y: newY }); - } - docu.data = new InkField(newPoints); - } - } - break; + return false; + } + + + @undoBatch + @action + rotate = (angle: number) => { + const _centerPoints: { X: number, Y: number }[] = []; + if (this.selectedDoc) { + const doc = this.selectedDoc; + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { + const xs = ink.map(p => p.X); + const ys = ink.map(p => p.Y); + const left = Math.min(...xs); + const top = Math.min(...ys); + const right = Math.max(...xs); + const bottom = Math.max(...ys); + _centerPoints.push({ X: left, Y: top }); } + } + + var index = 0; + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { + doc.rotation = Number(doc.rotation) + Number(angle); + const inks = Cast(doc.data, InkField)?.inkData; + if (inks) { + const newPoints: { X: number, Y: number }[] = []; + inks.forEach(ink => { + const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; + const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; + newPoints.push({ X: newX, Y: newY }); + }); + doc.data = new InkField(newPoints); + const xs = newPoints.map(p => p.X); + const ys = newPoints.map(p => p.Y); + const left = Math.min(...xs); + const top = Math.min(...ys); + const right = Math.max(...xs); + const bottom = Math.max(...ys); + + doc._height = (bottom - top); + doc._width = (right - left); + } + index++; + } } - - getField(key: string) { - //if (this.selectedDoc) { - return Field.toString(this.selectedDoc?.[key] as Field); - // } else { - // return undefined as Opt; - // } - } - - @computed get shapeXps() { return this.getField("x"); } - @computed get shapeYps() { return this.getField("y"); } - @computed get shapeRot() { return this.getField("rotation"); } - @computed get shapeHgt() { return this.getField("_height"); } - @computed get shapeWid() { return this.getField("_width"); } - set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); } - set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); } - set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); } - set shapeWid(value) { + } + + + + @computed + get controlPointsButton() { + const formatInstance = InkStrokeProperties.Instance; + return !formatInstance ? (null) :
+ {"Edit points"}
}> +
formatInstance._controlBtn = !formatInstance._controlBtn)} style={{ backgroundColor: formatInstance._controlBtn ? "black" : "" }}> + +
+ + {formatInstance._lock ? "Unlock ratio" : "Lock ratio"}}> +
formatInstance._lock = !formatInstance._lock)} > + +
+
+ {"Rotate 90˚"}}> +
this.rotate(Math.PI / 2))}> + +
+
+ ; + } + + inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => { + return
+
{title}
+ { + setter(e.target.value); + }} + onKeyPress={e => { + e.stopPropagation(); + }} /> +
+
this.upDownButtons("up", key)))} > + +
+
this.upDownButtons("down", key)))} > + +
+
+
; + } + + inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => { + return
+ {this.inputBox(key, value, setter, title1)} + {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)} +
; + } + + @action + upDownButtons = (dirs: string, field: string) => { + switch (field) { + case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break; + // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break; + case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break; + case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break; + case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break; + case "wid": const oldWidth = NumCast(this.selectedDoc?._width); - this.selectedDoc && (this.selectedDoc._width = Number(value)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth); - } - set shapeHgt(value) { const oldHeight = NumCast(this.selectedDoc?._height); - this.selectedDoc && (this.selectedDoc._height = Number(value)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight); - } - - @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); } - @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); } - @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); } - - - @observable private _fillBtn = false; - @observable private _lineBtn = false; - - private _lastFill = "#D0021B"; - private _lastLine = "#D0021B"; - private _lastDash: any = "2"; - - @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; } - @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } - set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } - set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } - - colorButton(value: string, type: string, setter: () => {}) { - // return
this.changeScrolling(false)} - // onPointerLeave={e => this.changeScrolling(true)}> - // - return
setter()))}> -
- {value === "" || value === "transparent" ?

: ""} -
; - // - //
; - - } - - @undoBatch - @action - switchStk = (color: ColorState) => { - const val = String(color.hex); - this.colorStk = val; - return true; - } - @undoBatch - @action - switchFil = (color: ColorState) => { - const val = String(color.hex); - this.colorFil = val; - return true; - } - - colorPicker(setter: (color: string) => {}, type: string) { - return ; - } - - @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } - @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } - - @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } - @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } - - @computed get strokeAndFill() { - return
-
-
-
Fill:
-
{this.fillButton}
-
-
-
Stroke:
-
{this.lineButton}
-
-
- {this._fillBtn ? this.fillPicker : ""} - {this._lineBtn ? this.linePicker : ""} -
; - } - - @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; } - @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; } - @computed get unStrokd() { return this.selectedDoc?.color ? true : false; } - @computed get widthStk() { return this.getField("strokeWidth") || "1"; } - @computed get markHead() { return this.getField("strokeStartMarker") || ""; } - @computed get markTail() { return this.getField("strokeEndMarker") || ""; } - set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; } - set dashdStk(value) { - value && (this._lastDash = value) && (this.unStrokd = false); - this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined); - } - set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); } - set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; } - set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); } - set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); } - - - @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); } - - - regInput = (key: string, value: any, setter: (val: string) => {}) => { - return
- setter(e.target.value)} /> -
-
this.upDownButtons("up", key)))} > - -
-
this.upDownButtons("down", key)))} > - -
-
-
; - } - - @computed get widthAndDash() { - return
-
-
-
Width:
-
{this.stkInput}
-
- this.widthStk = e.target.value))} - onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }} - onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }} - /> -
- -
-
-
Arrow Head:
- this.markHead = this.markHead ? "" : "arrow"))} /> -
-
-
Arrow End:
- this.markTail = this.markTail ? "" : "arrow"))} /> -
-
-
-
Dashed Line:
- -
-
; - } - - @undoBatch @action - changeDash = () => { - this.dashdStk = this.dashdStk === "2" ? "0" : "2"; - } - - @computed get appearanceEditor() { - return
- {this.widthAndDash} - {this.strokeAndFill} -
; - } - - @computed get transformEditor() { - return
- {this.controlPointsButton} - {this.hgtInput} - {this.XpsInput} - {this.rotInput} -
; + const oldX = NumCast(this.selectedDoc?.x); + const oldY = NumCast(this.selectedDoc?.y); + this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height))); + const doc = this.selectedDoc; + if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { + const newPoints: { X: number, Y: number }[] = []; + for (var j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth; + const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight; + newPoints.push({ X: newX, Y: newY }); + } + doc.data = new InkField(newPoints); + } + } + break; + case "hgt": + const oWidth = NumCast(this.selectedDoc?._width); + const oHeight = NumCast(this.selectedDoc?._height); + const oX = NumCast(this.selectedDoc?.x); + const oY = NumCast(this.selectedDoc?.y); + this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width))); + const docu = this.selectedDoc; + if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) { + const ink = Cast(docu.data, InkField)?.inkData; + if (ink) { + const newPoints: { X: number, Y: number }[] = []; + for (var j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth; + const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight; + newPoints.push({ X: newX, Y: newY }); + } + docu.data = new InkField(newPoints); + } + } + break; } + } - /** - * Handles adding and removing members from the sharing panel - */ - // handleUserChange = (selectedUser: string, add: boolean) => { - // if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc; - // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { - // sidebarUsersDisplayed![`display-${selectedUser}`] = add; - // !add && runInAction(() => this.selectedUser = ""); - // }); + getField(key: string) { + //if (this.selectedDoc) { + return Field.toString(this.selectedDoc?.[key] as Field); + // } else { + // return undefined as Opt; // } - - render() { - if (!this.selectedDoc && !this.isPres) { - return
-
- No Document Selected + } + + @computed get shapeXps() { return this.getField("x"); } + @computed get shapeYps() { return this.getField("y"); } + @computed get shapeRot() { return this.getField("rotation"); } + @computed get shapeHgt() { return this.getField("_height"); } + @computed get shapeWid() { return this.getField("_width"); } + set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); } + set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); } + set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); } + set shapeWid(value) { + const oldWidth = NumCast(this.selectedDoc?._width); + this.selectedDoc && (this.selectedDoc._width = Number(value)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth); + } + set shapeHgt(value) { + const oldHeight = NumCast(this.selectedDoc?._height); + this.selectedDoc && (this.selectedDoc._height = Number(value)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight); + } + + @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); } + @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); } + @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); } + + + @observable private _fillBtn = false; + @observable private _lineBtn = false; + + private _lastFill = "#D0021B"; + private _lastLine = "#D0021B"; + private _lastDash: any = "2"; + + @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; } + @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } + set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } + set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } + + colorButton(value: string, type: string, setter: () => {}) { + // return
this.changeScrolling(false)} + // onPointerLeave={e => this.changeScrolling(true)}> + // + return
setter()))}> +
+ {value === "" || value === "transparent" ?

: ""} +
; + // + //
; + + } + + @undoBatch + @action + switchStk = (color: ColorState) => { + const val = String(color.hex); + this.colorStk = val; + return true; + } + @undoBatch + @action + switchFil = (color: ColorState) => { + const val = String(color.hex); + this.colorFil = val; + return true; + } + + colorPicker(setter: (color: string) => {}, type: string) { + return ; + } + + @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } + @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } + + @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } + @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } + + @computed get strokeAndFill() { + return
+
+
+
Fill:
+
{this.fillButton}
+
+
+
Stroke:
+
{this.lineButton}
+
+
+ {this._fillBtn ? this.fillPicker : ""} + {this._lineBtn ? this.linePicker : ""} +
; + } + + @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; } + @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; } + @computed get unStrokd() { return this.selectedDoc?.color ? true : false; } + @computed get widthStk() { return this.getField("strokeWidth") || "1"; } + @computed get markHead() { return this.getField("strokeStartMarker") || ""; } + @computed get markTail() { return this.getField("strokeEndMarker") || ""; } + set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; } + set dashdStk(value) { + value && (this._lastDash = value) && (this.unStrokd = false); + this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined); + } + set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); } + set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; } + set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); } + set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); } + + + @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); } + + + regInput = (key: string, value: any, setter: (val: string) => {}) => { + return
+ setter(e.target.value)} /> +
+
this.upDownButtons("up", key)))} > + +
+
this.upDownButtons("down", key)))} > + +
+
+
; + } + + @computed get widthAndDash() { + return
+
+
+
Width:
+
{this.stkInput}
+
+ this.widthStk = e.target.value))} + onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }} + onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }} + /> +
+ +
+
+
Arrow Head:
+ this.markHead = this.markHead ? "" : "arrow"))} /> +
+
+
Arrow End:
+ this.markTail = this.markTail ? "" : "arrow"))} /> +
+
+
+
Dashed Line:
+ +
+
; + } + + @undoBatch @action + changeDash = () => { + this.dashdStk = this.dashdStk === "2" ? "0" : "2"; + } + + @computed get appearanceEditor() { + return
+ {this.widthAndDash} + {this.strokeAndFill} +
; + } + + @computed get transformEditor() { + return
+ {this.controlPointsButton} + {this.hgtInput} + {this.XpsInput} + {this.rotInput} +
; + } + + /** + * Handles adding and removing members from the sharing panel + */ + // handleUserChange = (selectedUser: string, add: boolean) => { + // if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc; + // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { + // sidebarUsersDisplayed![`display-${selectedUser}`] = add; + // !add && runInAction(() => this.selectedUser = ""); + // }); + // } + + render() { + if (!this.selectedDoc && !this.isPres) { + return
+
+ No Document Selected
-
; - - } else { - const novice = Doc.UserDoc().noviceMode; - - if (this.selectedDoc && !this.isPres) { - return
-
- Properties +
; + + } else { + const novice = Doc.UserDoc().noviceMode; + + if (this.selectedDoc && !this.isPres) { + return
+
+ Properties
-
- {this.editableTitle} -
-
this.inOptions = true)} - onPointerLeave={action(() => this.inOptions = false)}> -
this.openOptions = !this.openOptions)} - style={{ backgroundColor: this.openOptions ? "black" : "" }}> - Options +
+ {this.editableTitle} +
+
this.inOptions = true)} + onPointerLeave={action(() => this.inOptions = false)}> +
this.openOptions = !this.openOptions)} + style={{ backgroundColor: this.openOptions ? "black" : "" }}> + Options
- -
-
- {!this.openOptions ? (null) : -
- -
} -
-
-
this.openSharing = !this.openSharing)} - style={{ backgroundColor: this.openSharing ? "black" : "" }}> - Sharing {"&"} Permissions + +
+
+ {!this.openOptions ? (null) : +
+ +
} +
+
+
this.openSharing = !this.openSharing)} + style={{ backgroundColor: this.openSharing ? "black" : "" }}> + Sharing {"&"} Permissions
- -
-
- {!this.openSharing ? (null) : -
-
- {!novice ? (
- this.layoutDocAcls = !this.layoutDocAcls)} - checked={this.layoutDocAcls} - /> -
Layout
-
) : (null)} - -
- {this.sharingTable} -
} -
- - {!this.isInk ? (null) : -
-
this.openAppearance = !this.openAppearance)} - style={{ backgroundColor: this.openAppearance ? "black" : "" }}> - Appearance + +
+
+ {!this.openSharing ? (null) : +
+
+ {!novice ? (
+ this.layoutDocAcls = !this.layoutDocAcls)} + checked={this.layoutDocAcls} + /> +
Layout
+
) : (null)} +
{"Re-distribute sharing settings"}
}> + +
+
+ {this.sharingTable} +
} +
+ + {!this.isInk ? (null) : +
+
this.openAppearance = !this.openAppearance)} + style={{ backgroundColor: this.openAppearance ? "black" : "" }}> + Appearance
- -
-
- {!this.openAppearance ? (null) : -
- {this.appearanceEditor} -
} -
} - - {this.isInk ?
-
this.openTransform = !this.openTransform)} - style={{ backgroundColor: this.openTransform ? "black" : "" }}> - Transform + +
+
+ {!this.openAppearance ? (null) : +
+ {this.appearanceEditor} +
} +
} + + {this.isInk ?
+
this.openTransform = !this.openTransform)} + style={{ backgroundColor: this.openTransform ? "black" : "" }}> + Transform
- -
-
- {this.openTransform ?
- {this.transformEditor} -
: null} -
: null} - -
-
this.openFields = !this.openFields)} - style={{ backgroundColor: this.openFields ? "black" : "" }}> - Fields {"&"} Tags + +
+
+ {this.openTransform ?
+ {this.transformEditor} +
: null} +
: null} + +
+
this.openFields = !this.openFields)} + style={{ backgroundColor: this.openFields ? "black" : "" }}> + Fields {"&"} Tags
- -
-
- {!novice && this.openFields ?
- {this.fieldsCheckbox} -
Layout
-
: null} - {!this.openFields ? (null) : -
- {novice ? this.noviceFields : this.expandedField} -
} -
-
-
this.openContexts = !this.openContexts)} - style={{ backgroundColor: this.openContexts ? "black" : "" }}> - Contexts + +
+
+ {!novice && this.openFields ?
+ {this.fieldsCheckbox} +
Layout
+
: null} + {!this.openFields ? (null) : +
+ {novice ? this.noviceFields : this.expandedField} +
} +
+
+
this.openContexts = !this.openContexts)} + style={{ backgroundColor: this.openContexts ? "black" : "" }}> + Contexts
- -
-
- {this.openContexts ?
{this.contexts}
: null} -
-
-
this.openLayout = !this.openLayout)} - style={{ backgroundColor: this.openLayout ? "black" : "" }}> - Layout + +
+
+ {this.openContexts ?
{this.contexts}
: null} +
+
+
this.openLayout = !this.openLayout)} + style={{ backgroundColor: this.openLayout ? "black" : "" }}> + Layout
- -
-
- {this.openLayout ?
{this.layoutPreview}
: null} -
-
; - } - if (this.isPres) { - const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0; - const type = PresBox.Instance.activeItem?.type; - return
-
- Presentation + +
+
+ {this.openLayout ?
{this.layoutPreview}
: null} +
+ ; + } + if (this.isPres) { + const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0; + const type = PresBox.Instance.activeItem?.type; + return
+
+ Presentation
-
- {this.editableTitle} -
- {PresBox.Instance?._selectedArray.length} selected +
+ {this.editableTitle} +
+ {PresBox.Instance?._selectedArray.length} selected
- {PresBox.Instance?.listOfSelected} -
-
-
- {!selectedItem ? (null) :
-
{ this.openPresTransitions = !this.openPresTransitions; })} - style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> -     Transitions + {PresBox.Instance?.listOfSelected} +
+
+
+ {!selectedItem ? (null) :
+
{ this.openPresTransitions = !this.openPresTransitions; })} + style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> +     Transitions
- -
-
- {this.openPresTransitions ?
- {PresBox.Instance.transitionDropdown} -
: null} -
} - {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) :
-
this.openPresProgressivize = !this.openPresProgressivize)} - style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> -     Progressivize + +
+
+ {this.openPresTransitions ?
+ {PresBox.Instance.transitionDropdown} +
: null} +
} + {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) :
+
this.openPresProgressivize = !this.openPresProgressivize)} + style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> +     Progressivize
- -
-
- {this.openPresProgressivize ?
- {PresBox.Instance.progressivizeDropdown} -
: null} -
} - {!selectedItem || (type !== DocumentType.COL && type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) :
-
{ this.openSlideOptions = !this.openSlideOptions; })} - style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> -     {PresBox.Instance.stringType} options + +
+
+ {this.openPresProgressivize ?
+ {PresBox.Instance.progressivizeDropdown} +
: null} +
} + {!selectedItem || (type !== DocumentType.COL && type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) :
+
{ this.openSlideOptions = !this.openSlideOptions; })} + style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> +     {PresBox.Instance.stringType} options
- -
-
- {this.openSlideOptions ?
- {PresBox.Instance.optionsDropdown} -
: null} -
} -
-
{ this.openAddSlide = !this.openAddSlide; })} - style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> -     Add new slide + +
+
+ {this.openSlideOptions ?
+ {PresBox.Instance.optionsDropdown} +
: null} + } +
+
{ this.openAddSlide = !this.openAddSlide; })} + style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> +     Add new slide
- -
-
- {this.openAddSlide ?
- {PresBox.Instance.newDocumentDropdown} -
: null} -
- ; - } - } + + + + {this.openAddSlide ?
+ {PresBox.Instance.newDocumentDropdown} +
: null} + + ; + } } + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 92bccbcd1cefca43807541858a7be58d8c3c53c7 Mon Sep 17 00:00:00 2001 From: Melissa Zhang Date: Wed, 30 Sep 2020 22:47:12 -0600 Subject: fix 3DCarouselView mis-aligned slides --- .../views/collections/CollectionCarousel3DView.tsx | 318 ++++++++++----------- 1 file changed, 159 insertions(+), 159 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 4f1ef6e61..550a2f708 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -19,163 +19,163 @@ const Carousel3DDocument = makeInterface(documentSchema, collectionSchema); @observer export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocument) { - @computed get scrollSpeed() { - return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed - } - - private _dropDisposer?: DragManager.DragDropDisposer; - - componentWillUnmount() { this._dropDisposer?.(); } - - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view - this._dropDisposer?.(); - if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); - } - } - - panelWidth = () => this.props.PanelWidth() / 3; - panelHeight = () => this.props.PanelHeight() * 0.6; - onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); - @computed get content() { - const currentIndex = NumCast(this.layoutDoc._itemIndex); - const displayDoc = (childPair: { layout: Doc, data: Doc }) => { - const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout }); - const onChildClick = script && (() => script); - return ; - }; - - return (this.childLayoutPairs.map((childPair, index) => { - return ( -
- {displayDoc(childPair)} -
); - })); - } - - changeSlide = (direction: number) => { - this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length; - } - - onArrowClick = (e: React.MouseEvent, direction: number) => { - e.stopPropagation(); - this.changeSlide(direction); - !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = (direction === 1) ? "fwd" : "back"); // while autoscroll is on, keep the other autoscroll button hidden - !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on - } - - interval?: number; - startAutoScroll = (direction: number) => { - this.interval = window.setInterval(() => { - this.changeSlide(direction); - }, this.scrollSpeed); - } - - stopAutoScroll = () => { - window.clearInterval(this.interval); - this.interval = undefined; - this.fadeScrollButton(); - } - - toggleAutoScroll = (direction: number) => { - this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true; - this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll(); - } - - fadeScrollButton = () => { - window.setTimeout(() => { - !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = "none"); //fade away after 1.5s if it's not clicked. - }, 1500); - } - - _downX = 0; - _downY = 0; - onPointerDown = (e: React.PointerEvent) => { - this._downX = e.clientX; - this._downY = e.clientY; - document.addEventListener("pointerup", this.onpointerup); - } - private _lastTap: number = 0; - private _doubleTap = false; - onpointerup = (e: PointerEvent) => { - this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); - this._lastTap = Date.now(); - } - - onClick = (e: React.MouseEvent) => { - if (this._doubleTap) { - e.stopPropagation(); - this.props.Document.isLightboxOpen = true; - } - } - - @computed get buttons() { - if (!this.props.active()) return null; - return
-
this.onArrowClick(e, -1)} - > - -
-
this.onArrowClick(e, 1)} - > - -
- {this.autoScrollButton} -
; - } - - @computed get autoScrollButton() { - const whichButton = this.layoutDoc.showScrollButton; - return <> -
this.toggleAutoScroll(-1)}> - {this.layoutDoc.autoScrollOn ? : } -
-
this.toggleAutoScroll(1)}> - {this.layoutDoc.autoScrollOn ? : } -
- ; - } - - @computed get dots() { - return (this.childLayoutPairs.map((_child, index) => { - return
this.layoutDoc._itemIndex = index} />; - })); - } - - render() { - const index = NumCast(this.layoutDoc._itemIndex); - const translateX = (1 - index) / this.childLayoutPairs.length * 100; - - return
-
- {this.content} -
- {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)} -
- {this.dots} -
-
; - } + @computed get scrollSpeed() { + return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed + } + + private _dropDisposer?: DragManager.DragDropDisposer; + + componentWillUnmount() { this._dropDisposer?.(); } + + protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + this._dropDisposer?.(); + if (ele) { + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + } + } + + panelWidth = () => this.props.PanelWidth() / 3; + panelHeight = () => this.props.PanelHeight() * 0.6; + onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); + @computed get content() { + const currentIndex = NumCast(this.layoutDoc._itemIndex); + const displayDoc = (childPair: { layout: Doc, data: Doc }) => { + const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout }); + const onChildClick = script && (() => script); + return ; + }; + + return (this.childLayoutPairs.map((childPair, index) => { + return ( +
+ {displayDoc(childPair)} +
); + })); + } + + changeSlide = (direction: number) => { + this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length; + } + + onArrowClick = (e: React.MouseEvent, direction: number) => { + e.stopPropagation(); + this.changeSlide(direction); + !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = (direction === 1) ? "fwd" : "back"); // while autoscroll is on, keep the other autoscroll button hidden + !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on + } + + interval?: number; + startAutoScroll = (direction: number) => { + this.interval = window.setInterval(() => { + this.changeSlide(direction); + }, this.scrollSpeed); + } + + stopAutoScroll = () => { + window.clearInterval(this.interval); + this.interval = undefined; + this.fadeScrollButton(); + } + + toggleAutoScroll = (direction: number) => { + this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true; + this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll(); + } + + fadeScrollButton = () => { + window.setTimeout(() => { + !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = "none"); //fade away after 1.5s if it's not clicked. + }, 1500); + } + + _downX = 0; + _downY = 0; + onPointerDown = (e: React.PointerEvent) => { + this._downX = e.clientX; + this._downY = e.clientY; + document.addEventListener("pointerup", this.onpointerup); + } + private _lastTap: number = 0; + private _doubleTap = false; + onpointerup = (e: PointerEvent) => { + this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); + this._lastTap = Date.now(); + } + + onClick = (e: React.MouseEvent) => { + if (this._doubleTap) { + e.stopPropagation(); + this.props.Document.isLightboxOpen = true; + } + } + + @computed get buttons() { + if (!this.props.active()) return null; + return
+
this.onArrowClick(e, -1)} + > + +
+
this.onArrowClick(e, 1)} + > + +
+ {this.autoScrollButton} +
; + } + + @computed get autoScrollButton() { + const whichButton = this.layoutDoc.showScrollButton; + return <> +
this.toggleAutoScroll(-1)}> + {this.layoutDoc.autoScrollOn ? : } +
+
this.toggleAutoScroll(1)}> + {this.layoutDoc.autoScrollOn ? : } +
+ ; + } + + @computed get dots() { + return (this.childLayoutPairs.map((_child, index) => { + return
this.layoutDoc._itemIndex = index} />; + })); + } + + render() { + const index = NumCast(this.layoutDoc._itemIndex); + const translateX = 33 * (1 - index); + + return
+
+ {this.content} +
+ {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)} +
+ {this.dots} +
+
; + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From df25be868115b378e208fc729b75d606be279164 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 10:15:26 -0400 Subject: changed sharing to use __ to replace .'s in emails to avoid problems with storing keys in mongo --- src/client/util/SharingManager.tsx | 16 ++++++++-------- src/fields/util.ts | 9 ++++++++- 2 files changed, 16 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index bcd7d4056..984583ed5 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -7,7 +7,7 @@ import * as RequestPromise from "request-promise"; import { AclAdmin, AclPrivate, DataSym, Doc, DocListCast, Opt, AclSym, AclAddonly, AclEdit, AclReadonly } from "../../fields/Doc"; import { List } from "../../fields/List"; import { Cast, StrCast } from "../../fields/Types"; -import { distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from "../../fields/util"; +import { distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx, normalizeEmail } from "../../fields/util"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; import { CollectionView } from "../views/collections/CollectionView"; @@ -154,13 +154,13 @@ export class SharingManager extends React.Component<{}> { setInternalGroupSharing = (group: Doc | { groupName: string }, permission: string, targetDoc?: Doc) => { const target = targetDoc || this.targetDoc!; - const key = StrCast(group.groupName).replace(".", "_"); + const key = normalizeEmail(StrCast(group.groupName)); 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); + doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc); distributeAcls(acl, permission as SharingPermissions, doc); if (group instanceof Doc) { @@ -246,13 +246,13 @@ export class SharingManager extends React.Component<{}> { setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { const { user, notificationDoc } = recipient; const target = targetDoc || this.targetDoc!; - const key = user.email.replace('.', '_'); + const key = normalizeEmail(user.email); 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); + doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc); distributeAcls(acl, permission as SharingPermissions, doc); if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); @@ -456,8 +456,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('.', '_')}`) : docs[0]?.author !== user.email).map(({ user, notificationDoc, userColor }) => { - const userKey = `acl-${user.email.replace('.', '_')}`; + const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, notificationDoc, userColor }) => { + const userKey = `acl-${normalizeEmail(user.email)}`; 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-"; @@ -514,7 +514,7 @@ export class SharingManager extends React.Component<{}> { Me
- {targetDoc?.[`acl-${Doc.CurrentUserEmail.replace(".", "_")}`]} + {targetDoc?.[`acl-${Doc.CurrentUserEmailNormalized}`]}
diff --git a/src/fields/util.ts b/src/fields/util.ts index f0ff2dad4..6c92aef81 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -116,6 +116,13 @@ export function OVERRIDE_acl(val: boolean) { _overrideAcl = val; } +export function normalizeEmail(email: string) { + return email.replace('.', '__'); +} +export function denormalizeEmail(email: string) { + return email.replace('__', '.'); +} + // playground mode allows the user to add/delete documents or make layout changes without them saving to the server // let playgroundMode = false; @@ -184,7 +191,7 @@ export function GetEffectiveAcl(target: any, in_prop?: string | symbol | number, for (const [key, value] of Object.entries(target[AclSym])) { // there are issues with storing fields with . in the name, so they are replaced with _ during creation // as a result we need to restore them again during this comparison. - const entity = key.substring(4).replace('_', '.'); // an individual or a group + const entity = denormalizeEmail(key.substring(4)); // an individual or a group if (currentUserGroups.includes(entity) || userChecked === entity) { if (HierarchyMapping.get(value as symbol)! > HierarchyMapping.get(effectiveAcl)!) { effectiveAcl = value as symbol; -- cgit v1.2.3-70-g09d2 From 6b34d26af04f3325e0f0368668b2c90f1a2ce855 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 10:27:13 -0400 Subject: fixed clearing out doc data when permissions are lost. includes CurrentEmailNormalized from previous commit --- src/fields/Doc.ts | 74 +++++++++++++++++++++---------------------------------- 1 file changed, 28 insertions(+), 46 deletions(-) (limited to 'src') diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 48fd831d6..59be3a27c 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -19,42 +19,30 @@ import { DateField } from "./DateField"; import { listSpec } from "./Schema"; import { ComputedField, ScriptField } from "./ScriptField"; import { Cast, FieldValue, NumCast, StrCast, ToConstructor } from "./Types"; -import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl, SharingPermissions } from "./util"; +import { deleteProperty, getField, getter, makeEditable, makeReadOnly, setter, updateFunction, GetEffectiveAcl, SharingPermissions, normalizeEmail } from "./util"; import { LinkManager } from "../client/util/LinkManager"; import JSZip = require("jszip"); import { saveAs } from "file-saver"; import { CollectionDockingView } from "../client/views/collections/CollectionDockingView"; import { SelectionManager } from "../client/util/SelectionManager"; +import { CurrentUserUtils } from "../client/util/CurrentUserUtils"; export namespace Field { export function toKeyValueString(doc: Doc, key: string): string { const onDelegate = Object.keys(doc).includes(key); - const field = ComputedField.WithoutComputed(() => FieldValue(doc[key])); - if (Field.IsField(field)) { - return (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); - } - return ""; + return !Field.IsField(field) ? "" : (onDelegate ? "=" : "") + (field instanceof ComputedField ? `:=${field.script.originalScript}` : Field.toScriptString(field)); } export function toScriptString(field: Field): string { - if (typeof field === "string") { - return `"${field}"`; - } else if (typeof field === "number" || typeof field === "boolean") { - return String(field); - } else { - return field[ToScriptString](); - } + if (typeof field === "string") return `"${field}"`; + if (typeof field === "number" || typeof field === "boolean") return String(field); + return field[ToScriptString](); } export function toString(field: Field): string { - if (typeof field === "string") { - return field; - } else if (typeof field === "number" || typeof field === "boolean") { - return String(field); - } else if (field instanceof ObjectField) { - return field[ToString](); - } else if (field instanceof RefField) { - return field[ToString](); - } + if (typeof field === "string") return field; + if (typeof field === "number" || typeof field === "boolean") return String(field); + if (field instanceof ObjectField) return field[ToString](); + if (field instanceof RefField) return field[ToString](); return ""; } export function IsField(field: any): field is Field; @@ -86,16 +74,10 @@ export function DocListCastAsync(field: FieldResult, defaultValue?: Doc[]) { return list ? Promise.all(list).then(() => list) : Promise.resolve(defaultValue); } -export async function DocCastAsync(field: FieldResult): Promise> { - return Cast(field, Doc); -} +export async function DocCastAsync(field: FieldResult): Promise> { return Cast(field, Doc); } -export function DocListCast(field: FieldResult): Doc[] { - return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; -} -export function DocListCastOrNull(field: FieldResult) { - return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; -} +export function DocListCast(field: FieldResult) { return Cast(field, listSpec(Doc), []).filter(d => d instanceof Doc) as Doc[]; } +export function DocListCastOrNull(field: FieldResult) { return Cast(field, listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[] | undefined; } export const WidthSym = Symbol("Width"); export const HeightSym = Symbol("Height"); @@ -128,10 +110,6 @@ export function fetchProto(doc: Doc) { if (Object.keys(permissions).length) doc[AclSym] = permissions; - if (GetEffectiveAcl(doc) === AclPrivate) { - runInAction(() => doc[FieldsSym](true)); - } - if (doc.proto instanceof Promise) { doc.proto.then(fetchProto); return doc.proto; @@ -193,17 +171,11 @@ export class Doc extends RefField { } } private get __fieldKeys() { return this.___fieldKeys; } - private set __fieldKeys(value) { - this.___fieldKeys = value; - } - - @observable - private ___fields: any = {}; + private set __fieldKeys(value) { this.___fieldKeys = value; } - @observable - private ___fieldKeys: any = {}; - @observable - public [AclSym]: { [key: string]: symbol }; + @observable private ___fields: any = {}; + @observable private ___fieldKeys: any = {}; + @observable public [AclSym]: { [key: string]: symbol }; private [UpdatingFromServer]: boolean = false; @@ -213,7 +185,11 @@ export class Doc extends RefField { private [Self] = this; private [SelfProxy]: any; - public [FieldsSym](clear?: boolean) { return clear ? this.___fields = this.___fieldKeys = {} : this.___fields; } + public [FieldsSym](clear?: boolean) { + const self = this[SelfProxy]; + runInAction(() => clear && Array.from(Object.keys(self)).forEach(key => delete self[key])); + return this.___fields; + } public [WidthSym] = () => NumCast(this[SelfProxy]._width); public [HeightSym] = () => NumCast(this[SelfProxy]._height); public [ToScriptString] = () => `DOC-"${this[Self][Id]}"-`; @@ -242,6 +218,7 @@ export class Doc extends RefField { private [CachedUpdates]: { [key: string]: () => void | Promise } = {}; public static CurrentUserEmail: string = ""; + public static get CurrentUserEmailNormalized() { return normalizeEmail(Doc.CurrentUserEmail); } public async [HandleUpdate](diff: any) { const set = diff.$set; const sameAuthor = this.author === Doc.CurrentUserEmail; @@ -263,6 +240,11 @@ export class Doc extends RefField { if (prev === AclPrivate && GetEffectiveAcl(this) !== AclPrivate) { DocServer.GetRefField(this[Id], true); } + if (prev !== AclPrivate && GetEffectiveAcl(this) === AclPrivate) { + this[UpdatingFromServer] = true; + this[FieldsSym](true); + this[UpdatingFromServer] = false; + } }; if (sameAuthor || fKey.startsWith("acl") || DocServer.getFieldWriteMode(fKey) !== DocServer.WriteMode.Playground) { delete this[CachedUpdates][fKey]; -- cgit v1.2.3-70-g09d2 From 3c44131f75fe0c311363b36e824604c91f1babc8 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 11:15:41 -0400 Subject: fixed closed library flyout view to not render a DocumentView. fixed sidebar panels to not be selectable. fixed changing view of sidebar panel to only affect that panel --- src/client/util/CurrentUserUtils.ts | 14 ++--- src/client/util/SearchUtil.ts | 2 +- src/client/views/MainView.tsx | 69 +++++++++++++------------ src/client/views/collections/CollectionMenu.tsx | 3 +- 4 files changed, 46 insertions(+), 42 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 7cc35d67a..d45a2c90a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -566,7 +566,7 @@ export class CurrentUserUtils { title: "menuItemPanel", childDropAction: "alias", dropConverter: ScriptField.MakeScript("convertToButtons(dragData)", { dragData: DragManager.DocumentDragData.name }), - _backgroundColor: "black", + _backgroundColor: "black", ignoreClick: true, _gridGap: 0, _yMargin: 0, _yPadding: 0, _xMargin: 0, _autoHeight: false, _width: 60, _columnWidth: 60, lockedPosition: true, _chromeStatus: "disabled", system: true @@ -729,7 +729,7 @@ export class CurrentUserUtils { if (doc.myTools === undefined) { const toolsStack = new PrefetchProxy(Docs.Create.StackingDocument([doc.myCreators as Doc, doc.myColorPicker as Doc], { - title: "My Tools", _width: 500, _yMargin: 20, lockedPosition: true, _chromeStatus: "disabled", forceActive: true, system: true, _stayInCollection: true, _hideContextMenu: true, + title: "My Tools", _width: 500, _yMargin: 20, ignoreClick: true, lockedPosition: true, _chromeStatus: "disabled", forceActive: true, system: true, _stayInCollection: true, _hideContextMenu: true, })) as any as Doc; doc.myTools = toolsStack; @@ -743,7 +743,7 @@ export class CurrentUserUtils { doc.myDashboards = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Dashboards", _height: 400, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const newDashboard = ScriptField.MakeScript(`createNewDashboard(Doc.UserDoc())`); @@ -759,7 +759,7 @@ export class CurrentUserUtils { doc.myPresentations = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "My Presentations", _height: 100, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const newPresentations = ScriptField.MakeScript(`createNewPresentation()`); @@ -777,7 +777,7 @@ export class CurrentUserUtils { doc.myRecentlyClosedDocs = new PrefetchProxy(Docs.Create.TreeDocument([], { title: "Recently Closed", _height: 500, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "alias", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); @@ -792,7 +792,7 @@ export class CurrentUserUtils { doc.myFilter = new PrefetchProxy(Docs.Create.FilterDocument({ title: "FilterDoc", _height: 500, treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, childDropAction: "none", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })); const clearAll = ScriptField.MakeScript(`getProto(self).data = new List([])`); @@ -808,7 +808,7 @@ export class CurrentUserUtils { doc.treeViewExpandedView = "fields"; doc.myUserDoc = new PrefetchProxy(Docs.Create.TreeDocument([doc], { treeViewHideTitle: true, _xMargin: 5, _yMargin: 5, _gridGap: 5, forceActive: true, title: "My UserDoc", - treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, + treeViewTruncateTitleWidth: 150, treeViewPreventOpen: false, ignoreClick: true, lockedPosition: true, boxShadow: "0 0", dontRegisterChildViews: true, targetDropAction: "same", system: true })) as any as Doc; } diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index 08ad49dcc..79759a71d 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -44,7 +44,7 @@ export namespace SearchUtil { const header = query.match(/_[atnb]?:/) ? replacedQuery : "DEFAULT:" + replacedQuery; replacedQuery = `{!join from=id to=proto_i}* AND ${header}`; } - console.log("Q: " + replacedQuery + " fq: " + options.fq); + //console.log("Q: " + replacedQuery + " fq: " + options.fq); const gotten = await rp.get(rpquery, { qs: { ...options, q: replacedQuery } }); const result: IdSearchResult = gotten.startsWith("<") ? { ids: [], docs: [], numFound: 0, lines: [] } : JSON.parse(gotten); if (!returnDocs) { diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 5193c3c02..8e30eba2a 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -312,39 +312,40 @@ export class MainView extends React.Component { } @computed get flyout() { - return
-
- "lightgrey"} - /> -
- {this.docButtons} -
; + return !this._flyoutWidth ?
: +
+
+ "lightgrey"} + /> +
+ {this.docButtons} +
; } @computed get menuPanel() { @@ -394,6 +395,8 @@ export class MainView extends React.Component { SearchBox.Instance._searchFullDB = "My Stuff"; SearchBox.Instance.enter(undefined); break; + case "Help": + break; default: this.expandFlyout(button); } diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 51d1d5559..e9cb57fed 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -234,8 +234,9 @@ export class CollectionViewBaseChrome extends React.Component { + const target = this.document !== Doc.UserDoc().sidebar ? this.document : this.document.proto as Doc; //@ts-ignore - this.document._viewType = e.target.selectedOptions[0].value; + target._viewType = e.target.selectedOptions[0].value; } commandChanged = (e: React.ChangeEvent) => { -- cgit v1.2.3-70-g09d2 From 15e8f409f5b045c7946f363bb8572492a0b3d047 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 11:24:37 -0400 Subject: fixed tabbing in properties view --- src/client/views/PropertiesView.tsx | 1964 +++++++++++++++++------------------ 1 file changed, 982 insertions(+), 982 deletions(-) (limited to 'src') diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 4ce9ef154..b30691ff5 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -1,6 +1,7 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Checkbox, Tooltip } from "@material-ui/core"; +import { intersection } from "lodash"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { ColorState, SketchPicker } from "react-color"; @@ -10,7 +11,7 @@ import { InkField } from "../../fields/InkField"; import { ComputedField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; import { GetEffectiveAcl, SharingPermissions } from "../../fields/util"; -import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne, returnZero } from "../../Utils"; +import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne } from "../../Utils"; import { DocumentType } from "../documents/DocumentTypes"; import { DocumentManager } from "../util/DocumentManager"; import { SelectionManager } from "../util/SelectionManager"; @@ -26,1042 +27,1041 @@ import { PresBox } from "./nodes/PresBox"; import { PropertiesButtons } from "./PropertiesButtons"; import { PropertiesDocContextSelector } from "./PropertiesDocContextSelector"; import "./PropertiesView.scss"; -import { intersection } from "lodash"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; const _global = (window /* browser */ || global /* node */) as any; interface PropertiesViewProps { - width: number; - height: number; + width: number; + height: number; } @observer export class PropertiesView extends React.Component { - private _widthUndo?: UndoManager.Batch; - - @computed get MAX_EMBED_HEIGHT() { return 200; } - - @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } - @computed get selectedDocumentView() { - if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; - if (PresBox.Instance && PresBox.Instance._selectedArray) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); - return undefined; - } - @computed get isPres(): boolean { - if (this.selectedDoc?.type === DocumentType.PRES) return true; - return false; - } - @computed get dataDoc() { return this.selectedDoc?.[DataSym]; } - - @observable layoutFields: boolean = false; - - @observable openOptions: boolean = true; - @observable openSharing: boolean = true; - @observable openFields: boolean = true; - @observable openLayout: boolean = true; - @observable openContexts: boolean = true; - @observable openAppearance: boolean = true; - @observable openTransform: boolean = true; - // @observable selectedUser: string = ""; - // @observable addButtonPressed: boolean = false; - @observable layoutDocAcls: boolean = false; - - //Pres Trails booleans: - @observable openPresTransitions: boolean = false; - @observable openPresProgressivize: boolean = false; - @observable openAddSlide: boolean = false; - @observable openSlideOptions: boolean = false; - - @observable inOptions: boolean = false; - @observable _controlBtn: boolean = false; - @observable _lock: boolean = false; - - @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; } - - rtfWidth = () => { - return !this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20); - } - rtfHeight = () => { - return !this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; - } - - @action - docWidth = () => { - if (this.selectedDoc) { - const layoutDoc = this.selectedDoc; - 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.width - 20)); - return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20; - } else { - return 0; + private _widthUndo?: UndoManager.Batch; + + @computed get MAX_EMBED_HEIGHT() { return 200; } + + @computed get selectedDoc() { return SelectionManager.SelectedSchemaDoc() || this.selectedDocumentView?.rootDoc; } + @computed get selectedDocumentView() { + if (SelectionManager.SelectedDocuments().length) return SelectionManager.SelectedDocuments()[0]; + if (PresBox.Instance && PresBox.Instance._selectedArray) return DocumentManager.Instance.getDocumentView(PresBox.Instance.rootDoc); + return undefined; + } + @computed get isPres(): boolean { + if (this.selectedDoc?.type === DocumentType.PRES) return true; + return false; + } + @computed get dataDoc() { return this.selectedDoc?.[DataSym]; } + + @observable layoutFields: boolean = false; + + @observable openOptions: boolean = true; + @observable openSharing: boolean = true; + @observable openFields: boolean = true; + @observable openLayout: boolean = true; + @observable openContexts: boolean = true; + @observable openAppearance: boolean = true; + @observable openTransform: boolean = true; + // @observable selectedUser: string = ""; + // @observable addButtonPressed: boolean = false; + @observable layoutDocAcls: boolean = false; + + //Pres Trails booleans: + @observable openPresTransitions: boolean = false; + @observable openPresProgressivize: boolean = false; + @observable openAddSlide: boolean = false; + @observable openSlideOptions: boolean = false; + + @observable inOptions: boolean = false; + @observable _controlBtn: boolean = false; + @observable _lock: boolean = false; + + @computed get isInk() { return this.selectedDoc?.type === DocumentType.INK; } + + rtfWidth = () => { + return !this.selectedDoc ? 0 : Math.min(this.selectedDoc?.[WidthSym](), this.props.width - 20); } - } - - @action - docHeight = () => { - if (this.selectedDoc && this.dataDoc) { - const layoutDoc = this.selectedDoc; - 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; - return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) : - Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, - NumCast(this.props.height)))) : - NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; - })())); - } else { - return 0; + rtfHeight = () => { + return !this.selectedDoc ? 0 : this.rtfWidth() <= this.selectedDoc?.[WidthSym]() ? Math.min(this.selectedDoc?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; } - } - - @computed get expandedField() { - if (this.dataDoc && this.selectedDoc) { - const ids: { [key: string]: string } = {}; - const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : - SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? Doc.Layout(dv.layoutDoc) : dv.dataDoc); - docs.forEach(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()) { - const docvals = new Set(); - docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; - if (key[0] === "#") { - rows.push(
- {key} + + @action + docWidth = () => { + if (this.selectedDoc) { + const layoutDoc = this.selectedDoc; + 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.width - 20)); + return NumCast(layoutDoc._nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.width - 20) : this.props.width - 20; + } else { + return 0; + } + } + + @action + docHeight = () => { + if (this.selectedDoc && this.dataDoc) { + const layoutDoc = this.selectedDoc; + 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; + return layoutDoc._fitWidth ? (!this.dataDoc._nativeHeight ? NumCast(this.props.height) : + Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, + NumCast(this.props.height)))) : + NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; + })())); + } else { + return 0; + } + } + + @computed get expandedField() { + if (this.dataDoc && this.selectedDoc) { + const ids: { [key: string]: string } = {}; + const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : + SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? Doc.Layout(dv.layoutDoc) : dv.dataDoc); + docs.forEach(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()) { + const docvals = new Set(); + docs.forEach(doc => docvals.add(doc[key])); + const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; + if (key[0] === "#") { + rows.push(
+ {key}  
); - } else { - const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} - SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} - />; - rows.push(
- {key + ":"} + } else { + const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} + SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} + />; + rows.push(
+ {key + ":"}   {contentElement} -
); +
); + } + } + rows.push(
+ ""} + SetValue={this.setKeyValue} /> +
); + return rows; } - } - rows.push(
- ""} - SetValue={this.setKeyValue} /> -
); - return rows; } - } - - @computed get noviceFields() { - if (this.dataDoc) { - const ids: { [key: string]: string } = {}; - const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => dv.dataDoc); - docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); - 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"))), - ...noviceReqFields, ...noviceLayoutFields]; - for (const key of noviceKeys.sort()) { - const docvals = new Set(); - docs.forEach(doc => docvals.add(doc[key])); - const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; - if (key[0] === "#") { - rows.push(
- {key} + + @computed get noviceFields() { + if (this.dataDoc) { + const ids: { [key: string]: string } = {}; + const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => dv.dataDoc); + docs.forEach(doc => Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key))); + 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"))), + ...noviceReqFields, ...noviceLayoutFields]; + for (const key of noviceKeys.sort()) { + const docvals = new Set(); + docs.forEach(doc => docvals.add(doc[key])); + const contents = Array.from(docvals.keys()).length > 1 ? "-multiple" : docs[0][key]; + if (key[0] === "#") { + rows.push(
+ {key}  
); - } else if (contents !== undefined) { - const value = Field.toString(contents as Field); - if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) { - rows.push(
- {key + ": "} -
{value}
-
); - } else { - const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} - SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} - />; - - rows.push(
- {key + ":"} + } else if (contents !== undefined) { + const value = Field.toString(contents as Field); + if (noviceReqFields.includes(key) || key.indexOf("lastModified") !== -1) { + rows.push(
+ {key + ": "} +
{value}
+
); + } else { + const contentElement = contents !== undefined ? Field.toString(contents as Field) : "null"} + SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} + />; + + rows.push(
+ {key + ":"}   {contentElement} +
); + } + } + } + rows.push(
+ ""} + SetValue={this.setKeyValue} />
); - } + return rows; } - } - rows.push(
- ""} - SetValue={this.setKeyValue} /> -
); - return rows; } - } - - @undoBatch - setKeyValue = (value: string) => { - const docs = SelectionManager.SelectedDocuments().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc); - docs.forEach(doc => { - if (value.indexOf(":") !== -1) { - const newVal = value[0].toUpperCase() + value.substring(1, value.length); - const splits = newVal.split(":"); - KeyValueBox.SetField(doc, splits[0], splits[1], true); - const tags = StrCast(doc.tags, ":"); - if (tags.includes(`${splits[0]}:`) && splits[1] === "undefined") { - KeyValueBox.SetField(doc, "tags", `"${tags.replace(splits[0] + ":", "")}"`, true); - } - return true; - } else if (value[0] === "#") { - const newVal = value + `:'${value}'`; - doc[DataSym][value] = value; - const tags = StrCast(doc.tags, ":"); - if (!tags.includes(`${value}:`)) { - doc[DataSym].tags = `${tags + value + ':'}`; - } - return true; - } - }); - return false; - } - - @observable transform: Transform = Transform.Identity(); - getTransform = () => this.transform; - propertiesDocViewRef = (ref: HTMLDivElement) => { - const observer = new _global.ResizeObserver(action((entries: any) => { - const cliRect = ref.getBoundingClientRect(); - this.transform = new Transform(-cliRect.x, -cliRect.y, 1); - })); - ref && observer.observe(ref); - } - - @computed get contexts() { - return !this.selectedDoc ? (null) : CollectionDockingView.AddSplit(doc, "right")} />; - } - - previewBackground = () => "lightgrey"; - @computed get layoutPreview() { - if (SelectionManager.SelectedDocuments().length > 1) { - return "-- multiple selected --"; + + @undoBatch + setKeyValue = (value: string) => { + const docs = SelectionManager.SelectedDocuments().length < 2 && this.selectedDoc ? [this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc] : SelectionManager.SelectedDocuments().map(dv => this.layoutFields ? dv.layoutDoc : dv.dataDoc); + docs.forEach(doc => { + if (value.indexOf(":") !== -1) { + const newVal = value[0].toUpperCase() + value.substring(1, value.length); + const splits = newVal.split(":"); + KeyValueBox.SetField(doc, splits[0], splits[1], true); + const tags = StrCast(doc.tags, ":"); + if (tags.includes(`${splits[0]}:`) && splits[1] === "undefined") { + KeyValueBox.SetField(doc, "tags", `"${tags.replace(splits[0] + ":", "")}"`, true); + } + return true; + } else if (value[0] === "#") { + const newVal = value + `:'${value}'`; + doc[DataSym][value] = value; + const tags = StrCast(doc.tags, ":"); + if (!tags.includes(`${value}:`)) { + doc[DataSym].tags = `${tags + value + ':'}`; + } + return true; + } + }); + return false; } - if (this.selectedDoc) { - const layoutDoc = Doc.Layout(this.selectedDoc); - 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
- false} - whenActiveChanged={emptyFunction} - addDocTab={returnFalse} - pinToPres={emptyFunction} - bringToFront={returnFalse} - ContentScaling={returnOne} - dontRegisterView={true} - dropAction={undefined} - /> -
; - } else { - return null; + + @observable transform: Transform = Transform.Identity(); + getTransform = () => this.transform; + propertiesDocViewRef = (ref: HTMLDivElement) => { + const observer = new _global.ResizeObserver(action((entries: any) => { + const cliRect = ref.getBoundingClientRect(); + this.transform = new Transform(-cliRect.x, -cliRect.y, 1); + })); + ref && observer.observe(ref); } - } - - /** - * Handles the changing of a user's permissions from the permissions panel. - */ - @undoBatch - changePermissions = (e: any, user: string) => { - const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.selectedDoc!] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); - SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); - } - - /** - * @returns the options for the permissions dropdown. - */ - getPermissionsSelect(user: string, permission: string) { - const dropdownValues: string[] = Object.values(SharingPermissions); - if (permission === "-multiple-") dropdownValues.unshift(permission); - if (user === "Override") dropdownValues.unshift("None"); - return ; - } - - /** - * @returns the notification icon. On clicking, it should notify someone of a document been shared with them. - */ - @computed get notifyIcon() { - return Notify with message
}> -
- -
- ; - } - - /** - * ... next to the owner that opens the main SharingManager interface on click. - */ - @computed get expansionIcon() { - return {"Show more permissions"}
}> -
{ - if (this.selectedDocumentView || this.selectedDoc) { - SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc); + + @computed get contexts() { + return !this.selectedDoc ? (null) : CollectionDockingView.AddSplit(doc, "right")} />; + } + + previewBackground = () => "lightgrey"; + @computed get layoutPreview() { + if (SelectionManager.SelectedDocuments().length > 1) { + return "-- multiple selected --"; } - }}> - -
- ; - } - - /** - * @returns a row of the permissions panel - */ - sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { - return
this.selectedUser = this.selectedUser === name ? "" : name)} - > -
{name}
- {/* {name !== "Me" ? this.notifyIcon : null} */} -
- {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission} - {permission === "Owner" || showExpansionIcon ? this.expansionIcon : null} -
-
; - } - - /** - * @returns the sharing and permissions panel. - */ - @computed get sharingTable() { - const AclMap = new Map([ - [AclUnset, "None"], - [AclPrivate, SharingPermissions.None], - [AclReadonly, SharingPermissions.View], - [AclAddonly, SharingPermissions.Add], - [AclEdit, SharingPermissions.Edit], - [AclAdmin, SharingPermissions.Admin] - ]); - - // all selected docs - const docs = SelectionManager.SelectedDocuments().length < 2 ? - [this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym]] - : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]); - - const target = docs[0]; - - // tslint:disable-next-line: no-unnecessary-callback-wrapper - const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); - const showAdmin = effectiveAcls.every(acl => acl === AclAdmin); - - // users in common between all docs - const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym]))); - - const tableEntries = []; - - // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { - if (commonKeys.length) { - for (const key of commonKeys) { - const name = key.substring(4).replace("_", "."); - const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]); - if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) { - tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-")); + if (this.selectedDoc) { + const layoutDoc = Doc.Layout(this.selectedDoc); + 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
+ false} + whenActiveChanged={emptyFunction} + addDocTab={returnFalse} + pinToPres={emptyFunction} + bringToFront={returnFalse} + ContentScaling={returnOne} + dontRegisterView={true} + dropAction={undefined} + /> +
; + } else { + return null; } - } } - // 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)); - // } - // } - // }) - - const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); - // shifts the current user, owner, public to the top of the doc. - tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-")); - tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).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.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame)); - if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); - - return
- {tableEntries} -
; - } - - @computed get fieldsCheckbox() { - return ; - } - - @action - toggleCheckbox = () => { - this.layoutFields = !this.layoutFields; - } - - @computed get editableTitle() { - const titles = new Set(); - SelectionManager.SelectedDocuments().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); - const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title); - return
title} - SetValue={this.setTitle} />
; - } - - @undoBatch - @action - setTitle = (value: string) => { - if (SelectionManager.SelectedDocuments().length > 1) { - SelectionManager.SelectedDocuments().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true)); - return true; - } else if (this.dataDoc) { - if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true); - else KeyValueBox.SetField(this.dataDoc, "title", value, true); - return true; + /** + * Handles the changing of a user's permissions from the permissions panel. + */ + @undoBatch + changePermissions = (e: any, user: string) => { + const docs = SelectionManager.SelectedDocuments().length < 2 ? [this.selectedDoc!] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); + SharingManager.Instance.shareFromPropertiesSidebar(user, e.currentTarget.value as SharingPermissions, docs); + } + + /** + * @returns the options for the permissions dropdown. + */ + getPermissionsSelect(user: string, permission: string) { + const dropdownValues: string[] = Object.values(SharingPermissions); + if (permission === "-multiple-") dropdownValues.unshift(permission); + if (user === "Override") dropdownValues.unshift("None"); + return ; + } + + /** + * @returns the notification icon. On clicking, it should notify someone of a document been shared with them. + */ + @computed get notifyIcon() { + return Notify with message
}> +
+ +
+ ; + } + + /** + * ... next to the owner that opens the main SharingManager interface on click. + */ + @computed get expansionIcon() { + return {"Show more permissions"}
}> +
{ + if (this.selectedDocumentView || this.selectedDoc) { + SharingManager.Instance.open(this.selectedDocumentView?.props.Document === this.selectedDocumentView ? this.selectedDocumentView : undefined, this.selectedDoc); + } + }}> + +
+ ; + } + + /** + * @returns a row of the permissions panel + */ + sharingItem(name: string, admin: boolean, permission: string, showExpansionIcon?: boolean) { + return
this.selectedUser = this.selectedUser === name ? "" : name)} + > +
{name}
+ {/* {name !== "Me" ? this.notifyIcon : null} */} +
+ {admin && permission !== "Owner" ? this.getPermissionsSelect(name, permission) : permission} + {permission === "Owner" || showExpansionIcon ? this.expansionIcon : null} +
+
; } - return false; - } - - - @undoBatch - @action - rotate = (angle: number) => { - const _centerPoints: { X: number, Y: number }[] = []; - if (this.selectedDoc) { - const doc = this.selectedDoc; - if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - const xs = ink.map(p => p.X); - const ys = ink.map(p => p.Y); - const left = Math.min(...xs); - const top = Math.min(...ys); - const right = Math.max(...xs); - const bottom = Math.max(...ys); - _centerPoints.push({ X: left, Y: top }); + + /** + * @returns the sharing and permissions panel. + */ + @computed get sharingTable() { + const AclMap = new Map([ + [AclUnset, "None"], + [AclPrivate, SharingPermissions.None], + [AclReadonly, SharingPermissions.View], + [AclAddonly, SharingPermissions.Add], + [AclEdit, SharingPermissions.Edit], + [AclAdmin, SharingPermissions.Admin] + ]); + + // all selected docs + const docs = SelectionManager.SelectedDocuments().length < 2 ? + [this.layoutDocAcls ? this.selectedDoc! : this.selectedDoc![DataSym]] + : SelectionManager.SelectedDocuments().map(docView => this.layoutDocAcls ? docView.props.Document : docView.props.Document[DataSym]); + + const target = docs[0]; + + // tslint:disable-next-line: no-unnecessary-callback-wrapper + const effectiveAcls = docs.map(doc => GetEffectiveAcl(doc)); + const showAdmin = effectiveAcls.every(acl => acl === AclAdmin); + + // users in common between all docs + const commonKeys = intersection(...docs.map(doc => this.layoutDocAcls ? doc?.[AclSym] && Object.keys(doc[AclSym]) : doc?.[DataSym][AclSym] && Object.keys(doc[DataSym][AclSym]))); + + const tableEntries = []; + + // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { + if (commonKeys.length) { + for (const key of commonKeys) { + const name = key.substring(4).replace("_", "."); + const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]); + if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) { + tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-")); + } + } } - } - - var index = 0; - if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { - doc.rotation = Number(doc.rotation) + Number(angle); - const inks = Cast(doc.data, InkField)?.inkData; - if (inks) { - const newPoints: { X: number, Y: number }[] = []; - inks.forEach(ink => { - const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; - const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; - newPoints.push({ X: newX, Y: newY }); - }); - doc.data = new InkField(newPoints); - const xs = newPoints.map(p => p.X); - const ys = newPoints.map(p => p.Y); - const left = Math.min(...xs); - const top = Math.min(...ys); - const right = Math.max(...xs); - const bottom = Math.max(...ys); - - doc._height = (bottom - top); - doc._width = (right - left); + + // 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)); + // } + // } + // }) + + const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); + // shifts the current user, owner, public to the top of the doc. + tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-")); + tableEntries.unshift(this.sharingItem("Public", showAdmin, docs.filter(doc => doc).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.filter(doc => doc).every(doc => doc.author === Doc.CurrentUserEmail) ? "Owner" : effectiveAcls.every(acl => acl === effectiveAcls[0]) ? AclMap.get(effectiveAcls[0])! : "-multiple-", !ownerSame)); + if (ownerSame) tableEntries.unshift(this.sharingItem(StrCast(target.author), showAdmin, "Owner")); + + return
+ {tableEntries} +
; + } + + @computed get fieldsCheckbox() { + return ; + } + + @action + toggleCheckbox = () => { + this.layoutFields = !this.layoutFields; + } + + @computed get editableTitle() { + const titles = new Set(); + SelectionManager.SelectedDocuments().forEach(dv => titles.add(StrCast(dv.rootDoc.title))); + const title = Array.from(titles.keys()).length > 1 ? "--multiple selected--" : StrCast(this.selectedDoc?.title); + return
title} + SetValue={this.setTitle} />
; + } + + @undoBatch + @action + setTitle = (value: string) => { + if (SelectionManager.SelectedDocuments().length > 1) { + SelectionManager.SelectedDocuments().map(dv => Doc.SetInPlace(dv.rootDoc, "title", value, true)); + return true; + } else if (this.dataDoc) { + if (this.selectedDoc) Doc.SetInPlace(this.selectedDoc, "title", value, true); + else KeyValueBox.SetField(this.dataDoc, "title", value, true); + return true; } - index++; - } + return false; } - } - - - - @computed - get controlPointsButton() { - const formatInstance = InkStrokeProperties.Instance; - return !formatInstance ? (null) :
- {"Edit points"}
}> -
formatInstance._controlBtn = !formatInstance._controlBtn)} style={{ backgroundColor: formatInstance._controlBtn ? "black" : "" }}> - -
- - {formatInstance._lock ? "Unlock ratio" : "Lock ratio"}
}> -
formatInstance._lock = !formatInstance._lock)} > - -
- - {"Rotate 90˚"}}> -
this.rotate(Math.PI / 2))}> - -
-
- ; - } - - inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => { - return
-
{title}
- { - setter(e.target.value); - }} - onKeyPress={e => { - e.stopPropagation(); - }} /> -
-
this.upDownButtons("up", key)))} > - -
-
this.upDownButtons("down", key)))} > - -
-
-
; - } - - inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => { - return
- {this.inputBox(key, value, setter, title1)} - {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)} -
; - } - - @action - upDownButtons = (dirs: string, field: string) => { - switch (field) { - case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break; - // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break; - case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break; - case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break; - case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break; - case "wid": - const oldWidth = NumCast(this.selectedDoc?._width); - const oldHeight = NumCast(this.selectedDoc?._height); - const oldX = NumCast(this.selectedDoc?.x); - const oldY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height))); - const doc = this.selectedDoc; - if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { - const ink = Cast(doc.data, InkField)?.inkData; - if (ink) { - const newPoints: { X: number, Y: number }[] = []; - for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth; - const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight; - newPoints.push({ X: newX, Y: newY }); + + + @undoBatch + @action + rotate = (angle: number) => { + const _centerPoints: { X: number, Y: number }[] = []; + if (this.selectedDoc) { + const doc = this.selectedDoc; + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { + const xs = ink.map(p => p.X); + const ys = ink.map(p => p.Y); + const left = Math.min(...xs); + const top = Math.min(...ys); + const right = Math.max(...xs); + const bottom = Math.max(...ys); + _centerPoints.push({ X: left, Y: top }); + } } - doc.data = new InkField(newPoints); - } - } - break; - case "hgt": - const oWidth = NumCast(this.selectedDoc?._width); - const oHeight = NumCast(this.selectedDoc?._height); - const oX = NumCast(this.selectedDoc?.x); - const oY = NumCast(this.selectedDoc?.y); - this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width))); - const docu = this.selectedDoc; - if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) { - const ink = Cast(docu.data, InkField)?.inkData; - if (ink) { - const newPoints: { X: number, Y: number }[] = []; - for (var j = 0; j < ink.length; j++) { - // (new x — oldx) + (oldxpoint * newWidt)/oldWidth - const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth; - const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight; - newPoints.push({ X: newX, Y: newY }); + + var index = 0; + if (doc.type === DocumentType.INK && doc.x && doc.y && doc._width && doc._height && doc.data) { + doc.rotation = Number(doc.rotation) + Number(angle); + const inks = Cast(doc.data, InkField)?.inkData; + if (inks) { + const newPoints: { X: number, Y: number }[] = []; + inks.forEach(ink => { + const newX = Math.cos(angle) * (ink.X - _centerPoints[index].X) - Math.sin(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].X; + const newY = Math.sin(angle) * (ink.X - _centerPoints[index].X) + Math.cos(angle) * (ink.Y - _centerPoints[index].Y) + _centerPoints[index].Y; + newPoints.push({ X: newX, Y: newY }); + }); + doc.data = new InkField(newPoints); + const xs = newPoints.map(p => p.X); + const ys = newPoints.map(p => p.Y); + const left = Math.min(...xs); + const top = Math.min(...ys); + const right = Math.max(...xs); + const bottom = Math.max(...ys); + + doc._height = (bottom - top); + doc._width = (right - left); + } + index++; } - docu.data = new InkField(newPoints); - } } - break; } - } - getField(key: string) { - //if (this.selectedDoc) { - return Field.toString(this.selectedDoc?.[key] as Field); - // } else { - // return undefined as Opt; - // } - } - - @computed get shapeXps() { return this.getField("x"); } - @computed get shapeYps() { return this.getField("y"); } - @computed get shapeRot() { return this.getField("rotation"); } - @computed get shapeHgt() { return this.getField("_height"); } - @computed get shapeWid() { return this.getField("_width"); } - set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); } - set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); } - set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); } - set shapeWid(value) { - const oldWidth = NumCast(this.selectedDoc?._width); - this.selectedDoc && (this.selectedDoc._width = Number(value)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth); - } - set shapeHgt(value) { - const oldHeight = NumCast(this.selectedDoc?._height); - this.selectedDoc && (this.selectedDoc._height = Number(value)); - InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight); - } - - @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); } - @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); } - @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); } - - - @observable private _fillBtn = false; - @observable private _lineBtn = false; - - private _lastFill = "#D0021B"; - private _lastLine = "#D0021B"; - private _lastDash: any = "2"; - - @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; } - @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } - set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } - set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } - - colorButton(value: string, type: string, setter: () => {}) { - // return
this.changeScrolling(false)} - // onPointerLeave={e => this.changeScrolling(true)}> - // - return
setter()))}> -
- {value === "" || value === "transparent" ?

: ""} -
; - // - //
; - - } - - @undoBatch - @action - switchStk = (color: ColorState) => { - const val = String(color.hex); - this.colorStk = val; - return true; - } - @undoBatch - @action - switchFil = (color: ColorState) => { - const val = String(color.hex); - this.colorFil = val; - return true; - } - - colorPicker(setter: (color: string) => {}, type: string) { - return ; - } - - @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } - @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } - - @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } - @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } - - @computed get strokeAndFill() { - return
-
-
-
Fill:
-
{this.fillButton}
-
-
-
Stroke:
-
{this.lineButton}
-
-
- {this._fillBtn ? this.fillPicker : ""} - {this._lineBtn ? this.linePicker : ""} -
; - } - - @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; } - @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; } - @computed get unStrokd() { return this.selectedDoc?.color ? true : false; } - @computed get widthStk() { return this.getField("strokeWidth") || "1"; } - @computed get markHead() { return this.getField("strokeStartMarker") || ""; } - @computed get markTail() { return this.getField("strokeEndMarker") || ""; } - set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; } - set dashdStk(value) { - value && (this._lastDash = value) && (this.unStrokd = false); - this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined); - } - set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); } - set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; } - set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); } - set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); } - - - @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); } - - - regInput = (key: string, value: any, setter: (val: string) => {}) => { - return
- setter(e.target.value)} /> -
-
this.upDownButtons("up", key)))} > - -
-
this.upDownButtons("down", key)))} > - -
-
-
; - } - - @computed get widthAndDash() { - return
-
-
-
Width:
-
{this.stkInput}
-
- this.widthStk = e.target.value))} - onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }} - onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }} - /> -
- -
-
-
Arrow Head:
- this.markHead = this.markHead ? "" : "arrow"))} /> -
-
-
Arrow End:
- this.markTail = this.markTail ? "" : "arrow"))} /> -
-
-
-
Dashed Line:
- -
-
; - } - - @undoBatch @action - changeDash = () => { - this.dashdStk = this.dashdStk === "2" ? "0" : "2"; - } - - @computed get appearanceEditor() { - return
- {this.widthAndDash} - {this.strokeAndFill} -
; - } - - @computed get transformEditor() { - return
- {this.controlPointsButton} - {this.hgtInput} - {this.XpsInput} - {this.rotInput} -
; - } - - /** - * Handles adding and removing members from the sharing panel - */ - // handleUserChange = (selectedUser: string, add: boolean) => { - // if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc; - // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { - // sidebarUsersDisplayed![`display-${selectedUser}`] = add; - // !add && runInAction(() => this.selectedUser = ""); - // }); - // } - - render() { - if (!this.selectedDoc && !this.isPres) { - return
-
- No Document Selected + + + @computed + get controlPointsButton() { + const formatInstance = InkStrokeProperties.Instance; + return !formatInstance ? (null) :
+ {"Edit points"}
}> +
formatInstance._controlBtn = !formatInstance._controlBtn)} style={{ backgroundColor: formatInstance._controlBtn ? "black" : "" }}> + +
+ + {formatInstance._lock ? "Unlock ratio" : "Lock ratio"}
}> +
formatInstance._lock = !formatInstance._lock)} > + +
+ + {"Rotate 90˚"}
}> +
this.rotate(Math.PI / 2))}> + +
+ +
; + } + + inputBox = (key: string, value: any, setter: (val: string) => {}, title: string) => { + return
+
{title}
+ { + setter(e.target.value); + }} + onKeyPress={e => { + e.stopPropagation(); + }} /> +
+
this.upDownButtons("up", key)))} > + +
+
this.upDownButtons("down", key)))} > +
-
; - - } else { - const novice = Doc.UserDoc().noviceMode; - - if (this.selectedDoc && !this.isPres) { - return
-
- Properties -
-
- {this.editableTitle} -
-
this.inOptions = true)} - onPointerLeave={action(() => this.inOptions = false)}> -
this.openOptions = !this.openOptions)} - style={{ backgroundColor: this.openOptions ? "black" : "" }}> - Options -
- -
- {!this.openOptions ? (null) : -
- -
} -
-
-
this.openSharing = !this.openSharing)} - style={{ backgroundColor: this.openSharing ? "black" : "" }}> - Sharing {"&"} Permissions -
- -
+
; + } + + inputBoxDuo = (key: string, value: any, setter: (val: string) => {}, title1: string, key2: string, value2: any, setter2: (val: string) => {}, title2: string) => { + return
+ {this.inputBox(key, value, setter, title1)} + {title2 === "" ? (null) : this.inputBox(key2, value2, setter2, title2)} +
; + } + + @action + upDownButtons = (dirs: string, field: string) => { + switch (field) { + case "rot": this.rotate((dirs === "up" ? .1 : -.1)); break; + // case "rot": this.selectedInk?.forEach(i => i.rootDoc.rotation = NumCast(i.rootDoc.rotation) + (dirs === "up" ? 0.1 : -0.1)); break; + case "Xps": this.selectedDoc && (this.selectedDoc.x = NumCast(this.selectedDoc?.x) + (dirs === "up" ? 10 : -10)); break; + case "Yps": this.selectedDoc && (this.selectedDoc.y = NumCast(this.selectedDoc?.y) + (dirs === "up" ? 10 : -10)); break; + case "stk": this.selectedDoc && (this.selectedDoc.strokeWidth = NumCast(this.selectedDoc?.strokeWidth) + (dirs === "up" ? .1 : -.1)); break; + case "wid": + const oldWidth = NumCast(this.selectedDoc?._width); + const oldHeight = NumCast(this.selectedDoc?._height); + const oldX = NumCast(this.selectedDoc?.x); + const oldY = NumCast(this.selectedDoc?.y); + this.selectedDoc && (this.selectedDoc._width = oldWidth + (dirs === "up" ? 10 : - 10)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) / oldWidth * NumCast(this.selectedDoc?._height))); + const doc = this.selectedDoc; + if (doc?.type === DocumentType.INK && doc.x && doc.y && doc._height && doc._width) { + const ink = Cast(doc.data, InkField)?.inkData; + if (ink) { + const newPoints: { X: number, Y: number }[] = []; + for (var j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = (NumCast(doc.x) - oldX) + (ink[j].X * NumCast(doc._width)) / oldWidth; + const newY = (NumCast(doc.y) - oldY) + (ink[j].Y * NumCast(doc._height)) / oldHeight; + newPoints.push({ X: newX, Y: newY }); + } + doc.data = new InkField(newPoints); + } + } + break; + case "hgt": + const oWidth = NumCast(this.selectedDoc?._width); + const oHeight = NumCast(this.selectedDoc?._height); + const oX = NumCast(this.selectedDoc?.x); + const oY = NumCast(this.selectedDoc?.y); + this.selectedDoc && (this.selectedDoc._height = oHeight + (dirs === "up" ? 10 : - 10)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) / oHeight * NumCast(this.selectedDoc?._width))); + const docu = this.selectedDoc; + if (docu?.type === DocumentType.INK && docu.x && docu.y && docu._height && docu._width) { + const ink = Cast(docu.data, InkField)?.inkData; + if (ink) { + const newPoints: { X: number, Y: number }[] = []; + for (var j = 0; j < ink.length; j++) { + // (new x — oldx) + (oldxpoint * newWidt)/oldWidth + const newX = (NumCast(docu.x) - oX) + (ink[j].X * NumCast(docu._width)) / oWidth; + const newY = (NumCast(docu.y) - oY) + (ink[j].Y * NumCast(docu._height)) / oHeight; + newPoints.push({ X: newX, Y: newY }); + } + docu.data = new InkField(newPoints); + } + } + break; + } + } + + getField(key: string) { + //if (this.selectedDoc) { + return Field.toString(this.selectedDoc?.[key] as Field); + // } else { + // return undefined as Opt; + // } + } + + @computed get shapeXps() { return this.getField("x"); } + @computed get shapeYps() { return this.getField("y"); } + @computed get shapeRot() { return this.getField("rotation"); } + @computed get shapeHgt() { return this.getField("_height"); } + @computed get shapeWid() { return this.getField("_width"); } + set shapeXps(value) { this.selectedDoc && (this.selectedDoc.x = Number(value)); } + set shapeYps(value) { this.selectedDoc && (this.selectedDoc.y = Number(value)); } + set shapeRot(value) { this.selectedDoc && (this.selectedDoc.rotation = Number(value)); } + set shapeWid(value) { + const oldWidth = NumCast(this.selectedDoc?._width); + this.selectedDoc && (this.selectedDoc._width = Number(value)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._height = (NumCast(this.selectedDoc?._width) * NumCast(this.selectedDoc?._height)) / oldWidth); + } + set shapeHgt(value) { + const oldHeight = NumCast(this.selectedDoc?._height); + this.selectedDoc && (this.selectedDoc._height = Number(value)); + InkStrokeProperties.Instance?._lock && this.selectedDoc && (this.selectedDoc._width = (NumCast(this.selectedDoc?._height) * NumCast(this.selectedDoc?._width)) / oldHeight); + } + + @computed get hgtInput() { return this.inputBoxDuo("hgt", this.shapeHgt, (val: string) => { if (!isNaN(Number(val))) { this.shapeHgt = val; } return true; }, "H:", "wid", this.shapeWid, (val: string) => { if (!isNaN(Number(val))) { this.shapeWid = val; } return true; }, "W:"); } + @computed get XpsInput() { return this.inputBoxDuo("Xps", this.shapeXps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeXps = val; } return true; }, "X:", "Yps", this.shapeYps, (val: string) => { if (val !== "0" && !isNaN(Number(val))) { this.shapeYps = val; } return true; }, "Y:"); } + @computed get rotInput() { return this.inputBoxDuo("rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, "∠:", "rot", this.shapeRot, (val: string) => { if (!isNaN(Number(val))) { this.rotate(Number(val) - Number(this.shapeRot)); this.shapeRot = val; } return true; }, ""); } + + + @observable private _fillBtn = false; + @observable private _lineBtn = false; + + private _lastFill = "#D0021B"; + private _lastLine = "#D0021B"; + private _lastDash: any = "2"; + + @computed get colorFil() { const ccol = this.getField("fillColor") || ""; ccol && (this._lastFill = ccol); return ccol; } + @computed get colorStk() { const ccol = this.getField("color") || ""; ccol && (this._lastLine = ccol); return ccol; } + set colorFil(value) { value && (this._lastFill = value); this.selectedDoc && (this.selectedDoc.fillColor = value ? value : undefined); } + set colorStk(value) { value && (this._lastLine = value); this.selectedDoc && (this.selectedDoc.color = value ? value : undefined); } + + colorButton(value: string, type: string, setter: () => {}) { + // return
this.changeScrolling(false)} + // onPointerLeave={e => this.changeScrolling(true)}> + // + return
setter()))}> +
+ {value === "" || value === "transparent" ?

: ""} +
; + // + //
; + + } + + @undoBatch + @action + switchStk = (color: ColorState) => { + const val = String(color.hex); + this.colorStk = val; + return true; + } + @undoBatch + @action + switchFil = (color: ColorState) => { + const val = String(color.hex); + this.colorFil = val; + return true; + } + + colorPicker(setter: (color: string) => {}, type: string) { + return ; + } + + @computed get fillButton() { return this.colorButton(this.colorFil, "fill", () => { this._fillBtn = !this._fillBtn; this._lineBtn = false; return true; }); } + @computed get lineButton() { return this.colorButton(this.colorStk, "line", () => { this._lineBtn = !this._lineBtn; this._fillBtn = false; return true; }); } + + @computed get fillPicker() { return this.colorPicker((color: string) => this.colorFil = color, "fil"); } + @computed get linePicker() { return this.colorPicker((color: string) => this.colorStk = color, "stk"); } + + @computed get strokeAndFill() { + return
+
+
+
Fill:
+
{this.fillButton}
+
+
+
Stroke:
+
{this.lineButton}
+
- {!this.openSharing ? (null) : -
-
- {!novice ? (
- this.layoutDocAcls = !this.layoutDocAcls)} - checked={this.layoutDocAcls} - /> -
Layout
-
) : (null)} -
{"Re-distribute sharing settings"}
}> - -
+ {this._fillBtn ? this.fillPicker : ""} + {this._lineBtn ? this.linePicker : ""} +
; + } + + @computed get solidStk() { return this.selectedDoc?.color && (!this.selectedDoc?.strokeDash || this.selectedDoc?.strokeDash === "0") ? true : false; } + @computed get dashdStk() { return this.selectedDoc?.strokeDash || ""; } + @computed get unStrokd() { return this.selectedDoc?.color ? true : false; } + @computed get widthStk() { return this.getField("strokeWidth") || "1"; } + @computed get markHead() { return this.getField("strokeStartMarker") || ""; } + @computed get markTail() { return this.getField("strokeEndMarker") || ""; } + set solidStk(value) { this.dashdStk = ""; this.unStrokd = !value; } + set dashdStk(value) { + value && (this._lastDash = value) && (this.unStrokd = false); + this.selectedDoc && (this.selectedDoc.strokeDash = value ? this._lastDash : undefined); + } + set widthStk(value) { this.selectedDoc && (this.selectedDoc.strokeWidth = Number(value)); } + set unStrokd(value) { this.colorStk = value ? "" : this._lastLine; } + set markHead(value) { this.selectedDoc && (this.selectedDoc.strokeStartMarker = value); } + set markTail(value) { this.selectedDoc && (this.selectedDoc.strokeEndMarker = value); } + + + @computed get stkInput() { return this.regInput("stk", this.widthStk, (val: string) => this.widthStk = val); } + + + regInput = (key: string, value: any, setter: (val: string) => {}) => { + return
+ setter(e.target.value)} /> +
+
this.upDownButtons("up", key)))} > +
- {this.sharingTable} -
} -
- - {!this.isInk ? (null) : -
-
this.openAppearance = !this.openAppearance)} - style={{ backgroundColor: this.openAppearance ? "black" : "" }}> - Appearance -
- +
this.upDownButtons("down", key)))} > +
-
- {!this.openAppearance ? (null) : -
- {this.appearanceEditor} -
} -
} - - {this.isInk ?
-
this.openTransform = !this.openTransform)} - style={{ backgroundColor: this.openTransform ? "black" : "" }}> - Transform -
- -
- {this.openTransform ?
- {this.transformEditor} -
: null} -
: null} - -
-
this.openFields = !this.openFields)} - style={{ backgroundColor: this.openFields ? "black" : "" }}> - Fields {"&"} Tags -
- -
+
; + } + + @computed get widthAndDash() { + return
+
+
+
Width:
+
{this.stkInput}
+
+ this.widthStk = e.target.value))} + onMouseDown={(e) => { this._widthUndo = UndoManager.StartBatch("width undo"); }} + onMouseUp={(e) => { this._widthUndo?.end(); this._widthUndo = undefined; }} + />
- {!novice && this.openFields ?
- {this.fieldsCheckbox} -
Layout
-
: null} - {!this.openFields ? (null) : -
- {novice ? this.noviceFields : this.expandedField} -
} -
-
-
this.openContexts = !this.openContexts)} - style={{ backgroundColor: this.openContexts ? "black" : "" }}> - Contexts -
- -
+ +
+
+
Arrow Head:
+ this.markHead = this.markHead ? "" : "arrow"))} /> +
+
+
Arrow End:
+ this.markTail = this.markTail ? "" : "arrow"))} /> +
- {this.openContexts ?
{this.contexts}
: null} -
-
-
this.openLayout = !this.openLayout)} - style={{ backgroundColor: this.openLayout ? "black" : "" }}> - Layout -
- -
+
+
Dashed Line:
+
- {this.openLayout ?
{this.layoutPreview}
: null} -
; - } - if (this.isPres) { - const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0; - const type = PresBox.Instance.activeItem?.type; - return
-
- Presentation + } + + @undoBatch @action + changeDash = () => { + this.dashdStk = this.dashdStk === "2" ? "0" : "2"; + } + + @computed get appearanceEditor() { + return
+ {this.widthAndDash} + {this.strokeAndFill} +
; + } + + @computed get transformEditor() { + return
+ {this.controlPointsButton} + {this.hgtInput} + {this.XpsInput} + {this.rotInput} +
; + } + + /** + * Handles adding and removing members from the sharing panel + */ + // handleUserChange = (selectedUser: string, add: boolean) => { + // if (!Doc.UserDoc().sidebarUsersDisplayed) Doc.UserDoc().sidebarUsersDisplayed = new Doc; + // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { + // sidebarUsersDisplayed![`display-${selectedUser}`] = add; + // !add && runInAction(() => this.selectedUser = ""); + // }); + // } + + render() { + if (!this.selectedDoc && !this.isPres) { + return
+
+ No Document Selected +
+
; + + } else { + const novice = Doc.UserDoc().noviceMode; + + if (this.selectedDoc && !this.isPres) { + return
+
+ Properties +
+
+ {this.editableTitle} +
+
this.inOptions = true)} + onPointerLeave={action(() => this.inOptions = false)}> +
this.openOptions = !this.openOptions)} + style={{ backgroundColor: this.openOptions ? "black" : "" }}> + Options +
+ +
+
+ {!this.openOptions ? (null) : +
+ +
} +
+
+
this.openSharing = !this.openSharing)} + style={{ backgroundColor: this.openSharing ? "black" : "" }}> + Sharing {"&"} Permissions +
+ +
+
+ {!this.openSharing ? (null) : +
+
+ {!novice ? (
+ this.layoutDocAcls = !this.layoutDocAcls)} + checked={this.layoutDocAcls} + /> +
Layout
+
) : (null)} +
{"Re-distribute sharing settings"}
}> + +
+
+ {this.sharingTable} +
}
-
- {this.editableTitle} -
- {PresBox.Instance?._selectedArray.length} selected + + {!this.isInk ? (null) : +
+
this.openAppearance = !this.openAppearance)} + style={{ backgroundColor: this.openAppearance ? "black" : "" }}> + Appearance +
+ +
+
+ {!this.openAppearance ? (null) : +
+ {this.appearanceEditor} +
} +
} + + {this.isInk ?
+
this.openTransform = !this.openTransform)} + style={{ backgroundColor: this.openTransform ? "black" : "" }}> + Transform +
+ +
+
+ {this.openTransform ?
+ {this.transformEditor} +
: null} +
: null} + +
+
this.openFields = !this.openFields)} + style={{ backgroundColor: this.openFields ? "black" : "" }}> + Fields {"&"} Tags +
+ +
+
+ {!novice && this.openFields ?
+ {this.fieldsCheckbox} +
Layout
+
: null} + {!this.openFields ? (null) : +
+ {novice ? this.noviceFields : this.expandedField} +
} +
+
+
this.openContexts = !this.openContexts)} + style={{ backgroundColor: this.openContexts ? "black" : "" }}> + Contexts +
+ +
+
+ {this.openContexts ?
{this.contexts}
: null} +
+
+
this.openLayout = !this.openLayout)} + style={{ backgroundColor: this.openLayout ? "black" : "" }}> + Layout +
+ +
+
+ {this.openLayout ?
{this.layoutPreview}
: null} +
+
; + } + if (this.isPres) { + const selectedItem: boolean = PresBox.Instance?._selectedArray.length > 0; + const type = PresBox.Instance.activeItem?.type; + return
+
+ Presentation +
+
+ {this.editableTitle} +
+ {PresBox.Instance?._selectedArray.length} selected
- {PresBox.Instance?.listOfSelected} -
-
-
- {!selectedItem ? (null) :
-
{ this.openPresTransitions = !this.openPresTransitions; })} - style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> -     Transitions + {PresBox.Instance?.listOfSelected} +
+
+
+ {!selectedItem ? (null) :
+
{ this.openPresTransitions = !this.openPresTransitions; })} + style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> +     Transitions
- -
-
- {this.openPresTransitions ?
- {PresBox.Instance.transitionDropdown} -
: null} -
} - {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) :
-
this.openPresProgressivize = !this.openPresProgressivize)} - style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> -     Progressivize + +
+
+ {this.openPresTransitions ?
+ {PresBox.Instance.transitionDropdown} +
: null} +
} + {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) :
+
this.openPresProgressivize = !this.openPresProgressivize)} + style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> +     Progressivize
- -
-
- {this.openPresProgressivize ?
- {PresBox.Instance.progressivizeDropdown} -
: null} -
} - {!selectedItem || (type !== DocumentType.COL && type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) :
-
{ this.openSlideOptions = !this.openSlideOptions; })} - style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> -     {PresBox.Instance.stringType} options + +
+
+ {this.openPresProgressivize ?
+ {PresBox.Instance.progressivizeDropdown} +
: null} +
} + {!selectedItem || (type !== DocumentType.COL && type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) :
+
{ this.openSlideOptions = !this.openSlideOptions; })} + style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> +     {PresBox.Instance.stringType} options
- -
-
- {this.openSlideOptions ?
- {PresBox.Instance.optionsDropdown} -
: null} -
} -
-
{ this.openAddSlide = !this.openAddSlide; })} - style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> -     Add new slide + +
+
+ {this.openSlideOptions ?
+ {PresBox.Instance.optionsDropdown} +
: null} +
} +
+
{ this.openAddSlide = !this.openAddSlide; })} + style={{ backgroundColor: this.openAddSlide ? "black" : "" }}> +     Add new slide
- -
-
- {this.openAddSlide ?
- {PresBox.Instance.newDocumentDropdown} -
: null} -
-
; - } + +
+
+ {this.openAddSlide ?
+ {PresBox.Instance.newDocumentDropdown} +
: null} +
+
; + } + } } - } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From e7a6895ed4da4760c5e13c02eb08aaf7f0ffdb74 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 11:25:59 -0400 Subject: fixed tabs in carousel 3D view --- .../views/collections/CollectionCarousel3DView.tsx | 318 ++++++++++----------- 1 file changed, 159 insertions(+), 159 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 550a2f708..4e30709a6 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -19,163 +19,163 @@ const Carousel3DDocument = makeInterface(documentSchema, collectionSchema); @observer export class CollectionCarousel3DView extends CollectionSubView(Carousel3DDocument) { - @computed get scrollSpeed() { - return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed - } - - private _dropDisposer?: DragManager.DragDropDisposer; - - componentWillUnmount() { this._dropDisposer?.(); } - - protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view - this._dropDisposer?.(); - if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); - } - } - - panelWidth = () => this.props.PanelWidth() / 3; - panelHeight = () => this.props.PanelHeight() * 0.6; - onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); - @computed get content() { - const currentIndex = NumCast(this.layoutDoc._itemIndex); - const displayDoc = (childPair: { layout: Doc, data: Doc }) => { - const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout }); - const onChildClick = script && (() => script); - return ; - }; - - return (this.childLayoutPairs.map((childPair, index) => { - return ( -
- {displayDoc(childPair)} -
); - })); - } - - changeSlide = (direction: number) => { - this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length; - } - - onArrowClick = (e: React.MouseEvent, direction: number) => { - e.stopPropagation(); - this.changeSlide(direction); - !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = (direction === 1) ? "fwd" : "back"); // while autoscroll is on, keep the other autoscroll button hidden - !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on - } - - interval?: number; - startAutoScroll = (direction: number) => { - this.interval = window.setInterval(() => { - this.changeSlide(direction); - }, this.scrollSpeed); - } - - stopAutoScroll = () => { - window.clearInterval(this.interval); - this.interval = undefined; - this.fadeScrollButton(); - } - - toggleAutoScroll = (direction: number) => { - this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true; - this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll(); - } - - fadeScrollButton = () => { - window.setTimeout(() => { - !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = "none"); //fade away after 1.5s if it's not clicked. - }, 1500); - } - - _downX = 0; - _downY = 0; - onPointerDown = (e: React.PointerEvent) => { - this._downX = e.clientX; - this._downY = e.clientY; - document.addEventListener("pointerup", this.onpointerup); - } - private _lastTap: number = 0; - private _doubleTap = false; - onpointerup = (e: PointerEvent) => { - this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); - this._lastTap = Date.now(); - } - - onClick = (e: React.MouseEvent) => { - if (this._doubleTap) { - e.stopPropagation(); - this.props.Document.isLightboxOpen = true; - } - } - - @computed get buttons() { - if (!this.props.active()) return null; - return
-
this.onArrowClick(e, -1)} - > - -
-
this.onArrowClick(e, 1)} - > - -
- {this.autoScrollButton} -
; - } - - @computed get autoScrollButton() { - const whichButton = this.layoutDoc.showScrollButton; - return <> -
this.toggleAutoScroll(-1)}> - {this.layoutDoc.autoScrollOn ? : } -
-
this.toggleAutoScroll(1)}> - {this.layoutDoc.autoScrollOn ? : } -
- ; - } - - @computed get dots() { - return (this.childLayoutPairs.map((_child, index) => { - return
this.layoutDoc._itemIndex = index} />; - })); - } - - render() { - const index = NumCast(this.layoutDoc._itemIndex); - const translateX = 33 * (1 - index); - - return
-
- {this.content} -
- {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)} -
- {this.dots} -
-
; - } + @computed get scrollSpeed() { + return this.layoutDoc._autoScrollSpeed ? NumCast(this.layoutDoc._autoScrollSpeed) : 1000; //default scroll speed + } + + private _dropDisposer?: DragManager.DragDropDisposer; + + componentWillUnmount() { this._dropDisposer?.(); } + + protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view + this._dropDisposer?.(); + if (ele) { + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); + } + } + + panelWidth = () => this.props.PanelWidth() / 3; + panelHeight = () => this.props.PanelHeight() * 0.6; + onChildDoubleClick = () => ScriptCast(this.layoutDoc.onChildDoubleClick); + @computed get content() { + const currentIndex = NumCast(this.layoutDoc._itemIndex); + const displayDoc = (childPair: { layout: Doc, data: Doc }) => { + const script = ScriptField.MakeScript("child._showCaption = 'caption'", { child: Doc.name }, { child: childPair.layout }); + const onChildClick = script && (() => script); + return ; + }; + + return (this.childLayoutPairs.map((childPair, index) => { + return ( +
+ {displayDoc(childPair)} +
); + })); + } + + changeSlide = (direction: number) => { + this.layoutDoc._itemIndex = (NumCast(this.layoutDoc._itemIndex) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length; + } + + onArrowClick = (e: React.MouseEvent, direction: number) => { + e.stopPropagation(); + this.changeSlide(direction); + !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = (direction === 1) ? "fwd" : "back"); // while autoscroll is on, keep the other autoscroll button hidden + !this.layoutDoc.autoScrollOn && this.fadeScrollButton(); // keep pause button visible while autoscroll is on + } + + interval?: number; + startAutoScroll = (direction: number) => { + this.interval = window.setInterval(() => { + this.changeSlide(direction); + }, this.scrollSpeed); + } + + stopAutoScroll = () => { + window.clearInterval(this.interval); + this.interval = undefined; + this.fadeScrollButton(); + } + + toggleAutoScroll = (direction: number) => { + this.layoutDoc.autoScrollOn = this.layoutDoc.autoScrollOn ? false : true; + this.layoutDoc.autoScrollOn ? this.startAutoScroll(direction) : this.stopAutoScroll(); + } + + fadeScrollButton = () => { + window.setTimeout(() => { + !this.layoutDoc.autoScrollOn && (this.layoutDoc.showScrollButton = "none"); //fade away after 1.5s if it's not clicked. + }, 1500); + } + + _downX = 0; + _downY = 0; + onPointerDown = (e: React.PointerEvent) => { + this._downX = e.clientX; + this._downY = e.clientY; + document.addEventListener("pointerup", this.onpointerup); + } + private _lastTap: number = 0; + private _doubleTap = false; + onpointerup = (e: PointerEvent) => { + this._doubleTap = (Date.now() - this._lastTap < 300 && e.button === 0 && Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2); + this._lastTap = Date.now(); + } + + onClick = (e: React.MouseEvent) => { + if (this._doubleTap) { + e.stopPropagation(); + this.props.Document.isLightboxOpen = true; + } + } + + @computed get buttons() { + if (!this.props.active()) return null; + return
+
this.onArrowClick(e, -1)} + > + +
+
this.onArrowClick(e, 1)} + > + +
+ {this.autoScrollButton} +
; + } + + @computed get autoScrollButton() { + const whichButton = this.layoutDoc.showScrollButton; + return <> +
this.toggleAutoScroll(-1)}> + {this.layoutDoc.autoScrollOn ? : } +
+
this.toggleAutoScroll(1)}> + {this.layoutDoc.autoScrollOn ? : } +
+ ; + } + + @computed get dots() { + return (this.childLayoutPairs.map((_child, index) => { + return
this.layoutDoc._itemIndex = index} />; + })); + } + + render() { + const index = NumCast(this.layoutDoc._itemIndex); + const translateX = 33 * (1 - index); + + return
+
+ {this.content} +
+ {this.props.Document._chromeStatus !== "replaced" ? this.buttons : (null)} +
+ {this.dots} +
+
; + } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 266590937d3fda5dd96729edaa7c9bfac42370d9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 20:31:39 -0400 Subject: major performance fix by adding a SharingDocument to the user's DB account so that inquiring the users' UserDoc (and everything it referecens) is no longer necessary. --- src/client/DocServer.ts | 10 ++++++ src/client/util/CurrentUserUtils.ts | 39 +++++++++++++--------- src/client/util/GroupManager.tsx | 12 +++---- src/client/util/SettingsManager.tsx | 4 +-- src/client/util/SharingManager.tsx | 37 +++++++++----------- src/client/views/GlobalKeyHandler.ts | 3 +- src/client/views/nodes/DocumentView.tsx | 2 +- src/fields/Doc.ts | 2 ++ src/mobile/AudioUpload.tsx | 2 +- src/mobile/ImageUpload.tsx | 4 +-- src/mobile/MobileInterface.tsx | 8 ++--- src/server/ApiManagers/UserManager.ts | 14 +++++--- src/server/GarbageCollector.ts | 2 +- src/server/authentication/AuthenticationManager.ts | 3 +- src/server/authentication/DashUserModel.ts | 4 ++- 15 files changed, 82 insertions(+), 64 deletions(-) (limited to 'src') diff --git a/src/client/DocServer.ts b/src/client/DocServer.ts index d7dfa4498..9683eab45 100644 --- a/src/client/DocServer.ts +++ b/src/client/DocServer.ts @@ -9,6 +9,7 @@ import { GestureOverlay } from './views/GestureOverlay'; import MobileInkOverlay from '../mobile/MobileInkOverlay'; import { runInAction } from 'mobx'; import { ObjectField } from '../fields/ObjectField'; +import { StrCast } from '../fields/Types'; /** * This class encapsulates the transfer and cross-client synchronization of @@ -25,6 +26,15 @@ import { ObjectField } from '../fields/ObjectField'; */ export namespace DocServer { let _cache: { [id: string]: RefField | Promise> } = {}; + + export function PRINT_CACHE() { + const strings: string[] = []; + Array.from(Object.keys(_cache)).forEach(key => { + const doc = _cache[key]; + if (doc instanceof Doc) strings.push(StrCast(doc.author) + " " + StrCast(doc.title) + " " + StrCast(Doc.GetT(doc, "title", "string", true))); + }); + strings.sort().forEach((str, i) => console.log(i.toString() + " " + str)); + } export let _socket: SocketIOClient.Socket; // this client's distinct GUID created at initialization let GUID: string; diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index d45a2c90a..c00ad2334 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -404,7 +404,7 @@ export class CurrentUserUtils { const headerTemplate = Docs.Create.RTFDocument(new RichTextField(JSON.stringify(json), ""), { title: "header", version: headerViewVersion, target: doc, _height: 70, _headerHeight: 12, _headerFontSize: 9, _autoHeight: true, system: true, cloneFieldFilter: new List(["system"]) }, "header"); // text needs to be a space to allow templateText to be created headerTemplate[DataSym].layout = "
" + - " " + + " " + " " + "
"; (headerTemplate.proto as Doc).isTemplateDoc = makeTemplate(headerTemplate.proto as Doc, true, "headerView"); @@ -513,10 +513,7 @@ export class CurrentUserUtils { return doc.myItemCreators as Doc; } - static menuBtnDescriptions(doc: Doc): { - title: string, target: Doc, icon: string, click: string, watchedDocuments?: Doc - }[] { - this.setupSharingSidebar(doc); // sets up the right sidebar collection for mobile upload documents and sharing + static async menuBtnDescriptions(doc: Doc) { return [ { title: "Dashboards", target: Cast(doc.myDashboards, Doc, null), icon: "desktop", click: 'selectMainMenu(self)' }, { title: "Recently Closed", target: Cast(doc.myRecentlyClosedDocs, Doc, null), icon: "archive", click: 'selectMainMenu(self)' }, @@ -540,9 +537,10 @@ export class CurrentUserUtils { })) as any as Doc; } } - static setupMenuPanel(doc: Doc) { + static async setupMenuPanel(doc: Doc, sharingDocumentId: string) { if (doc.menuStack === undefined) { - const menuBtns = CurrentUserUtils.menuBtnDescriptions(doc).map(({ title, target, icon, click, watchedDocuments }) => + await this.setupSharingSidebar(doc, sharingDocumentId); // sets up the right sidebar collection for mobile upload documents and sharing + const menuBtns = (await CurrentUserUtils.menuBtnDescriptions(doc)).map(({ title, target, icon, click, watchedDocuments }) => Docs.Create.FontIconDocument({ icon, iconShape: "square", @@ -874,9 +872,18 @@ export class CurrentUserUtils { } // Sharing sidebar is where shared documents are contained - static setupSharingSidebar(doc: Doc) { + static async setupSharingSidebar(doc: Doc, sharingDocumentId: string) { if (doc.mySharedDocs === undefined) { - doc.mySharedDocs = new PrefetchProxy(Docs.Create.StackingDocument([], { title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true })); + let sharedDocs = await DocServer.GetRefField(sharingDocumentId); + if (!sharedDocs) { + sharedDocs = Docs.Create.StackingDocument([], { + title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true + }, sharingDocumentId); + } + if (sharedDocs instanceof Doc) { + sharedDocs.userColor = sharedDocs.userColor || "#12121233"; + } + doc.mySharedDocs = new PrefetchProxy(sharedDocs); } } @@ -943,11 +950,10 @@ export class CurrentUserUtils { return doc.clickFuncs as Doc; } - static async updateUserDocument(doc: Doc) { + static async updateUserDocument(doc: Doc, sharingDocumentId: string) { doc.system = true; doc.noviceMode = doc.noviceMode === undefined ? "true" : doc.noviceMode; doc.title = Doc.CurrentUserEmail; - doc.userColor = doc.userColor || "#12121233"; doc._raiseWhenDragged = true; doc.activeInkPen = doc; doc.activeInkColor = StrCast(doc.activeInkColor, "rgb(0, 0, 0)"); @@ -976,7 +982,7 @@ export class CurrentUserUtils { this.setupOverlays(doc); // documents in overlay layer this.setupDockedButtons(doc); // the bottom bar of font icons await this.setupSidebarButtons(doc); // the pop-out left sidebar of tools/panels - this.setupMenuPanel(doc); + await this.setupMenuPanel(doc, sharingDocumentId); doc.globalLinkDatabase = Docs.Prototypes.MainLinkDocument(); doc.globalScriptDatabase = Docs.Prototypes.MainScriptDocument(); doc.globalGroupDatabase = Docs.Prototypes.MainGroupDocument(); @@ -1013,10 +1019,11 @@ export class CurrentUserUtils { public static async loadUserDocument({ id, email }: { id: string, email: string }) { this.curr_id = id; Doc.CurrentUserEmail = email; - await rp.get(Utils.prepend("/getUserDocumentId")).then(id => { - if (id && id !== "guest") { - return DocServer.GetRefField(id).then(async field => - Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(id, true)))); + await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { + const { userDocumentId, sharingDocumentId } = JSON.parse(ids) as any; + if (userDocumentId !== "guest") { + return DocServer.GetRefField(userDocumentId).then(async field => + Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(userDocumentId, true), sharingDocumentId))); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index cb512bca8..70ea48ab8 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -64,14 +64,10 @@ export class GroupManager extends React.Component<{}> { const userList = await RequestPromise.get(Utils.prepend("/getUsers")); const raw = JSON.parse(userList) as User[]; const evaluating = raw.map(async user => { - const userDocument = await DocServer.GetRefField(user.userDocumentId); - if (userDocument instanceof Doc) { - const notificationDoc = await Cast(userDocument.mySharedDocs, Doc); - runInAction(() => { - if (notificationDoc instanceof Doc) { - this.users.push(user.email); - } - }); + const userSharingDocument = await DocServer.GetRefField(user.sharingDocumentId); + if (userSharingDocument instanceof Doc) { + const notificationDoc = await Cast(userSharingDocument.mySharedDocs, Doc, null); + runInAction(() => notificationDoc && this.users.push(user.email)); } }); return Promise.all(evaluating).then(() => this.populating = false); diff --git a/src/client/util/SettingsManager.tsx b/src/client/util/SettingsManager.tsx index cd01fea5a..9934f26d3 100644 --- a/src/client/util/SettingsManager.tsx +++ b/src/client/util/SettingsManager.tsx @@ -56,7 +56,7 @@ export class SettingsManager extends React.Component<{}> { @undoBatch changeFontFamily = action((e: React.ChangeEvent) => Doc.UserDoc().fontFamily = (e.currentTarget as any).value); @undoBatch changeFontSize = action((e: React.ChangeEvent) => Doc.UserDoc().fontSize = (e.currentTarget as any).value); @undoBatch switchActiveBackgroundColor = action((color: ColorState) => Doc.UserDoc().activeCollectionBackground = String(color.hex)); - @undoBatch switchUserColor = action((color: ColorState) => Doc.UserDoc().userColor = String(color.hex)); + @undoBatch switchUserColor = action((color: ColorState) => Doc.SharingDoc().userColor = String(color.hex)); @undoBatch playgroundModeToggle = action(() => { this.playgroundMode = !this.playgroundMode; @@ -205,6 +205,6 @@ export class SettingsManager extends React.Component<{}> { isDisplayed={this.isOpen} interactive={true} closeOnExternalClick={this.close} - dialogueBoxStyle={{ width: "600px", background: Cast(Doc.UserDoc().userColor, "string", null) }} />; + dialogueBoxStyle={{ width: "600px", background: Cast(Doc.SharingDoc().userColor, "string", null) }} />; } } \ No newline at end of file diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 984583ed5..0cf6cc87c 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -25,7 +25,7 @@ import { SearchBox } from "../views/search/SearchBox"; export interface User { email: string; - userDocumentId: string; + sharingDocumentId: string; } /** @@ -50,12 +50,11 @@ const storage = "data"; * A user who also has a notificationDoc. */ interface ValidatedUser { - user: User; - notificationDoc: Doc; - userColor: string; + user: User; // database minimal info to identify / communicate with a user (email, sharing doc id) + sharingDoc: Doc; // document to share/message another user + userColor: string; // stored on the sharinDoc, extracted for convenience? } - @observer export class SharingManager extends React.Component<{}> { public static Instance: SharingManager; @@ -119,7 +118,7 @@ export class SharingManager extends React.Component<{}> { } /** - * Populates the list of validated users (this.users) by adding registered users which have a mySharedDocs. + * Populates the list of validated users (this.users) by adding registered users which have a sharingDocument. */ populateUsers = async () => { if (!this.populating) { @@ -130,15 +129,9 @@ export class SharingManager extends React.Component<{}> { const evaluating = raw.map(async user => { const isCandidate = user.email !== Doc.CurrentUserEmail; if (isCandidate) { - const userDocument = await DocServer.GetRefField(user.userDocumentId); - if (userDocument instanceof Doc) { - const notificationDoc = await Cast(userDocument.mySharedDocs, Doc); - const userColor = StrCast(userDocument.userColor); - runInAction(() => { - if (notificationDoc instanceof Doc) { - this.users.push({ user, notificationDoc, userColor }); - } - }); + const userSharingDoc = await DocServer.GetRefField(user.sharingDocumentId); + if (userSharingDoc instanceof Doc) { + runInAction(() => this.users.push({ user, sharingDoc: userSharingDoc, userColor: StrCast(userSharingDoc.userColor) })); } } }); @@ -170,7 +163,7 @@ export class SharingManager extends React.Component<{}> { // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(doc) : group.docsShared = new List([doc]); - users.forEach(({ user, notificationDoc }) => { + users.forEach(({ user, sharingDoc: notificationDoc }) => { if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); // add the doc to the notificationDoc if it hasn't already been added 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)); // remove the doc from the list if it already exists }); @@ -185,7 +178,7 @@ export class SharingManager extends React.Component<{}> { */ shareWithAddedMember = (group: Doc, emailId: string) => { const user: ValidatedUser = this.users.find(({ user: { email } }) => email === emailId)!; - if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) === -1 && Doc.AddDocToList(user.notificationDoc, storage, doc)); + if (group.docsShared) DocListCast(group.docsShared).forEach(doc => Doc.IndexOf(doc, DocListCast(user.sharingDoc[storage])) === -1 && Doc.AddDocToList(user.sharingDoc, storage, doc)); } /** @@ -216,7 +209,7 @@ export class SharingManager extends React.Component<{}> { if (group.docsShared) { DocListCast(group.docsShared).forEach(doc => { - Doc.IndexOf(doc, DocListCast(user.notificationDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.notificationDoc, storage, doc); // remove the doc only if it is in the list + Doc.IndexOf(doc, DocListCast(user.sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(user.sharingDoc, storage, doc); // remove the doc only if it is in the list }); } } @@ -235,7 +228,7 @@ export class SharingManager extends React.Component<{}> { const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); - users.forEach(({ notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc)); + users.forEach(({ sharingDoc: notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc)); }); } } @@ -244,7 +237,7 @@ export class SharingManager extends React.Component<{}> { * Shares the document with a user. */ setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { - const { user, notificationDoc } = recipient; + const { user, sharingDoc: notificationDoc } = recipient; const target = targetDoc || this.targetDoc!; const key = normalizeEmail(user.email); const acl = `acl-${key}`; @@ -456,7 +449,7 @@ 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-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, notificationDoc, userColor }) => { + const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, sharingDoc: notificationDoc, userColor }) => { const userKey = `acl-${normalizeEmail(user.email)}`; 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-"; @@ -472,7 +465,7 @@ export class SharingManager extends React.Component<{}> { diff --git a/src/client/views/GlobalKeyHandler.ts b/src/client/views/GlobalKeyHandler.ts index 1b2395423..2ea1c464f 100644 --- a/src/client/views/GlobalKeyHandler.ts +++ b/src/client/views/GlobalKeyHandler.ts @@ -55,7 +55,8 @@ export class KeyManager { }); public handle = action(async (e: KeyboardEvent) => { - if (e.key.toLowerCase() === "shift") KeyManager.Instance.ShiftPressed = true; + if (e.key.toLowerCase() === "shift" && e.ctrlKey && e.altKey) KeyManager.Instance.ShiftPressed = true; + if (!Doc.UserDoc().noviceMode && e.key.toLocaleLowerCase() === "shift") DocServer.PRINT_CACHE(); const keyname = e.key && e.key.toLowerCase(); this.handleGreedy(keyname); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5f99f27b1..ddcf7f6f4 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1049,7 +1049,7 @@ export class DocumentView extends DocComponent(Docu const titleView = (!this.ShowTitle ? (null) :
users.user.email === this.dataDoc.author)?.userColor || (this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.UserDoc().userColor) : "rgba(0,0,0,0.4)"), + background: SharingManager.Instance.users.find(users => users.user.email === this.dataDoc.author)?.userColor || (this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"), pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined, }}> manager._searchQuery = query); } export function UserDoc(): Doc { return manager._user_doc; } + export function SharingDoc(): Doc { return Cast(Doc.UserDoc().mySharedDocs, Doc, null); } export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } export function GetSelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } diff --git a/src/mobile/AudioUpload.tsx b/src/mobile/AudioUpload.tsx index 36663c85f..ebc8bc8a7 100644 --- a/src/mobile/AudioUpload.tsx +++ b/src/mobile/AudioUpload.tsx @@ -53,7 +53,7 @@ export class AudioUpload extends React.Component { * Pushing the audio doc onto Dash Web through the right side bar */ uploadAudio = () => { - const audioRightSidebar = Cast(Doc.UserDoc().mySharedDocs, Doc) as Doc; + const audioRightSidebar = Cast(Doc.SharingDoc(), Doc, null); const audioDoc = this._audioCol; const data = Cast(audioRightSidebar.data, listSpec(Doc)); for (let i = 1; i < 8; i++) { diff --git a/src/mobile/ImageUpload.tsx b/src/mobile/ImageUpload.tsx index 1ee4c7815..65f9e40ff 100644 --- a/src/mobile/ImageUpload.tsx +++ b/src/mobile/ImageUpload.tsx @@ -63,11 +63,11 @@ export class Uploader extends React.Component { doc = Docs.Create.ImageDocument(path, { _nativeWidth: defaultNativeImageDim, _width: 400, title: name }); } this.setOpacity(4, "1"); // Slab 4 - const res = await rp.get(Utils.prepend("/getUserDocumentId")); + const res = await rp.get(Utils.prepend("/getUserDocumentIds")); if (!res) { throw new Error("No user id returned"); } - const field = await DocServer.GetRefField(res); + const field = await DocServer.GetRefField(JSON.parse(res).userDocumentId); let pending: Opt; if (field instanceof Doc) { pending = col; diff --git a/src/mobile/MobileInterface.tsx b/src/mobile/MobileInterface.tsx index 72914e62b..a42d85b56 100644 --- a/src/mobile/MobileInterface.tsx +++ b/src/mobile/MobileInterface.tsx @@ -440,7 +440,7 @@ export class MobileInterface extends React.Component { // DocButton that uses UndoManager and handles the opacity change if CanUndo is true @computed get undo() { if (this.mainContainer && this._activeDoc.type === "collection" && this._activeDoc !== this._homeDoc && - this._activeDoc !== Doc.UserDoc().mySharedDocs && this._activeDoc.title !== "WORKSPACES") { + this._activeDoc !== Doc.SharingDoc() && this._activeDoc.title !== "WORKSPACES") { return (
; } @@ -632,7 +632,7 @@ export class MobileInterface extends React.Component { */ @action switchToMobileUploads = () => { - const mobileUpload = Cast(Doc.UserDoc().mySharedDocs, Doc) as Doc; + const mobileUpload = Cast(Doc.SharingDoc(), Doc) as Doc; this.switchCurrentView(mobileUpload); this._homeMenu = false; } diff --git a/src/server/ApiManagers/UserManager.ts b/src/server/ApiManagers/UserManager.ts index 0d1d8f218..c9ffaff4c 100644 --- a/src/server/ApiManagers/UserManager.ts +++ b/src/server/ApiManagers/UserManager.ts @@ -19,16 +19,22 @@ export default class UserManager extends ApiManager { method: Method.GET, subscription: "/getUsers", secureHandler: async ({ res }) => { - const cursor = await Database.Instance.query({}, { email: 1, userDocumentId: 1 }, "users"); + const cursor = await Database.Instance.query({}, { email: 1, sharingDocumentId: 1 }, "users"); const results = await cursor.toArray(); - res.send(results.map(user => ({ email: user.email, userDocumentId: user.userDocumentId }))); + res.send(results.map(user => ({ email: user.email, sharingDocumentId: user.sharingDocumentId }))); } }); register({ method: Method.GET, - subscription: "/getUserDocumentId", - secureHandler: ({ res, user }) => res.send(user.userDocumentId) + subscription: "/getUserDocumentIds", + secureHandler: ({ res, user }) => res.send({ userDocumentId: user.userDocumentId, sharingDocumentId: user.sharingDocumentId }) + }); + + register({ + method: Method.GET, + subscription: "/getSharingDocumentId", + secureHandler: ({ res, user }) => res.send(user.sharingDocumentId) }); register({ diff --git a/src/server/GarbageCollector.ts b/src/server/GarbageCollector.ts index a9a3b0481..6bd0e5163 100644 --- a/src/server/GarbageCollector.ts +++ b/src/server/GarbageCollector.ts @@ -65,7 +65,7 @@ async function GarbageCollect(full: boolean = true) { // await new Promise(res => setTimeout(res, 3000)); const cursor = await Database.Instance.query({}, { userDocumentId: 1 }, 'users'); const users = await cursor.toArray(); - const ids: string[] = users.map(user => user.userDocumentId); + const ids: string[] = [...users.map(user => user.userDocumentId), ...users.map(user => user.sharingDocumentId)]]; const visited = new Set(); const files: { [name: string]: string[] } = {}; diff --git a/src/server/authentication/AuthenticationManager.ts b/src/server/authentication/AuthenticationManager.ts index 00f1fe44e..36363e3cf 100644 --- a/src/server/authentication/AuthenticationManager.ts +++ b/src/server/authentication/AuthenticationManager.ts @@ -47,7 +47,8 @@ export let postSignup = (req: Request, res: Response, next: NextFunction) => { const model = { email, password, - userDocumentId: Utils.GenerateGuid() + userDocumentId: Utils.GenerateGuid(), + sharingDocumentId: Utils.GenerateGuid() } as Partial; const user = new User(model); diff --git a/src/server/authentication/DashUserModel.ts b/src/server/authentication/DashUserModel.ts index 51d920a8f..0bdc25644 100644 --- a/src/server/authentication/DashUserModel.ts +++ b/src/server/authentication/DashUserModel.ts @@ -10,6 +10,7 @@ export type DashUserModel = mongoose.Document & { passwordResetExpires?: Date, userDocumentId: string; + sharingDocumentId: string; profile: { name: string, @@ -35,7 +36,8 @@ const userSchema = new mongoose.Schema({ passwordResetToken: String, passwordResetExpires: Date, - userDocumentId: String, + userDocumentId: String, // id that identifies a document which hosts all of a user's account data + sharingDocumentId: String, // id that identifies a document that stores documents shared to a user, their user color, and any additional info needed to communicate between users facebook: String, twitter: String, -- cgit v1.2.3-70-g09d2 From ae10a9c7e6188236023f790c1ff6abb0e211311e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 20:49:31 -0400 Subject: from last --- src/client/util/CurrentUserUtils.ts | 2 +- src/fields/Doc.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index c00ad2334..add595cd1 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1023,7 +1023,7 @@ export class CurrentUserUtils { const { userDocumentId, sharingDocumentId } = JSON.parse(ids) as any; if (userDocumentId !== "guest") { return DocServer.GetRefField(userDocumentId).then(async field => - Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(userDocumentId, true), sharingDocumentId))); + await this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId)); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index d9c641973..a8a5ba9bd 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -888,7 +888,7 @@ export namespace Doc { export function SetSelectedTool(tool: InkTool) { Doc.UserDoc().activeInkTool = tool; } export function GetSelectedTool(): InkTool { return StrCast(Doc.UserDoc().activeInkTool, InkTool.None) as InkTool; } - export function SetUserDoc(doc: Doc) { manager._user_doc = doc; } + export function SetUserDoc(doc: Doc) { return (manager._user_doc = doc); } export function IsSearchMatch(doc: Doc) { return computedFn(function IsSearchMatch(doc: Doc) { -- cgit v1.2.3-70-g09d2 From e5a889f5fb285d70ea75d07d889aeb8c45b4c393 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 20:51:40 -0400 Subject: fixed warnings --- src/client/util/CurrentUserUtils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index add595cd1..cff07d48e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -1020,10 +1020,10 @@ export class CurrentUserUtils { this.curr_id = id; Doc.CurrentUserEmail = email; await rp.get(Utils.prepend("/getUserDocumentIds")).then(ids => { - const { userDocumentId, sharingDocumentId } = JSON.parse(ids) as any; + const { userDocumentId, sharingDocumentId } = JSON.parse(ids); if (userDocumentId !== "guest") { return DocServer.GetRefField(userDocumentId).then(async field => - await this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId)); + this.updateUserDocument(Doc.SetUserDoc(field instanceof Doc ? field : new Doc(userDocumentId, true)), sharingDocumentId)); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } -- cgit v1.2.3-70-g09d2 From 3c7f85dbcddca0ffacd82c2d39e88bba8c4b3f3a Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 1 Oct 2020 23:04:31 -0400 Subject: various fixes to new sharing / email normalization --- src/client/documents/Documents.ts | 12 ++++----- src/client/util/CurrentUserUtils.ts | 9 ++++--- src/client/util/GroupManager.tsx | 2 +- src/client/util/SharingManager.tsx | 31 +++++++++++----------- src/client/views/DocComponent.tsx | 4 +-- src/client/views/PropertiesView.tsx | 11 ++------ src/client/views/collections/CollectionMenu.tsx | 4 +-- src/client/views/collections/CollectionView.tsx | 4 +-- .../views/nodes/formattedText/RichTextRules.ts | 8 +++--- 9 files changed, 40 insertions(+), 45 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 47fa7067d..7ee8267f8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -574,7 +574,7 @@ export namespace Docs { * only when creating a DockDocument from the current user's already existing * main document. */ - export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data") { + export function InstanceFromProto(proto: Doc, data: Field | undefined, options: DocumentOptions, delegId?: string, fieldKey: string = "data", protoId?: string) { const { omit: protoProps, extract: delegateProps } = OmitKeys(options, delegateKeys, "^_"); protoProps.system = delegateProps.system; @@ -590,7 +590,7 @@ export namespace Docs { protoProps.isPrototype = true; - const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey); + const dataDoc = MakeDataDelegate(proto, protoProps, data, fieldKey, protoId); const viewDoc = Doc.MakeDelegate(dataDoc, delegId); // so that the list of annotations is already initialised, prevents issues in addonly. @@ -620,8 +620,8 @@ export namespace Docs { * @param options initial values to apply to this new delegate * @param value the data to store in this new delegate */ - function MakeDataDelegate(proto: Doc, options: DocumentOptions, value?: D, fieldKey: string = "data") { - const deleg = Doc.MakeDelegate(proto); + function MakeDataDelegate(proto: Doc, options: DocumentOptions, value?: D, fieldKey: string = "data", id: string | undefined = undefined) { + const deleg = Doc.MakeDelegate(proto, id); if (value !== undefined) { deleg[fieldKey] = value; } @@ -807,8 +807,8 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", dontRegisterChildViews: true, ...options, _viewType: CollectionViewType.Tree }, id); } - export function StackingDocument(documents: Array, options: DocumentOptions, id?: string) { - return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Stacking }, id); + export function StackingDocument(documents: Array, options: DocumentOptions, id?: string, protoId?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { _chromeStatus: "collapsed", ...options, _viewType: CollectionViewType.Stacking }, id, undefined, protoId); } export function MulticolumnDocument(documents: Array, options: DocumentOptions) { diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index cff07d48e..ec550c15a 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -30,6 +30,7 @@ import { Scripting } from "./Scripting"; import { SearchUtil } from "./SearchUtil"; import { SelectionManager } from "./SelectionManager"; import { UndoManager } from "./UndoManager"; +import { SharingPermissions } from "../../fields/util"; const headerViewVersion = "0.1"; @@ -874,11 +875,13 @@ export class CurrentUserUtils { // Sharing sidebar is where shared documents are contained static async setupSharingSidebar(doc: Doc, sharingDocumentId: string) { if (doc.mySharedDocs === undefined) { - let sharedDocs = await DocServer.GetRefField(sharingDocumentId); + let sharedDocs = await DocServer.GetRefField(sharingDocumentId + "outer"); if (!sharedDocs) { sharedDocs = Docs.Create.StackingDocument([], { - title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, _showTitle: "title", ignoreClick: true, lockedPosition: true - }, sharingDocumentId); + title: "My SharedDocs", childDropAction: "alias", system: true, contentPointerEvents: "none", childLimitHeight: 0, _yMargin: 50, _gridGap: 15, + _showTitle: "title", ignoreClick: true, lockedPosition: true, + }, sharingDocumentId + "outer", sharingDocumentId); + (sharedDocs as Doc)["acl-Public"] = Doc.GetProto(sharedDocs as Doc)["acl-Public"] = SharingPermissions.Add; } if (sharedDocs instanceof Doc) { sharedDocs.userColor = sharedDocs.userColor || "#12121233"; diff --git a/src/client/util/GroupManager.tsx b/src/client/util/GroupManager.tsx index 70ea48ab8..e81c95d83 100644 --- a/src/client/util/GroupManager.tsx +++ b/src/client/util/GroupManager.tsx @@ -66,7 +66,7 @@ export class GroupManager extends React.Component<{}> { const evaluating = raw.map(async user => { const userSharingDocument = await DocServer.GetRefField(user.sharingDocumentId); if (userSharingDocument instanceof Doc) { - const notificationDoc = await Cast(userSharingDocument.mySharedDocs, Doc, null); + const notificationDoc = await Cast(userSharingDocument.data, Doc, null); runInAction(() => notificationDoc && this.users.push(user.email)); } }); diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index 0cf6cc87c..c67ec2db5 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -47,7 +47,7 @@ const groupType = "!groupType/"; const storage = "data"; /** - * A user who also has a notificationDoc. + * A user who also has a sharing doc. */ interface ValidatedUser { user: User; // database minimal info to identify / communicate with a user (email, sharing doc id) @@ -59,7 +59,7 @@ interface ValidatedUser { export class SharingManager extends React.Component<{}> { public static Instance: SharingManager; @observable private isOpen = false; // whether the SharingManager modal is open or not - @observable public users: ValidatedUser[] = []; // the list of users with notificationDocs + @observable public users: ValidatedUser[] = []; // the list of users with sharing docs @observable private targetDoc: Doc | undefined; // the document being shared @observable private targetDocView: DocumentView | undefined; // the DocumentView of the document being shared // @observable private copied = false; @@ -163,9 +163,9 @@ export class SharingManager extends React.Component<{}> { // if documents have been shared, add the doc to that list if it doesn't already exist, otherwise create a new list with the doc group.docsShared ? Doc.IndexOf(doc, DocListCast(group.docsShared)) === -1 && (group.docsShared as List).push(doc) : group.docsShared = new List([doc]); - users.forEach(({ user, sharingDoc: notificationDoc }) => { - if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(notificationDoc[storage])) === -1 && Doc.AddDocToList(notificationDoc, storage, doc); // add the doc to the notificationDoc if it hasn't already been added - 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)); // remove the doc from the list if it already exists + users.forEach(({ user, sharingDoc }) => { + if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(sharingDoc[storage])) === -1 && Doc.AddDocToList(sharingDoc, storage, doc); // add the doc to the sharingDoc if it hasn't already been added + else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); // remove the doc from the list if it already exists }); } }); @@ -228,7 +228,7 @@ export class SharingManager extends React.Component<{}> { const members: string[] = JSON.parse(StrCast(group.members)); const users: ValidatedUser[] = this.users.filter(({ user: { email } }) => members.includes(email)); - users.forEach(({ sharingDoc: notificationDoc }) => Doc.RemoveDocFromList(notificationDoc, storage, doc)); + users.forEach(({ sharingDoc }) => Doc.RemoveDocFromList(sharingDoc, storage, doc)); }); } } @@ -237,19 +237,18 @@ export class SharingManager extends React.Component<{}> { * Shares the document with a user. */ setInternalSharing = (recipient: ValidatedUser, permission: string, targetDoc?: Doc) => { - const { user, sharingDoc: notificationDoc } = recipient; + const { user, sharingDoc } = recipient; const target = targetDoc || this.targetDoc!; - const key = normalizeEmail(user.email); - const acl = `acl-${key}`; + const acl = `acl-${normalizeEmail(user.email)}`; + const myAcl = `acl-${Doc.CurrentUserEmailNormalized}`; const docs = SelectionManager.SelectedDocuments().length < 2 ? [target] : SelectionManager.SelectedDocuments().map(docView => docView.props.Document); - docs.forEach(doc => { - doc.author === Doc.CurrentUserEmail && !doc[`acl-${Doc.CurrentUserEmailNormalized}`] && distributeAcls(`acl-${Doc.CurrentUserEmailNormalized}`, SharingPermissions.Admin, doc); + doc.author === Doc.CurrentUserEmail && !doc[myAcl] && distributeAcls(myAcl, SharingPermissions.Admin, doc); 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)); + if (permission !== SharingPermissions.None) Doc.IndexOf(doc, DocListCast(sharingDoc[storage])) === -1 && Doc.AddDocToList(sharingDoc, storage, doc); + else GetEffectiveAcl(doc, undefined, user.email) === AclPrivate && Doc.IndexOf((doc.aliasOf as Doc || doc), DocListCast(sharingDoc[storage])) !== -1 && Doc.RemoveDocFromList(sharingDoc, storage, (doc.aliasOf as Doc || doc)); }); } @@ -449,7 +448,7 @@ 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-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, sharingDoc: notificationDoc, userColor }) => { + const userListContents: (JSX.Element | null)[] = users.filter(({ user }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(user.email)}`) : docs[0]?.author !== user.email).map(({ user, sharingDoc, userColor }) => { const userKey = `acl-${normalizeEmail(user.email)}`; 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-"; @@ -465,7 +464,7 @@ export class SharingManager extends React.Component<{}> { @@ -516,7 +515,7 @@ export class SharingManager extends React.Component<{}> { // the list of groups shared with - const groupListMap: (Doc | { groupName: string })[] = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${StrCast(groupName).replace('.', '_')}`) : true); + const groupListMap: (Doc | { groupName: string })[] = groups.filter(({ groupName }) => docs.length > 1 ? commonKeys.includes(`acl-${normalizeEmail(StrCast(groupName))}`) : true); groupListMap.unshift({ groupName: "Public" }, { groupName: "Override" }); const groupListContents = groupListMap.map(group => { const groupKey = `acl-${StrCast(group.groupName)}`; diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 893b74d75..b3fbe418b 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -7,7 +7,7 @@ import { InteractionUtils } from '../util/InteractionUtils'; import { List } from '../../fields/List'; import { DateField } from '../../fields/DateField'; import { ScriptField } from '../../fields/ScriptField'; -import { GetEffectiveAcl, SharingPermissions, distributeAcls } from '../../fields/util'; +import { GetEffectiveAcl, SharingPermissions, distributeAcls, denormalizeEmail } from '../../fields/util'; /// DocComponent returns a generic React base class used by views that don't have 'fieldKey' props (e.g.,CollectionFreeFormDocumentView, DocumentView) @@ -166,7 +166,7 @@ export function ViewBoxAnnotatableComponent

{ for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - if (d.author === key.substring(4).replace("_", ".") && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); + if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); //else if (this.props.Document[key] === SharingPermissions.Admin) distributeAcls(key, SharingPermissions.Add, d, true); // else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); } diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index b30691ff5..f3241e8d9 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -10,7 +10,7 @@ import { Id } from "../../fields/FieldSymbols"; import { InkField } from "../../fields/InkField"; import { ComputedField } from "../../fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../fields/Types"; -import { GetEffectiveAcl, SharingPermissions } from "../../fields/util"; +import { GetEffectiveAcl, SharingPermissions, denormalizeEmail } from "../../fields/util"; import { emptyFunction, emptyPath, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnOne } from "../../Utils"; import { DocumentType } from "../documents/DocumentTypes"; import { DocumentManager } from "../util/DocumentManager"; @@ -409,7 +409,7 @@ export class PropertiesView extends React.Component { // DocCastAsync(Doc.UserDoc().sidebarUsersDisplayed).then(sidebarUsersDisplayed => { if (commonKeys.length) { for (const key of commonKeys) { - const name = key.substring(4).replace("_", "."); + const name = denormalizeEmail(key.substring(4)); const uniform = docs.every(doc => this.layoutDocAcls ? doc?.[AclSym]?.[key] === docs[0]?.[AclSym]?.[key] : doc?.[DataSym]?.[AclSym]?.[key] === docs[0]?.[DataSym]?.[AclSym]?.[key]); if (name !== Doc.CurrentUserEmail && name !== target.author && name !== "Public" && name !== "Override"/* && sidebarUsersDisplayed![name] !== false*/) { tableEntries.push(this.sharingItem(name, showAdmin, uniform ? AclMap.get(this.layoutDocAcls ? target[AclSym][key] : target[DataSym][AclSym][key])! : "-multiple-")); @@ -417,13 +417,6 @@ export class PropertiesView extends React.Component { } } - // 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)); - // } - // } - // }) - const ownerSame = Doc.CurrentUserEmail !== target.author && docs.filter(doc => doc).every(doc => doc.author === docs[0].author); // shifts the current user, owner, public to the top of the doc. tableEntries.unshift(this.sharingItem("Override", showAdmin, docs.filter(doc => doc).every(doc => doc["acl-Override"] === docs[0]["acl-Override"]) ? (AclMap.get(target[AclSym]?.["acl-Override"]) || "None") : "-multiple-")); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index e9cb57fed..09ff3bb0c 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -894,14 +894,14 @@ export class CollectionStackingViewChrome extends React.Component 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[0] !== "_")); return keys.filter(key => key.toLowerCase().indexOf(val) > -1); } else { const keys = new Set(); 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[0] !== "_")); return noviceKeys.filter(key => key.toLowerCase().indexOf(val) > -1); } } diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 522f46280..2bdc8e2f3 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -10,7 +10,7 @@ import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { BoolCast, Cast, ScriptCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util'; +import { distributeAcls, GetEffectiveAcl, SharingPermissions, TraceMobx, normalizeEmail, denormalizeEmail } from '../../../fields/util'; import { returnFalse, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; @@ -143,7 +143,7 @@ export class CollectionView extends Touchable { for (const [key, value] of Object.entries(this.props.Document[AclSym])) { - if (d.author === key.substring(4).replace("_", ".") && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); + if (d.author === denormalizeEmail(key.substring(4)) && !d.aliasOf) distributeAcls(key, SharingPermissions.Admin, d, true); //else if (this.props.Document[key] === SharingPermissions.Admin) distributeAcls(key, SharingPermissions.Add, d, true); //else distributeAcls(key, this.AclMap.get(value) as SharingPermissions, d, true); } diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 921c0e128..72b91c955 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -3,7 +3,8 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { DataSym, Doc } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { ComputedField } from "../../../../fields/ScriptField"; -import { Cast, NumCast, StrCast } from "../../../../fields/Types"; +import { NumCast, StrCast } from "../../../../fields/Types"; +import { normalizeEmail } from "../../../../fields/util"; import { returnFalse, Utils } from "../../../../Utils"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; @@ -11,7 +12,6 @@ import { FormattedTextBox } from "./FormattedTextBox"; import { wrappingInputRule } from "./prosemirrorPatches"; import { RichTextMenu } from "./RichTextMenu"; import { schema } from "./schema_rts"; -import { List } from "../../../../fields/List"; export class RichTextRules { public Document: Doc; @@ -271,7 +271,7 @@ export class RichTextRules { (state, match, start, end) => { const fieldKey = match[1]; const rawdocid = match[3]?.substring(1); - const docid = rawdocid ? (!rawdocid.includes("@") ? Doc.CurrentUserEmail + "@" + rawdocid : rawdocid).replace(".", "_") : undefined; + const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined; const value = match[2]?.substring(1); if (!fieldKey) { const linkId = Utils.GenerateGuid(); @@ -304,7 +304,7 @@ export class RichTextRules { const fieldKey = match[1] || ""; const fieldParam = match[2]?.replace("…", "...") || ""; const rawdocid = match[3]?.substring(1); - const docid = rawdocid ? (!rawdocid.includes("@") ? Doc.CurrentUserEmail + "@" + rawdocid : rawdocid).replace(".", "_") : undefined; + const docid = rawdocid ? (!rawdocid.includes("@") ? normalizeEmail(Doc.CurrentUserEmail) + "@" + rawdocid : rawdocid) : undefined; if (!fieldKey && !docid) return state.tr; docid && DocServer.GetRefField(docid).then(docx => { if (!(docx instanceof Doc && docx)) { -- cgit v1.2.3-70-g09d2 From 1dee63242684f02543cf7667b53baa00d10ab6c1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 2 Oct 2020 00:34:30 -0400 Subject: fixed email (de)normalization. fixed sharing to hide objects whose permissions have been revoked --- src/client/views/collections/CollectionSubView.tsx | 7 +++++-- src/client/views/nodes/FontIconBox.tsx | 2 +- src/fields/util.ts | 4 ++-- 3 files changed, 8 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 649b8c175..fa80c8062 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,6 +1,6 @@ import { action, computed, IReactionDisposer, reaction, observable, runInAction } from "mobx"; import CursorField from "../../../fields/CursorField"; -import { Doc, Opt, Field, DocListCast } from "../../../fields/Doc"; +import { Doc, Opt, Field, DocListCast, AclPrivate } from "../../../fields/Doc"; import { Id, ToString } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; @@ -101,7 +101,10 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T, moreProps?: get childLayoutPairs(): { layout: Doc; data: Doc; }[] { const { Document, DataDoc } = this.props; - const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)).filter(pair => pair.layout); + const validPairs = this.childDocs.map(doc => Doc.GetLayoutDataDocPair(Document, !this.props.annotationsKey ? DataDoc : undefined, doc)). + filter(pair => { // filter out any documents that have a proto that we don't have permissions to (which we determine by not having any keys + return pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && Object.keys(pair.layout.proto).length)); + }); return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types } get childDocList() { diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 6e96513c7..156256fe5 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -105,7 +105,7 @@ export class FontIconBadge extends React.Component { render() { if (!(this.props.collection instanceof Doc)) return (null); - const length = DocListCast(this.props.collection.data).length; + const length = DocListCast(this.props.collection.data).filter(d => Object.keys(d).length).length; // filter out any documents that we can't read return

0 ? { "display": "initial" } : { "display": "none" }} onPointerDown={this.onPointerDown} > diff --git a/src/fields/util.ts b/src/fields/util.ts index 6c92aef81..4da9fce74 100644 --- a/src/fields/util.ts +++ b/src/fields/util.ts @@ -117,10 +117,10 @@ export function OVERRIDE_acl(val: boolean) { } export function normalizeEmail(email: string) { - return email.replace('.', '__'); + return email.replace(/\./g, '__'); } export function denormalizeEmail(email: string) { - return email.replace('__', '.'); + return email.replace(/__/g, '.'); } // playground mode allows the user to add/delete documents or make layout changes without them saving to the server -- cgit v1.2.3-70-g09d2