diff --git a/v3/src/components/case-card/case-card.v2.js b/v3/src/components/case-card/case-card.v2.js index 91083ac989..2f477fd99b 100644 --- a/v3/src/components/case-card/case-card.v2.js +++ b/v3/src/components/case-card/case-card.v2.js @@ -2,12 +2,12 @@ import createReactClass from "create-react-class" import PropTypes from 'prop-types' import React from 'react' import ReactDOMFactories from "react-dom-factories" +import { getSharedCaseMetadataFromDataset } from "../../models/shared/shared-data-utils" +import { uiState } from "../../models/ui-state" import { preventCollectionReorg } from "../../utilities/plugin-utils" import { createReactFactory, DG } from "../../v2/dg-compat.v2" import { SC } from "../../v2/sc-compat" -import { getSharedCaseMetadataFromDataset } from "../../models/shared/shared-data-utils" import { EditAttributePropertiesModal } from "../case-tile-common/attribute-menu/edit-attribute-properties-modal" -import { EditFormulaModal } from "../case-tile-common/attribute-menu/edit-formula-modal" import "./attribute-name-cell.v2" import "./attribute-value-cell.v2" @@ -249,17 +249,6 @@ iDataContext.doSelectCases({ this.setState({ editAttributePropModalIsOpen: false, currentAttributeId: null }) }, - editFormulaModal(attributeId, isOpen) { - if (attributeId) { - this.setState({editFormulaModalIsOpen: isOpen, currentAttributeId: attributeId}) - } - }, - - closeEditFormulaModal() { - this.setState({ editFormulaModalIsOpen: false, currentAttributeId: null }) - this.incrementStateCount() - }, - /** * ------------------Below here are rendering functions--------------- */ @@ -534,7 +523,7 @@ iDataContext.doSelectCases({ }.bind(this), editFormula = function () { - this.editFormulaModal(iAttr.get('id'), true) + uiState.setEditFormulaAttributeId(iAttr.get('id')) }.bind(this), hideAttribute = function () { @@ -934,11 +923,6 @@ return tResult attributeId: this.state.currentAttributeId, isOpen: this.state.editAttributePropModalIsOpen, onClose: this.closeEditAttributePropModal - }), - this.state.editFormulaModalIsOpen && React.createElement(EditFormulaModal, { - attributeId: this.state.currentAttributeId, - isOpen: this.state.editFormulaModalIsOpen, - onClose: this.closeEditFormulaModal })) } } diff --git a/v3/src/components/case-tile-common/attribute-menu/attribute-menu-list.tsx b/v3/src/components/case-tile-common/attribute-menu/attribute-menu-list.tsx index 71f5ada032..e0b3e50741 100644 --- a/v3/src/components/case-tile-common/attribute-menu/attribute-menu-list.tsx +++ b/v3/src/components/case-tile-common/attribute-menu/attribute-menu-list.tsx @@ -9,12 +9,12 @@ import { } from "../../../models/data/data-set-notifications" import { IAttributeChangeResult } from "../../../models/data/data-set-types" import { sortItemsWithCustomUndoRedo } from "../../../models/data/data-set-undo" +import { uiState } from "../../../models/ui-state" import { allowAttributeDeletion, preventCollectionReorg, preventTopLevelReorg } from "../../../utilities/plugin-utils" import { t } from "../../../utilities/translation/translate" import { EditAttributePropertiesModal } from "./edit-attribute-properties-modal" -import { EditFormulaModal } from "./edit-formula-modal" interface IProps { attributeId: string @@ -28,7 +28,6 @@ const AttributeMenuListComp = forwardRef( const caseMetadata = useCaseMetadata() // each use of useDisclosure() maintains its own state and callbacks so they can be used for independent dialogs const propertiesModal = useDisclosure() - const formulaModal = useDisclosure() if (!attributeId) return null @@ -47,13 +46,7 @@ const AttributeMenuListComp = forwardRef( } const handleEditFormulaOpen = () => { - formulaModal.onOpen() - onModalOpen(true) - } - - const handleEditFormulaClose = () => { - formulaModal.onClose() - onModalOpen(false) + uiState.setEditFormulaAttributeId(attributeId) } const handleSortCases = (item: IMenuItem) => { @@ -206,7 +199,6 @@ const AttributeMenuListComp = forwardRef( - ) }) diff --git a/v3/src/components/case-tile-common/attribute-menu/attribute-menu.scss b/v3/src/components/case-tile-common/attribute-menu/attribute-menu.scss index 4fa8af86e7..98d48839f1 100644 --- a/v3/src/components/case-tile-common/attribute-menu/attribute-menu.scss +++ b/v3/src/components/case-tile-common/attribute-menu/attribute-menu.scss @@ -20,34 +20,3 @@ margin-left: 10px; } } - -// Formula Editor Styles -.codap-modal-content { - .formula-modal-body { - cursor: default; - - .formula-form-control { - input { - max-width: 270px; - } - } - - .formula-editor-button { - padding: 0 10px; - &:hover { - background: $codap-teal-lighter; - } - &:active, &.menu-open { - background: $codap-teal-light; - color: white; - } - &.insert-value { - margin-left: 0; - } - } - } - - .formula-modal-footer { - cursor: default; - } -} diff --git a/v3/src/components/case-tile-common/attribute-menu/edit-formula-modal.tsx b/v3/src/components/case-tile-common/attribute-menu/edit-formula-modal.tsx deleted file mode 100644 index 2c1223d7cf..0000000000 --- a/v3/src/components/case-tile-common/attribute-menu/edit-formula-modal.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { Box, Button, Flex, FormControl, FormLabel, Input, ModalBody, ModalCloseButton, - ModalFooter, ModalHeader, Tooltip } from "@chakra-ui/react" -import React, { useEffect, useState } from "react" -import { observer } from "mobx-react-lite" -import { clsx } from "clsx" -import { useDataSetContext } from "../../../hooks/use-data-set-context" -import { logStringifiedObjectMessage } from "../../../lib/log-message" -import { updateAttributesNotification, updateCasesNotification } from "../../../models/data/data-set-notifications" -import { t } from "../../../utilities/translation/translate" -import { FormulaEditor } from "../../common/formula-editor" -import { FormulaEditorContext, useFormulaEditorState } from "../../common/formula-editor-context" -import { CodapModal } from "../../codap-modal" -import { InsertFunctionMenu } from "../../common/formula-insert-function-menu" -import { InsertValuesMenu } from "../../common/formula-insert-values-menu" - -import "./attribute-menu.scss" - -interface IProps { - attributeId: string - isOpen: boolean - onClose: () => void -} - -export const EditFormulaModal = observer(function EditFormulaModal({ attributeId, isOpen, onClose }: IProps) { - const dataSet = useDataSetContext() - const attribute = dataSet?.attrFromID(attributeId) - const [showValuesMenu, setShowValuesMenu] = useState(false) - const [showFunctionMenu, setShowFunctionMenu] = useState(false) - - const formulaEditorState = useFormulaEditorState(attribute?.formula?.display ?? "") - const { formula, setFormula } = formulaEditorState - - - useEffect(() => { - setFormula(attribute?.formula?.display || "") - }, [attribute?.formula?.display, setFormula]) - - const applyFormula = () => { - if (attribute) { - dataSet?.applyModelChange(() => { - attribute.setDisplayExpression(formula) - }, { - // TODO Should also broadcast notify component edit formula notification - notify: [ - updateCasesNotification(dataSet), - updateAttributesNotification([attribute], dataSet) - ], - undoStringKey: "DG.Undo.caseTable.editAttributeFormula", - redoStringKey: "DG.Redo.caseTable.editAttributeFormula", - log: logStringifiedObjectMessage("Edit attribute formula: %@", - {name: attribute.name, collection: dataSet?.getCollectionForAttribute(attributeId)?.name, formula}, - "data") - }) - } - closeModal() - } - - const closeModal = () => { - setShowValuesMenu(false) - setShowFunctionMenu(false) - onClose() - } - - const handleModalWhitespaceClick = () => { - setShowValuesMenu(false) - setShowFunctionMenu(false) - } - - const handleInsertValuesOpen = (e: React.MouseEvent) => { - e.stopPropagation() - setShowValuesMenu(true) - setShowFunctionMenu(false) - } - - const handleInsertFunctionsOpen = (e: React.MouseEvent) => { - e.stopPropagation() - setShowFunctionMenu(true) - setShowValuesMenu(false) - } - - const footerButtons = [{ - label: t("DG.AttrFormView.cancelBtnTitle"), - tooltip: t("DG.AttrFormView.cancelBtnTooltip"), - onClick: closeModal - }, { - label: t("DG.AttrFormView.applyBtnTitle"), - onClick: applyFormula, - default: true - }] - - return ( - - - -
-
- - - e.stopPropagation()}> - - {t("DG.AttrFormView.attrNamePrompt")} - - - {t("DG.AttrFormView.formulaPrompt")} - - - - - - - {showValuesMenu && - - } - - - - {showFunctionMenu && - - } - - - - - { footerButtons.map((b, idx) => { - const key = `${idx}-${b.label}` - return ( - - - - ) - }) - } - - - - ) -}) diff --git a/v3/src/components/common/edit-formula-modal.scss b/v3/src/components/common/edit-formula-modal.scss new file mode 100644 index 0000000000..10a6ab5a39 --- /dev/null +++ b/v3/src/components/common/edit-formula-modal.scss @@ -0,0 +1,31 @@ +@import "../vars.scss"; + +.codap-modal-content { + .formula-modal-body { + cursor: default; + + .formula-form-control { + input { + max-width: 270px; + } + } + + .formula-editor-button { + padding: 0 10px; + &:hover { + background: $codap-teal-lighter; + } + &:active, &.menu-open { + background: $codap-teal-light; + color: white; + } + &.insert-value { + margin-left: 0; + } + } + } + + .formula-modal-footer { + cursor: default; + } +} diff --git a/v3/src/components/common/edit-formula-modal.tsx b/v3/src/components/common/edit-formula-modal.tsx new file mode 100644 index 0000000000..5453f85836 --- /dev/null +++ b/v3/src/components/common/edit-formula-modal.tsx @@ -0,0 +1,168 @@ +import { + Box, Button, Flex, FormControl, FormLabel, Input, ModalBody, ModalCloseButton, ModalFooter, ModalHeader, Tooltip +} from "@chakra-ui/react" +import React, { useEffect, useState } from "react" +import { observer } from "mobx-react-lite" +import { clsx } from "clsx" +import { DataSetContext } from "../../hooks/use-data-set-context" +import { logStringifiedObjectMessage } from "../../lib/log-message" +import { appState } from "../../models/app-state" +import { updateAttributesNotification, updateCasesNotification } from "../../models/data/data-set-notifications" +import { getSharedDataSets } from "../../models/shared/shared-data-utils" +import { t } from "../../utilities/translation/translate" +import { FormulaEditor } from "./formula-editor" +import { FormulaEditorContext, useFormulaEditorState } from "./formula-editor-context" +import { CodapModal } from "../codap-modal" +import { InsertFunctionMenu } from "./formula-insert-function-menu" +import { InsertValuesMenu } from "./formula-insert-values-menu" + +import "./edit-formula-modal.scss" + +interface IProps { + attributeId: string + isOpen: boolean + onClose: () => void +} + +export const EditFormulaModal = observer(function EditFormulaModal({ attributeId, isOpen, onClose }: IProps) { + const dataSet = getSharedDataSets(appState.document).find(ds => ds.dataSet.attrFromID(attributeId))?.dataSet + const attribute = dataSet?.attrFromID(attributeId) + const [showValuesMenu, setShowValuesMenu] = useState(false) + const [showFunctionMenu, setShowFunctionMenu] = useState(false) + + const formulaEditorState = useFormulaEditorState(attribute?.formula?.display ?? "") + const { formula, setFormula } = formulaEditorState + + + useEffect(() => { + setFormula(attribute?.formula?.display || "") + }, [attribute, attribute?.formula?.display, setFormula]) + + const applyFormula = () => { + if (attribute) { + dataSet?.applyModelChange(() => { + attribute.setDisplayExpression(formula) + }, { + // TODO Should also broadcast notify component edit formula notification + notify: [ + updateCasesNotification(dataSet), + updateAttributesNotification([attribute], dataSet) + ], + undoStringKey: "DG.Undo.caseTable.editAttributeFormula", + redoStringKey: "DG.Redo.caseTable.editAttributeFormula", + log: logStringifiedObjectMessage("Edit attribute formula: %@", + {name: attribute.name, collection: dataSet?.getCollectionForAttribute(attributeId)?.name, formula}, + "data") + }) + } + closeModal() + } + + const closeModal = () => { + setShowValuesMenu(false) + setShowFunctionMenu(false) + onClose() + } + + const handleModalWhitespaceClick = () => { + setShowValuesMenu(false) + setShowFunctionMenu(false) + } + + const handleInsertValuesOpen = (e: React.MouseEvent) => { + e.stopPropagation() + setShowValuesMenu(true) + setShowFunctionMenu(false) + } + + const handleInsertFunctionsOpen = (e: React.MouseEvent) => { + e.stopPropagation() + setShowFunctionMenu(true) + setShowValuesMenu(false) + } + + const footerButtons = [{ + label: t("DG.AttrFormView.cancelBtnTitle"), + tooltip: t("DG.AttrFormView.cancelBtnTooltip"), + onClick: closeModal + }, { + label: t("DG.AttrFormView.applyBtnTitle"), + onClick: applyFormula, + default: true + }] + + return ( + + + + +
+
+ + + e.stopPropagation()}> + + {t("DG.AttrFormView.attrNamePrompt")} + + + {t("DG.AttrFormView.formulaPrompt")} + + + + + + + {showValuesMenu && + + } + + + + {showFunctionMenu && + + } + + + + + { footerButtons.map((b, idx) => { + const key = `${idx}-${b.label}` + return ( + + + + ) + }) + } + + + + + ) +}) diff --git a/v3/src/components/container/container.tsx b/v3/src/components/container/container.tsx index f237b3dd49..7c1f1cf003 100644 --- a/v3/src/components/container/container.tsx +++ b/v3/src/components/container/container.tsx @@ -1,6 +1,7 @@ import { useMergeRefs } from "@chakra-ui/react" import { useDndContext } from "@dnd-kit/core" import { clsx } from "clsx" +import { observer } from "mobx-react-lite" import React, { useCallback, useRef } from "react" import { dataInteractiveState } from "../../data-interactive/data-interactive-state" import { DocumentContainerContext } from "../../hooks/use-document-container-context" @@ -10,7 +11,9 @@ import { logMessageWithReplacement, logStringifiedObjectMessage } from "../../li import { isFreeTileRow } from "../../models/document/free-tile-row" import { isMosaicTileRow } from "../../models/document/mosaic-tile-row" import { getSharedModelManager } from "../../models/tiles/tile-environment" +import { uiState } from "../../models/ui-state" import { urlParams } from "../../utilities/url-params" +import { EditFormulaModal } from "../common/edit-formula-modal" import { AttributeDragOverlay } from "../drag-drop/attribute-drag-overlay" import { PluginAttributeDrag } from "../drag-drop/plugin-attribute-drag" import { kContainerClass } from "./container-constants" @@ -19,7 +22,7 @@ import { MosaicTileRowComponent } from "./mosaic-tile-row" import "./container.scss" -export const Container: React.FC = () => { +export const Container: React.FC = observer(function Container() { const documentContent = useDocumentContent() const isScrollBehaviorAuto = urlParams.scrollBehavior === "auto" // TODO: handle the possibility of multiple rows @@ -80,7 +83,12 @@ export const Container: React.FC = () => { xOffset={dataInteractiveState.draggingXOffset} yOffset={dataInteractiveState.draggingYOffset} /> + uiState.setEditFormulaAttributeId()} + />
) -} +}) diff --git a/v3/src/data-interactive/handlers/attribute-handler.ts b/v3/src/data-interactive/handlers/attribute-handler.ts index c2b87f5f70..178c730dd3 100644 --- a/v3/src/data-interactive/handlers/attribute-handler.ts +++ b/v3/src/data-interactive/handlers/attribute-handler.ts @@ -6,6 +6,7 @@ import { IAttribute } from "../../models/data/attribute" import { createAttributesNotification, updateAttributesNotification } from "../../models/data/data-set-notifications" import { IFreeTileLayout, isFreeTileRow } from "../../models/document/free-tile-row" import { getSharedCaseMetadataFromDataset } from "../../models/shared/shared-data-utils" +import { uiState } from "../../models/ui-state" import { t } from "../../utilities/translation/translate" import { registerDIHandler } from "../data-interactive-handler" import { dataInteractiveState } from "../data-interactive-state" @@ -142,6 +143,9 @@ export const diAttributeHandler: DIHandler = { bubbles, cancelable, clientX, clientY, isPrimary, pointerId, pointerType })) return { success: true } + } else if (request === "formulaEditor") { + uiState.setEditFormulaAttributeId(attribute.id) + return { success: true } } return errorResult(t("V3.DI.Error.unknownRequest", { vars: [request] })) diff --git a/v3/src/models/ui-state.ts b/v3/src/models/ui-state.ts index 72fbbbcc59..b33e8fecae 100644 --- a/v3/src/models/ui-state.ts +++ b/v3/src/models/ui-state.ts @@ -43,6 +43,9 @@ export class UIState { @observable private _interruptionCount = 0 + // Id of attribute whose formula is being edited using the editor modal + @observable private _editFormulaAttributeId = "" + constructor() { makeObservable(this) } @@ -71,6 +74,10 @@ export class UIState { return this._interruptionCount } + get editFormulaAttributeId() { + return this._editFormulaAttributeId + } + isFocusedTile(tileId?: string) { return this.focusTileId === tileId } @@ -119,6 +126,10 @@ export class UIState { this._interruptionCount += 1 } + @action setEditFormulaAttributeId(id?: string) { + this._editFormulaAttributeId = id ?? "" + } + @action setAttrIdToEdit(attrId?: string) { this.attrIdToEdit = attrId }