diff --git a/v3/cypress/e2e/adornments.spec.ts b/v3/cypress/e2e/adornments.spec.ts index 535fed66a7..b731b2efc5 100644 --- a/v3/cypress/e2e/adornments.spec.ts +++ b/v3/cypress/e2e/adornments.spec.ts @@ -541,7 +541,8 @@ context("Graph adornments", () => { cy.get("[data-testid=adornment-wrapper]").should("have.length", 1) cy.get("[data-testid=adornment-wrapper]").should("have.class", "visible") cy.get("[data-testid=plotted-value-control-value]").click() - cy.get("[data-testid=edit-formula-value-form]").find("[data-testid=formula-value-input]").type("10") + cy.get("[data-testid=formula-editor-input] .cm-content").should("be.visible").and("have.focus") + cy.get("[data-testid=formula-editor-input] .cm-content").realType("10") cy.get("[data-testid=Apply-button]").click() cy.get("[data-testid=graph-adornments-grid]").find("*[data-testid^=plotted-value]").should("exist") cy.get("*[data-testid^=plotted-value-line]").should("exist") @@ -562,13 +563,17 @@ context("Graph adornments", () => { cy.get("[data-testid=adornment-wrapper]").should("have.length", 1) cy.get("[data-testid=adornment-wrapper]").should("have.class", "visible") cy.get("[data-testid=plotted-value-control-value]").click() - cy.get("[data-testid=edit-formula-value-form]").find("[data-testid=formula-value-input]").type("mean(Sl") + cy.get("[data-testid=formula-editor-input] .cm-content").should("be.visible").and("have.focus") + cy.get("[data-testid=formula-editor-input] .cm-content").realType("mean(Sl") cy.get("[data-testid=Apply-button]").click() - cy.get("[data-testid=plotted-value-error]").should("exist").should("include.text", "Syntax error") + cy.get("[data-testid=plotted-value-error]").should("exist").should("include.text", "Undefined symbol") // Error should be cleaned up after the formula is fixed cy.get("[data-testid=plotted-value-control-value]").click() - cy.get("[data-testid=edit-formula-value-form]").find("[data-testid=formula-value-input]") - .clear().type("mean(Sleep)") + cy.get("[data-testid=formula-editor-input] .cm-content").should("be.visible").and("have.focus") + for (let i = 0; i < 8; i++) { + cy.get("[data-testid=formula-editor-input] .cm-content").clear() + } + cy.get("[data-testid=formula-editor-input] .cm-content").realType("mean(Sleep)") cy.get("[data-testid=Apply-button]").click() cy.get("[data-testid=plotted-value-error]").should("not.exist") }) @@ -579,14 +584,16 @@ context("Graph adornments", () => { graph.getInspectorPalette().find("[data-testid=adornment-toggle-otherValues]").click() graph.getInspectorPalette().find("[data-testid=adornment-checkbox-plotted-value]").click() cy.get("[data-testid=plotted-value-control-value]").click() - cy.get("[data-testid=edit-formula-value-form]").find("[data-testid=formula-value-input]").type("10") + cy.get("[data-testid=formula-editor-input] .cm-content").should("be.visible").and("have.focus") + cy.get("[data-testid=formula-editor-input] .cm-content").realType("10") cy.get("[data-testid=Apply-button]").click() cy.get("[data-testid=graph-adornments-grid]").find("*[data-testid^=plotted-value]").should("exist") cy.get("*[data-testid^=plotted-value-line]").should("exist") cy.get("*[data-testid^=plotted-value-cover]").should("exist") cy.get("*[data-testid^=plotted-value-tip]").should("exist").should("have.text", "value=10") cy.get("[data-testid=plotted-value-control-value]").click() - cy.get("[data-testid=edit-formula-value-form]").find("[data-testid=formula-value-input]").clear() + cy.get("[data-testid=formula-editor-input] .cm-content").should("be.visible").and("have.focus") + cy.get("[data-testid=formula-editor-input] .cm-content").clear().clear() cy.get("[data-testid=Apply-button]").click() cy.get("*[data-testid^=plotted-value-line]").should("not.exist") cy.get("*[data-testid^=plotted-value-cover]").should("not.exist") diff --git a/v3/cypress/e2e/bivariate-adornments.spec.ts b/v3/cypress/e2e/bivariate-adornments.spec.ts index 6f52254d9a..90cf70a690 100644 --- a/v3/cypress/e2e/bivariate-adornments.spec.ts +++ b/v3/cypress/e2e/bivariate-adornments.spec.ts @@ -192,7 +192,8 @@ context("Graph adornments", () => { cy.get("[data-testid=adornment-wrapper]").should("have.length", 1) cy.get("[data-testid=adornment-wrapper]").should("have.class", "visible") cy.get("[data-testid=plotted-function-control-value]").click() - cy.get("[data-testid=edit-formula-value-form]").find("[data-testid=formula-value-input]").type("10") + cy.get("[data-testid=formula-editor-input] .cm-content").should("be.visible").and("have.focus") + cy.get("[data-testid=formula-editor-input] .cm-content").realType("10") cy.get("[data-testid=Apply-button]").click() cy.get("[data-testid=graph-adornments-grid]").find("*[data-testid^=plotted-function]").should("exist") cy.get("*[data-testid^=plotted-function-path]").should("exist") diff --git a/v3/src/components/common/edit-attribute-formula-modal.tsx b/v3/src/components/common/edit-attribute-formula-modal.tsx new file mode 100644 index 0000000000..7c41ea32ef --- /dev/null +++ b/v3/src/components/common/edit-attribute-formula-modal.tsx @@ -0,0 +1,51 @@ +import React from "react" +import { observer } from "mobx-react-lite" +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 { uiState } from "../../models/ui-state" +import { t } from "../../utilities/translation/translate" +import { EditFormulaModal } from "./edit-formula-modal" + +export const EditAttributeFormulaModal = observer(function EditAttributeFormulaModal() { + const attributeId = uiState.editFormulaAttributeId + const dataSet = getSharedDataSets(appState.document).find(ds => ds.dataSet.attrFromID(attributeId))?.dataSet + const attribute = dataSet?.attrFromID(attributeId) + const value = attribute?.formula?.display + + const applyFormula = (formula: string) => { + 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") + }) + } + } + + return ( + + uiState.setEditFormulaAttributeId()} + titleInput={attribute?.name} + titleLabel={t("DG.AttrFormView.attrNamePrompt")} + titlePlaceholder={t("V3.AttrFormView.attrPlaceholder")} + value={value} + /> + + ) +}) diff --git a/v3/src/components/common/edit-formula-modal.scss b/v3/src/components/common/edit-formula-modal.scss index 10a6ab5a39..c977f23bf1 100644 --- a/v3/src/components/common/edit-formula-modal.scss +++ b/v3/src/components/common/edit-formula-modal.scss @@ -5,6 +5,10 @@ cursor: default; .formula-form-control { + .title-label { + width: 140px; + } + input { max-width: 270px; } diff --git a/v3/src/components/common/edit-formula-modal.tsx b/v3/src/components/common/edit-formula-modal.tsx index 5453f85836..c385dcc51e 100644 --- a/v3/src/components/common/edit-formula-modal.tsx +++ b/v3/src/components/common/edit-formula-modal.tsx @@ -4,11 +4,6 @@ import { 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" @@ -19,49 +14,39 @@ import { InsertValuesMenu } from "./formula-insert-values-menu" import "./edit-formula-modal.scss" interface IProps { - attributeId: string + applyFormula: (formula: string) => void + formulaPrompt?: string isOpen: boolean - onClose: () => void + onClose?: () => void + titleInput?: string + titleLabel: string + titlePlaceholder?: string + value?: string } -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) +export const EditFormulaModal = observer(function EditFormulaModal({ + applyFormula, formulaPrompt, isOpen, onClose, titleInput, titleLabel, titlePlaceholder, value +}: IProps) { const [showValuesMenu, setShowValuesMenu] = useState(false) const [showFunctionMenu, setShowFunctionMenu] = useState(false) - const formulaEditorState = useFormulaEditorState(attribute?.formula?.display ?? "") + const formulaEditorState = useFormulaEditorState(value ?? "") const { formula, setFormula } = formulaEditorState - useEffect(() => { - setFormula(attribute?.formula?.display || "") - }, [attribute, attribute?.formula?.display, setFormula]) + setFormula(value || "") + }, [value, 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") - }) - } + const handleApplyClick = () => { + applyFormula(formula) closeModal() } const closeModal = () => { setShowValuesMenu(false) setShowFunctionMenu(false) - onClose() + setFormula("") + onClose?.() } const handleModalWhitespaceClick = () => { @@ -87,82 +72,82 @@ export const EditFormulaModal = observer(function EditFormulaModal({ attributeId onClick: closeModal }, { label: t("DG.AttrFormView.applyBtnTitle"), - onClick: applyFormula, + onClick: handleApplyClick, default: true }] return ( - - - - -
-
- - - e.stopPropagation()}> - - {t("DG.AttrFormView.attrNamePrompt")} - - - {t("DG.AttrFormView.formulaPrompt")} - - - - - - - {showValuesMenu && - - } - - - + {showValuesMenu && + + } + + + + {showFunctionMenu && + + } + + + + + { footerButtons.map((b, idx) => { + const key = `${idx}-${b.label}` + return ( + - {t("DG.AttrFormView.functionMenuTitle")} - - {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 7c1f1cf003..963bd9fb64 100644 --- a/v3/src/components/container/container.tsx +++ b/v3/src/components/container/container.tsx @@ -11,9 +11,8 @@ 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 { EditAttributeFormulaModal } from "../common/edit-attribute-formula-modal" import { AttributeDragOverlay } from "../drag-drop/attribute-drag-overlay" import { PluginAttributeDrag } from "../drag-drop/plugin-attribute-drag" import { kContainerClass } from "./container-constants" @@ -83,11 +82,7 @@ export const Container: React.FC = observer(function Container() { xOffset={dataInteractiveState.draggingXOffset} yOffset={dataInteractiveState.draggingYOffset} /> - uiState.setEditFormulaAttributeId()} - /> +
) diff --git a/v3/src/components/graph/adornments/plotted-function/edit-formula-modal.tsx b/v3/src/components/graph/adornments/plotted-function/edit-formula-modal.tsx deleted file mode 100644 index f43c1984f6..0000000000 --- a/v3/src/components/graph/adornments/plotted-function/edit-formula-modal.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import { - Button, FormControl, FormLabel, ModalBody, ModalCloseButton, ModalFooter, ModalHeader, - Textarea, Tooltip -} from "@chakra-ui/react" -import React, { useState } from "react" -import { CodapModal } from "../../../codap-modal" -import { t } from "../../../../utilities/translation/translate" - -interface IProps { - currentValue?: string - leftHandSideLabel?: string - isOpen: boolean - onClose: (value: string) => void -} - -export const EditFormulaModal = ({ currentValue="", isOpen, onClose, leftHandSideLabel }: IProps) => { - const [value, setValue] = useState(currentValue) - - const formulaPrompt = leftHandSideLabel || t("DG.PlottedFunction.formulaPrompt") - - const applyValue = () => { - closeModal() - } - - const closeModal = () => { - onClose(value) - } - - const handleValueChange = (e: React.ChangeEvent) => setValue(e.target.value) - - const buttons = [{ - label: t("DG.AttrFormView.cancelBtnTitle"), - tooltip: t("DG.AttrFormView.cancelBtnTooltip"), - onClick: closeModal - }, { - label: t("DG.AttrFormView.applyBtnTitle"), - onClick: applyValue - }] - - return ( - - -
-
- {t("DG.Inspector.graphPlottedFunction")} -
- - - - - - {formulaPrompt} - -