From cbf179ac597368f272d7f31c38f6afad90f57a2f Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 26 Aug 2020 21:40:37 -0400 Subject: fixed (largely) properties buttons & view to work with multiple selections. fixed exceptions with clusters in freeformview --- src/client/views/PropertiesButtons.tsx | 191 +++++---------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +- .../collectionFreeForm/PropertiesView.tsx | 51 ++++-- 3 files changed, 80 insertions(+), 170 deletions(-) (limited to 'src') diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 29aa1ff61..f79a68b5a 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -3,33 +3,24 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from '@material-ui/core'; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { AclAdmin, AclEdit, DataSym, Doc } from "../../fields/Doc"; +import { Doc } from "../../fields/Doc"; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; import { Cast, NumCast } from "../../fields/Types"; import { ImageField } from '../../fields/URLField'; -import { GetEffectiveAcl } from '../../fields/util'; -import { emptyFunction, setupMoveUpEvents } from "../../Utils"; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { GooglePhotos } from '../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from '../documents/Documents'; import { DocumentType } from '../documents/DocumentTypes'; -import { DragManager } from '../util/DragManager'; import { SelectionManager } from '../util/SelectionManager'; -import { SharingManager } from '../util/SharingManager'; import { undoBatch } from '../util/UndoManager'; -import { CollectionDockingView, DockedFrameRenderer } from './collections/CollectionDockingView'; -import { ParentDocSelector } from './collections/ParentDocumentSelector'; +import { CollectionDockingView } from './collections/CollectionDockingView'; import './collections/ParentDocumentSelector.scss'; -import { MetadataEntryMenu } from './MetadataEntryMenu'; -import { DocumentView } from './nodes/DocumentView'; import { GoogleRef } from "./nodes/formattedText/FormattedTextBox"; -import { PresBox } from './nodes/PresBox'; import './PropertiesButtons.scss'; -import { TemplateMenu } from "./TemplateMenu"; -import { Template, Templates } from "./Templates"; import React = require("react"); +import { CollectionViewType } from './collections/CollectionView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -69,7 +60,6 @@ export class PropertiesButtons extends React.Component<{}, {}> { return SelectionManager.SelectedDocuments()[0]; } else return undefined; } - @computed get dataDoc() { return this.selectedDocumentView?.dataDoc; } @computed get onClick() { return this.selectedDoc?.onClickBehavior ? this.selectedDoc?.onClickBehavior : "nothing"; } @@ -200,55 +190,9 @@ export class PropertiesButtons extends React.Component<{}, {}> { ; } - @computed - get metadataButton() { - //const view0 = this.view0; - if (this.selectedDoc) { - return
Show metadata panel
} placement="top"> -
- /* tfs: @bcz This might need to be the data document? */}> -
-
e.stopPropagation()} > - {} -
-
Metadata
-
-
-
; - } else { - return null; - } - - } - - @computed - get templateButton() { - const docView = this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined; - const templates: Map = new Map(); - const views = [this.selectedDocumentView]; - Array.from(Object.values(Templates.TemplateList)).map(template => - templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template.Name] ? true : false, false as boolean))); - return !docView ? (null) : - Customize layout} placement="top"> -
- this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} - content={ v).map(v => v as DocumentView)} templates={templates} />}> -
-
- -
-
Layout
-
-
-
; - } - - @action @undoBatch onLock = () => { - const docView = this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined; - docView?.toggleLockPosition(); + SelectionManager.SelectedDocuments().forEach(dv => dv.toggleLockPosition()); } @computed @@ -282,31 +226,10 @@ export class PropertiesButtons extends React.Component<{}, {}> { ; } - @computed - get deleteButton() { - const targetDoc = this.selectedDoc; - return !targetDoc ? (null) : Close Document} placement="top"> -
-
- -
-
close
-
-
; - } - - @undoBatch - @action - deleteDocument = () => { - const removeDoc = this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView?.props.removeDocument : SelectionManager.SelectedSchemaCollection()?.props.removeDocument; - this.selectedDoc && removeDoc?.(this.selectedDoc); - SelectionManager.DeselectAll(); - } - @undoBatch @action setDictation = () => { - this.selectedDoc && (this.selectedDoc._showAudio = !this.selectedDoc._showAudio); + SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._showAudio = dv.rootDoc._showAudio === !dv.rootDoc._showAudio); } @computed @@ -326,7 +249,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action setTitle = () => { - this.selectedDoc && (this.selectedDoc._showTitle = this.selectedDoc._showTitle === undefined ? "title" : undefined); + SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._showTitle = dv.rootDoc._showTitle === undefined ? "title" : undefined); } @computed @@ -345,7 +268,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action setCaption = () => { - this.selectedDoc && (this.selectedDoc._showCaption = this.selectedDoc._showCaption === undefined ? "caption" : undefined); + SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._showCaption = dv.rootDoc._showCaption === undefined ? "caption" : undefined); } @computed @@ -364,7 +287,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action setChrome = () => { - this.selectedDoc && (this.selectedDoc._chromeStatus = this.selectedDoc._chromeStatus === "disabled" ? "enabled" : "disabled"); + SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._chromeStatus = dv.rootDoc._chromeStatus === "disabled" ? "enabled" : "disabled"); } @computed @@ -406,28 +329,31 @@ export class PropertiesButtons extends React.Component<{}, {}> { handleOptionChange = (e: any) => { const value = e.target.value; this.selectedDoc && (this.selectedDoc.onClickBehavior = e.target.value); - const docView = this.selectedDocumentView?.props.Document === this.selectedDoc ? this.selectedDocumentView : undefined; - if (value === "nothing") { - docView?.noOnClick(); - } else if (value === "enterPortal") { - docView?.noOnClick(); - docView?.makeIntoPortal(); - } else if (value === "toggleDetail") { - docView?.noOnClick(); - docView?.toggleDetail(); - } else if (value === "linkInPlace") { - docView?.noOnClick(); - docView?.toggleFollowLink("inPlace", true, false); - } else if (value === "linkOnRight") { - docView?.noOnClick(); - docView?.toggleFollowLink("onRight", false, false); - } + + SelectionManager.SelectedDocuments().forEach(dv => { + if (value === "nothing") { + dv.noOnClick(); + } else if (value === "enterPortal") { + dv.noOnClick(); + dv.makeIntoPortal(); + } else if (value === "toggleDetail") { + dv.noOnClick(); + dv.toggleDetail(); + } else if (value === "linkInPlace") { + dv.noOnClick(); + dv.toggleFollowLink("inPlace", true, false); + } else if (value === "linkOnRight") { + dv.noOnClick(); + dv.toggleFollowLink("onRight", false, false); + } + }); } @undoBatch @action editOnClickScript = () => { if (this.selectedDoc) { - DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick"); + if (SelectionManager.SelectedDocuments().length) SelectionManager.SelectedDocuments().forEach(dv => DocUtils.makeCustomViewClicked(dv.rootDoc, undefined, "onClick")); + else DocUtils.makeCustomViewClicked(this.selectedDoc, undefined, "onClick"); } } @@ -510,12 +436,18 @@ export class PropertiesButtons extends React.Component<{}, {}> { @action @undoBatch changeFitToBox = () => { - this.selectedDoc && (this.selectedDoc._fitToBox = !this.selectedDoc._fitToBox); + if (this.selectedDoc) { + if (SelectionManager.SelectedDocuments().length) SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._fitToBox = !dv.rootDoc._fitToBox); + else this.selectedDoc._fitToBox = !this.selectedDoc._fitToBox; + } } @action @undoBatch changeClusters = () => { - this.selectedDoc && (this.selectedDoc._useClusters = !this.selectedDoc._useClusters); + if (this.selectedDoc) { + if (SelectionManager.SelectedDocuments().length) SelectionManager.SelectedDocuments().forEach(dv => dv.rootDoc._useClusters = !dv.rootDoc._useClusters); + else this.selectedDoc._useClusters = !this.selectedDoc._useClusters; + } } @computed @@ -558,43 +490,19 @@ export class PropertiesButtons extends React.Component<{}, {}> { ; } - // @computed - // get importButton() { - // const targetDoc = this.selectedDoc; - // return !targetDoc ? (null) :
{"Import a Document"}
}> - //
{ - // if (this.selectedDocumentView) { - // CollectionFreeFormView.importDocument(100, 100); - // } - // }}> - // {} - //
- //
; - // } - - render() { if (!this.selectedDoc) return (null); - const isText = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof RichTextField; + const layoutField = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)]; + const isText = layoutField instanceof RichTextField; + const isImage = layoutField instanceof ImageField; + const isInk = layoutField instanceof InkField; + const isCollection = this.selectedDoc.type === DocumentType.COL; + const isFreeForm = this.selectedDoc._viewType === CollectionViewType.Freeform; const considerPull = isText && this.considerGoogleDocsPull; const considerPush = isText && this.considerGoogleDocsPush; - const isImage = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof ImageField; - const isInk = this.selectedDoc[Doc.LayoutFieldKey(this.selectedDoc)] instanceof InkField; - const isCollection = this.selectedDoc.type === DocumentType.COL ? true : false; - const isFreeForm = this.selectedDoc._viewType === "freeform" ? true : false; - //const collectionAcl = GetEffectiveAcl(this.selectedDocumentView?.props.ContainingCollectionDoc?.[DataSym]); - - return
- {/*
- {this.templateButton} -
*/} - {/*
- {this.metadataButton} -
*/} + + return
{this.titleButton}
@@ -610,11 +518,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
{this.dictationButton}
- {/* {collectionAcl === AclAdmin || collectionAcl === AclEdit ? -
- {this.deleteButton} -
- : (null)} */}
{this.onClickButton}
@@ -627,22 +530,16 @@ export class PropertiesButtons extends React.Component<{}, {}> {
{this.googlePhotosButton}
- {/*
- {this.importButton} -
*/}
{this.clustersButton}
-
{this.fitContentButton}
-
{this.maskButton}
-
; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8bd454249..aae324b07 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -320,7 +320,7 @@ export class CollectionFreeFormView extends CollectionSubView Doc.IndexOf(member, childLayouts) !== -1).length)) { + if (docFirst.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { docFirst.cluster = preferredInd; } this._clusterSets.map((set, i) => { @@ -333,7 +333,7 @@ export class CollectionFreeFormView extends CollectionSubView this._clusterSets[doc.cluster = NumCast(docFirst.cluster)].push(doc)); } @@ -354,7 +354,7 @@ export class CollectionFreeFormView extends CollectionSubView Doc.IndexOf(member, childLayouts) !== -1).length)) { + if (doc.cluster === -1 && preferredInd !== -1 && this._clusterSets.length > preferredInd && (!this._clusterSets[preferredInd] || !this._clusterSets[preferredInd].filter(member => Doc.IndexOf(member, childLayouts) !== -1).length)) { doc.cluster = preferredInd; } this._clusterSets.map((set, i) => { @@ -365,7 +365,7 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.selectedDoc?.type === DocumentType.PRES) return true; return false; } - @computed get dataDoc() { return this.selectedDocumentView?.dataDoc; } + @computed get dataDoc() { return this.selectedDoc?.[DataSym]; } @observable layoutFields: boolean = false; @@ -134,24 +134,26 @@ export class PropertiesView extends React.Component { @computed get expandedField() { if (this.dataDoc && this.selectedDoc) { const ids: { [key: string]: string } = {}; - const doc = this.layoutFields ? Doc.Layout(this.selectedDoc) : this.dataDoc; - doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); + 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 contents = doc[key]; + 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 { - let contentElement: (JSX.Element | null)[] | JSX.Element = []; - contentElement = Field.toKeyValueString(doc, key)} - SetValue={(value: string) => KeyValueBox.SetField(doc, key, value, true)} + GetValue={() => 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 + ":"} @@ -174,17 +176,19 @@ export class PropertiesView extends React.Component { } @computed get noviceFields() { - if (this.dataDoc && this.selectedDoc) { + if (this.dataDoc) { const ids: { [key: string]: string } = {}; - const doc = this.dataDoc; - doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); + 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"]; 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") && key !== "UseCors")), ...noviceReqFields, ...noviceLayoutFields]; for (const key of noviceKeys.sort()) { - const contents = this.selectedDoc[key]; + 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} @@ -202,8 +206,8 @@ export class PropertiesView extends React.Component { contents={value} height={13} fontSize={10} - GetValue={() => Field.toKeyValueString(this.selectedDoc!, key)} - SetValue={(value: string) => KeyValueBox.SetField(noviceLayoutFields.includes(key) ? this.selectedDoc! : doc, key, value, true)} + GetValue={() => contents !== undefined ? Field.toString(contents as Field) : "null"} + SetValue={(value: string) => { docs.map(doc => KeyValueBox.SetField(doc, key, value, true)); return true; }} />; rows.push(
@@ -260,6 +264,9 @@ export class PropertiesView extends React.Component { 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; @@ -430,21 +437,27 @@ export class PropertiesView extends React.Component { } @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
StrCast(this.selectedDoc?.title)} + GetValue={() => title} SetValue={this.setTitle} />
; } @undoBatch @action setTitle = (value: string) => { - if (this.dataDoc) { - this.selectedDoc && Doc.SetInPlace(this.selectedDoc, "title", value, true); - KeyValueBox.SetField(this.dataDoc, "title", value, true); + 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; } return false; -- cgit v1.2.3-70-g09d2