From 5af38fa54bbbb6c251b6a05734db3f42a242152e Mon Sep 17 00:00:00 2001 From: Guillermo Espinosa Date: Fri, 28 Jun 2024 11:04:32 -0400 Subject: [PATCH] fix: upgrade react-dom --- example-demos/oveViteDemo/src/index.jsx | 10 +- .../src/{index.js => index.jsx} | 10 +- helperUtils/renderDemo.js | 2 - helperUtils/renderDemo.jsx | 3 + .../ui/cypress/e2e/EditableCellTable.spec.js | 20 +- .../ui/cypress/e2e/UploadCsvWizard.spec.js | 70 +- packages/ui/cypress/e2e/dataTable.spec.js | 5 +- packages/ui/cypress/e2e/upload.spec.js | 2 +- packages/ui/cypress/support/index.js | 4 +- .../ui/demo/src/examples/UploadCsvWizard.js | 2 +- packages/ui/demo/src/examples/UploaderDemo.js | 4 - packages/ui/demo/src/index.js | 5 +- packages/ui/src/DataTable/CellDragHandle.js | 13 +- packages/ui/src/DataTable/EditabelCell.js | 51 +- packages/ui/src/DataTable/PagingTool.js | 2 +- packages/ui/src/DataTable/index.js | 554 ++++++------ .../src/DataTable/utils/getIdOrCodeOrIndex.js | 2 +- .../DataTable/utils/getLastSelectedEntity.js | 2 +- .../ui/src/DataTable/utils/getRowCopyText.js | 10 +- .../src/DataTable/utils/handleCopyColumn.js | 2 +- packages/ui/src/DataTable/utils/index.js | 2 +- .../ui/src/DataTable/utils/isEntityClean.js | 14 +- .../ui/src/DataTable/utils/removeCleanRows.js | 6 +- packages/ui/src/DataTable/utils/rowClick.js | 2 +- packages/ui/src/DataTable/utils/selection.js | 2 +- packages/ui/src/DataTable/utils/utils.js | 2 +- .../src/DataTable/validateTableWideErrors.js | 2 +- packages/ui/src/FillWindow.js | 5 +- packages/ui/src/FormComponents/Uploader.js | 800 +++++++++--------- .../src/FormComponents/tryToMatchSchemas.js | 6 - packages/ui/src/TgSelect/index.js | 1 - packages/ui/src/UploadCsvWizard.js | 683 +++++++-------- packages/ui/src/index.js | 6 +- packages/ui/src/showDialogOnDocBody.js | 14 +- packages/ui/src/useDialog.js | 11 +- packages/ui/src/utils/renderOnDoc.js | 13 +- 36 files changed, 1124 insertions(+), 1218 deletions(-) rename example-demos/oveWebpackDemo/src/{index.js => index.jsx} (57%) delete mode 100644 helperUtils/renderDemo.js create mode 100644 helperUtils/renderDemo.jsx diff --git a/example-demos/oveViteDemo/src/index.jsx b/example-demos/oveViteDemo/src/index.jsx index 122a5b87..8cf9dcc2 100644 --- a/example-demos/oveViteDemo/src/index.jsx +++ b/example-demos/oveViteDemo/src/index.jsx @@ -1,6 +1,6 @@ import "./shimGlobal"; import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import { Provider } from "react-redux"; import { Loading } from "@teselagen/ui"; @@ -10,14 +10,16 @@ import App from "./App"; import * as serviceWorker from "./serviceWorker"; -ReactDOM.render( +const domNode = document.getElementById("root"); +const root = createRoot(domNode); + +root.render( - , - document.getElementById("root") + ); // If you want your app to work offline and load faster, you can change diff --git a/example-demos/oveWebpackDemo/src/index.js b/example-demos/oveWebpackDemo/src/index.jsx similarity index 57% rename from example-demos/oveWebpackDemo/src/index.js rename to example-demos/oveWebpackDemo/src/index.jsx index d772e46f..5f969828 100644 --- a/example-demos/oveWebpackDemo/src/index.js +++ b/example-demos/oveWebpackDemo/src/index.jsx @@ -1,15 +1,17 @@ import React from "react"; -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import { Provider } from "react-redux"; import store from "./store"; import "./index.css"; import App from "./App"; -ReactDOM.render( +const domNode = document.getElementById("root"); +const root = createRoot(domNode); + +root.render( - , - document.getElementById("root") + ); diff --git a/helperUtils/renderDemo.js b/helperUtils/renderDemo.js deleted file mode 100644 index e1439ab7..00000000 --- a/helperUtils/renderDemo.js +++ /dev/null @@ -1,2 +0,0 @@ -import { render } from "react-dom"; -export default Demo => render(, document.querySelector("#demo")); diff --git a/helperUtils/renderDemo.jsx b/helperUtils/renderDemo.jsx new file mode 100644 index 00000000..e47a951f --- /dev/null +++ b/helperUtils/renderDemo.jsx @@ -0,0 +1,3 @@ +import { createRoot } from "react-dom/client"; +const root = createRoot(document.querySelector("#demo")); +export default Demo => root.render(); diff --git a/packages/ui/cypress/e2e/EditableCellTable.spec.js b/packages/ui/cypress/e2e/EditableCellTable.spec.js index 5d1cdd5e..9fefb64e 100644 --- a/packages/ui/cypress/e2e/EditableCellTable.spec.js +++ b/packages/ui/cypress/e2e/EditableCellTable.spec.js @@ -29,8 +29,6 @@ describe("EditableCellTable.spec", () => { cy.get(".cellDragHandle"); cy.get(`[data-test="tgCell_name"]:first`).dblclick(); cy.get(".cellDragHandle").should("not.exist"); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(0); cy.focused().type("_zonk{enter}"); cy.get( `[data-tip="Must include the letter 'a'"] [data-test="tgCell_name"]:first` @@ -39,8 +37,6 @@ describe("EditableCellTable.spec", () => { }); it(`typing a letter should start edit`, () => { cy.visit("#/DataTable%20-%20EditableCellTable"); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(0); cy.get(`[data-test="tgCell_name"]:first`).type("zonk{enter}"); cy.get(`[data-test="tgCell_name"]:first`).should("contain", "zonk"); }); @@ -126,8 +122,6 @@ describe("EditableCellTable.spec", () => { `[data-tip="Must be a number"] [data-test="tgCell_howMany"]:first` ).should("contain", "NaN"); //should lowercase "Tom" cy.get(`[data-test="tgCell_howMany"]:first`).dblclick(); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(0); cy.focused().type("11{enter}"); cy.get(`[data-test="tgCell_howMany"]:first`).should("contain", "12"); //should have 12 post format cy.get( @@ -166,10 +160,10 @@ describe("EditableCellTable.spec", () => { // cy.get( // `.rt-td.isSelectedCell.isPrimarySelected [data-test="tgCell_name"]` // ).should("not.exist"); - // cy.get(`[data-test="tgCell_name"]`).eq(3).click({ force: true }); + // cy.get(`[data-test="tgCell_name"]`).eq(3).click(); // cy.get(`.rt-td.isSelectedCell.isPrimarySelected [data-test="tgCell_name"]`); - // cy.focused().type(`{enter}`) - // cy.focused().type(`haha{enter}`) + // cy.focused().type(`{enter}`); + // cy.focused().type(`haha{enter}`); // cy.get( // `.rt-td.isSelectedCell.isPrimarySelected [data-test="tgCell_name"]` // ).should("not.exist"); @@ -231,9 +225,7 @@ describe("EditableCellTable.spec", () => { cy.get( `[data-index="1"] .rt-td.isSelectedCell.isPrimarySelected [data-test="tgCell_type"]` ); - cy.get(`[data-index="49"] [data-test="tgCell_isProtein"]`).click({ - force: true - }); + cy.get(`[data-index="49"] [data-test="tgCell_isProtein"]`).click(); cy.get( `[data-index="49"] .rt-td.isSelectedCell.isPrimarySelected [data-test="tgCell_isProtein"]` ); @@ -253,12 +245,8 @@ describe("EditableCellTable.spec", () => { const redoCmd = IS_LINUX ? `{alt}{shift}z` : "{meta}{shift}z"; cy.visit("#/DataTable%20-%20EditableCellTable"); cy.get(`.rt-td:contains(tom88)`).dblclick(); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(0); cy.focused().type("{selectall}tasty55{enter}"); cy.get(`.rt-td:contains(tasty55)`).dblclick(); - // eslint-disable-next-line cypress/no-unnecessary-waiting - cy.wait(0); cy.focused().type("{selectall}delishhh{enter}"); cy.get(`.rt-td:contains(delishhh)`); cy.focused().type(undoCmd); diff --git a/packages/ui/cypress/e2e/UploadCsvWizard.spec.js b/packages/ui/cypress/e2e/UploadCsvWizard.spec.js index fcbfcc6d..874a46a9 100644 --- a/packages/ui/cypress/e2e/UploadCsvWizard.spec.js +++ b/packages/ui/cypress/e2e/UploadCsvWizard.spec.js @@ -147,17 +147,6 @@ describe("UploadCsvWizard.spec", () => { }); it(`messed up headers should trigger the wizard. editing the added file should work`, () => { cy.visit("#/UploadCsvWizard"); - // cy.contains("Build CSV File").click(); - // cy.get(`.rt-td [data-test="tgCell_description"]`) - // .eq(1) - // .click({ force: true }); - // cy.focused().type("description{enter}"); - - // cy.get( - // `.hasCellError[data-tip="Please enter a value here"] [data-test="tgCell_name"]:first` - // ).dblclick({ force: true }); - // cy.focused().type("a{enter}"); - // cy.contains(`.bp3-dialog button`, "Cancel").click(); cy.uploadFile( ".tg-dropzone", "testUploadWizard_messedUpHeaders.csv", @@ -177,19 +166,21 @@ describe("UploadCsvWizard.spec", () => { ); cy.get(`.tg-test-is-regex`).click(); - cy.contains(".tg-select-option", "typo").click({ force: true }); + cy.contains(".tg-select-option", "typo").click(); cy.contains(".bp3-dialog", `zonk`).should("exist"); //the data from the file should be previewed cy.contains("Review and Edit Data").click(); cy.get( `.hasCellError[data-tip="Please enter a value here"] [data-test="tgCell_name"]:first` - ).dblclick({ force: true }); - cy.focused().type("a{enter}"); + ) + .parent() + .type("a{enter}"); cy.get( `.hasCellError[data-tip="Please enter a value here"] [data-test="tgCell_sequence"]:first` - ).dblclick({ force: true }); - cy.focused().type("g{enter}"); + ) + .parent() + .type("g{enter}"); cy.dragBetween(`.cellDragHandle`, `.rt-tr-last-row`); cy.contains("Add File").click(); cy.contains(`testUploadWizard_messedUpHeaders.csv`); @@ -197,7 +188,7 @@ describe("UploadCsvWizard.spec", () => { cy.get(`.tg-upload-file-list-item-edit`).click(); cy.contains(`Edit your data here.`); cy.get( - `[data-index="4"] [data-test="tgCell_sequence"]:contains(g)` + `[data-index="4"] [data-test="tgCell_sequence"]:contains(g):last` ).click(); cy.focused().type(`{backspace}`); cy.get(`.bp3-disabled:contains(Edit Data)`); @@ -646,13 +637,7 @@ a,,desc,,false,dna,misc_feature cy.contains(".bp3-dialog", `DEscription`); //the matched headers should show up cy.contains(".bp3-dialog", `Description`); //the expected headers should show up cy.contains("Review and Edit Data").click(); - cy.get(`[data-tip="Please enter a value here"]`); - cy.get(`.hasCellError:last [data-test="tgCell_name"]`); - cy.get(`button:contains(Next File).bp3-disabled`); - cy.get(`.hasCellError:last [data-test="tgCell_name"]`).click({ - force: true - }); - cy.focused().type("haha{enter}"); + cy.get(`.hasCellError`).type("haha{enter}"); // cy.get(`.hasCellError:last [data-test="tgCell_name"]`).type("haha{enter}", {force: true}); cy.get(`button:contains(Next File):first`).click(); cy.get( @@ -676,9 +661,7 @@ a,,desc,,false,dna,misc_feature cy.get( `.tg-upload-file-list-item:contains(testUploadWizard_invalidDataNonUnique.csv) .tg-upload-file-list-item-edit` ).click(); - cy.get(`[data-index="0"] [data-test="tgCell_sequence"]`).click({ - force: true - }); + cy.get(`[data-index="0"] [data-test="tgCell_sequence"]:last`).click(); cy.focused().type(`tom{enter}`); cy.get(`.bp3-button:contains(Edit Data)`).click(); cy.contains(`File Updated`); @@ -687,9 +670,7 @@ a,,desc,,false,dna,misc_feature cy.get( `.tg-upload-file-list-item:contains(testUploadWizard_invalidData.csv) .tg-upload-file-list-item-edit` ).click(); - cy.get(`[data-index="0"] [data-test="tgCell_sequence"]`).click({ - force: true - }); + cy.get(`[data-index="0"] [data-test="tgCell_sequence"]`).click(); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(200); cy.focused().type(`robbin{enter}`, { delay: 100 }); @@ -1065,14 +1046,14 @@ thomas,,g,false,dna,misc_feature`, cy.visit("#/UploadCsvWizard"); cy.tgToggle("allowMultipleFiles"); cy.contains("Build CSV File").click(); - cy.get(`[data-test="tgCell_name"]:first`).click({ force: true }); + cy.get(`.rt-tbody [role="gridcell"]:first`).click(); cy.focused().paste(`Thomas Wee agagag False dna misc_feature`); cy.contains(".bp3-button", "Add File").click(); cy.contains("manual_data_entry.csv"); // eslint-disable-next-line cypress/no-unnecessary-waiting cy.wait(200); cy.contains("Build CSV File").click(); - cy.get(`[data-test="tgCell_name"]:first`).click({ force: true }); + cy.get(`.rt-tbody [role="gridcell"]:first`).click(); cy.focused().paste(`Thomas Wee agagag False dna misc_feature`); cy.contains(".bp3-button", "Add File").click(); cy.contains("manual_data_entry(1).csv"); @@ -1081,13 +1062,11 @@ thomas,,g,false,dna,misc_feature`, ).click(); cy.contains(`Edit your data here.`); cy.contains(`Add 10 Rows`).click(); - cy.get(`[data-index="4"] [data-test="tgCell_sequence"]`).click({ - force: true - }); + cy.get(`[data-index="4"] [role="gridcell"]`).eq(2).click(); cy.focused().type(`{backspace}`); cy.get(`.bp3-disabled:contains(Edit Data)`).should("not.exist"); cy.focused().type(`tom{enter}`); - cy.get(`[data-index="4"] [data-test="tgCell_name"]`).click({ force: true }); + cy.get(`[data-index="4"] [role="gridcell"]:first`).click(); cy.focused().type(`taoh{enter}`); cy.get(`.bp3-button:contains(Edit Data)`).click(); cy.contains(`File Updated`); @@ -1096,9 +1075,7 @@ thomas,,g,false,dna,misc_feature`, cy.get( `.tg-upload-file-list-item:contains(manual_data_entry(1).csv) .tg-upload-file-list-item-edit` ).click(); - cy.get(`[data-index="0"] [data-test="tgCell_sequence"]`).click({ - force: true - }); + cy.get(`[data-index="0"] [role="gridcell"]`).eq(2).click(); cy.focused().type(`tom{enter}`); cy.get(`.bp3-button:contains(Edit Data)`).click(); cy.contains(`File Updated`); @@ -1213,23 +1190,14 @@ thomas,,g,false,dna,misc_feature`, cy.contains( `Input your data here. Hover table headers for additional instructions` ); - cy.get(`.rt-td [data-test="tgCell_description"]`) - .eq(1) - .click({ force: true }); - cy.focused().type("description{enter}"); + cy.get(".rt-td").eq(1).type("description{enter}"); //there should be a checkbox in the isRegex boolean column cy.get(`[data-test="Is Regex"] .bp3-checkbox`); //should be able to edit and then drag to continue that edit further down - cy.get( - `.hasCellError[data-tip="Please enter a value here"] [data-test="tgCell_name"]:first` - ).dblclick({ force: true }); - cy.focused().type("a{enter}"); - cy.get( - `.hasCellError[data-tip="Please enter a value here"] [data-test="tgCell_sequence"]:first` - ).dblclick({ force: true }); - cy.focused().type("g{enter}"); + cy.get(".rt-td").eq(0).type("a{enter}"); + cy.get(".rt-td").eq(2).type("g{enter}"); cy.contains(".bp3-button", "Add File").click(); cy.contains("File Added"); diff --git a/packages/ui/cypress/e2e/dataTable.spec.js b/packages/ui/cypress/e2e/dataTable.spec.js index dc60bc2d..33f1eef8 100644 --- a/packages/ui/cypress/e2e/dataTable.spec.js +++ b/packages/ui/cypress/e2e/dataTable.spec.js @@ -9,8 +9,7 @@ describe("dataTable.spec", () => { cy.tgToggle("getRowClassName"); cy.get(".rt-tr-group.custom-getRowClassName").should("exist"); }); - //TODO THIS IS BREAKING! - it.skip(`it can select entities across pages`, () => { + it(`it can select entities across pages`, () => { cy.visit("#/DataTable"); cy.contains("0 Selected"); //select first entity @@ -162,7 +161,7 @@ describe("dataTable.spec", () => { checkIndices("greaterThan"); }); - it.skip("page size will persist on reload", () => { + it("page size will persist on reload", () => { cy.visit("#/DataTable"); cy.get(".data-table-container .paging-page-size").should("have.value", "5"); cy.get(".data-table-container .paging-page-size").select("50"); diff --git a/packages/ui/cypress/e2e/upload.spec.js b/packages/ui/cypress/e2e/upload.spec.js index b7174025..9e7ff732 100644 --- a/packages/ui/cypress/e2e/upload.spec.js +++ b/packages/ui/cypress/e2e/upload.spec.js @@ -59,7 +59,7 @@ describe("upload", () => { true ); - cy.contains("type must be .json"); + cy.contains("type must be .zip, .json"); cy.uploadBlobFiles( ".fileUploadLimitAndType.tg-dropzone", [ diff --git a/packages/ui/cypress/support/index.js b/packages/ui/cypress/support/index.js index f8c861a6..f09f7121 100644 --- a/packages/ui/cypress/support/index.js +++ b/packages/ui/cypress/support/index.js @@ -46,9 +46,7 @@ Cypress.Commands.add("dragBetween", (dragSelector, dropSelector) => { getOrWrap(dragSelector) .trigger("mousedown") .trigger("mousemove", 10, 10, { force: true }); - getOrWrap(dropSelector) - .trigger("mousemove", { force: true }) - .trigger("mouseup", { force: true }); + getOrWrap(dropSelector).trigger("mousemove").trigger("mouseup"); }); Cypress.Commands.add( diff --git a/packages/ui/demo/src/examples/UploadCsvWizard.js b/packages/ui/demo/src/examples/UploadCsvWizard.js index 3f68dd88..c76c8fca 100644 --- a/packages/ui/demo/src/examples/UploadCsvWizard.js +++ b/packages/ui/demo/src/examples/UploadCsvWizard.js @@ -6,7 +6,7 @@ import { FileUploadField } from "../../../src"; import DemoWrapper from "../DemoWrapper"; import { reduxForm } from "redux-form"; import { useToggle } from "../useToggle"; -import getIdOrCodeOrIndex from "../../../src/DataTable/utils/getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "../../../src/DataTable/utils"; const simpleValidateAgainst = { fields: [{ path: "name" }, { path: "description" }, { path: "sequence" }] diff --git a/packages/ui/demo/src/examples/UploaderDemo.js b/packages/ui/demo/src/examples/UploaderDemo.js index 8a200429..1770aa6d 100644 --- a/packages/ui/demo/src/examples/UploaderDemo.js +++ b/packages/ui/demo/src/examples/UploaderDemo.js @@ -21,10 +21,6 @@ export default function UploaderDemo() { const [autoUnzip, autoUnzipToggleComp] = useToggle({ type: "autoUnzip" }); - // const [advancedAccept, advancedAcceptToggleComp] = useToggle({ - // type: "accept", - // label: "Toggle Advance Accept" - // }); return (
diff --git a/packages/ui/demo/src/index.js b/packages/ui/demo/src/index.js index adad2335..6e11ad22 100644 --- a/packages/ui/demo/src/index.js +++ b/packages/ui/demo/src/index.js @@ -25,7 +25,6 @@ import ScrollToTopDemo from "./examples/ScrollToTop"; import showAppSpinnerDemo from "./examples/showAppSpinnerDemo"; import EditableCellTable from "./examples/EditableCellTable"; import React from "react"; -import { render } from "react-dom"; import { Provider } from "react-redux"; import store from "./store"; import { FocusStyleManager } from "@blueprintjs/core"; @@ -33,6 +32,7 @@ import AdvancedOptionsDemo from "./examples/AdvancedOptionsDemo"; import FormComponents from "./examples/FormComponents"; import UploadCsvWizard from "./examples/UploadCsvWizard"; import TagSelectDemo from "./examples/TagSelectDemo"; +import { createRoot } from "react-dom/client"; FocusStyleManager.onlyShowFocusOnTabs(); @@ -261,4 +261,5 @@ const Demo = () => { ); }; -render(, document.querySelector("#demo")); +const root = createRoot(document.querySelector("#demo")); +root.render(); diff --git a/packages/ui/src/DataTable/CellDragHandle.js b/packages/ui/src/DataTable/CellDragHandle.js index 154918d7..855d93d2 100644 --- a/packages/ui/src/DataTable/CellDragHandle.js +++ b/packages/ui/src/DataTable/CellDragHandle.js @@ -1,21 +1,20 @@ import { flatMap } from "lodash-es"; import { forEach } from "lodash-es"; import React, { useRef } from "react"; -import ReactDOM from "react-dom"; -export function CellDragHandle({ +export const CellDragHandle = ({ thisTable, onDragEnd, cellId, isSelectionARectangle -}) { +}) => { const xStart = useRef(0); const timeoutkey = useRef(); const rowsToSelect = useRef(); const rectangleCellPaths = useRef(); const handleDrag = useRef(e => { - const table = ReactDOM.findDOMNode(thisTable).querySelector(".rt-table"); + const table = thisTable.querySelector(".rt-table"); const trs = table.querySelectorAll(`.rt-tr-group.with-row-data`); const [rowId, path] = cellId.split(":"); const selectedTr = table.querySelector( @@ -83,7 +82,7 @@ export function CellDragHandle({ const mouseup = useRef(() => { clearTimeout(timeoutkey.current); - const table = ReactDOM.findDOMNode(thisTable); + const table = thisTable; const trs = table.querySelectorAll(`.rt-tr-group.with-row-data`); const [, path] = cellId.split(":"); //remove the dashed borders @@ -126,6 +125,6 @@ export function CellDragHandle({ document.addEventListener("mouseup", mouseup.current, false); }} className="cellDragHandle" - >
+ /> ); -} +}; diff --git a/packages/ui/src/DataTable/EditabelCell.js b/packages/ui/src/DataTable/EditabelCell.js index 176bdb3a..c6453c92 100644 --- a/packages/ui/src/DataTable/EditabelCell.js +++ b/packages/ui/src/DataTable/EditabelCell.js @@ -1,15 +1,31 @@ -import React, { useState } from "react"; +import React, { useEffect, useRef, useState } from "react"; export const EditableCell = ({ - shouldSelectAll, - stopSelectAll, - initialValue, - finishEdit, cancelEdit, + dataTest, + finishEdit, + initialValue, + isEditableCellInitialValue, isNumeric, - dataTest + shouldSelectAll, + stopSelectAll }) => { - const [v, setV] = useState(initialValue); + const [value, setValue] = useState(initialValue); + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + if (isEditableCellInitialValue && !isNumeric) { + inputRef.current.selectionStart = inputRef.current.value.length; + inputRef.current.selectionEnd = inputRef.current.value.length; + } else if (shouldSelectAll) { + inputRef.current.select(); + stopSelectAll(); + } + } + }, [isEditableCellInitialValue, isNumeric, shouldSelectAll, stopSelectAll]); + return ( { - if (shouldSelectAll && r) { - r?.select(); - stopSelectAll(); - } - }} + ref={inputRef} {...dataTest} - type={isNumeric ? "number" : undefined} - value={v} autoFocus onKeyDown={e => { if (e.key === "Enter") { - finishEdit(v); + finishEdit(value); e.stopPropagation(); } else if (e.key === "Escape") { e.stopPropagation(); cancelEdit(); } }} - onBlur={() => { - finishEdit(v); - }} - onChange={e => { - setV(e.target.value); - }} + onBlur={() => finishEdit(value)} + onChange={e => setValue(e.target.value)} + type={isNumeric ? "number" : undefined} + value={value} /> ); }; diff --git a/packages/ui/src/DataTable/PagingTool.js b/packages/ui/src/DataTable/PagingTool.js index d13335ec..f422bef5 100644 --- a/packages/ui/src/DataTable/PagingTool.js +++ b/packages/ui/src/DataTable/PagingTool.js @@ -5,7 +5,7 @@ import { noop, get, toInteger } from "lodash-es"; import { Button, Classes } from "@blueprintjs/core"; import { onEnterOrBlurHelper } from "../utils/handlerHelpers"; import { defaultPageSizes } from "./utils/queryParams"; -import getIdOrCodeOrIndex from "./utils/getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "./utils"; function PagingInput({ disabled, onBlur, defaultPage }) { const [page, setPage] = useState(defaultPage); diff --git a/packages/ui/src/DataTable/index.js b/packages/ui/src/DataTable/index.js index c49f533c..ce5eb73a 100644 --- a/packages/ui/src/DataTable/index.js +++ b/packages/ui/src/DataTable/index.js @@ -1,5 +1,4 @@ -import React from "react"; -import ReactDOM from "react-dom"; +import React, { createRef } from "react"; import { invert, toNumber, @@ -27,7 +26,6 @@ import { every } from "lodash-es"; import joinUrl from "url-join"; - import { Button, Menu, @@ -124,6 +122,8 @@ const itemSizeEstimators = { class DataTable extends React.Component { constructor(props) { super(props); + + this.tableRef = createRef(); if (this.props.helperProp) { this.props.helperProp.updateValidationHelper = this.updateValidationHelper; @@ -207,11 +207,41 @@ class DataTable extends React.Component { state = { columns: [], - fullscreen: false + fullscreen: false, + // This state prevents the first letter from not being written, + // when the user starts typing in a cell. It is quite hacky, we should + // refactor this in the future. + editableCellInitialValue: "" }; - static defaultProps = defaultProps; + getPrimarySelectedCellId = () => { + const { reduxFormSelectedCells = {} } = this.props; + for (const k of Object.keys(reduxFormSelectedCells)) { + if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) { + return k; + } + } + }; + + startCellEdit = (cellId, { shouldSelectAll } = {}) => { + const { + change, + reduxFormSelectedCells = {}, + reduxFormEditingCell + } = computePresets(this.props); + const newSelectedCells = { ...reduxFormSelectedCells }; + newSelectedCells[cellId] = PRIMARY_SELECTED_VAL; + //check if the cell is already selected and editing and if so, don't change it + if (reduxFormEditingCell === cellId) return; + change("reduxFormSelectedCells", newSelectedCells); + change("reduxFormEditingCell", cellId); + if (shouldSelectAll) { + //we should select the text + change("reduxFormEditingCellSelectAll", true); + } + }; + handleEnterStartCellEdit = e => { e.stopPropagation(); this.startCellEdit(this.getPrimarySelectedCellId()); @@ -219,7 +249,7 @@ class DataTable extends React.Component { flashTableBorder = () => { try { - const table = ReactDOM.findDOMNode(this.table); + const table = this.tableRef.current.tableRef; table.classList.add("tgBorderBlue"); setTimeout(() => { table.classList.remove("tgBorderBlue"); @@ -229,6 +259,58 @@ class DataTable extends React.Component { } }; + formatAndValidateEntities = ( + entities, + { useDefaultValues, indexToStartAt } = {} + ) => { + const { schema } = this.props; + const editableFields = schema.fields.filter(f => !f.isNotEditable); + const validationErrors = {}; + + const newEnts = immer(entities, entities => { + entities.forEach((e, index) => { + editableFields.forEach(columnSchema => { + if (useDefaultValues) { + if (e[columnSchema.path] === undefined) { + if (isFunction(columnSchema.defaultValue)) { + e[columnSchema.path] = columnSchema.defaultValue( + index + indexToStartAt, + e + ); + } else e[columnSchema.path] = columnSchema.defaultValue; + } + } + //mutative + const { error } = editCellHelper({ + entity: e, + columnSchema, + newVal: e[columnSchema.path] + }); + if (error) { + const rowId = getIdOrCodeOrIndex(e, index); + validationErrors[`${rowId}:${columnSchema.path}`] = error; + } + }); + }); + }); + return { + newEnts, + validationErrors + }; + }; + + updateValidation = (entities, newCellValidate) => { + const { change, schema } = computePresets(this.props); + const tableWideErr = validateTableWideErrors({ + entities, + schema, + newCellValidate, + props: this.props + }); + change("reduxFormCellValidation", tableWideErr); + this.forceUpdate(); + }; + handleUndo = () => { const { change, @@ -291,7 +373,6 @@ class DataTable extends React.Component { reduxFormExpandedEntityIdMap, change } = newProps; - const table = ReactDOM.findDOMNode(this.table); const idMap = reduxFormSelectedEntityIdMap; @@ -357,7 +438,8 @@ class DataTable extends React.Component { // if not changing selectedIds then we just want to make sure selected entities // stored in redux are in proper format // if selected ids have changed then it will handle redux selection - const tableScrollElement = table.getElementsByClassName("rt-table")[0]; + const tableScrollElement = + this.tableRef.current.tableRef.getElementsByClassName("rt-table")[0]; const { entities: oldEntities = [], reduxFormSelectedEntityIdMap: oldIdMap @@ -406,8 +488,9 @@ class DataTable extends React.Component { const entityIndexToScrollTo = entities.findIndex( e => e.id === idToScrollTo || e.code === idToScrollTo ); - if (entityIndexToScrollTo === -1 || !table) return; - const tableBody = table.querySelector(".rt-tbody"); + if (entityIndexToScrollTo === -1 || !this.tableRef.current) return; + const tableBody = + this.tableRef.current.tableRef.querySelector(".rt-tbody"); if (!tableBody) return; const rowEl = tableBody.getElementsByClassName("rt-tr-group")[entityIndexToScrollTo]; @@ -423,46 +506,6 @@ class DataTable extends React.Component { } }; - formatAndValidateEntities = ( - entities, - { useDefaultValues, indexToStartAt } = {} - ) => { - const { schema } = this.props; - const editableFields = schema.fields.filter(f => !f.isNotEditable); - const validationErrors = {}; - - const newEnts = immer(entities, entities => { - entities.forEach((e, index) => { - editableFields.forEach(columnSchema => { - if (useDefaultValues) { - if (e[columnSchema.path] === undefined) { - if (isFunction(columnSchema.defaultValue)) { - e[columnSchema.path] = columnSchema.defaultValue( - index + indexToStartAt, - e - ); - } else e[columnSchema.path] = columnSchema.defaultValue; - } - } - //mutative - const { error } = editCellHelper({ - entity: e, - columnSchema, - newVal: e[columnSchema.path] - }); - if (error) { - const rowId = getIdOrCodeOrIndex(e, index); - validationErrors[`${rowId}:${columnSchema.path}`] = error; - } - }); - }); - }); - return { - newEnts, - validationErrors - }; - }; - formatAndValidateTableInitial = () => { const { _origEntities, @@ -489,153 +532,26 @@ class DataTable extends React.Component { }); }; - componentDidMount() { - const { - isCellEditable, - entities = [], - isLoading, - showForcedHiddenColumns, - setShowForcedHidden - } = this.props; - isCellEditable && this.formatAndValidateTableInitial(); - this.updateFromProps({}, computePresets(this.props)); - document.addEventListener("paste", this.handlePaste); - - if (!entities.length && !isLoading && !showForcedHiddenColumns) { - setShowForcedHidden(true); - } - // const table = ReactDOM.findDOMNode(this.table); - // let theads = table.getElementsByClassName("rt-thead"); - // let tbody = table.getElementsByClassName("rt-tbody")[0]; - - // tbody.addEventListener("scroll", () => { - // for (let i = 0; i < theads.length; i++) { - // theads.item(i).scrollLeft = tbody.scrollLeft; - // } - // }); - } - - componentDidUpdate(oldProps) { - // const tableBody = table.querySelector(".rt-tbody"); - // const headerNode = table.querySelector(".rt-thead.-header"); - // if (headerNode) headerNode.style.overflowY = "inherit"; - // if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) { - // if (headerNode) { - // headerNode.style.overflowY = "scroll"; - // headerNode.style.overflowX = "hidden"; - // } - // } - - this.updateFromProps(computePresets(oldProps), computePresets(this.props)); - - // comment in to test what is causing re-render - // Object.entries(this.props).forEach( - // ([key, val]) => - // oldProps[key] !== val && console.info(`Prop '${key}' changed`) - // ); - } - - componentWillUnmount() { - document.removeEventListener("paste", this.handlePaste); - } - - handleRowMove = (type, shiftHeld) => e => { - e.preventDefault(); - e.stopPropagation(); - const props = computePresets(this.props); - const { - noSelect, - entities, - reduxFormSelectedEntityIdMap: idMap, - isEntityDisabled, - isSingleSelect - } = props; - let newIdMap = {}; - const lastSelectedEnt = getLastSelectedEntity(idMap); - - if (noSelect) return; - if (lastSelectedEnt) { - let lastSelectedIndex = entities.findIndex( - ent => ent === lastSelectedEnt - ); - if (lastSelectedIndex === -1) { - if (lastSelectedEnt.id !== undefined) { - lastSelectedIndex = entities.findIndex( - ent => ent.id === lastSelectedEnt.id - ); - } else if (lastSelectedEnt.code !== undefined) { - lastSelectedIndex = entities.findIndex( - ent => ent.code === lastSelectedEnt.code - ); - } - } - if (lastSelectedIndex === -1) { - return; - } - const newEntToSelect = getNewEntToSelect({ - type, - lastSelectedIndex, - entities, - isEntityDisabled - }); - - if (!newEntToSelect) return; - if (shiftHeld && !isSingleSelect) { - if (idMap[newEntToSelect.id || newEntToSelect.code]) { - //the entity being moved to has already been selected - newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]); - newIdMap[newEntToSelect.id || newEntToSelect.code].time = - Date.now() + 1; - } else { - //the entity being moved to has NOT been selected yet - newIdMap = { - ...idMap, - [newEntToSelect.id || newEntToSelect.code]: { - entity: newEntToSelect, - time: Date.now() - } - }; - } - } else { - //no shiftHeld - newIdMap[newEntToSelect.id || newEntToSelect.code] = { - entity: newEntToSelect, - time: Date.now() - }; + updateEntitiesHelper = (ents, fn) => { + const { change, reduxFormEntitiesUndoRedoStack = { currentVersion: 0 } } = + this.props; + const [nextState, patches, inversePatches] = produceWithPatches(ents, fn); + if (!inversePatches.length) return; + const thatNewNew = [...nextState]; + thatNewNew.isDirty = true; + change("reduxFormEntities", thatNewNew); + change("reduxFormEntitiesUndoRedoStack", { + ...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => { + return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1; + }), + currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1, + [reduxFormEntitiesUndoRedoStack.currentVersion + 1]: { + inversePatches, + patches } - } - - finalizeSelection({ - idMap: newIdMap, - entities, - props }); }; - handleCopyHotkey = e => { - const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets( - this.props - ); - - if (isCellEditable) { - this.handleCopySelectedCells(e); - } else { - this.handleCopySelectedRows( - getRecordsFromIdMap(reduxFormSelectedEntityIdMap), - e - ); - } - }; - - getPrimarySelectedCellId = () => { - const { reduxFormSelectedCells = {} } = this.props; - for (const k of Object.keys(reduxFormSelectedCells)) { - if (reduxFormSelectedCells[k] === PRIMARY_SELECTED_VAL) { - return k; - } - } - }; - handlePaste = e => { const { isCellEditable, @@ -790,6 +706,145 @@ class DataTable extends React.Component { } } }; + + componentDidMount() { + const { + isCellEditable, + entities = [], + isLoading, + showForcedHiddenColumns, + setShowForcedHidden + } = this.props; + isCellEditable && this.formatAndValidateTableInitial(); + this.updateFromProps({}, computePresets(this.props)); + document.addEventListener("paste", this.handlePaste); + + if (!entities.length && !isLoading && !showForcedHiddenColumns) { + setShowForcedHidden(true); + } + // const table = this.tableRef.current.tableRef; + // let theads = table.getElementsByClassName("rt-thead"); + // let tbody = table.getElementsByClassName("rt-tbody")[0]; + + // tbody.addEventListener("scroll", () => { + // for (let i = 0; i < theads.length; i++) { + // theads.item(i).scrollLeft = tbody.scrollLeft; + // } + // }); + } + + componentDidUpdate(oldProps) { + // const tableBody = table.querySelector(".rt-tbody"); + // const headerNode = table.querySelector(".rt-thead.-header"); + // if (headerNode) headerNode.style.overflowY = "inherit"; + // if (tableBody && tableBody.scrollHeight > tableBody.clientHeight) { + // if (headerNode) { + // headerNode.style.overflowY = "scroll"; + // headerNode.style.overflowX = "hidden"; + // } + // } + + this.updateFromProps(computePresets(oldProps), computePresets(this.props)); + + // comment in to test what is causing re-render + // Object.entries(this.props).forEach( + // ([key, val]) => + // oldProps[key] !== val && console.info(`Prop '${key}' changed`) + // ); + } + + componentWillUnmount() { + document.removeEventListener("paste", this.handlePaste); + } + + handleRowMove = (type, shiftHeld) => e => { + e.preventDefault(); + e.stopPropagation(); + const props = computePresets(this.props); + const { + noSelect, + entities, + reduxFormSelectedEntityIdMap: idMap, + isEntityDisabled, + isSingleSelect + } = props; + let newIdMap = {}; + const lastSelectedEnt = getLastSelectedEntity(idMap); + + if (noSelect) return; + if (lastSelectedEnt) { + let lastSelectedIndex = entities.findIndex( + ent => ent === lastSelectedEnt + ); + if (lastSelectedIndex === -1) { + if (lastSelectedEnt.id !== undefined) { + lastSelectedIndex = entities.findIndex( + ent => ent.id === lastSelectedEnt.id + ); + } else if (lastSelectedEnt.code !== undefined) { + lastSelectedIndex = entities.findIndex( + ent => ent.code === lastSelectedEnt.code + ); + } + } + if (lastSelectedIndex === -1) { + return; + } + const newEntToSelect = getNewEntToSelect({ + type, + lastSelectedIndex, + entities, + isEntityDisabled + }); + + if (!newEntToSelect) return; + if (shiftHeld && !isSingleSelect) { + if (idMap[newEntToSelect.id || newEntToSelect.code]) { + //the entity being moved to has already been selected + newIdMap = omit(idMap, [lastSelectedEnt.id || lastSelectedEnt.code]); + newIdMap[newEntToSelect.id || newEntToSelect.code].time = + Date.now() + 1; + } else { + //the entity being moved to has NOT been selected yet + newIdMap = { + ...idMap, + [newEntToSelect.id || newEntToSelect.code]: { + entity: newEntToSelect, + time: Date.now() + } + }; + } + } else { + //no shiftHeld + newIdMap[newEntToSelect.id || newEntToSelect.code] = { + entity: newEntToSelect, + time: Date.now() + }; + } + } + + finalizeSelection({ + idMap: newIdMap, + entities, + props + }); + }; + + handleCopyHotkey = e => { + const { isCellEditable, reduxFormSelectedEntityIdMap } = computePresets( + this.props + ); + + if (isCellEditable) { + this.handleCopySelectedCells(e); + } else { + this.handleCopySelectedRows( + getRecordsFromIdMap(reduxFormSelectedEntityIdMap), + e + ); + } + }; + handleSelectAllRows = e => { const { change, @@ -834,18 +889,6 @@ class DataTable extends React.Component { this.updateValidation(entities, reduxFormCellValidation); }; - updateValidation = (entities, newCellValidate) => { - const { change, schema } = computePresets(this.props); - const tableWideErr = validateTableWideErrors({ - entities, - schema, - newCellValidate, - props: this.props - }); - change("reduxFormCellValidation", tableWideErr); - this.forceUpdate(); - }; - handleDeleteCell = () => { const { reduxFormSelectedCells, @@ -886,26 +929,6 @@ class DataTable extends React.Component { this.handleCopyHotkey(e); }; - updateEntitiesHelper = (ents, fn) => { - const { change, reduxFormEntitiesUndoRedoStack = { currentVersion: 0 } } = - this.props; - const [nextState, patches, inversePatches] = produceWithPatches(ents, fn); - if (!inversePatches.length) return; - const thatNewNew = [...nextState]; - thatNewNew.isDirty = true; - change("reduxFormEntities", thatNewNew); - change("reduxFormEntitiesUndoRedoStack", { - ...omitBy(reduxFormEntitiesUndoRedoStack, (v, k) => { - return toNumber(k) > reduxFormEntitiesUndoRedoStack.currentVersion + 1; - }), - currentVersion: reduxFormEntitiesUndoRedoStack.currentVersion + 1, - [reduxFormEntitiesUndoRedoStack.currentVersion + 1]: { - inversePatches, - patches - } - }); - }; - handleCopyTable = (e, opts) => { try { const allRowEls = getAllRows(e); @@ -1438,24 +1461,17 @@ class DataTable extends React.Component { {...(isCellEditable && { tabIndex: -1, onKeyDown: e => { - // const isArrowKey = - // (e.keyCode >= 37 && e.keyCode <= 40) || e.keyCode === 9; - // if (isArrowKey && e.target?.tagName !== "INPUT") { - const isTabKey = e.keyCode === 9; - // const isEnter = e.keyCode === 13; - // console.log(`onKeydown datatable inner`); - // console.log(`isEnter:`, isEnter) - const isArrowKey = e.keyCode >= 37 && e.keyCode <= 40; - // console.log(`e.target?.tagName:`,e.target?.tagName) + const isTabKey = e.key === "Tab"; + const isArrowKey = e.key.startsWith("Arrow"); if ( (isArrowKey && e.target?.tagName !== "INPUT") || isTabKey // || (isEnter && e.target?.tagName === "INPUT") ) { const { schema, entities } = computePresets(this.props); - const left = e.keyCode === 37; - const up = e.keyCode === 38; - const down = e.keyCode === 40 || e.keyCode === 13; + const left = e.key === "ArrowLeft"; + const up = e.key === "ArrowUp"; + const down = e.key === "ArrowDown" || e.key === "Enter"; let cellIdToUse = this.getPrimarySelectedCellId(); const pathToIndex = getFieldPathToIndex(schema); const entityMap = getEntityIdToEntity(entities); @@ -1548,13 +1564,23 @@ class DataTable extends React.Component { const entity = entityIdToEntity[rowId].e; if (!entity) return; const rowDisabled = isEntityDisabled(entity); - const isNum = e.keyCode >= 48 && e.keyCode <= 57; - const isLetter = e.keyCode >= 65 && e.keyCode <= 90; - if (!isNum && !isLetter) return; + const isNum = e.code?.startsWith("Digit"); + const isLetter = e.code?.startsWith("Key"); + if (!isNum && !isLetter) { + this.setState(prev => ({ + ...prev, + editableCellInitialValue: "" + })); + return; + } else { + this.setState(prev => ({ + ...prev, + editableCellInitialValue: e.key + })); + } if (rowDisabled) return; this.startCellEdit(cellId, { shouldSelectAll: true }); e.stopPropagation(); - // e.preventDefault(); } })} > @@ -1694,10 +1720,7 @@ class DataTable extends React.Component { )} { - if (n) this.table = n; - }} - // additionalBodyEl={} + ref={this.tableRef} className={classNames({ isCellEditable, "tg-table-loading": isLoading, @@ -1929,24 +1952,6 @@ class DataTable extends React.Component { }; }; - startCellEdit = (cellId, { shouldSelectAll } = {}) => { - const { - change, - reduxFormSelectedCells = {}, - reduxFormEditingCell - } = computePresets(this.props); - const newSelectedCells = { ...reduxFormSelectedCells }; - newSelectedCells[cellId] = PRIMARY_SELECTED_VAL; - //check if the cell is already selected and editing and if so, don't change it - if (reduxFormEditingCell === cellId) return; - change("reduxFormSelectedCells", newSelectedCells); - change("reduxFormEditingCell", cellId); - if (shouldSelectAll) { - //we should select the text - change("reduxFormEditingCellSelectAll", true); - } - }; - getTableCellProps = (state, rowInfo, column) => { const { entities, @@ -2268,7 +2273,7 @@ class DataTable extends React.Component { refocusTable = () => { setTimeout(() => { - const table = ReactDOM.findDOMNode(this.table)?.closest( + const table = this.tableRef.current?.tableRef?.closest( ".data-table-container>div" ); table?.focus(); @@ -2545,7 +2550,6 @@ class DataTable extends React.Component { { const checked = e.target.checked; @@ -2555,9 +2559,6 @@ class DataTable extends React.Component { ); noEllipsis = true; } else { - // if (column.type === "genericSelect") { - // val = - // } if (reduxFormEditingCell === cellId) { if (column.type === "genericSelect") { const GenericSelectComp = column.GenericSelectComp; @@ -2600,7 +2601,14 @@ class DataTable extends React.Component { shouldSelectAll={reduxFormEditingCellSelectAll} cancelEdit={this.cancelCellEdit} isNumeric={column.type === "number"} - initialValue={text} + initialValue={ + this.state.editableCellInitialValue.length + ? this.state.editableCellInitialValue + : text + } + isEditableCellInitialValue={ + !!this.state.editableCellInitialValue.length + } finishEdit={newVal => { this.finishCellEdit(cellId, newVal); }} @@ -2687,7 +2695,7 @@ class DataTable extends React.Component { : isSelectedCell === PRIMARY_SELECTED_VAL) && ( { this.insertRows({ above: true }); }} - > + /> { this.insertRows({}); }} - > + /> 1 ? "s" : ""}`} @@ -3393,7 +3401,7 @@ class DataTable extends React.Component { }} indeterminate={isIndeterminate} checked={isChecked} - > + /> ); } diff --git a/packages/ui/src/DataTable/utils/getIdOrCodeOrIndex.js b/packages/ui/src/DataTable/utils/getIdOrCodeOrIndex.js index bcbf17c1..2a210198 100644 --- a/packages/ui/src/DataTable/utils/getIdOrCodeOrIndex.js +++ b/packages/ui/src/DataTable/utils/getIdOrCodeOrIndex.js @@ -1,4 +1,4 @@ -export default (record, rowIndex) => { +export const getIdOrCodeOrIndex = (record, rowIndex) => { if (record.id || record.id === 0) { return record.id; } else if (record.code) { diff --git a/packages/ui/src/DataTable/utils/getLastSelectedEntity.js b/packages/ui/src/DataTable/utils/getLastSelectedEntity.js index eb09a2b6..ecfbae5b 100644 --- a/packages/ui/src/DataTable/utils/getLastSelectedEntity.js +++ b/packages/ui/src/DataTable/utils/getLastSelectedEntity.js @@ -1,7 +1,7 @@ export const getLastSelectedEntity = idMap => { let lastSelectedEnt; let latestTime; - idMap.forEach(({ time, entity }) => { + Object.values(idMap).forEach(({ time, entity }) => { if (!latestTime || time > latestTime) { lastSelectedEnt = entity; latestTime = time; diff --git a/packages/ui/src/DataTable/utils/getRowCopyText.js b/packages/ui/src/DataTable/utils/getRowCopyText.js index 88a389a5..c706bd63 100644 --- a/packages/ui/src/DataTable/utils/getRowCopyText.js +++ b/packages/ui/src/DataTable/utils/getRowCopyText.js @@ -7,22 +7,22 @@ export const getRowCopyText = (rowEl, { specificColumn } = {}) => { const textContent = []; const jsonText = []; - rowEl.children.forEach(cellEl => { + for (const cellEl of rowEl.children) { const cellChild = cellEl.querySelector(`[data-copy-text]`); if (!cellChild) { - if (specificColumn) return []; //strip it - return; //just leave it blank + if (specificColumn) continue; //strip it + continue; //just leave it blank } if ( specificColumn && cellChild.getAttribute("data-test") !== specificColumn ) { - return []; + continue; } const [t, j] = getCellCopyText(cellChild); textContent.push(t); jsonText.push(j); - }); + } return [flatMap(textContent).join("\t"), jsonText]; }; diff --git a/packages/ui/src/DataTable/utils/handleCopyColumn.js b/packages/ui/src/DataTable/utils/handleCopyColumn.js index d306053e..4aa04870 100644 --- a/packages/ui/src/DataTable/utils/handleCopyColumn.js +++ b/packages/ui/src/DataTable/utils/handleCopyColumn.js @@ -1,5 +1,5 @@ import { getAllRows } from "./getAllRows"; -import getIdOrCodeOrIndex from "./getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex"; import { handleCopyRows } from "./handleCopyRows"; export const handleCopyColumn = (e, cellWrapper, selectedRecords) => { diff --git a/packages/ui/src/DataTable/utils/index.js b/packages/ui/src/DataTable/utils/index.js index 48307406..2d85f6cb 100644 --- a/packages/ui/src/DataTable/utils/index.js +++ b/packages/ui/src/DataTable/utils/index.js @@ -1,7 +1,7 @@ import { isEntityClean } from "./isEntityClean"; import { getSelectedRowsFromEntities } from "./selection"; import { removeCleanRows } from "./removeCleanRows"; -import getIdOrCodeOrIndex from "./getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex"; import computePresets from "./computePresets"; import { getRecordsFromIdMap } from "./withSelectedEntities"; import { formatPasteData } from "./formatPasteData"; diff --git a/packages/ui/src/DataTable/utils/isEntityClean.js b/packages/ui/src/DataTable/utils/isEntityClean.js index acf34e37..f9d507d4 100644 --- a/packages/ui/src/DataTable/utils/isEntityClean.js +++ b/packages/ui/src/DataTable/utils/isEntityClean.js @@ -1,13 +1,15 @@ export function isEntityClean(e) { + if (typeof e !== "object" || e === null) { + return true; // or return false depending on what you want for non-object inputs + } let isClean = true; - e.some((val, key) => { - if (key === "id") return false; - if (key === "_isClean") return false; + for (const [key, val] of Object.entries(e)) { + if (key === "id") continue; + if (key === "_isClean") continue; if (val) { isClean = false; - return true; + break; } - return false; - }); + } return isClean; } diff --git a/packages/ui/src/DataTable/utils/removeCleanRows.js b/packages/ui/src/DataTable/utils/removeCleanRows.js index b95f5552..5958166a 100644 --- a/packages/ui/src/DataTable/utils/removeCleanRows.js +++ b/packages/ui/src/DataTable/utils/removeCleanRows.js @@ -1,7 +1,7 @@ import { isEntityClean } from "./isEntityClean"; import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex"; -export function removeCleanRows(reduxFormEntities, reduxFormCellValidation) { +export const removeCleanRows = (reduxFormEntities, reduxFormCellValidation) => { const toFilterOut = {}; const entsToUse = (reduxFormEntities || []).filter(e => { if (!(e._isClean || isEntityClean(e))) return true; @@ -12,11 +12,11 @@ export function removeCleanRows(reduxFormEntities, reduxFormCellValidation) { }); const validationToUse = {}; - reduxFormCellValidation.forEach((v, k) => { + Object.entries(reduxFormCellValidation || {}).forEach(([k, v]) => { const [rowId] = k.split(":"); if (!toFilterOut[rowId]) { validationToUse[k] = v; } }); return { entsToUse, validationToUse }; -} +}; diff --git a/packages/ui/src/DataTable/utils/rowClick.js b/packages/ui/src/DataTable/utils/rowClick.js index 8c4c5570..e4ee123a 100644 --- a/packages/ui/src/DataTable/utils/rowClick.js +++ b/packages/ui/src/DataTable/utils/rowClick.js @@ -1,6 +1,6 @@ import { isEmpty, forEach, range } from "lodash-es"; import { getSelectedRowsFromEntities } from "./selection"; -import getIdOrCodeOrIndex from "./getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex"; import { getRecordsFromIdMap } from "./withSelectedEntities"; export default function rowClick(e, rowInfo, entities, props) { diff --git a/packages/ui/src/DataTable/utils/selection.js b/packages/ui/src/DataTable/utils/selection.js index ea4a08d8..39757606 100644 --- a/packages/ui/src/DataTable/utils/selection.js +++ b/packages/ui/src/DataTable/utils/selection.js @@ -1,4 +1,4 @@ -import getIdOrCodeOrIndex from "./getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex"; export const getSelectedRowsFromEntities = (entities, idMap) => { if (!idMap) return []; diff --git a/packages/ui/src/DataTable/utils/utils.js b/packages/ui/src/DataTable/utils/utils.js index b15021e3..fe7b4d1c 100644 --- a/packages/ui/src/DataTable/utils/utils.js +++ b/packages/ui/src/DataTable/utils/utils.js @@ -1,4 +1,4 @@ -import getIdOrCodeOrIndex from "./getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "./getIdOrCodeOrIndex"; export const getFieldPathToIndex = schema => { const fieldToIndex = {}; diff --git a/packages/ui/src/DataTable/validateTableWideErrors.js b/packages/ui/src/DataTable/validateTableWideErrors.js index 11c2eb93..c29a36b2 100644 --- a/packages/ui/src/DataTable/validateTableWideErrors.js +++ b/packages/ui/src/DataTable/validateTableWideErrors.js @@ -1,4 +1,4 @@ -import getIdOrCodeOrIndex from "./utils/getIdOrCodeOrIndex"; +import { getIdOrCodeOrIndex } from "./utils"; import { getCellVal } from "./getCellVal"; import { forEach, isArray } from "lodash-es"; import { startCase } from "lodash-es"; diff --git a/packages/ui/src/FillWindow.js b/packages/ui/src/FillWindow.js index ab25adc5..c333c342 100644 --- a/packages/ui/src/FillWindow.js +++ b/packages/ui/src/FillWindow.js @@ -1,6 +1,5 @@ -import React from "react"; +import React, { createPortal } from "react"; import { isFunction } from "lodash-es"; -import reactDom from "react-dom"; import rerenderOnWindowResize from "./rerenderOnWindowResize"; import "./FillWindow.css"; @@ -63,7 +62,7 @@ export default class FillWindow extends React.Component { : this.props.children} ); - if (asPortal) return reactDom.createPortal(inner, window.document.body); + if (asPortal) return createPortal(inner, window.document.body); return inner; } } diff --git a/packages/ui/src/FormComponents/Uploader.js b/packages/ui/src/FormComponents/Uploader.js index e6c08cfa..6b0a9284 100644 --- a/packages/ui/src/FormComponents/Uploader.js +++ b/packages/ui/src/FormComponents/Uploader.js @@ -1,4 +1,4 @@ -import React, { useEffect, useRef, useState } from "react"; +import React, { useEffect, useMemo, useRef, useState } from "react"; import { Button, Callout, @@ -16,7 +16,6 @@ import classnames from "classnames"; import { nanoid } from "nanoid"; import papaparse, { unparse } from "papaparse"; import downloadjs from "downloadjs"; -import { configure, makeObservable, observable } from "mobx"; import { observer } from "mobx-react"; import UploadCsvWizardDialog, { SimpleInsertDataDialog @@ -30,7 +29,7 @@ import { removeExt } from "@teselagen/file-utils"; import tryToMatchSchemas from "./tryToMatchSchemas"; -import { forEach, isArray, isFunction, isPlainObject, noop } from "lodash-es"; +import { isArray, isFunction, isPlainObject, noop } from "lodash-es"; import { flatMap } from "lodash-es"; import urljoin from "url-join"; import popoverOverflowModifiers from "../utils/popoverOverflowModifiers"; @@ -38,14 +37,12 @@ import writeXlsxFile from "write-excel-file"; import { startCase } from "lodash-es"; import { getNewName } from "./getNewName"; import { isObject } from "lodash-es"; -import { connect } from "react-redux"; +import { useDispatch } from "react-redux"; import { initialize } from "redux-form"; import classNames from "classnames"; -import { compose } from "recompose"; import convertSchema from "../DataTable/utils/convertSchema"; import { LoadingDots } from "./LoadingDots"; -configure({ isolateGlobalState: true }); const helperText = [ `How to Use This Template to Upload New Data`, `1. Go to the first tab and delete the example data.`, @@ -64,58 +61,106 @@ const helperSchema = [ } ]; -class ValidateAgainstSchema { - fields = []; - - constructor() { - makeObservable(this, { - fields: observable.shallow - }); - } - - setValidateAgainstSchema(newValidateAgainstSchema) { - if (!newValidateAgainstSchema) { - this.fields = []; - return; - } - const schema = convertSchema(newValidateAgainstSchema); - if ( - schema.fields.some(f => { - if (f.path === "id") { - return true; - } - return false; - }) - ) { - throw new Error( - `Uploader was passed a validateAgainstSchema with a fields array that contains a field with a path of "id". This is not allowed.` - ); - } - forEach(schema, (v, k) => { - this[k] = v; - }); +const setValidateAgainstSchema = newValidateAgainstSchema => { + if (!newValidateAgainstSchema) return { fields: [] }; + const schema = convertSchema(newValidateAgainstSchema); + if ( + schema.fields.some(f => { + if (f.path === "id") { + return true; + } + return false; + }) + ) { + throw new Error( + `Uploader was passed a validateAgainstSchema with a fields array that contains a field with a path of "id". This is not allowed.` + ); } -} - -// autorun(() => { -// console.log( -// `validateAgainstSchemaStore?.fields:`, -// JSON.stringify(validateAgainstSchemaStore?.fields, null, 4) -// ); -// }); -// validateAgainstSchemaStore.fields = ["hahah"]; -// validateAgainstSchemaStore.fields.push("yaa"); + return schema; +}; -// const validateAgainstSchema = observable.shallow({ -// fields: [] -// }) - -// validateAgainstSchema.fields = ["hahah"]; +const InnerDropZone = ({ + getRootProps, + getInputProps, + isDragAccept, + isDragReject, + isDragActive, + className, + minimal, + dropzoneDisabled, + contentOverride, + simpleAccept, + innerIcon, + innerText, + validateAgainstSchema, + handleManuallyEnterData, + noBuildCsvOption, + showFilesCount, + fileList + // isDragActive + // isDragReject + // isDragAccept +}) => ( +
+
+ + {contentOverride || ( +
+ {innerIcon || } + {innerText || (minimal ? "Upload" : "Click or drag to upload")} + {validateAgainstSchema && !noBuildCsvOption && ( +
+ ...or {manualEnterMessage} + {/*
+ {manualEnterSubMessage} +
*/} +
+ )} +
+ )} +
-// wink wink -const emptyPromise = Promise.resolve.bind(Promise); + {showFilesCount ? ( +
+ Files: {fileList ? fileList.length : 0} +
+ ) : null} +
+); -function UploaderInner({ +const UploaderInner = ({ accept: __accept, contentOverride: maybeContentOverride, innerIcon, @@ -130,7 +175,9 @@ function UploaderInner({ showUploadList = true, beforeUpload, fileList, //list of files with options: {name, loading, error, url, originalName, downloadName} - onFileSuccess = emptyPromise, //called each time a file is finished and before the file.loading gets set to false, needs to return a promise! + onFileSuccess = async () => { + return; + }, //called each time a file is finished and before the file.loading gets set to false, needs to return a promise! onFieldSubmit = noop, //called when all files have successfully uploaded // fileFinished = noop, onRemove = noop, //called when a file has been selected to be removed @@ -141,22 +188,27 @@ function UploaderInner({ autoUnzip, disabled: _disabled, noBuildCsvOption, - initializeForm, showFilesCount, threeDotMenuItems, onPreviewClick -}) { +}) => { + const dispatch = useDispatch(); let dropzoneDisabled = _disabled; let _accept = __accept; - const validateAgainstSchemaStore = useRef(new ValidateAgainstSchema()); const [acceptLoading, setAcceptLoading] = useState(); const [resolvedAccept, setResolvedAccept] = useState(); + if (resolvedAccept) { _accept = resolvedAccept; } - const isAcceptPromise = - __accept?.then || - (Array.isArray(__accept) ? __accept.some(a => a?.then) : false); + + const isAcceptPromise = useMemo( + () => + __accept?.then || + (Array.isArray(__accept) ? __accept.some(a => a?.then) : false), + [__accept] + ); + useEffect(() => { if (isAcceptPromise) { setAcceptLoading(true); @@ -169,35 +221,36 @@ function UploaderInner({ ); } }, [__accept, isAcceptPromise]); + if (isAcceptPromise && !resolvedAccept) { _accept = []; } + if (acceptLoading) dropzoneDisabled = true; - const accept = !_accept - ? undefined - : isAcceptPromise && !resolvedAccept - ? [] - : isPlainObject(_accept) - ? [_accept] - : isArray(_accept) - ? _accept - : _accept.split(",").map(a => ({ type: a })); - const callout = _callout || accept?.find?.(a => a?.callout)?.callout; + const accept = useMemo( + () => + !_accept + ? undefined + : isAcceptPromise && !resolvedAccept + ? [] + : isPlainObject(_accept) + ? [_accept] + : isArray(_accept) + ? _accept + : _accept.split(",").map(a => ({ type: a })), + [_accept, isAcceptPromise, resolvedAccept] + ); - const validateAgainstSchemaToUse = - _validateAgainstSchema || - accept?.find?.(a => a?.validateAgainstSchema)?.validateAgainstSchema; + const callout = _callout || accept?.find?.(a => a?.callout)?.callout; - useEffect(() => { - // validateAgainstSchema - validateAgainstSchemaStore.current.setValidateAgainstSchema( - validateAgainstSchemaToUse - ); - }, [validateAgainstSchemaToUse]); - let validateAgainstSchema; - if (validateAgainstSchemaToUse) { - validateAgainstSchema = validateAgainstSchemaStore.current; - } + const validateAgainstSchema = useMemo( + () => + setValidateAgainstSchema( + _validateAgainstSchema || + accept?.find?.(a => a?.validateAgainstSchema)?.validateAgainstSchema + ), + [_validateAgainstSchema, accept] + ); if ( (validateAgainstSchema || autoUnzip) && @@ -215,6 +268,7 @@ function UploaderInner({ const { showDialogPromise: showUploadCsvWizardDialog, comp } = useDialog({ ModalComponent: UploadCsvWizardDialog }); + const { showDialogPromise: showSimpleInsertDataDialog, comp: comp2 } = useDialog({ ModalComponent: SimpleInsertDataDialog @@ -553,7 +607,7 @@ function UploaderInner({ } {...getFileDownloadAttr(exampleFile)} key={i} - > + /> ); } )} @@ -619,7 +673,7 @@ function UploaderInner({ }} size={10} icon="download" - > + /> )} @@ -631,7 +685,8 @@ function UploaderInner({ // make the dots below "load" <> - Accept Loading + Accept Loading + ) : ( <>Accepts {simpleAccept} @@ -650,135 +705,135 @@ function UploaderInner({ .join(", ") : undefined } - {...{ - onDrop: async (_acceptedFiles, rejectedFiles) => { - let acceptedFiles = []; - for (const file of _acceptedFiles) { - if ((validateAgainstSchema || autoUnzip) && isZipFile(file)) { - const files = await filterFilesInZip( - file, - simpleAccept - ?.split(", ") - ?.map(a => (a.startsWith(".") ? a : "." + a)) || [] - ); - acceptedFiles.push(...files.map(f => f.originFileObj)); - } else { - acceptedFiles.push(file); - } - } - cleanupFiles(); - if (rejectedFiles.length) { - let msg = ""; - rejectedFiles.forEach(file => { - if (msg) msg += "\n"; - msg += - `${file.file.name}: ` + - file.errors.map(err => err.message).join(", "); - }); - window.toastr && - window.toastr.warning( -
{msg}
- ); + onDrop={async (_acceptedFiles, rejectedFiles) => { + let acceptedFiles = []; + for (const file of _acceptedFiles) { + if ((validateAgainstSchema || autoUnzip) && isZipFile(file)) { + const files = await filterFilesInZip( + file, + simpleAccept + ?.split(", ") + ?.map(a => (a.startsWith(".") ? a : "." + a)) || [] + ); + acceptedFiles.push(...files.map(f => f.originFileObj)); + } else { + acceptedFiles.push(file); } - if (!acceptedFiles.length) return; - setLoading(true); - acceptedFiles = trimFiles(acceptedFiles, fileLimit); - - acceptedFiles.forEach(file => { - file.preview = URL.createObjectURL(file); - file.loading = true; - if (!file.id) { - file.id = nanoid(); - } - filesToClean.current.push(file); + } + cleanupFiles(); + if (rejectedFiles.length) { + let msg = ""; + rejectedFiles.forEach(file => { + if (msg) msg += "\n"; + msg += + `${file.file.name}: ` + + file.errors.map(err => err.message).join(", "); }); - - if (readBeforeUpload) { - acceptedFiles = await Promise.all( - acceptedFiles.map(file => { - return new Promise((resolve, reject) => { - const reader = new FileReader(); - reader.readAsText(file, "UTF-8"); - reader.onload = evt => { - file.parsedString = evt.target.result; - resolve(file); - }; - reader.onerror = err => { - console.error("err:", err); - reject(err); - }; - }); - }) + window.toastr && + window.toastr.warning( +
{msg}
); + } + if (!acceptedFiles.length) return; + setLoading(true); + acceptedFiles = trimFiles(acceptedFiles, fileLimit); + + acceptedFiles.forEach(file => { + file.preview = URL.createObjectURL(file); + file.loading = true; + if (!file.id) { + file.id = nanoid(); } - const cleanedAccepted = acceptedFiles.map(file => { - return { - originFileObj: file, - originalFileObj: file, - id: file.id, - lastModified: file.lastModified, - lastModifiedDate: file.lastModifiedDate, - loading: file.loading, - name: file.name, - preview: file.preview, - size: file.size, - type: file.type, - ...(file.parsedString - ? { parsedString: file.parsedString } - : {}) - }; - }); + filesToClean.current.push(file); + }); - const toKeep = []; - if (validateAgainstSchema) { - const filesWIssues = []; - const filesWOIssues = []; - for (const [i, file] of cleanedAccepted.entries()) { - if (isCsvOrExcelFile(file)) { - let parsedF; - try { - parsedF = await parseCsvOrExcelFile(file, { - csvParserOptions: isFunction( - validateAgainstSchema.csvParserOptions - ) - ? validateAgainstSchema.csvParserOptions({ - validateAgainstSchema - }) - : validateAgainstSchema.csvParserOptions - }); - } catch (error) { - console.error("error:", error); - window.toastr && - window.toastr.error( - `There was an error parsing your file. Please try again. ${ - error.message || error - }` - ); - return; - } + if (readBeforeUpload) { + acceptedFiles = await Promise.all( + acceptedFiles.map(file => { + return new Promise((resolve, reject) => { + const reader = new FileReader(); + reader.readAsText(file, "UTF-8"); + reader.onload = evt => { + file.parsedString = evt.target.result; + resolve(file); + }; + reader.onerror = err => { + console.error("err:", err); + reject(err); + }; + }); + }) + ); + } + const cleanedAccepted = acceptedFiles.map(file => { + return { + originFileObj: file, + originalFileObj: file, + id: file.id, + lastModified: file.lastModified, + lastModifiedDate: file.lastModifiedDate, + loading: file.loading, + name: file.name, + preview: file.preview, + size: file.size, + type: file.type, + ...(file.parsedString + ? { parsedString: file.parsedString } + : {}) + }; + }); - const { - csvValidationIssue: _csvValidationIssue, - matchedHeaders, - userSchema, - searchResults, - ignoredHeadersMsg - } = await tryToMatchSchemas({ - incomingData: parsedF.data, - validateAgainstSchema + const toKeep = []; + if (validateAgainstSchema) { + const filesWIssues = []; + const filesWOIssues = []; + for (const [i, file] of cleanedAccepted.entries()) { + if (isCsvOrExcelFile(file)) { + let parsedF; + try { + parsedF = await parseCsvOrExcelFile(file, { + csvParserOptions: isFunction( + validateAgainstSchema.csvParserOptions + ) + ? validateAgainstSchema.csvParserOptions({ + validateAgainstSchema + }) + : validateAgainstSchema.csvParserOptions }); - if (userSchema?.userData?.length === 0) { - console.error( - `userSchema, parsedF.data:`, - userSchema, - parsedF.data + } catch (error) { + console.error("error:", error); + window.toastr && + window.toastr.error( + `There was an error parsing your file. Please try again. ${ + error.message || error + }` ); - } else { - toKeep.push(file); - let csvValidationIssue = _csvValidationIssue; - if (csvValidationIssue) { - if (isObject(csvValidationIssue)) { - initializeForm( + return; + } + + const { + csvValidationIssue: _csvValidationIssue, + matchedHeaders, + userSchema, + searchResults, + ignoredHeadersMsg + } = await tryToMatchSchemas({ + incomingData: parsedF.data, + validateAgainstSchema + }); + if (userSchema?.userData?.length === 0) { + console.error( + `userSchema, parsedF.data:`, + userSchema, + parsedF.data + ); + } else { + toKeep.push(file); + let csvValidationIssue = _csvValidationIssue; + if (csvValidationIssue) { + if (isObject(csvValidationIssue)) { + dispatch( + initialize( `editableCellTable${ cleanedAccepted.length > 1 ? `-${i}` : "" }`, @@ -790,149 +845,142 @@ function UploaderInner({ keepValues: true, updateUnregisteredFields: true } + ) + ); + const err = Object.values(csvValidationIssue)[0]; + // csvValidationIssue = `It looks like there was an error with your data - \n\n${ + // err && err.message ? err.message : err + // }.\n\nPlease review your headers and then correct any errors on the next page.`; //pass just the first error as a string + const errMsg = err && err.message ? err.message : err; + if (isPlainObject(errMsg)) { + throw new Error( + `errMsg is an object ${JSON.stringify( + errMsg, + null, + 4 + )}` ); - const err = Object.values(csvValidationIssue)[0]; - // csvValidationIssue = `It looks like there was an error with your data - \n\n${ - // err && err.message ? err.message : err - // }.\n\nPlease review your headers and then correct any errors on the next page.`; //pass just the first error as a string - const errMsg = - err && err.message ? err.message : err; - if (isPlainObject(errMsg)) { - throw new Error( - `errMsg is an object ${JSON.stringify( - errMsg, - null, - 4 - )}` - ); - } - csvValidationIssue = ( + } + csvValidationIssue = ( +
-
- It looks like there was an error with your - data (Correct on the Review Data page): -
-
{errMsg}
-
- Please review your headers and then correct - any errors on the next page. -
+ It looks like there was an error with your data + (Correct on the Review Data page):
- ); - } - filesWIssues.push({ - file, - csvValidationIssue, - ignoredHeadersMsg, - matchedHeaders, - userSchema, - searchResults - }); - } else { - filesWOIssues.push({ - file, - csvValidationIssue, - ignoredHeadersMsg, - matchedHeaders, - userSchema, - searchResults - }); - const newFileName = removeExt(file.name) + `.csv`; - - const { newFile, cleanedEntities } = getNewCsvFile( - userSchema.userData, - newFileName +
{errMsg}
+
+ Please review your headers and then correct any + errors on the next page. +
+
); - - file.meta = parsedF.meta; - file.hasEditClick = true; - file.parsedData = cleanedEntities; - file.name = newFileName; - file.originFileObj = newFile; - file.originalFileObj = newFile; } - } - } else { - toKeep.push(file); - } - } - if (filesWIssues.length) { - const { file } = filesWIssues[0]; - const allFiles = [...filesWIssues, ...filesWOIssues]; - const doAllFilesHaveSameHeaders = allFiles.every(f => { - if (f.userSchema.fields && f.userSchema.fields.length) { - return f.userSchema.fields.every((h, i) => { - return ( - h.path === allFiles[0].userSchema.fields[i].path - ); + filesWIssues.push({ + file, + csvValidationIssue, + ignoredHeadersMsg, + matchedHeaders, + userSchema, + searchResults }); - } - return false; - }); - const multipleFiles = allFiles.length > 1; - const { res } = await showUploadCsvWizardDialog( - "onUploadWizardFinish", - { - dialogProps: { - title: `Fix Up File${multipleFiles ? "s" : ""} ${ - multipleFiles - ? "" - : file.name - ? `"${file.name}"` - : "" - }` - }, - doAllFilesHaveSameHeaders, - filesWIssues: allFiles, - validateAgainstSchema - } - ); + } else { + filesWOIssues.push({ + file, + csvValidationIssue, + ignoredHeadersMsg, + matchedHeaders, + userSchema, + searchResults + }); + const newFileName = removeExt(file.name) + `.csv`; - if (!res) { - window.toastr.warning(`File Upload Aborted`); - return; - } else { - allFiles.forEach(({ file }, i) => { - const newEntities = res[i]; - // const newFileName = removeExt(file.name) + `_updated.csv`; - //swap out file with a new csv file const { newFile, cleanedEntities } = getNewCsvFile( - newEntities, - file.name + userSchema.userData, + newFileName ); + file.meta = parsedF.meta; file.hasEditClick = true; file.parsedData = cleanedEntities; - // file.name = newFileName; + file.name = newFileName; file.originFileObj = newFile; file.originalFileObj = newFile; - }); - setTimeout(() => { - //inside a timeout for cypress purposes - window.toastr.success( - `Added Fixed Up File${ - allFiles.length > 1 ? "s" : "" - } ${allFiles.map(({ file }) => file.name).join(", ")}` - ); - }, 200); + } } + } else { + toKeep.push(file); } - } else { - toKeep.push(...cleanedAccepted); } + if (filesWIssues.length) { + const { file } = filesWIssues[0]; + const allFiles = [...filesWIssues, ...filesWOIssues]; + const doAllFilesHaveSameHeaders = allFiles.every(f => { + if (f.userSchema.fields && f.userSchema.fields.length) { + return f.userSchema.fields.every((h, i) => { + return h.path === allFiles[0].userSchema.fields[i].path; + }); + } + return false; + }); + const multipleFiles = allFiles.length > 1; + const { res } = await showUploadCsvWizardDialog( + "onUploadWizardFinish", + { + dialogProps: { + title: `Fix Up File${multipleFiles ? "s" : ""} ${ + multipleFiles ? "" : file.name ? `"${file.name}"` : "" + }` + }, + doAllFilesHaveSameHeaders, + filesWIssues: allFiles, + validateAgainstSchema + } + ); + + if (!res) { + window.toastr.warning(`File Upload Aborted`); + return; + } else { + allFiles.forEach(({ file }, i) => { + const newEntities = res[i]; + // const newFileName = removeExt(file.name) + `_updated.csv`; + //swap out file with a new csv file + const { newFile, cleanedEntities } = getNewCsvFile( + newEntities, + file.name + ); - if (toKeep.length === 0) { - window.toastr && - window.toastr.error( - `It looks like there wasn't any data in your file. Please add some data and try again` - ); + file.hasEditClick = true; + file.parsedData = cleanedEntities; + // file.name = newFileName; + file.originFileObj = newFile; + file.originalFileObj = newFile; + }); + setTimeout(() => { + //inside a timeout for cypress purposes + window.toastr.success( + `Added Fixed Up File${ + allFiles.length > 1 ? "s" : "" + } ${allFiles.map(({ file }) => file.name).join(", ")}` + ); + }, 200); + } } - const cleanedFileList = trimFiles( - [...toKeep, ...fileListToUse], - fileLimit - ); - handleSecondHalfOfUpload({ acceptedFiles, cleanedFileList }); + } else { + toKeep.push(...cleanedAccepted); } + + if (toKeep.length === 0) { + window.toastr && + window.toastr.error( + `It looks like there wasn't any data in your file. Please add some data and try again` + ); + } + const cleanedFileList = trimFiles( + [...toKeep, ...fileListToUse], + fileLimit + ); + handleSecondHalfOfUpload({ acceptedFiles, cleanedFileList }); }} {...dropzoneProps} > @@ -942,71 +990,26 @@ function UploaderInner({ isDragAccept, isDragReject, isDragActive - // isDragActive - // isDragReject - // isDragAccept }) => ( -
-
- - {contentOverride || ( -
- {innerIcon || ( - - )} - {innerText || - (minimal ? "Upload" : "Click or drag to upload")} - {validateAgainstSchema && !noBuildCsvOption && ( -
- ...or {manualEnterMessage} - {/*
- {manualEnterSubMessage} -
*/} -
- )} -
- )} -
- - {showFilesCount ? ( -
- Files: {fileList ? fileList.length : 0} -
- ) : null} -
+ )} {/* {validateAgainstSchema && } */} @@ -1188,12 +1191,9 @@ function UploaderInner({ ); -} +}; -const Uploader = compose( - connect(undefined, { initializeForm: initialize }), - observer -)(UploaderInner); +const Uploader = observer(UploaderInner); export default Uploader; diff --git a/packages/ui/src/FormComponents/tryToMatchSchemas.js b/packages/ui/src/FormComponents/tryToMatchSchemas.js index 46887e7f..0d167c0a 100644 --- a/packages/ui/src/FormComponents/tryToMatchSchemas.js +++ b/packages/ui/src/FormComponents/tryToMatchSchemas.js @@ -14,12 +14,6 @@ const getSchema = data => ({ return { path, type: "string" }; }), userData: data - // userData: data.map((d) => { - // if (!d.id) { - // d.id = nanoid(); - // } - // return d - // }) }); export default async function tryToMatchSchemas({ incomingData, diff --git a/packages/ui/src/TgSelect/index.js b/packages/ui/src/TgSelect/index.js index 34728a1e..b6403e9d 100644 --- a/packages/ui/src/TgSelect/index.js +++ b/packages/ui/src/TgSelect/index.js @@ -512,7 +512,6 @@ export const itemListPredicate = (_queryString = "", items, isSimpleSearch) => { export function simplesearch(needle, haystack) { return (haystack || "").indexOf(needle) !== -1; } - function tagOptionRender(vals) { if (vals.noTagStyle) return vals.label; return ; diff --git a/packages/ui/src/UploadCsvWizard.js b/packages/ui/src/UploadCsvWizard.js index 92595324..bf9a7918 100644 --- a/packages/ui/src/UploadCsvWizard.js +++ b/packages/ui/src/UploadCsvWizard.js @@ -1,4 +1,4 @@ -import React, { useRef, useState } from "react"; +import React, { useRef, useState, useEffect } from "react"; import { reduxForm, change, formValueSelector, destroy } from "redux-form"; import { Callout, Icon, Intent, Tab, Tabs } from "@blueprintjs/core"; import immer from "immer"; @@ -12,10 +12,11 @@ import { tgFormValueSelector } from "./utils/tgFormValues"; import { some } from "lodash-es"; import { times } from "lodash-es"; import DialogFooter from "./DialogFooter"; -import DataTable, { removeCleanRows } from "./DataTable"; +import DataTable from "./DataTable"; +import { removeCleanRows } from "./DataTable/utils"; import wrapDialog from "./wrapDialog"; import { omit } from "lodash-es"; -import { connect } from "react-redux"; +import { useDispatch, useSelector } from "react-redux"; import { MatchHeaders } from "./MatchHeaders"; import { isEmpty } from "lodash-es"; import { addSpecialPropToAsyncErrs } from "./FormComponents/tryToMatchSchemas"; @@ -35,64 +36,62 @@ const UploadCsvWizardDialog = compose( reduxForm({ form: "UploadCsvWizardDialog" }), - connect( - (state, props) => { - if (props.filesWIssues.length > 0) { - const reduxFormEntitiesArray = []; - const finishedFiles = props.filesWIssues.map((f, i) => { - const { reduxFormEntities, reduxFormCellValidation } = - formValueSelector(`editableCellTable-${i}`)( - state, - "reduxFormEntities", - "reduxFormCellValidation" - ); - reduxFormEntitiesArray.push(reduxFormEntities); - const { entsToUse, validationToUse } = removeCleanRows( - reduxFormEntities, - reduxFormCellValidation - ); - return ( - entsToUse && - entsToUse.length && - !some(validationToUse, v => v) && - entsToUse - ); - }); - return { - reduxFormEntitiesArray, - finishedFiles - }; - } - }, - { changeForm: change, destroyForms: destroy } - ), observer )(function UploadCsvWizardDialogOuter({ - validateAgainstSchema, - reduxFormEntitiesArray, - filesWIssues: _filesWIssues, - finishedFiles, - onUploadWizardFinish, - doAllFilesHaveSameHeaders, - destroyForms, csvValidationIssue, + doAllFilesHaveSameHeaders, + filesWIssues: _filesWIssues, + flippedMatchedHeaders, ignoredHeadersMsg, - searchResults, matchedHeaders, + onUploadWizardFinish, + searchResults, userSchema, - flippedMatchedHeaders, - changeForm + validateAgainstSchema }) { + const dispatch = useDispatch(); // will unmount state hook - React.useEffect(() => { + useEffect(() => { return () => { - destroyForms( - "editableCellTable", - ...times(_filesWIssues.length, i => `editableCellTable-${i}`) + dispatch( + destroy( + "editableCellTable", + ...times(_filesWIssues.length, i => `editableCellTable-${i}`) + ) ); }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); + }, [_filesWIssues.length, dispatch]); + + const changeForm = (...args) => dispatch(change(...args)); + const { reduxFormEntitiesArray, finishedFiles } = useSelector(state => { + if (_filesWIssues.length > 0) { + const reduxFormEntitiesArray = []; + const finishedFiles = _filesWIssues.map((f, i) => { + const { reduxFormEntities, reduxFormCellValidation } = + formValueSelector(`editableCellTable-${i}`)( + state, + "reduxFormEntities", + "reduxFormCellValidation" + ); + reduxFormEntitiesArray.push(reduxFormEntities); + const { entsToUse, validationToUse } = removeCleanRows( + reduxFormEntities, + reduxFormCellValidation + ); + return ( + entsToUse && + entsToUse.length && + !some(validationToUse, v => v) && + entsToUse + ); + }); + return { + reduxFormEntitiesArray, + finishedFiles + }; + } + }); + const [hasSubmittedOuter, setSubmittedOuter] = useState(); const [steps, setSteps] = useState(getInitialSteps(true)); @@ -118,7 +117,6 @@ const UploadCsvWizardDialog = compose( > {filesWIssues.map((f, i) => { const isGood = finishedFiles[i]; - const isThisTheLastBadFile = finishedFiles.every((ff, j) => { if (i === j) { return true; @@ -135,108 +133,98 @@ const UploadCsvWizardDialog = compose( {" "} + />{" "} {f.file.name} } panel={ { - setSubmittedOuter(false); - setSteps(getInitialSteps(true)); - }), - onMultiFileUploadSubmit: async () => { - let nextUnfinishedFile; - //find the next unfinished file - for ( - let j = (i + 1) % finishedFiles.length; - j < finishedFiles.length; - j++ - ) { - if (j === i) { - break; - } else if (!finishedFiles[j]) { - nextUnfinishedFile = j; - break; - } else if (j === finishedFiles.length - 1) { - j = -1; - } + isThisTheLastBadFile={isThisTheLastBadFile} + onBackClick={ + doAllFilesHaveSameHeaders && + (() => { + setSubmittedOuter(false); + setSteps(getInitialSteps(true)); + }) + } + onMultiFileUploadSubmit={async () => { + let nextUnfinishedFile; + //find the next unfinished file + for ( + let j = (i + 1) % finishedFiles.length; + j < finishedFiles.length; + j++ + ) { + if (j === i) { + break; + } else if (!finishedFiles[j]) { + nextUnfinishedFile = j; + break; + } else if (j === finishedFiles.length - 1) { + j = -1; } - - if (nextUnfinishedFile !== undefined) { - //do async validation here if needed - - const currentEnts = - reduxFormEntitiesArray[focusedTab]; - + } + if (nextUnfinishedFile !== undefined) { + //do async validation here if needed + const currentEnts = reduxFormEntitiesArray[focusedTab]; + if ( + await asyncValidateHelper( + validateAgainstSchema, + currentEnts, + changeForm, + `editableCellTable-${focusedTab}` + ) + ) + return; + setFocusedTab(nextUnfinishedFile); + } else { + //do async validation here if needed + for (const [i, ents] of finishedFiles.entries()) { if ( await asyncValidateHelper( validateAgainstSchema, - currentEnts, + ents, changeForm, - `editableCellTable-${focusedTab}` + `editableCellTable-${i}` ) ) return; - - setFocusedTab(nextUnfinishedFile); - } else { - //do async validation here if needed - - for (const [i, ents] of finishedFiles.entries()) { - if ( - await asyncValidateHelper( - validateAgainstSchema, - ents, - changeForm, - `editableCellTable-${i}` - ) - ) - return; - } - - //we are done - onUploadWizardFinish({ - res: finishedFiles.map(ents => { - return maybeStripIdFromEntities( - ents, - f.validateAgainstSchema - ); - }) - }); } - }, - validateAgainstSchema, - reduxFormEntitiesArray, - filesWIssues, - finishedFiles, - onUploadWizardFinish, - doAllFilesHaveSameHeaders, - destroyForms, - setFilesWIssues, - csvValidationIssue, - ignoredHeadersMsg, - searchResults, - matchedHeaders, - userSchema, - flippedMatchedHeaders, - // reduxFormEntities, - changeForm, - fileIndex: i, - form: `correctCSVHeadersForm-${i}`, - datatableFormName: `editableCellTable-${i}`, - ...f, - ...(doAllFilesHaveSameHeaders && { - csvValidationIssue: false - }) + //we are done + onUploadWizardFinish({ + res: finishedFiles.map(ents => { + return maybeStripIdFromEntities( + ents, + f.validateAgainstSchema + ); + }) + }); + } }} + validateAgainstSchema={validateAgainstSchema} + reduxFormEntitiesArray={reduxFormEntitiesArray} + filesWIssues={filesWIssues} + finishedFiles={finishedFiles} + onUploadWizardFinish={onUploadWizardFinish} + doAllFilesHaveSameHeaders={doAllFilesHaveSameHeaders} + setFilesWIssues={setFilesWIssues} + csvValidationIssue={csvValidationIssue} + ignoredHeadersMsg={ignoredHeadersMsg} + searchResults={searchResults} + matchedHeader={matchedHeaders} + userSchema={userSchema} + flippedMatchedHeaders={flippedMatchedHeaders} + changeForm={changeForm} + fileIndex={i} + form={`correctCSVHeadersForm-${i}`} + datatableFormName={`editableCellTable-${i}`} + {...f} + {...(doAllFilesHaveSameHeaders && { + csvValidationIssue: false + })} /> } - > + /> ); })} @@ -248,34 +236,27 @@ const UploadCsvWizardDialog = compose( comp = ( <> {doAllFilesHaveSameHeaders && ( - + )} {!hasSubmittedOuter && ( { - return `editableCellTable-${i}`; - }), - reduxFormEntitiesArray, - // onMultiFileUploadSubmit, - csvValidationIssue, - ignoredHeadersMsg, - searchResults, - matchedHeaders, - userSchema, - flippedMatchedHeaders, - // reduxFormEntities, - changeForm, - setFilesWIssues, - filesWIssues, - fileIndex: 0, - ...filesWIssues[0] - }} + doAllFilesHaveSameHeaders={doAllFilesHaveSameHeaders} + datatableFormNames={filesWIssues.map((f, i) => { + return `editableCellTable-${i}`; + })} + reduxFormEntitiesArray={reduxFormEntitiesArray} + csvValidationIssue={csvValidationIssue} + ignoredHeadersMsg={ignoredHeadersMsg} + searchResults={searchResults} + matchedHeaders={matchedHeaders} + userSchema={userSchema} + flippedMatchedHeaders={flippedMatchedHeaders} + changeForm={changeForm} + setFilesWIssues={setFilesWIssues} + filesWIssues={filesWIssues} + fileIndex={0} + {...filesWIssues[0]} /> )} {hasSubmittedOuter && tabs} @@ -287,230 +268,200 @@ const UploadCsvWizardDialog = compose( setSteps(getInitialSteps(false)); }} text="Review and Edit Data" - > + /> )} ); } - return ( -
- {comp} -
- ); + return
{comp}
; } else { return ( ); } }); -const UploadCsvWizardDialogInner = compose( - reduxForm(), - connect((state, props) => { - return formValueSelector(props.datatableFormName || "editableCellTable")( - state, - "reduxFormEntities", - "reduxFormCellValidation" - ); - }) -)(function UploadCsvWizardDialogInner({ - validateAgainstSchema, - userSchema, - searchResults, - onUploadWizardFinish, - csvValidationIssue, - ignoredHeadersMsg, - matchedHeaders, - //fromRedux: - handleSubmit, - fileIndex, - reduxFormEntities, - onBackClick, - reduxFormCellValidation, - changeForm, - setFilesWIssues, - doAllFilesHaveSameHeaders, - filesWIssues, - datatableFormName = "editableCellTable", - onMultiFileUploadSubmit, - isThisTheLastBadFile, - submitting -}) { - const [hasSubmitted, setSubmitted] = useState(!csvValidationIssue); - const [steps, setSteps] = useState(getInitialSteps(csvValidationIssue)); +const UploadCsvWizardDialogInner = reduxForm()( + function UploadCsvWizardDialogInner({ + validateAgainstSchema, + userSchema, + searchResults, + onUploadWizardFinish, + csvValidationIssue, + ignoredHeadersMsg, + matchedHeaders, + handleSubmit, + fileIndex, + onBackClick, + changeForm, + setFilesWIssues, + doAllFilesHaveSameHeaders, + filesWIssues, + datatableFormName = "editableCellTable", + onMultiFileUploadSubmit, + isThisTheLastBadFile, + submitting + }) { + const [hasSubmitted, setSubmitted] = useState(!csvValidationIssue); + const [steps, setSteps] = useState(getInitialSteps(csvValidationIssue)); - let inner; - if (hasSubmitted) { - inner = ( - + const { reduxFormEntities, reduxFormCellValidation } = useSelector(state => + formValueSelector(datatableFormName)( + state, + "reduxFormEntities", + "reduxFormCellValidation" + ) ); - } else { - inner = ( - + + let inner; + if (hasSubmitted) { + inner = ( + + ); + } else { + inner = ( + + ); + } + const { entsToUse, validationToUse } = removeCleanRows( + reduxFormEntities, + reduxFormCellValidation ); - } - const { entsToUse, validationToUse } = removeCleanRows( - reduxFormEntities, - reduxFormCellValidation - ); - return ( -
- {!doAllFilesHaveSameHeaders && ( - - )} -
{inner}
- v)) - } - intent={ - hasSubmitted && onMultiFileUploadSubmit && isThisTheLastBadFile - ? Intent.SUCCESS - : Intent.PRIMARY - } - noCancel={onMultiFileUploadSubmit} - {...(hasSubmitted && { - onBackClick: - onBackClick || - (() => { + return ( +
+ {!doAllFilesHaveSameHeaders && ( + + )} +
{inner}
+ v)) + } + intent={ + hasSubmitted && onMultiFileUploadSubmit && isThisTheLastBadFile + ? Intent.SUCCESS + : Intent.PRIMARY + } + noCancel={onMultiFileUploadSubmit} + {...(hasSubmitted && { + onBackClick: + onBackClick || + (() => { + setSteps( + immer(steps, draft => { + draft[0].active = true; + draft[0].completed = false; + draft[1].active = false; + }) + ); + setSubmitted(false); + }) + })} + onClick={handleSubmit(async function () { + if (!hasSubmitted) { + //step 1 submit setSteps( immer(steps, draft => { - draft[0].active = true; - draft[0].completed = false; - draft[1].active = false; + draft[0].active = false; + draft[0].completed = true; + draft[1].active = true; }) ); - setSubmitted(false); - }) - })} - onClick={handleSubmit(async function () { - if (!hasSubmitted) { - //step 1 submit - setSteps( - immer(steps, draft => { - draft[0].active = false; - draft[0].completed = true; - draft[1].active = true; - }) - ); - setSubmitted(true); - } else { - if (!onMultiFileUploadSubmit) { - //do async validation here if needed - if ( - await asyncValidateHelper( - validateAgainstSchema, - entsToUse, - changeForm, - `editableCellTable` + setSubmitted(true); + } else { + if (!onMultiFileUploadSubmit) { + //do async validation here if needed + if ( + await asyncValidateHelper( + validateAgainstSchema, + entsToUse, + changeForm, + `editableCellTable` + ) ) - ) - return; + return; + } + //step 2 submit + const payload = maybeStripIdFromEntities( + entsToUse, + validateAgainstSchema + ); + return onMultiFileUploadSubmit + ? await onMultiFileUploadSubmit() + : onUploadWizardFinish({ res: [payload] }); } - //step 2 submit - const payload = maybeStripIdFromEntities( - entsToUse, - validateAgainstSchema - ); - return onMultiFileUploadSubmit - ? await onMultiFileUploadSubmit() - : onUploadWizardFinish({ res: [payload] }); - } - })} - style={{ alignSelf: "end" }} - > -
- ); -}); + })} + style={{ alignSelf: "end" }} + /> +
+ ); + } +); export default UploadCsvWizardDialog; const exampleData = { userData: times(5).map(() => ({ _isClean: true })) }; -export const PreviewCsvData = observer(function (props) { + +export const PreviewCsvData = observer(props => { const { matchedHeaders, isEditingExistingFile, showDoesDataLookCorrectMsg, headerMessage, datatableFormName, - // onlyShowRowsWErrors, validateAgainstSchema, userSchema = exampleData, initialEntities } = props; const rerenderKey = useRef(0); rerenderKey.current = rerenderKey.current + 1; - // const useExampleData = userSchema === exampleData; - // const [loading, setLoading] = useState(true); - // useEffect(() => { - // // simulate layout change outside of React lifecycle - // setTimeout(() => { - // setLoading(false); - // }, 400); - // }, []); - - // const [val, forceUpdate] = useForceUpdate(); - const data = userSchema.userData && userSchema.userData.length && @@ -575,7 +526,7 @@ export const PreviewCsvData = observer(function (props) { + /> )} + /> ); }); @@ -608,14 +559,12 @@ export const SimpleInsertDataDialog = compose( "reduxFormEntities", "reduxFormCellValidation" ), - connect(undefined, { changeForm: change }), observer )(function SimpleInsertDataDialog({ onSimpleInsertDialogFinish, reduxFormEntities, reduxFormCellValidation, validateAgainstSchema, - changeForm, submitting, isEditingExistingFile, matchedHeaders, @@ -625,11 +574,14 @@ export const SimpleInsertDataDialog = compose( userSchema, initialEntities }) { + const dispatch = useDispatch(); const { entsToUse, validationToUse } = removeCleanRows( reduxFormEntities, reduxFormCellValidation ); + const changeForm = (...args) => dispatch(change(...args)); + return ( <>
@@ -642,20 +594,17 @@ export const SimpleInsertDataDialog = compose( label="File Name:" defaultValue={"manual_data_entry"} name="fileName" - > + /> + matchedHeaders={matchedHeaders} + isEditingExistingFile={isEditingExistingFile} + showDoesDataLookCorrectMsg={showDoesDataLookCorrectMsg} + headerMessage={headerMessage} + validateAgainstSchema={validateAgainstSchema} + userSchema={userSchema} + initialEntities={initialEntities} + datatableFormName={"simpleInsertEditableTable"} + />
e)} text={isEditingExistingFile ? "Edit Data" : "Add File"} - > + /> ); }); @@ -715,11 +664,3 @@ function maybeStripIdFromEntities(ents, validateAgainstSchema) { } return toRet?.map(e => omit(e, ["_isClean"])); } - -//create your forceUpdate hook -// function useForceUpdate() { -// const [val, setValue] = useState(0); // integer state -// return [val, () => setValue(value => value + 1)]; // update state to force render -// // A function that increment 👆🏻 the previous state like here -// // is better than directly setting `setValue(value + 1)` -// } diff --git a/packages/ui/src/index.js b/packages/ui/src/index.js index 72816cc1..ca5addc5 100644 --- a/packages/ui/src/index.js +++ b/packages/ui/src/index.js @@ -18,11 +18,11 @@ export { } from "./DataTable/utils/withSelectedEntities"; export { default as DataTable, - ConnectedPagingTool as PagingTool, - removeCleanRows + ConnectedPagingTool as PagingTool } from "./DataTable"; +export { removeCleanRows } from "./DataTable/utils"; -export { default as getIdOrCodeOrIndex } from "./DataTable/utils/getIdOrCodeOrIndex"; +export { getIdOrCodeOrIndex } from "./DataTable/utils"; export { default as convertSchema } from "./DataTable/utils/convertSchema"; export { default as Loading } from "./Loading"; export { throwFormError } from "./throwFormError"; diff --git a/packages/ui/src/showDialogOnDocBody.js b/packages/ui/src/showDialogOnDocBody.js index 4d4f8bd9..2f1452d4 100644 --- a/packages/ui/src/showDialogOnDocBody.js +++ b/packages/ui/src/showDialogOnDocBody.js @@ -1,4 +1,4 @@ -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; import React from "react"; // import withDialog from "./enhancers/withDialog"; import { Dialog } from "@blueprintjs/core"; @@ -19,19 +19,15 @@ export default function showDialogOnDocBody(DialogComp, options = {}) { DialogCompToUse = props => { return ( - + ); }; } else { DialogCompToUse = DialogComp; } - ReactDOM.render( - , - dialogHolder + const root = createRoot(dialogHolder); + root.render( + ); } diff --git a/packages/ui/src/useDialog.js b/packages/ui/src/useDialog.js index 38b1f65e..a9f559c6 100644 --- a/packages/ui/src/useDialog.js +++ b/packages/ui/src/useDialog.js @@ -1,6 +1,6 @@ import React, { useState } from "react"; -/* +/* const {toggleDialog, comp} = useDialog({ ModalComponent: SimpleInsertData, @@ -31,12 +31,14 @@ export const useDialog = ({ ModalComponent, ...rest }) => { ...rest?.dialogProps, ...additionalProps?.dialogProps }} - > + /> ); + const toggleDialog = () => { setOpen(!isOpen); }; - async function showDialogPromise(handlerName, moreProps = {}) { + + const showDialogPromise = async (handlerName, moreProps = {}) => { return new Promise(resolve => { //return a promise that can be awaited setAdditionalProps({ @@ -59,6 +61,7 @@ export const useDialog = ({ ModalComponent, ...rest }) => { }); setOpen(true); //open the dialog }); - } + }; + return { comp, showDialogPromise, toggleDialog, setAdditionalProps }; }; diff --git a/packages/ui/src/utils/renderOnDoc.js b/packages/ui/src/utils/renderOnDoc.js index 2ce4b142..e36f1e14 100644 --- a/packages/ui/src/utils/renderOnDoc.js +++ b/packages/ui/src/utils/renderOnDoc.js @@ -1,29 +1,32 @@ -import ReactDOM from "react-dom"; +import { createRoot } from "react-dom/client"; export function renderOnDoc(fn) { const elemDiv = document.createElement("div"); elemDiv.style.cssText = "position:absolute;width:100%;height:100%;top:0px;opacity:0.3;z-index:0;"; document.body.appendChild(elemDiv); + const root = createRoot(elemDiv); const handleClose = () => { setTimeout(() => { - ReactDOM.unmountComponentAtNode(elemDiv); + root.unmount(elemDiv); document.body.removeChild(elemDiv); }); }; - return ReactDOM.render(fn(handleClose), elemDiv); + root.render(fn(handleClose)); } + export function renderOnDocSimple(el) { const elemDiv = document.createElement("div"); elemDiv.style.cssText = "position:absolute;width:100%;height:100%;top:0px;opacity:1;z-index:10000;"; document.body.appendChild(elemDiv); + const root = createRoot(elemDiv); + root.render(el); const handleClose = () => { setTimeout(() => { - ReactDOM.unmountComponentAtNode(elemDiv); + root.unmount(); document.body.removeChild(elemDiv); }); }; - ReactDOM.render(el, elemDiv); return handleClose; }